diff --git a/cli/README.md b/cli/README.md index 40c7866..a3220a2 100644 --- a/cli/README.md +++ b/cli/README.md @@ -33,7 +33,7 @@ uipro init --force # Overwrite existing files # Other commands uipro versions # List available versions -uipro update # Refresh skill files from installed CLI package +uipro update # Update the global CLI to the latest release ``` ## GitHub Authentication @@ -63,13 +63,15 @@ uipro init ## How It Works -`uipro init` generates assistant-specific files from the templates bundled with the installed CLI package. To get newer templates and data, update the package first: +`uipro init` generates assistant-specific files from the templates bundled with the installed CLI package. To get newer templates and data, update the package, then regenerate: ```bash -npm install -g uipro-cli@latest -uipro init --ai codex +uipro update # updates the global CLI to the latest release +uipro init --ai codex --force # regenerate skill files from the new package ``` +`uipro update` runs `npm install -g uipro-cli@latest` for you (it shells out to `npm` only on Windows, where `npm` is a `.cmd`). You can still run that command manually if you prefer. When the CLI is already current, `uipro update` just refreshes the installed skill files. + ## Development ```bash diff --git a/cli/src/commands/update.ts b/cli/src/commands/update.ts index 947c30f..eea1d62 100644 --- a/cli/src/commands/update.ts +++ b/cli/src/commands/update.ts @@ -1,3 +1,4 @@ +import { execFileSync } from 'node:child_process'; import { readFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -38,9 +39,38 @@ export async function updateCommand(options: UpdateOptions): Promise { if (currentVersion !== latestVersion) { console.log(); - logger.warn(`Installed CLI package is ${chalk.cyan(currentVersion)}, but latest release is ${chalk.cyan(release.tag_name)}.`); - logger.info(`Update the CLI package first: ${chalk.cyan(`npm install -g uipro-cli@${latestVersion}`)}`); - logger.info('Then rerun: uipro init --ai '); + + // Only auto-run with a well-formed semver, so nothing unexpected can + // reach the shell that npm.cmd requires on Windows. + if (!/^\d+\.\d+\.\d+([.-][0-9A-Za-z.-]+)?$/.test(latestVersion)) { + logger.warn(`Installed CLI is ${chalk.cyan(currentVersion)}; latest release is ${chalk.cyan(release.tag_name)}.`); + logger.info(`Update the CLI package: ${chalk.cyan(`npm install -g uipro-cli@${latestVersion}`)}`); + logger.info('Then rerun: uipro init --ai --force'); + return; + } + + logger.info(`Updating CLI from ${chalk.cyan(currentVersion)} to ${chalk.cyan(latestVersion)}...`); + console.log(); + + const isWindows = process.platform === 'win32'; + try { + // execFileSync with an explicit args array — no shell string to expand. + // On Windows npm is npm.cmd, which Node only spawns via a shell. + execFileSync( + isWindows ? 'npm.cmd' : 'npm', + ['install', '-g', `uipro-cli@${latestVersion}`], + { stdio: 'inherit', shell: isWindows } + ); + } catch { + console.log(); + logger.error('Automatic update failed (you may need elevated/admin permissions).'); + logger.info(`Update manually: ${chalk.cyan(`npm install -g uipro-cli@${latestVersion}`)}`); + process.exit(1); + } + + console.log(); + logger.success(`Updated to ${chalk.cyan(latestVersion)}.`); + logger.info(`Now rerun ${chalk.cyan('uipro init --ai --force')} to refresh your skill files.`); return; }