From 71275eec25852a659ad1087217d00891973761bc Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 2 Apr 2026 02:04:07 +0800 Subject: [PATCH 1/8] color.command to accept `string[]` --- packages/sv-utils/src/color.ts | 3 ++- packages/sv/src/addons/better-auth.ts | 4 ++-- packages/sv/src/addons/drizzle.ts | 10 +++------- packages/sv/src/addons/sveltekit-adapter.ts | 2 +- packages/sv/src/cli/check.ts | 4 ++-- packages/sv/src/cli/create.ts | 5 ++--- packages/sv/src/cli/migrate.ts | 2 +- packages/sv/src/core/engine.ts | 2 +- 8 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/sv-utils/src/color.ts b/packages/sv-utils/src/color.ts index 168c59528..f3690e3e6 100644 --- a/packages/sv-utils/src/color.ts +++ b/packages/sv-utils/src/color.ts @@ -3,7 +3,8 @@ import { styleText } from 'node:util'; export const color = { // Semantic colors addon: (str: string): string => styleText('greenBright', str), - command: (str: string): string => styleText(['bold', 'cyanBright'], str), + command: (str: string | string[]): string => + styleText(['bold', 'cyanBright'], Array.isArray(str) ? str.join(' ') : str), env: (str: string): string => styleText('yellow', str), path: (str: string): string => styleText('blueBright', str), route: (str: string): string => styleText(['bold', 'underline'], str), diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 3dc18859b..6984bfc19 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -522,8 +522,8 @@ export default defineAddon({ ])!; 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([authCmd, ...authArgs])} to generate the auth schema`, + `Run ${color.command([dbCmd, ...dbArgs])} 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..05fbcc17a 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -492,15 +492,13 @@ export default defineAddon({ `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([command, ...args])} 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` - ); + steps.push(`Run ${color.command([command, ...args])} to start the docker container`); } else if (options.database !== 'd1') { steps.push( `Check ${color.env('DATABASE_URL')} in ${color.path('.env')} and adjust it to your needs` @@ -508,9 +506,7 @@ export default defineAddon({ } const { command, args } = resolveCommand(packageManager, 'run', ['db:push'])!; - steps.push( - `Run ${color.command(`${command} ${args.join(' ')}`)} to update your database schema` - ); + steps.push(`Run ${color.command([command, ...args])} 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..a15423218 100644 --- a/packages/sv/src/addons/sveltekit-adapter.ts +++ b/packages/sv/src/addons/sveltekit-adapter.ts @@ -233,7 +233,7 @@ export default defineAddon({ 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([command, ...args])} 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..a5c58e2db 100644 --- a/packages/sv/src/cli/check.ts +++ b/packages/sv/src/cli/check.ts @@ -28,7 +28,7 @@ async function runCheck(cwd: string, args: string[]) { 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([cmd.command, ...cmd.args])}` ); process.exit(1); } @@ -41,7 +41,7 @@ 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 }); + execSync([cmd.command, ...cmd.args].join(' '), { 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..bb51eef75 100644 --- a/packages/sv/src/cli/create.ts +++ b/packages/sv/src/cli/create.ts @@ -146,14 +146,13 @@ 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([command, ...args])}`); } const { args, command } = resolveCommand(pm, 'run', ['dev', '--open'])!; - const pmRunCmd = `${command} ${args.join(' ')}`; const steps = [ ...initialSteps, - ` ${i++}: ${color.command(pmRunCmd)}`, + ` ${i++}: ${color.command([command, ...args])}`, '', `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..8ce493a2d 100644 --- a/packages/sv/src/cli/migrate.ts +++ b/packages/sv/src/cli/migrate.ts @@ -24,7 +24,7 @@ async function runMigrate(cwd: string, args: string[]) { if (pm === 'npm') cmdArgs.unshift('--yes'); const cmd = resolveCommand(pm, 'execute', cmdArgs)!; - execSync(`${cmd.command} ${cmd.args.join(' ')}`, { stdio: 'inherit', cwd }); + execSync([cmd.command, ...cmd.args].join(' '), { stdio: 'inherit', cwd }); } catch (error) { forwardExitCode(error); } 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})`)}` From dff5aaec0ad8a31ef6b9c69f848cc438bb612e76 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 2 Apr 2026 02:08:42 +0800 Subject: [PATCH 2/8] no square brackets --- packages/sv-utils/src/color.ts | 4 ++-- packages/sv/src/addons/better-auth.ts | 4 ++-- packages/sv/src/addons/drizzle.ts | 6 +++--- packages/sv/src/addons/sveltekit-adapter.ts | 2 +- packages/sv/src/cli/check.ts | 2 +- packages/sv/src/cli/create.ts | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/sv-utils/src/color.ts b/packages/sv-utils/src/color.ts index f3690e3e6..42d1943ca 100644 --- a/packages/sv-utils/src/color.ts +++ b/packages/sv-utils/src/color.ts @@ -3,8 +3,8 @@ import { styleText } from 'node:util'; export const color = { // Semantic colors addon: (str: string): string => styleText('greenBright', str), - command: (str: string | string[]): string => - styleText(['bold', 'cyanBright'], Array.isArray(str) ? str.join(' ') : str), + command: (...args: string[]): string => + styleText(['bold', 'cyanBright'], args.length === 1 ? args[0] : args.join(' ')), env: (str: string): string => styleText('yellow', str), path: (str: string): string => styleText('blueBright', str), route: (str: string): string => styleText(['bold', 'underline'], str), diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 6984bfc19..1e72b7553 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -522,8 +522,8 @@ export default defineAddon({ ])!; const { command: dbCmd, args: dbArgs } = resolveCommand(packageManager, 'run', ['db:push'])!; const steps = [ - `Run ${color.command([authCmd, ...authArgs])} to generate the auth schema`, - `Run ${color.command([dbCmd, ...dbArgs])} to update your database`, + `Run ${color.command(authCmd, ...authArgs)} to generate the auth schema`, + `Run ${color.command(dbCmd, ...dbArgs)} 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 05fbcc17a..4c961e2dc 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -492,13 +492,13 @@ export default defineAddon({ `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])} to generate a D1 database ID for your ${color.path(`wrangler.${ext}`)}` + `Run ${color.command(command, ...args)} 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])} to start the docker container`); + steps.push(`Run ${color.command(command, ...args)} to start the docker container`); } else if (options.database !== 'd1') { steps.push( `Check ${color.env('DATABASE_URL')} in ${color.path('.env')} and adjust it to your needs` @@ -506,7 +506,7 @@ export default defineAddon({ } const { command, args } = resolveCommand(packageManager, 'run', ['db:push'])!; - steps.push(`Run ${color.command([command, ...args])} to update your database schema`); + steps.push(`Run ${color.command(command, ...args)} 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 a15423218..adfb1d0eb 100644 --- a/packages/sv/src/addons/sveltekit-adapter.ts +++ b/packages/sv/src/addons/sveltekit-adapter.ts @@ -233,7 +233,7 @@ export default defineAddon({ if (options.adapter === 'cloudflare') { const { command, args } = resolveCommand(packageManager, 'run', ['gen'])!; steps.push( - `Run ${color.command([command, ...args])} to update ${color.addon('cloudflare')} types` + `Run ${color.command(command, ...args)} 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 a5c58e2db..3dce1caf1 100644 --- a/packages/sv/src/cli/check.ts +++ b/packages/sv/src/cli/check.ts @@ -28,7 +28,7 @@ async function runCheck(cwd: string, args: string[]) { 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])}` + `'svelte-check' is not installed locally. Install it with: ${color.command(cmd.command, ...cmd.args)}` ); process.exit(1); } diff --git a/packages/sv/src/cli/create.ts b/packages/sv/src/cli/create.ts index bb51eef75..823d76bf5 100644 --- a/packages/sv/src/cli/create.ts +++ b/packages/sv/src/cli/create.ts @@ -146,13 +146,13 @@ export const create = new Command('create') } if (!packageManager) { const { args, command } = resolveCommand(pm, 'install', [])!; - initialSteps.push(` ${i++}: ${color.command([command, ...args])}`); + initialSteps.push(` ${i++}: ${color.command(command, ...args)}`); } const { args, command } = resolveCommand(pm, 'run', ['dev', '--open'])!; const steps = [ ...initialSteps, - ` ${i++}: ${color.command([command, ...args])}`, + ` ${i++}: ${color.command(command, ...args)}`, '', `To close the dev server, hit ${color.command('Ctrl-C')}` ]; From e9186765ac6853566d5e3f30434e0fc2590e5433 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 2 Apr 2026 02:22:59 +0800 Subject: [PATCH 3/8] nit --- packages/sv/src/addons/better-auth.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 1e72b7553..881865356 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -517,13 +517,11 @@ 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 auth = resolveCommand(packageManager, 'run', ['auth:schema'])!; + const db = resolveCommand(packageManager, 'run', ['db:push'])!; const steps = [ - `Run ${color.command(authCmd, ...authArgs)} to generate the auth schema`, - `Run ${color.command(dbCmd, ...dbArgs)} to update your database`, + `Run ${color.command(auth.command, ...auth.args)} to generate the auth schema`, + `Run ${color.command(db.command, ...db.args)} 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')) { From b961dfabee8f885582a719caca04f351e8dde01e Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 2 Apr 2026 02:25:03 +0800 Subject: [PATCH 4/8] changeset --- .changeset/chilled-melons-switch.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/chilled-melons-switch.md diff --git a/.changeset/chilled-melons-switch.md b/.changeset/chilled-melons-switch.md new file mode 100644 index 000000000..c99e62fc5 --- /dev/null +++ b/.changeset/chilled-melons-switch.md @@ -0,0 +1,6 @@ +--- +"@sveltejs/sv-utils": patch +"sv": patch +--- + +feat(sv-utils): `color.command` now also accepts `string[]` From 46f7b06ce60ace55129a93da79c052427a339ffc Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 2 Apr 2026 00:26:39 +0200 Subject: [PATCH 5/8] fun tentative --- .changeset/chilled-melons-switch.md | 6 ++--- packages/sv-utils/src/color.ts | 28 +++++++++++---------- packages/sv-utils/src/index.ts | 11 ++++++++ packages/sv/src/addons/better-auth.ts | 8 +++--- packages/sv/src/addons/drizzle.ts | 21 ++++++---------- packages/sv/src/addons/sveltekit-adapter.ts | 5 ++-- packages/sv/src/cli/check.ts | 9 +++---- packages/sv/src/cli/create.ts | 8 +++--- packages/sv/src/cli/migrate.ts | 5 ++-- packages/sv/src/core/common.ts | 7 +++--- 10 files changed, 55 insertions(+), 53 deletions(-) diff --git a/.changeset/chilled-melons-switch.md b/.changeset/chilled-melons-switch.md index c99e62fc5..404c34acc 100644 --- a/.changeset/chilled-melons-switch.md +++ b/.changeset/chilled-melons-switch.md @@ -1,6 +1,6 @@ --- -"@sveltejs/sv-utils": patch -"sv": patch +'@sveltejs/sv-utils': patch +'sv': patch --- -feat(sv-utils): `color.command` now also accepts `string[]` +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 42d1943ca..e1496dbfa 100644 --- a/packages/sv-utils/src/color.ts +++ b/packages/sv-utils/src/color.ts @@ -1,22 +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: (...args: string[]): string => - styleText(['bold', 'cyanBright'], args.length === 1 ? args[0] : args.join(' ')), - 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 881865356..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,11 +517,9 @@ export default defineAddon({ }, nextSteps: ({ options, packageManager }) => { - const auth = resolveCommand(packageManager, 'run', ['auth:schema'])!; - const db = resolveCommand(packageManager, 'run', ['db:push'])!; const steps = [ - `Run ${color.command(auth.command, ...auth.args)} to generate the auth schema`, - `Run ${color.command(db.command, ...db.args)} 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 4c961e2dc..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,32 +481,27 @@ 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)} 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)} to start the docker container`); + steps.push( + `Run ${color.command(resolveCommandArray(packageManager, 'run', ['db:start']))} to start the docker container` + ); } else if (options.database !== 'd1') { steps.push( `Check ${color.env('DATABASE_URL')} in ${color.path('.env')} and adjust it to your needs` ); } - const { command, args } = resolveCommand(packageManager, 'run', ['db:push'])!; - steps.push(`Run ${color.command(command, ...args)} to update your database schema`); + steps.push( + `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 adfb1d0eb..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)} 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 3dce1caf1..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)}` + `'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 823d76bf5..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,14 +145,12 @@ export const create = new Command('create') ); } if (!packageManager) { - const { args, command } = resolveCommand(pm, 'install', [])!; - initialSteps.push(` ${i++}: ${color.command(command, ...args)}`); + initialSteps.push(` ${i++}: ${color.command(resolveCommandArray(pm, 'install', []))}`); } - const { args, command } = resolveCommand(pm, 'run', ['dev', '--open'])!; const steps = [ ...initialSteps, - ` ${i++}: ${color.command(command, ...args)}`, + ` ${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 8ce493a2d..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/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 }); From 1b787e45713652ced9dddff265411bd2a7780866 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 2 Apr 2026 10:55:46 +0800 Subject: [PATCH 6/8] refactor --- packages/sv-utils/src/color.ts | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/sv-utils/src/color.ts b/packages/sv-utils/src/color.ts index e1496dbfa..863ccef77 100644 --- a/packages/sv-utils/src/color.ts +++ b/packages/sv-utils/src/color.ts @@ -1,24 +1,25 @@ import { styleText } from 'node:util'; -type ColorInput = string | string[]; -const toStr = (input: ColorInput): string => (Array.isArray(input) ? input.join(' ') : input); +type WithFormat = (format: Parameters[0]) => (input: string | string[]) => string; +const withFormat: WithFormat = (format) => (input) => + styleText(format, Array.isArray(input) ? input.join(' ') : input); export const color = { // Semantic colors - 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 + addon: withFormat('greenBright'), + command: withFormat(['bold', 'cyanBright']), + env: withFormat('yellow'), + path: withFormat('blueBright'), + route: withFormat(['bold', 'underline']), + website: withFormat('cyan'), + optional: withFormat('gray'), + dim: withFormat(['gray', 'dim']), // needed for terminal that don't support `dim` well // Status colors - success: (str: ColorInput): string => styleText('green', toStr(str)), - warning: (str: ColorInput): string => styleText('yellow', toStr(str)), - error: (str: ColorInput): string => styleText('red', toStr(str)), + success: withFormat('green'), + warning: withFormat('yellow'), + error: withFormat('red'), // Visibility - hidden: (str: ColorInput): string => styleText('hidden', toStr(str)) + hidden: withFormat('hidden') }; From cc38a05f004230781e6a54271019150ce51c3752 Mon Sep 17 00:00:00 2001 From: Scott Wu Date: Thu, 2 Apr 2026 11:11:13 +0800 Subject: [PATCH 7/8] make rolldown happy --- packages/sv-utils/src/color.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv-utils/src/color.ts b/packages/sv-utils/src/color.ts index 863ccef77..0ca2bf717 100644 --- a/packages/sv-utils/src/color.ts +++ b/packages/sv-utils/src/color.ts @@ -4,7 +4,7 @@ type WithFormat = (format: Parameters[0]) => (input: string | const withFormat: WithFormat = (format) => (input) => styleText(format, Array.isArray(input) ? input.join(' ') : input); -export const color = { +export const color: Record string> = { // Semantic colors addon: withFormat('greenBright'), command: withFormat(['bold', 'cyanBright']), From ab8afc28fdf9a7b2562052ca7688cc2426f5498c Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 2 Apr 2026 18:05:27 +0200 Subject: [PATCH 8/8] ~~Record[0]) => (input: string | string[]) => string; -const withFormat: WithFormat = (format) => (input) => - styleText(format, Array.isArray(input) ? input.join(' ') : input); +type ColorInput = string | string[]; +const toStr = (input: ColorInput): string => (Array.isArray(input) ? input.join(' ') : input); -export const color: Record string> = { +export const color = { // Semantic colors - addon: withFormat('greenBright'), - command: withFormat(['bold', 'cyanBright']), - env: withFormat('yellow'), - path: withFormat('blueBright'), - route: withFormat(['bold', 'underline']), - website: withFormat('cyan'), - optional: withFormat('gray'), - dim: withFormat(['gray', 'dim']), // 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: withFormat('green'), - warning: withFormat('yellow'), - error: withFormat('red'), + 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: withFormat('hidden') + hidden: (str: ColorInput): string => styleText('hidden', toStr(str)) }; 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;