diff --git a/.changeset/chilled-melons-switch.md b/.changeset/chilled-melons-switch.md new file mode 100644 index 000000000..404c34acc --- /dev/null +++ b/.changeset/chilled-melons-switch.md @@ -0,0 +1,6 @@ +--- +'@sveltejs/sv-utils': patch +'sv': patch +--- + +feat(sv-utils): all semantic colors now accept `string | string[]` diff --git a/packages/sv-utils/src/color.ts b/packages/sv-utils/src/color.ts index 168c59528..e1496dbfa 100644 --- a/packages/sv-utils/src/color.ts +++ b/packages/sv-utils/src/color.ts @@ -1,21 +1,24 @@ import { styleText } from 'node:util'; +type ColorInput = string | string[]; +const toStr = (input: ColorInput): string => (Array.isArray(input) ? input.join(' ') : input); + export const color = { // Semantic colors - addon: (str: string): string => styleText('greenBright', str), - command: (str: string): string => styleText(['bold', 'cyanBright'], str), - env: (str: string): string => styleText('yellow', str), - path: (str: string): string => styleText('blueBright', str), - route: (str: string): string => styleText(['bold', 'underline'], str), - website: (str: string): string => styleText('cyan', str), - optional: (str: string): string => styleText('gray', str), - dim: (str: string): string => styleText(['gray', 'dim'], str), // needed for terminal that don't support `dim` well + addon: (str: ColorInput): string => styleText('greenBright', toStr(str)), + command: (str: ColorInput): string => styleText(['bold', 'cyanBright'], toStr(str)), + env: (str: ColorInput): string => styleText('yellow', toStr(str)), + path: (str: ColorInput): string => styleText('blueBright', toStr(str)), + route: (str: ColorInput): string => styleText(['bold', 'underline'], toStr(str)), + website: (str: ColorInput): string => styleText('cyan', toStr(str)), + optional: (str: ColorInput): string => styleText('gray', toStr(str)), + dim: (str: ColorInput): string => styleText(['gray', 'dim'], toStr(str)), // needed for terminal that don't support `dim` well // Status colors - success: (str: string): string => styleText('green', str), - warning: (str: string): string => styleText('yellow', str), - error: (str: string): string => styleText('red', str), + success: (str: ColorInput): string => styleText('green', toStr(str)), + warning: (str: ColorInput): string => styleText('yellow', toStr(str)), + error: (str: ColorInput): string => styleText('red', toStr(str)), // Visibility - hidden: (str: string): string => styleText('hidden', str) + hidden: (str: ColorInput): string => styleText('hidden', toStr(str)) }; diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index ad6df04f4..29005fa4a 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -1,3 +1,8 @@ +import { + resolveCommand as _resolveCommand, + type Agent, + type Command +} from 'package-manager-detector'; import { parseCss, parseHtml, @@ -20,6 +25,12 @@ export { resolveCommand } from 'package-manager-detector'; +/** Resolves a package manager command and returns it as a string array (command + args). */ +export function resolveCommandArray(agent: Agent, command: Command, args: string[]): string[] { + const cmd = _resolveCommand(agent, command, args)!; + return [cmd.command, ...cmd.args]; +} + // Parsing & language namespaces export * as css from './tooling/css/index.ts'; export * as js from './tooling/js/index.ts'; diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 3dc18859b..f19c3f82e 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -5,7 +5,7 @@ import { color, dedent, transforms, - resolveCommand, + resolveCommandArray, createPrinter, type TransformFn } from '@sveltejs/sv-utils'; @@ -517,13 +517,9 @@ export default defineAddon({ }, nextSteps: ({ options, packageManager }) => { - const { command: authCmd, args: authArgs } = resolveCommand(packageManager, 'run', [ - 'auth:schema' - ])!; - const { command: dbCmd, args: dbArgs } = resolveCommand(packageManager, 'run', ['db:push'])!; const steps = [ - `Run ${color.command(`${authCmd} ${authArgs.join(' ')}`)} to generate the auth schema`, - `Run ${color.command(`${dbCmd} ${dbArgs.join(' ')}`)} to update your database`, + `Run ${color.command(resolveCommandArray(packageManager, 'run', ['auth:schema']))} to generate the auth schema`, + `Run ${color.command(resolveCommandArray(packageManager, 'run', ['db:push']))} to update your database`, `Check ${color.env('ORIGIN')} & ${color.env('BETTER_AUTH_SECRET')} in ${color.path('.env')} and adjust it to your needs` ]; if (options.demo.includes('github')) { diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index 897949172..f807f11cc 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -3,7 +3,7 @@ import { dedent, type TransformFn, transforms, - resolveCommand, + resolveCommandArray, fileExists, createPrinter } from '@sveltejs/sv-utils'; @@ -481,25 +481,17 @@ export default defineAddon({ const steps: string[] = []; if (options.database === 'd1') { const ext = fileExists(cwd, 'wrangler.toml') ? 'toml' : 'jsonc'; - const { command, args } = resolveCommand(packageManager, 'run', [ - 'wrangler', - 'd1', - 'create', - `` - ])!; - steps.push( `Add your ${color.env('CLOUDFLARE_ACCOUNT_ID')}, ${color.env('CLOUDFLARE_DATABASE_ID')}, and ${color.env('CLOUDFLARE_D1_TOKEN')} to ${color.path('.env')}` ); steps.push( - `Run ${color.command(`${command} ${args.join(' ')}`)} to generate a D1 database ID for your ${color.path(`wrangler.${ext}`)}` + `Run ${color.command(resolveCommandArray(packageManager, 'run', ['wrangler', 'd1', 'create', '']))} to generate a D1 database ID for your ${color.path(`wrangler.${ext}`)}` ); } if (options.docker) { - const { command, args } = resolveCommand(packageManager, 'run', ['db:start'])!; steps.push( - `Run ${color.command(`${command} ${args.join(' ')}`)} to start the docker container` + `Run ${color.command(resolveCommandArray(packageManager, 'run', ['db:start']))} to start the docker container` ); } else if (options.database !== 'd1') { steps.push( @@ -507,9 +499,8 @@ export default defineAddon({ ); } - const { command, args } = resolveCommand(packageManager, 'run', ['db:push'])!; steps.push( - `Run ${color.command(`${command} ${args.join(' ')}`)} to update your database schema` + `Run ${color.command(resolveCommandArray(packageManager, 'run', ['db:push']))} to update your database schema` ); return steps; diff --git a/packages/sv/src/addons/sveltekit-adapter.ts b/packages/sv/src/addons/sveltekit-adapter.ts index bbb9f5fb2..dd3f9a3a2 100644 --- a/packages/sv/src/addons/sveltekit-adapter.ts +++ b/packages/sv/src/addons/sveltekit-adapter.ts @@ -1,6 +1,6 @@ import { color, - resolveCommand, + resolveCommandArray, text, transforms, fileExists, @@ -231,9 +231,8 @@ export default defineAddon({ nextSteps({ options, packageManager }) { const steps: string[] = []; if (options.adapter === 'cloudflare') { - const { command, args } = resolveCommand(packageManager, 'run', ['gen'])!; steps.push( - `Run ${color.command(`${command} ${args.join(' ')}`)} to update ${color.addon('cloudflare')} types` + `Run ${color.command(resolveCommandArray(packageManager, 'run', ['gen']))} to update ${color.addon('cloudflare')} types` ); } return steps; diff --git a/packages/sv/src/cli/check.ts b/packages/sv/src/cli/check.ts index 98cbbc657..fd5f2dfa0 100644 --- a/packages/sv/src/cli/check.ts +++ b/packages/sv/src/cli/check.ts @@ -1,4 +1,4 @@ -import { color, resolveCommand } from '@sveltejs/sv-utils'; +import { color, resolveCommandArray } from '@sveltejs/sv-utils'; import { Command } from 'commander'; import * as resolve from 'empathic/resolve'; import { execSync } from 'node:child_process'; @@ -26,9 +26,8 @@ async function runCheck(cwd: string, args: string[]) { // validates that `svelte-check` is locally installed const resolved = resolve.from(cwd, 'svelte-check', true); if (!resolved) { - const cmd = resolveCommand(pm, 'add', ['-D', 'svelte-check'])!; console.error( - `'svelte-check' is not installed locally. Install it with: ${color.command(`${cmd.command} ${cmd.args.join(' ')}`)}` + `'svelte-check' is not installed locally. Install it with: ${color.command(resolveCommandArray(pm, 'add', ['-D', 'svelte-check']))}` ); process.exit(1); } @@ -40,8 +39,8 @@ async function runCheck(cwd: string, args: string[]) { // avoids printing the stack trace for `sv` when `svelte-check` exits with an error code try { - const cmd = resolveCommand(pm, 'execute-local', ['svelte-check', ...args])!; - execSync(`${cmd.command} ${cmd.args.join(' ')}`, { stdio: 'inherit', cwd }); + const cmd = resolveCommandArray(pm, 'execute-local', ['svelte-check', ...args]).join(' '); + execSync(cmd, { stdio: 'inherit', cwd }); } catch (error) { forwardExitCode(error); } finally { diff --git a/packages/sv/src/cli/create.ts b/packages/sv/src/cli/create.ts index b31804536..d81ff99ce 100644 --- a/packages/sv/src/cli/create.ts +++ b/packages/sv/src/cli/create.ts @@ -1,5 +1,5 @@ import * as p from '@clack/prompts'; -import { color, resolveCommand, commonFilePaths, getPackageJson } from '@sveltejs/sv-utils'; +import { color, resolveCommandArray, commonFilePaths, getPackageJson } from '@sveltejs/sv-utils'; import { Command, Option } from 'commander'; import fs from 'node:fs'; import path from 'node:path'; @@ -145,15 +145,12 @@ export const create = new Command('create') ); } if (!packageManager) { - const { args, command } = resolveCommand(pm, 'install', [])!; - initialSteps.push(` ${i++}: ${color.command(`${command} ${args.join(' ')}`)}`); + initialSteps.push(` ${i++}: ${color.command(resolveCommandArray(pm, 'install', []))}`); } - const { args, command } = resolveCommand(pm, 'run', ['dev', '--open'])!; - const pmRunCmd = `${command} ${args.join(' ')}`; const steps = [ ...initialSteps, - ` ${i++}: ${color.command(pmRunCmd)}`, + ` ${i++}: ${color.command(resolveCommandArray(pm, 'run', ['dev', '--open']))}`, '', `To close the dev server, hit ${color.command('Ctrl-C')}` ]; diff --git a/packages/sv/src/cli/migrate.ts b/packages/sv/src/cli/migrate.ts index e4c9095fa..22886dc60 100644 --- a/packages/sv/src/cli/migrate.ts +++ b/packages/sv/src/cli/migrate.ts @@ -1,4 +1,4 @@ -import { resolveCommand } from '@sveltejs/sv-utils'; +import { resolveCommandArray } from '@sveltejs/sv-utils'; import { Command } from 'commander'; import { execSync } from 'node:child_process'; import process from 'node:process'; @@ -23,8 +23,7 @@ async function runMigrate(cwd: string, args: string[]) { // skips the download confirmation prompt for `npx` if (pm === 'npm') cmdArgs.unshift('--yes'); - const cmd = resolveCommand(pm, 'execute', cmdArgs)!; - execSync(`${cmd.command} ${cmd.args.join(' ')}`, { stdio: 'inherit', cwd }); + execSync(resolveCommandArray(pm, 'execute', cmdArgs).join(' '), { stdio: 'inherit', cwd }); } catch (error) { forwardExitCode(error); } diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index fe8e37883..f801dd534 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -45,7 +45,7 @@ describe('cli', () => { it.for(testCases)( 'should create a new project with name $projectName', - { timeout: 111_000 }, + { timeout: 123_000 }, async (testCase) => { const { projectName, args, template = 'minimal' } = testCase; diff --git a/packages/sv/src/core/common.ts b/packages/sv/src/core/common.ts index 578f2f689..856b8e0c5 100644 --- a/packages/sv/src/core/common.ts +++ b/packages/sv/src/core/common.ts @@ -2,7 +2,7 @@ import * as p from '@clack/prompts'; import { type AgentName, color, - resolveCommand, + resolveCommandArray, isVersionUnsupportedBelow } from '@sveltejs/sv-utils'; import type { Argument, Command, Help, HelpConfiguration, Option } from 'commander'; @@ -236,8 +236,9 @@ export function buildAndLogArgs( if (agent === null || agent === undefined) allArgs.push('--no-install'); else allArgs.push('--install', agent); - const res = resolveCommand(agent ?? 'npm', 'execute', [...allArgs, ...lastArgs])!; - const message = [res.command, ...res.args].join(' '); + const message = resolveCommandArray(agent ?? 'npm', 'execute', [...allArgs, ...lastArgs]).join( + ' ' + ); p.log.message(color.optional(color.dim(`To skip prompts next time, run:`))); p.log.info(color.optional(message), { spacing: -1 }); diff --git a/packages/sv/src/core/engine.ts b/packages/sv/src/core/engine.ts index 1c7656467..9cfa02a47 100644 --- a/packages/sv/src/core/engine.ts +++ b/packages/sv/src/core/engine.ts @@ -198,7 +198,7 @@ async function runAddon({ addon, loaded, multiple, workspace, workspaceOptions } const { command, args } = resolveCommand(workspace.packageManager, 'execute', commandArgs)!; const addonPrefix = multiple ? `${addon.id}: ` : ''; - const executedCommand = `${command} ${args.join(' ')}`; + const executedCommand = [command, ...args].join(' '); if (!TESTING) { p.log.step( `${addonPrefix}Running external command ${color.optional(`(${executedCommand})`)}`