pnpm-catalog-updates 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/index.js +22031 -10684
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/src/cli/__tests__/commandRegistrar.test.ts +248 -0
- package/src/cli/commandRegistrar.ts +785 -0
- package/src/cli/commands/__tests__/aiCommand.test.ts +161 -0
- package/src/cli/commands/__tests__/analyzeCommand.test.ts +283 -0
- package/src/cli/commands/__tests__/checkCommand.test.ts +435 -0
- package/src/cli/commands/__tests__/graphCommand.test.ts +312 -0
- package/src/cli/commands/__tests__/initCommand.test.ts +317 -0
- package/src/cli/commands/__tests__/rollbackCommand.test.ts +400 -0
- package/src/cli/commands/__tests__/securityCommand.test.ts +467 -0
- package/src/cli/commands/__tests__/themeCommand.test.ts +166 -0
- package/src/cli/commands/__tests__/updateCommand.test.ts +720 -0
- package/src/cli/commands/__tests__/workspaceCommand.test.ts +286 -0
- package/src/cli/commands/aiCommand.ts +163 -0
- package/src/cli/commands/analyzeCommand.ts +219 -0
- package/src/cli/commands/checkCommand.ts +91 -98
- package/src/cli/commands/graphCommand.ts +475 -0
- package/src/cli/commands/initCommand.ts +64 -54
- package/src/cli/commands/rollbackCommand.ts +334 -0
- package/src/cli/commands/securityCommand.ts +165 -100
- package/src/cli/commands/themeCommand.ts +148 -0
- package/src/cli/commands/updateCommand.ts +215 -263
- package/src/cli/commands/workspaceCommand.ts +73 -0
- package/src/cli/constants/cliChoices.ts +93 -0
- package/src/cli/formatters/__tests__/__snapshots__/outputFormatter.test.ts.snap +557 -0
- package/src/cli/formatters/__tests__/ciFormatter.test.ts +526 -0
- package/src/cli/formatters/__tests__/outputFormatter.test.ts +448 -0
- package/src/cli/formatters/__tests__/progressBar.test.ts +709 -0
- package/src/cli/formatters/ciFormatter.ts +964 -0
- package/src/cli/formatters/colorUtils.ts +145 -0
- package/src/cli/formatters/outputFormatter.ts +615 -332
- package/src/cli/formatters/progressBar.ts +43 -52
- package/src/cli/formatters/versionFormatter.ts +132 -0
- package/src/cli/handlers/aiAnalysisHandler.ts +205 -0
- package/src/cli/handlers/changelogHandler.ts +113 -0
- package/src/cli/handlers/index.ts +9 -0
- package/src/cli/handlers/installHandler.ts +130 -0
- package/src/cli/index.ts +175 -726
- package/src/cli/interactive/InteractiveOptionsCollector.ts +387 -0
- package/src/cli/interactive/interactivePrompts.ts +189 -83
- package/src/cli/interactive/optionUtils.ts +89 -0
- package/src/cli/themes/colorTheme.ts +43 -16
- package/src/cli/utils/cliOutput.ts +118 -0
- package/src/cli/utils/commandHelpers.ts +249 -0
- package/src/cli/validators/commandValidator.ts +321 -336
- package/src/cli/validators/index.ts +37 -2
- package/src/cli/options/globalOptions.ts +0 -437
- package/src/cli/options/index.ts +0 -5
package/src/cli/index.ts
CHANGED
|
@@ -10,796 +10,245 @@
|
|
|
10
10
|
import { readFileSync } from 'node:fs'
|
|
11
11
|
import { dirname, join } from 'node:path'
|
|
12
12
|
import { fileURLToPath } from 'node:url'
|
|
13
|
-
|
|
14
|
-
import type { AnalysisType } from '@pcu/core'
|
|
13
|
+
import { startCacheInitialization } from '@pcu/core'
|
|
15
14
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import { ConfigLoader, VersionChecker } from '@pcu/utils'
|
|
15
|
+
ConfigLoader,
|
|
16
|
+
I18n,
|
|
17
|
+
isCommandExitError,
|
|
18
|
+
Logger,
|
|
19
|
+
logger,
|
|
20
|
+
preloadPackageSuggestions,
|
|
21
|
+
t,
|
|
22
|
+
VersionChecker,
|
|
23
|
+
} from '@pcu/utils'
|
|
26
24
|
import chalk from 'chalk'
|
|
27
25
|
import { Command } from 'commander'
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import { SecurityCommand } from './commands/securityCommand.js'
|
|
31
|
-
import { UpdateCommand } from './commands/updateCommand.js'
|
|
32
|
-
import { type OutputFormat, OutputFormatter } from './formatters/outputFormatter.js'
|
|
33
|
-
import { InteractivePrompts } from './interactive/interactivePrompts.js'
|
|
34
|
-
import { StyledText, ThemeManager } from './themes/colorTheme.js'
|
|
26
|
+
import { isExitPromptError, LazyServiceFactory, registerCommands } from './commandRegistrar.js'
|
|
27
|
+
import { cliOutput } from './utils/cliOutput.js'
|
|
35
28
|
|
|
36
|
-
//
|
|
29
|
+
// Only read when version info is actually needed
|
|
37
30
|
const __filename = fileURLToPath(import.meta.url)
|
|
38
31
|
const __dirname = dirname(__filename)
|
|
39
|
-
|
|
32
|
+
|
|
33
|
+
let _packageJson: { version: string; name: string } | null = null
|
|
34
|
+
function getPackageJson(): { version: string; name: string } {
|
|
35
|
+
if (!_packageJson) {
|
|
36
|
+
_packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'))
|
|
37
|
+
}
|
|
38
|
+
return _packageJson!
|
|
39
|
+
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Exit with proper logger cleanup
|
|
43
|
+
*
|
|
44
|
+
* Ensures all log messages are flushed and file handles are closed
|
|
45
|
+
* before process exit to prevent log data loss.
|
|
43
46
|
*/
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const catalogUpdateService = CatalogUpdateService.createWithConfig(
|
|
49
|
-
workspaceRepository,
|
|
50
|
-
workspacePath
|
|
51
|
-
)
|
|
52
|
-
const workspaceService = new WorkspaceService(workspaceRepository)
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
fileSystemService,
|
|
56
|
-
workspaceRepository,
|
|
57
|
-
catalogUpdateService,
|
|
58
|
-
workspaceService,
|
|
59
|
-
}
|
|
47
|
+
function exitWithCleanup(code: number): never {
|
|
48
|
+
// Synchronously close all loggers to ensure logs are written
|
|
49
|
+
Logger.closeAll()
|
|
50
|
+
process.exit(code)
|
|
60
51
|
}
|
|
61
52
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return
|
|
53
|
+
/**
|
|
54
|
+
* Check for version updates at startup (non-blocking background check)
|
|
55
|
+
*
|
|
56
|
+
* Runs version check in background to avoid blocking CLI startup.
|
|
57
|
+
* Only notifies user after command execution if an update is available.
|
|
58
|
+
*/
|
|
59
|
+
function startBackgroundVersionCheck(
|
|
60
|
+
config: Awaited<ReturnType<typeof ConfigLoader.loadConfig>>
|
|
61
|
+
): Promise<{ hasUpdate: boolean; message?: string }> | null {
|
|
62
|
+
if (!VersionChecker.shouldCheckForUpdates() || config.advanced?.checkForUpdates === false) {
|
|
63
|
+
return null
|
|
73
64
|
}
|
|
74
|
-
|
|
65
|
+
|
|
66
|
+
// Start check in background, don't await
|
|
67
|
+
return VersionChecker.checkVersion(getPackageJson().version, {
|
|
68
|
+
skipPrompt: true, // Don't prompt during background check
|
|
69
|
+
timeout: 3000,
|
|
70
|
+
})
|
|
71
|
+
.then((result) => {
|
|
72
|
+
if (result.shouldPrompt && result.latestVersion) {
|
|
73
|
+
return {
|
|
74
|
+
hasUpdate: true,
|
|
75
|
+
message: chalk.yellow(
|
|
76
|
+
`\n${t('cli.updateAvailable', { current: getPackageJson().version, latest: result.latestVersion })}`
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { hasUpdate: false }
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
logger.debug('Background version check failed', {
|
|
84
|
+
error: error instanceof Error ? error.message : error,
|
|
85
|
+
})
|
|
86
|
+
return { hasUpdate: false }
|
|
87
|
+
})
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
/**
|
|
78
|
-
*
|
|
91
|
+
* Show update notification after command execution if available
|
|
79
92
|
*/
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
async function showUpdateNotificationIfAvailable(
|
|
94
|
+
checkPromise: Promise<{ hasUpdate: boolean; message?: string }> | null
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
if (!checkPromise) return
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
try {
|
|
99
|
+
const result = await checkPromise
|
|
100
|
+
if (result.hasUpdate && result.message) {
|
|
101
|
+
cliOutput.print(result.message)
|
|
102
|
+
cliOutput.print(chalk.gray(t('cli.updateHint')))
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Update check is best-effort, log at debug level for troubleshooting
|
|
106
|
+
logger.debug('Version update check failed', {
|
|
107
|
+
error: error instanceof Error ? error.message : String(error),
|
|
108
|
+
})
|
|
90
109
|
}
|
|
110
|
+
}
|
|
91
111
|
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Handle custom --version with update checking
|
|
114
|
+
*/
|
|
115
|
+
async function handleVersionFlag(
|
|
116
|
+
args: string[],
|
|
117
|
+
config: Awaited<ReturnType<typeof ConfigLoader.loadConfig>>
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
if (!args.includes('--version')) return
|
|
94
120
|
|
|
95
|
-
|
|
121
|
+
cliOutput.print(getPackageJson().version)
|
|
122
|
+
|
|
123
|
+
// Check for updates if not in CI and enabled in config
|
|
96
124
|
if (VersionChecker.shouldCheckForUpdates() && config.advanced?.checkForUpdates !== false) {
|
|
97
125
|
try {
|
|
98
|
-
|
|
126
|
+
cliOutput.print(chalk.gray(t('cli.checkingUpdates')))
|
|
127
|
+
const versionResult = await VersionChecker.checkVersion(getPackageJson().version, {
|
|
99
128
|
skipPrompt: false,
|
|
100
|
-
timeout:
|
|
129
|
+
timeout: 5000, // Longer timeout for explicit version check
|
|
101
130
|
})
|
|
102
131
|
|
|
103
132
|
if (versionResult.shouldPrompt) {
|
|
104
133
|
const didUpdate = await VersionChecker.promptAndUpdate(versionResult)
|
|
105
134
|
if (didUpdate) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
process.exit(0)
|
|
135
|
+
cliOutput.print(chalk.blue(t('cli.runAgain')))
|
|
136
|
+
exitWithCleanup(0)
|
|
109
137
|
}
|
|
138
|
+
} else if (versionResult.isLatest) {
|
|
139
|
+
cliOutput.print(chalk.green(t('cli.latestVersion')))
|
|
110
140
|
}
|
|
111
141
|
} catch (error) {
|
|
112
|
-
// Silently fail
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
// Silently fail update check for version command
|
|
143
|
+
logger.debug('Version flag update check failed', {
|
|
144
|
+
error: error instanceof Error ? error.message : error,
|
|
145
|
+
})
|
|
146
|
+
if (args.includes('-v') || args.includes('--verbose')) {
|
|
147
|
+
cliOutput.warn(chalk.yellow(`⚠️ ${t('cli.couldNotCheckUpdates')}`), error)
|
|
115
148
|
}
|
|
116
149
|
}
|
|
117
150
|
}
|
|
118
151
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// Configure the main command
|
|
123
|
-
program
|
|
124
|
-
.name('pcu')
|
|
125
|
-
.description('A CLI tool to check and update pnpm workspace catalog dependencies')
|
|
126
|
-
.option('--version', 'show version information')
|
|
127
|
-
.option('-v, --verbose', 'enable verbose logging')
|
|
128
|
-
.option('-w, --workspace <path>', 'workspace directory path')
|
|
129
|
-
.option('--no-color', 'disable colored output')
|
|
130
|
-
.option('-u, --update', 'shorthand for update command')
|
|
131
|
-
.option('-c, --check', 'shorthand for check command')
|
|
132
|
-
.option('-a, --analyze', 'shorthand for analyze command')
|
|
133
|
-
.option('-s, --workspace-info', 'shorthand for workspace command')
|
|
134
|
-
.option('-t, --theme', 'shorthand for theme command')
|
|
135
|
-
.option('--security-audit', 'shorthand for security command')
|
|
136
|
-
.option('--security-fix', 'shorthand for security --fix-vulns command')
|
|
137
|
-
|
|
138
|
-
// Check command
|
|
139
|
-
program
|
|
140
|
-
.command('check')
|
|
141
|
-
.alias('chk')
|
|
142
|
-
.description('check for outdated catalog dependencies')
|
|
143
|
-
.option('--catalog <name>', 'check specific catalog only')
|
|
144
|
-
.option('-f, --format <type>', 'output format: table, json, yaml, minimal', 'table')
|
|
145
|
-
.option(
|
|
146
|
-
'-t, --target <type>',
|
|
147
|
-
'update target: latest, greatest, minor, patch, newest',
|
|
148
|
-
'latest'
|
|
149
|
-
)
|
|
150
|
-
.option('--prerelease', 'include prerelease versions')
|
|
151
|
-
.option('--include <pattern>', 'include packages matching pattern', [])
|
|
152
|
-
.option('--exclude <pattern>', 'exclude packages matching pattern', [])
|
|
153
|
-
.action(async (options, command) => {
|
|
154
|
-
try {
|
|
155
|
-
const globalOptions = command.parent.opts()
|
|
156
|
-
const checkCommand = new CheckCommand(services.catalogUpdateService)
|
|
157
|
-
|
|
158
|
-
await checkCommand.execute({
|
|
159
|
-
workspace: globalOptions.workspace,
|
|
160
|
-
catalog: options.catalog,
|
|
161
|
-
format: options.format,
|
|
162
|
-
target: options.target,
|
|
163
|
-
prerelease: options.prerelease,
|
|
164
|
-
include: Array.isArray(options.include)
|
|
165
|
-
? options.include
|
|
166
|
-
: [options.include].filter(Boolean),
|
|
167
|
-
exclude: Array.isArray(options.exclude)
|
|
168
|
-
? options.exclude
|
|
169
|
-
: [options.exclude].filter(Boolean),
|
|
170
|
-
verbose: globalOptions.verbose,
|
|
171
|
-
color: !globalOptions.noColor,
|
|
172
|
-
})
|
|
173
|
-
process.exit(0)
|
|
174
|
-
} catch (error) {
|
|
175
|
-
console.error(chalk.red('❌ Error:'), error)
|
|
176
|
-
process.exit(1)
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
// Update command
|
|
181
|
-
program
|
|
182
|
-
.command('update')
|
|
183
|
-
.alias('u')
|
|
184
|
-
.description('update catalog dependencies')
|
|
185
|
-
.option('-i, --interactive', 'interactive mode to choose updates')
|
|
186
|
-
.option('-d, --dry-run', 'preview changes without writing files')
|
|
187
|
-
.option(
|
|
188
|
-
'-t, --target <type>',
|
|
189
|
-
'update target: latest, greatest, minor, patch, newest',
|
|
190
|
-
'latest'
|
|
191
|
-
)
|
|
192
|
-
.option('--catalog <name>', 'update specific catalog only')
|
|
193
|
-
.option('--include <pattern>', 'include packages matching pattern', [])
|
|
194
|
-
.option('--exclude <pattern>', 'exclude packages matching pattern', [])
|
|
195
|
-
.option('--force', 'force updates even if risky')
|
|
196
|
-
.option('--prerelease', 'include prerelease versions')
|
|
197
|
-
.option('-b, --create-backup', 'create backup files before updating')
|
|
198
|
-
.option('-f, --format <type>', 'output format: table, json, yaml, minimal', 'table')
|
|
199
|
-
.option('--ai', 'enable AI-powered batch analysis for all updates')
|
|
200
|
-
.option('--provider <name>', 'AI provider: auto, claude, gemini, codex', 'auto')
|
|
201
|
-
.option(
|
|
202
|
-
'--analysis-type <type>',
|
|
203
|
-
'AI analysis type: impact, security, compatibility, recommend',
|
|
204
|
-
'impact'
|
|
205
|
-
)
|
|
206
|
-
.option('--skip-cache', 'skip AI analysis cache')
|
|
207
|
-
.action(async (options, command) => {
|
|
208
|
-
try {
|
|
209
|
-
const globalOptions = command.parent.opts()
|
|
210
|
-
const updateCommand = new UpdateCommand(services.catalogUpdateService)
|
|
211
|
-
|
|
212
|
-
await updateCommand.execute({
|
|
213
|
-
workspace: globalOptions.workspace,
|
|
214
|
-
catalog: options.catalog,
|
|
215
|
-
format: options.format,
|
|
216
|
-
target: options.target,
|
|
217
|
-
interactive: options.interactive,
|
|
218
|
-
dryRun: options.dryRun,
|
|
219
|
-
force: options.force,
|
|
220
|
-
prerelease: options.prerelease,
|
|
221
|
-
include: Array.isArray(options.include)
|
|
222
|
-
? options.include
|
|
223
|
-
: [options.include].filter(Boolean),
|
|
224
|
-
exclude: Array.isArray(options.exclude)
|
|
225
|
-
? options.exclude
|
|
226
|
-
: [options.exclude].filter(Boolean),
|
|
227
|
-
createBackup: options.createBackup,
|
|
228
|
-
verbose: globalOptions.verbose,
|
|
229
|
-
color: !globalOptions.noColor,
|
|
230
|
-
// AI batch analysis options
|
|
231
|
-
ai: parseBooleanFlag(options.ai),
|
|
232
|
-
provider: options.provider,
|
|
233
|
-
analysisType: options.analysisType as AnalysisType,
|
|
234
|
-
skipCache: parseBooleanFlag(options.skipCache),
|
|
235
|
-
})
|
|
236
|
-
process.exit(0)
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error(chalk.red('❌ Error:'), error)
|
|
239
|
-
process.exit(1)
|
|
240
|
-
}
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
// Analyze command
|
|
244
|
-
program
|
|
245
|
-
.command('analyze')
|
|
246
|
-
.alias('a')
|
|
247
|
-
.description('analyze the impact of updating a specific dependency')
|
|
248
|
-
.argument('<package>', 'package name')
|
|
249
|
-
.argument('[version]', 'new version (default: latest)')
|
|
250
|
-
.option('--catalog <name>', 'catalog name (auto-detected if not specified)')
|
|
251
|
-
.option('-f, --format <type>', 'output format: table, json, yaml, minimal', 'table')
|
|
252
|
-
.option('--no-ai', 'disable AI-powered analysis')
|
|
253
|
-
.option('--provider <name>', 'AI provider: auto, claude, gemini, codex', 'auto')
|
|
254
|
-
.option(
|
|
255
|
-
'--analysis-type <type>',
|
|
256
|
-
'AI analysis type: impact, security, compatibility, recommend',
|
|
257
|
-
'impact'
|
|
258
|
-
)
|
|
259
|
-
.option('--skip-cache', 'skip AI analysis cache')
|
|
260
|
-
.action(async (packageName, version, options, command) => {
|
|
261
|
-
try {
|
|
262
|
-
const globalOptions = command.parent.opts()
|
|
263
|
-
const formatter = new OutputFormatter(
|
|
264
|
-
options.format as OutputFormat,
|
|
265
|
-
!globalOptions.noColor
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
// Auto-detect catalog if not specified
|
|
269
|
-
let catalog = options.catalog
|
|
270
|
-
if (!catalog) {
|
|
271
|
-
console.log(chalk.gray(`🔍 Auto-detecting catalog for ${packageName}...`))
|
|
272
|
-
catalog = await services.catalogUpdateService.findCatalogForPackage(
|
|
273
|
-
packageName,
|
|
274
|
-
globalOptions.workspace
|
|
275
|
-
)
|
|
276
|
-
if (!catalog) {
|
|
277
|
-
console.error(chalk.red(`❌ Package "${packageName}" not found in any catalog`))
|
|
278
|
-
console.log(chalk.gray('Use --catalog <name> to specify the catalog manually'))
|
|
279
|
-
process.exit(1)
|
|
280
|
-
}
|
|
281
|
-
console.log(chalk.gray(` Found in catalog: ${catalog}`))
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Get latest version if not specified
|
|
285
|
-
let targetVersion = version
|
|
286
|
-
if (!targetVersion) {
|
|
287
|
-
const tempRegistryService = new NpmRegistryService()
|
|
288
|
-
targetVersion = (await tempRegistryService.getLatestVersion(packageName)).toString()
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Get basic impact analysis first
|
|
292
|
-
const analysis = await services.catalogUpdateService.analyzeImpact(
|
|
293
|
-
catalog,
|
|
294
|
-
packageName,
|
|
295
|
-
targetVersion,
|
|
296
|
-
globalOptions.workspace
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
// AI analysis is enabled by default (use --no-ai to disable)
|
|
300
|
-
const aiEnabled = options.ai !== false
|
|
301
|
-
|
|
302
|
-
if (aiEnabled) {
|
|
303
|
-
console.log(chalk.blue('🤖 Running AI-powered analysis...'))
|
|
304
|
-
|
|
305
|
-
const aiService = new AIAnalysisService({
|
|
306
|
-
config: {
|
|
307
|
-
preferredProvider: options.provider === 'auto' ? 'auto' : options.provider,
|
|
308
|
-
cache: { enabled: !parseBooleanFlag(options.skipCache), ttl: 3600 },
|
|
309
|
-
fallback: { enabled: true, useRuleEngine: true },
|
|
310
|
-
},
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
// Get workspace info
|
|
314
|
-
const workspaceInfo = await services.workspaceService.getWorkspaceInfo(
|
|
315
|
-
globalOptions.workspace
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
// Build packages info for AI analysis
|
|
319
|
-
const packages = [
|
|
320
|
-
{
|
|
321
|
-
name: packageName,
|
|
322
|
-
currentVersion: analysis.currentVersion,
|
|
323
|
-
targetVersion: analysis.proposedVersion,
|
|
324
|
-
updateType: analysis.updateType,
|
|
325
|
-
},
|
|
326
|
-
]
|
|
327
|
-
|
|
328
|
-
// Build workspace info for AI
|
|
329
|
-
const wsInfo = {
|
|
330
|
-
name: workspaceInfo.name,
|
|
331
|
-
path: workspaceInfo.path,
|
|
332
|
-
packageCount: workspaceInfo.packageCount,
|
|
333
|
-
catalogCount: workspaceInfo.catalogCount,
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
try {
|
|
337
|
-
const aiResult = await aiService.analyzeUpdates(packages, wsInfo, {
|
|
338
|
-
analysisType: options.analysisType as AnalysisType,
|
|
339
|
-
skipCache: parseBooleanFlag(options.skipCache),
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
// Format and display AI analysis result
|
|
343
|
-
const aiOutput = formatter.formatAIAnalysis(aiResult, analysis)
|
|
344
|
-
console.log(aiOutput)
|
|
345
|
-
process.exit(0)
|
|
346
|
-
} catch (aiError) {
|
|
347
|
-
console.warn(chalk.yellow('⚠️ AI analysis failed, showing basic analysis:'))
|
|
348
|
-
if (globalOptions.verbose) {
|
|
349
|
-
console.warn(chalk.gray(String(aiError)))
|
|
350
|
-
}
|
|
351
|
-
// Fall back to basic analysis
|
|
352
|
-
const formattedOutput = formatter.formatImpactAnalysis(analysis)
|
|
353
|
-
console.log(formattedOutput)
|
|
354
|
-
process.exit(0)
|
|
355
|
-
}
|
|
356
|
-
} else {
|
|
357
|
-
// Standard analysis without AI
|
|
358
|
-
const formattedOutput = formatter.formatImpactAnalysis(analysis)
|
|
359
|
-
console.log(formattedOutput)
|
|
360
|
-
}
|
|
361
|
-
} catch (error) {
|
|
362
|
-
console.error(chalk.red('❌ Error:'), error)
|
|
363
|
-
process.exit(1)
|
|
364
|
-
}
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
// Workspace command
|
|
368
|
-
program
|
|
369
|
-
.command('workspace')
|
|
370
|
-
.alias('w')
|
|
371
|
-
.description('workspace information and validation')
|
|
372
|
-
.option('--validate', 'validate workspace configuration')
|
|
373
|
-
.option('-s, --stats', 'show workspace statistics')
|
|
374
|
-
.option('-f, --format <type>', 'output format: table, json, yaml, minimal', 'table')
|
|
375
|
-
.action(async (options, command) => {
|
|
376
|
-
try {
|
|
377
|
-
const globalOptions = command.parent.opts()
|
|
378
|
-
const formatter = new OutputFormatter(
|
|
379
|
-
options.format as OutputFormat,
|
|
380
|
-
!globalOptions.noColor
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
if (options.validate) {
|
|
384
|
-
const report = await services.workspaceService.validateWorkspace(globalOptions.workspace)
|
|
385
|
-
const formattedOutput = formatter.formatValidationReport(report)
|
|
386
|
-
console.log(formattedOutput)
|
|
387
|
-
process.exit(0)
|
|
388
|
-
if (!report.isValid) {
|
|
389
|
-
process.exit(1)
|
|
390
|
-
}
|
|
391
|
-
} else if (options.stats) {
|
|
392
|
-
const stats = await services.workspaceService.getWorkspaceStats(globalOptions.workspace)
|
|
393
|
-
const formattedOutput = formatter.formatWorkspaceStats(stats)
|
|
394
|
-
console.log(formattedOutput)
|
|
395
|
-
process.exit(0)
|
|
396
|
-
} else {
|
|
397
|
-
const info = await services.workspaceService.getWorkspaceInfo(globalOptions.workspace)
|
|
398
|
-
console.log(formatter.formatMessage(`Workspace: ${info.name}`, 'info'))
|
|
399
|
-
console.log(formatter.formatMessage(`Path: ${info.path}`, 'info'))
|
|
400
|
-
console.log(formatter.formatMessage(`Packages: ${info.packageCount}`, 'info'))
|
|
401
|
-
console.log(formatter.formatMessage(`Catalogs: ${info.catalogCount}`, 'info'))
|
|
402
|
-
|
|
403
|
-
if (info.catalogNames.length > 0) {
|
|
404
|
-
console.log(
|
|
405
|
-
formatter.formatMessage(`Catalog names: ${info.catalogNames.join(', ')}`, 'info')
|
|
406
|
-
)
|
|
407
|
-
}
|
|
408
|
-
process.exit(0)
|
|
409
|
-
}
|
|
410
|
-
} catch (error) {
|
|
411
|
-
console.error(chalk.red('❌ Error:'), error)
|
|
412
|
-
process.exit(1)
|
|
413
|
-
}
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
// Theme command
|
|
417
|
-
program
|
|
418
|
-
.command('theme')
|
|
419
|
-
.alias('t')
|
|
420
|
-
.description('configure color theme')
|
|
421
|
-
.option('-s, --set <theme>', 'set theme: default, modern, minimal, neon')
|
|
422
|
-
.option('-l, --list', 'list available themes')
|
|
423
|
-
.option('-i, --interactive', 'interactive theme selection')
|
|
424
|
-
.action(async (options, _command) => {
|
|
425
|
-
try {
|
|
426
|
-
if (options.list) {
|
|
427
|
-
const themes = ThemeManager.listThemes()
|
|
428
|
-
console.log(StyledText.iconInfo('Available themes:'))
|
|
429
|
-
themes.forEach((theme) => {
|
|
430
|
-
console.log(` • ${theme}`)
|
|
431
|
-
})
|
|
432
|
-
return
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (options.set) {
|
|
436
|
-
const themes = ThemeManager.listThemes()
|
|
437
|
-
if (!themes.includes(options.set)) {
|
|
438
|
-
console.error(StyledText.iconError(`Invalid theme: ${options.set}`))
|
|
439
|
-
console.log(StyledText.muted(`Available themes: ${themes.join(', ')}`))
|
|
440
|
-
process.exit(1)
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
ThemeManager.setTheme(options.set)
|
|
444
|
-
console.log(StyledText.iconSuccess(`Theme set to: ${options.set}`))
|
|
445
|
-
|
|
446
|
-
// Show a preview
|
|
447
|
-
console.log('\nTheme preview:')
|
|
448
|
-
const theme = ThemeManager.getTheme()
|
|
449
|
-
console.log(` ${theme.success('✓ Success message')}`)
|
|
450
|
-
console.log(` ${theme.warning('⚠ Warning message')}`)
|
|
451
|
-
console.log(` ${theme.error('✗ Error message')}`)
|
|
452
|
-
console.log(` ${theme.info('ℹ Info message')}`)
|
|
453
|
-
console.log(
|
|
454
|
-
` ${theme.major('Major update')} | ${theme.minor('Minor update')} | ${theme.patch('Patch update')}`
|
|
455
|
-
)
|
|
456
|
-
return
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (options.interactive) {
|
|
460
|
-
const interactivePrompts = new InteractivePrompts()
|
|
461
|
-
const config = await interactivePrompts.configurationWizard()
|
|
462
|
-
|
|
463
|
-
if (config.theme) {
|
|
464
|
-
ThemeManager.setTheme(config.theme)
|
|
465
|
-
console.log(StyledText.iconSuccess(`Theme configured: ${config.theme}`))
|
|
466
|
-
}
|
|
467
|
-
return
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Default: show current theme and list
|
|
471
|
-
const currentTheme = ThemeManager.getTheme()
|
|
472
|
-
console.log(StyledText.iconInfo('Current theme settings:'))
|
|
473
|
-
console.log(` Theme: ${currentTheme ? 'custom' : 'default'}`)
|
|
474
|
-
console.log('\nAvailable themes:')
|
|
475
|
-
ThemeManager.listThemes().forEach((theme) => {
|
|
476
|
-
console.log(` • ${theme}`)
|
|
477
|
-
})
|
|
478
|
-
console.log(
|
|
479
|
-
StyledText.muted('\nUse --set <theme> to change theme or --interactive for guided setup')
|
|
480
|
-
)
|
|
481
|
-
} catch (error) {
|
|
482
|
-
console.error(StyledText.iconError('Error configuring theme:'), error)
|
|
483
|
-
process.exit(1)
|
|
484
|
-
}
|
|
485
|
-
})
|
|
486
|
-
|
|
487
|
-
// Security command
|
|
488
|
-
program
|
|
489
|
-
.command('security')
|
|
490
|
-
.alias('sec')
|
|
491
|
-
.description('security vulnerability scanning and automated fixes')
|
|
492
|
-
.option('-f, --format <type>', 'output format: table, json, yaml, minimal', 'table')
|
|
493
|
-
.option('--audit', 'perform npm audit scan (default: true)', true)
|
|
494
|
-
.option('--fix-vulns', 'automatically fix vulnerabilities')
|
|
495
|
-
.option('--severity <level>', 'filter by severity: low, moderate, high, critical')
|
|
496
|
-
.option('--include-dev', 'include dev dependencies in scan')
|
|
497
|
-
.option('--snyk', 'include Snyk scan (requires snyk CLI)')
|
|
498
|
-
.action(async (options, command) => {
|
|
499
|
-
try {
|
|
500
|
-
const globalOptions = command.parent.opts()
|
|
501
|
-
const formatter = new OutputFormatter(
|
|
502
|
-
options.format as OutputFormat,
|
|
503
|
-
!globalOptions.noColor
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
const securityCommand = new SecurityCommand(formatter)
|
|
507
|
-
|
|
508
|
-
await securityCommand.execute({
|
|
509
|
-
workspace: globalOptions.workspace,
|
|
510
|
-
format: options.format,
|
|
511
|
-
audit: options.audit,
|
|
512
|
-
fixVulns: options.fixVulns,
|
|
513
|
-
severity: options.severity,
|
|
514
|
-
includeDev: options.includeDev,
|
|
515
|
-
snyk: options.snyk,
|
|
516
|
-
verbose: globalOptions.verbose,
|
|
517
|
-
color: !globalOptions.noColor,
|
|
518
|
-
})
|
|
519
|
-
process.exit(0)
|
|
520
|
-
} catch (error) {
|
|
521
|
-
console.error(chalk.red('❌ Error:'), error)
|
|
522
|
-
process.exit(1)
|
|
523
|
-
}
|
|
524
|
-
})
|
|
525
|
-
|
|
526
|
-
// Init command
|
|
527
|
-
program
|
|
528
|
-
.command('init')
|
|
529
|
-
.alias('i')
|
|
530
|
-
.description('initialize PCU configuration and PNPM workspace')
|
|
531
|
-
.option('--force', 'overwrite existing configuration file')
|
|
532
|
-
.option('--full', 'generate full configuration with all options')
|
|
533
|
-
.option(
|
|
534
|
-
'--create-workspace',
|
|
535
|
-
'create PNPM workspace structure if missing (default: true)',
|
|
536
|
-
true
|
|
537
|
-
)
|
|
538
|
-
.option('--no-create-workspace', 'skip creating PNPM workspace structure')
|
|
539
|
-
.option('-f, --format <type>', 'output format: table, json, yaml, minimal', 'table')
|
|
540
|
-
.action(async (options, command) => {
|
|
541
|
-
try {
|
|
542
|
-
const globalOptions = command.parent.opts()
|
|
543
|
-
const initCommand = new InitCommand()
|
|
544
|
-
|
|
545
|
-
await initCommand.execute({
|
|
546
|
-
workspace: globalOptions.workspace,
|
|
547
|
-
force: options.force,
|
|
548
|
-
full: options.full,
|
|
549
|
-
createWorkspace: options.createWorkspace,
|
|
550
|
-
verbose: globalOptions.verbose,
|
|
551
|
-
color: !globalOptions.noColor,
|
|
552
|
-
})
|
|
553
|
-
process.exit(0)
|
|
554
|
-
} catch (error) {
|
|
555
|
-
console.error(chalk.red('❌ Error:'), error)
|
|
556
|
-
process.exit(1)
|
|
557
|
-
}
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
// AI command - check AI provider status and availability
|
|
561
|
-
program
|
|
562
|
-
.command('ai')
|
|
563
|
-
.description('check AI provider status and availability')
|
|
564
|
-
.option('--status', 'show status of all AI providers (default)')
|
|
565
|
-
.option('--test', 'test AI analysis with a sample request')
|
|
566
|
-
.option('--cache-stats', 'show AI analysis cache statistics')
|
|
567
|
-
.option('--clear-cache', 'clear AI analysis cache')
|
|
568
|
-
.action(async (options) => {
|
|
569
|
-
try {
|
|
570
|
-
const aiDetector = new AIDetector()
|
|
571
|
-
|
|
572
|
-
if (options.clearCache) {
|
|
573
|
-
// Import the cache singleton
|
|
574
|
-
const { analysisCache } = await import('@pcu/core')
|
|
575
|
-
analysisCache.clear()
|
|
576
|
-
console.log(chalk.green('✅ AI analysis cache cleared'))
|
|
577
|
-
process.exit(0)
|
|
578
|
-
return
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
if (options.cacheStats) {
|
|
582
|
-
const { analysisCache } = await import('@pcu/core')
|
|
583
|
-
const stats = analysisCache.getStats()
|
|
584
|
-
console.log(chalk.blue('📊 AI Analysis Cache Statistics'))
|
|
585
|
-
console.log(chalk.gray('─────────────────────────────────'))
|
|
586
|
-
console.log(` Total entries: ${chalk.cyan(stats.totalEntries)}`)
|
|
587
|
-
console.log(` Cache hits: ${chalk.green(stats.hits)}`)
|
|
588
|
-
console.log(` Cache misses: ${chalk.yellow(stats.misses)}`)
|
|
589
|
-
console.log(` Hit rate: ${chalk.cyan(`${(stats.hitRate * 100).toFixed(1)}%`)}`)
|
|
590
|
-
process.exit(0)
|
|
591
|
-
return
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (options.test) {
|
|
595
|
-
console.log(chalk.blue('🧪 Testing AI analysis...'))
|
|
596
|
-
|
|
597
|
-
const aiService = new AIAnalysisService({
|
|
598
|
-
config: {
|
|
599
|
-
fallback: { enabled: true, useRuleEngine: true },
|
|
600
|
-
},
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
const testPackages = [
|
|
604
|
-
{
|
|
605
|
-
name: 'lodash',
|
|
606
|
-
currentVersion: '4.17.20',
|
|
607
|
-
targetVersion: '4.17.21',
|
|
608
|
-
updateType: 'patch' as const,
|
|
609
|
-
},
|
|
610
|
-
]
|
|
152
|
+
exitWithCleanup(0)
|
|
153
|
+
}
|
|
611
154
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}
|
|
155
|
+
/**
|
|
156
|
+
* Main CLI function
|
|
157
|
+
*/
|
|
158
|
+
export async function main(): Promise<void> {
|
|
159
|
+
const program = new Command()
|
|
618
160
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
analysisType: 'impact',
|
|
622
|
-
})
|
|
623
|
-
console.log(chalk.green('✅ AI analysis test successful!'))
|
|
624
|
-
console.log(chalk.gray('─────────────────────────────────'))
|
|
625
|
-
console.log(` Provider: ${chalk.cyan(result.provider)}`)
|
|
626
|
-
console.log(` Confidence: ${chalk.cyan(`${(result.confidence * 100).toFixed(0)}%`)}`)
|
|
627
|
-
console.log(` Summary: ${result.summary}`)
|
|
628
|
-
} catch (error) {
|
|
629
|
-
console.log(chalk.yellow('⚠️ AI analysis test failed:'))
|
|
630
|
-
console.log(chalk.gray(String(error)))
|
|
631
|
-
}
|
|
632
|
-
process.exit(0)
|
|
633
|
-
return
|
|
634
|
-
}
|
|
161
|
+
// Parse arguments first to get workspace path
|
|
162
|
+
let workspacePath: string | undefined
|
|
635
163
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
164
|
+
// Extract workspace path from arguments for service creation
|
|
165
|
+
const workspaceIndex = process.argv.findIndex((arg) => arg === '-w' || arg === '--workspace')
|
|
166
|
+
if (workspaceIndex !== -1 && workspaceIndex + 1 < process.argv.length) {
|
|
167
|
+
workspacePath = process.argv[workspaceIndex + 1]
|
|
168
|
+
}
|
|
639
169
|
|
|
640
|
-
|
|
641
|
-
|
|
170
|
+
// Load configuration to check if version updates are enabled
|
|
171
|
+
const config = await ConfigLoader.loadConfig(workspacePath || process.cwd())
|
|
642
172
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
console.log(chalk.blue('📋 Provider Details'))
|
|
646
|
-
console.log(chalk.gray('─────────────────────────────────'))
|
|
173
|
+
// Initialize i18n with config locale (priority: config > env > system)
|
|
174
|
+
I18n.init(config.locale)
|
|
647
175
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const priorityText = chalk.gray(`(priority: ${provider.priority})`)
|
|
176
|
+
// Start cache initialization in background (non-blocking)
|
|
177
|
+
// This allows the cache to load from disk while CLI continues startup
|
|
178
|
+
startCacheInitialization()
|
|
652
179
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
180
|
+
// PERF-001: Preload package suggestions in background (non-blocking)
|
|
181
|
+
// This loads the suggestions file asynchronously during CLI startup
|
|
182
|
+
preloadPackageSuggestions()
|
|
656
183
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
if (provider.available && provider.version) {
|
|
661
|
-
console.log(chalk.gray(` Version: ${provider.version}`))
|
|
662
|
-
}
|
|
663
|
-
}
|
|
184
|
+
// Start background version check (non-blocking)
|
|
185
|
+
const versionCheckPromise = startBackgroundVersionCheck(config)
|
|
664
186
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
console.log(chalk.green(`✨ Best available provider: ${best.name}`))
|
|
669
|
-
}
|
|
670
|
-
process.exit(0)
|
|
671
|
-
} catch (error) {
|
|
672
|
-
console.error(chalk.red('❌ Error:'), error)
|
|
673
|
-
process.exit(1)
|
|
674
|
-
}
|
|
675
|
-
})
|
|
187
|
+
// Create lazy service factory - services will only be instantiated when actually needed
|
|
188
|
+
// This means --help and --version won't trigger service creation
|
|
189
|
+
const serviceFactory = new LazyServiceFactory(workspacePath)
|
|
676
190
|
|
|
677
|
-
//
|
|
191
|
+
// Configure the main command
|
|
678
192
|
program
|
|
679
|
-
.
|
|
680
|
-
.
|
|
681
|
-
.
|
|
682
|
-
.
|
|
683
|
-
.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
cmd.help()
|
|
688
|
-
} else {
|
|
689
|
-
console.log(chalk.red(`Unknown command: ${command}`))
|
|
690
|
-
}
|
|
691
|
-
} else {
|
|
692
|
-
program.help()
|
|
693
|
-
}
|
|
694
|
-
})
|
|
695
|
-
|
|
696
|
-
// Let commander handle help and version normally
|
|
697
|
-
// program.exitOverride() removed to fix help/version output
|
|
193
|
+
.name('pcu')
|
|
194
|
+
.description(t('cli.description.main'))
|
|
195
|
+
.helpCommand(t('cli.help.command'), t('cli.help.description'))
|
|
196
|
+
.helpOption('-h, --help', t('cli.help.option'))
|
|
197
|
+
.option('--version', t('cli.option.version'))
|
|
198
|
+
.option('-v, --verbose', t('cli.option.verbose'))
|
|
199
|
+
.option('-w, --workspace <path>', t('cli.option.workspace'))
|
|
200
|
+
.option('--no-color', t('cli.option.noColor'))
|
|
698
201
|
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
// Map single-letter command 'i' -> init (changed from interactive mode)
|
|
702
|
-
if (
|
|
703
|
-
args.includes('i') &&
|
|
704
|
-
!args.some(
|
|
705
|
-
(a) =>
|
|
706
|
-
a === 'init' ||
|
|
707
|
-
a === 'update' ||
|
|
708
|
-
a === '-u' ||
|
|
709
|
-
a === '--update' ||
|
|
710
|
-
a === '-i' ||
|
|
711
|
-
a === '--interactive'
|
|
712
|
-
)
|
|
713
|
-
) {
|
|
714
|
-
const index = args.indexOf('i')
|
|
715
|
-
args.splice(index, 1, 'init')
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (args.includes('-u') || args.includes('--update')) {
|
|
719
|
-
const index = args.findIndex((arg) => arg === '-u' || arg === '--update')
|
|
720
|
-
args.splice(index, 1, 'update')
|
|
721
|
-
} else if (
|
|
722
|
-
(args.includes('-i') || args.includes('--interactive')) &&
|
|
723
|
-
!args.some((a) => a === 'update' || a === '-u' || a === '--update')
|
|
724
|
-
) {
|
|
725
|
-
// Map standalone -i to `update -i`
|
|
726
|
-
const index = args.findIndex((arg) => arg === '-i' || arg === '--interactive')
|
|
727
|
-
// Replace the flag position with 'update' and keep the flag after it
|
|
728
|
-
args.splice(index, 1, 'update', '-i')
|
|
729
|
-
} else if (args.includes('-c') || args.includes('--check')) {
|
|
730
|
-
const index = args.findIndex((arg) => arg === '-c' || arg === '--check')
|
|
731
|
-
args.splice(index, 1, 'check')
|
|
732
|
-
} else if (args.includes('-a') || args.includes('--analyze')) {
|
|
733
|
-
const index = args.findIndex((arg) => arg === '-a' || arg === '--analyze')
|
|
734
|
-
args.splice(index, 1, 'analyze')
|
|
735
|
-
} else if (args.includes('-s') || args.includes('--workspace-info')) {
|
|
736
|
-
const index = args.findIndex((arg) => arg === '-s' || arg === '--workspace-info')
|
|
737
|
-
args.splice(index, 1, 'workspace')
|
|
738
|
-
} else if (args.includes('-t') || args.includes('--theme')) {
|
|
739
|
-
const index = args.findIndex((arg) => arg === '-t' || arg === '--theme')
|
|
740
|
-
args.splice(index, 1, 'theme')
|
|
741
|
-
} else if (args.includes('--security-audit')) {
|
|
742
|
-
const index = args.indexOf('--security-audit')
|
|
743
|
-
args.splice(index, 1, 'security')
|
|
744
|
-
} else if (args.includes('--security-fix')) {
|
|
745
|
-
const index = args.indexOf('--security-fix')
|
|
746
|
-
args.splice(index, 1, 'security', '--fix-vulns')
|
|
747
|
-
}
|
|
202
|
+
// Register all commands with lazy service factory
|
|
203
|
+
registerCommands(program, serviceFactory, getPackageJson())
|
|
748
204
|
|
|
749
205
|
// Show help if no arguments provided
|
|
750
|
-
if (
|
|
206
|
+
if (process.argv.length <= 2) {
|
|
751
207
|
program.help()
|
|
752
208
|
}
|
|
753
209
|
|
|
754
210
|
// Handle custom --version with update checking
|
|
755
|
-
|
|
756
|
-
console.log(packageJson.version)
|
|
757
|
-
|
|
758
|
-
// Check for updates if not in CI and enabled in config
|
|
759
|
-
if (VersionChecker.shouldCheckForUpdates() && config.advanced?.checkForUpdates !== false) {
|
|
760
|
-
try {
|
|
761
|
-
console.log(chalk.gray('Checking for updates...'))
|
|
762
|
-
const versionResult = await VersionChecker.checkVersion(packageJson.version, {
|
|
763
|
-
skipPrompt: false,
|
|
764
|
-
timeout: 5000, // Longer timeout for explicit version check
|
|
765
|
-
})
|
|
766
|
-
|
|
767
|
-
if (versionResult.shouldPrompt) {
|
|
768
|
-
const didUpdate = await VersionChecker.promptAndUpdate(versionResult)
|
|
769
|
-
if (didUpdate) {
|
|
770
|
-
console.log(chalk.blue('Please run your command again to use the updated version.'))
|
|
771
|
-
process.exit(0)
|
|
772
|
-
}
|
|
773
|
-
} else if (versionResult.isLatest) {
|
|
774
|
-
console.log(chalk.green('You are using the latest version!'))
|
|
775
|
-
}
|
|
776
|
-
} catch (error) {
|
|
777
|
-
// Silently fail update check for version command
|
|
778
|
-
if (args.includes('-v') || args.includes('--verbose')) {
|
|
779
|
-
console.warn(chalk.yellow('⚠️ Could not check for updates:'), error)
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
process.exit(0)
|
|
785
|
-
}
|
|
211
|
+
await handleVersionFlag(process.argv, config)
|
|
786
212
|
|
|
787
213
|
// Parse command line arguments
|
|
788
214
|
try {
|
|
789
|
-
await program.parseAsync(
|
|
215
|
+
await program.parseAsync(process.argv)
|
|
216
|
+
|
|
217
|
+
// Show update notification after successful command execution
|
|
218
|
+
await showUpdateNotificationIfAvailable(versionCheckPromise)
|
|
790
219
|
} catch (error) {
|
|
791
|
-
|
|
220
|
+
if (isCommandExitError(error)) {
|
|
221
|
+
exitWithCleanup(error.exitCode)
|
|
222
|
+
}
|
|
223
|
+
// Handle user cancellation (Ctrl+C) gracefully
|
|
224
|
+
if (isExitPromptError(error)) {
|
|
225
|
+
cliOutput.print(chalk.gray(`\n${t('cli.cancelled')}`))
|
|
226
|
+
exitWithCleanup(0)
|
|
227
|
+
}
|
|
228
|
+
logger.error('CLI parse error', error instanceof Error ? error : undefined, {
|
|
229
|
+
args: process.argv,
|
|
230
|
+
})
|
|
231
|
+
cliOutput.error(chalk.red(`❌ ${t('cli.unexpectedError')}`), error)
|
|
792
232
|
if (error instanceof Error && error.stack) {
|
|
793
|
-
|
|
233
|
+
cliOutput.error(chalk.gray(error.stack))
|
|
794
234
|
}
|
|
795
|
-
|
|
235
|
+
exitWithCleanup(1)
|
|
796
236
|
}
|
|
797
237
|
}
|
|
798
238
|
|
|
799
239
|
// Run the CLI if this file is executed directly
|
|
800
240
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
801
241
|
main().catch((error) => {
|
|
802
|
-
|
|
803
|
-
|
|
242
|
+
if (isCommandExitError(error)) {
|
|
243
|
+
exitWithCleanup(error.exitCode)
|
|
244
|
+
}
|
|
245
|
+
// Handle user cancellation (Ctrl+C) gracefully
|
|
246
|
+
if (isExitPromptError(error)) {
|
|
247
|
+
cliOutput.print(chalk.gray(`\n${t('cli.cancelled')}`))
|
|
248
|
+
exitWithCleanup(0)
|
|
249
|
+
}
|
|
250
|
+
logger.error('Fatal CLI error', error instanceof Error ? error : undefined)
|
|
251
|
+
cliOutput.error(chalk.red(`❌ ${t('cli.fatalError')}`), error)
|
|
252
|
+
exitWithCleanup(1)
|
|
804
253
|
})
|
|
805
254
|
}
|