@inspecto-dev/cli 0.2.0-alpha.4 → 0.2.0-alpha.6
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/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-test.log +16 -21
- package/CHANGELOG.md +12 -0
- package/README.md +58 -11
- package/bin/inspecto.js +5 -1
- package/dist/bin.d.ts +5 -1
- package/dist/bin.js +89 -50
- package/dist/{chunk-EUCQCD3Y.js → chunk-PDDFPQJS.js} +1954 -1053
- package/dist/index.d.ts +128 -2
- package/dist/index.js +15 -3
- package/package.json +2 -1
- package/src/bin.ts +139 -67
- package/src/commands/apply.ts +114 -0
- package/src/commands/detect.ts +59 -0
- package/src/commands/doctor.ts +225 -72
- package/src/commands/init.ts +106 -183
- package/src/commands/plan.ts +41 -0
- package/src/detect/build-tool.ts +107 -3
- package/src/index.ts +13 -2
- package/src/inject/ast-injector.ts +20 -9
- package/src/inject/extension.ts +3 -1
- package/src/inject/strategies/vite.ts +2 -1
- package/src/instructions.ts +60 -46
- package/src/onboarding/apply.ts +325 -0
- package/src/onboarding/context.ts +36 -0
- package/src/onboarding/planner.ts +278 -0
- package/src/prompts.ts +54 -11
- package/src/types.ts +95 -0
- package/src/utils/fs.ts +2 -1
- package/src/utils/logger.ts +9 -0
- package/src/utils/output.ts +40 -0
- package/tests/apply.test.ts +537 -0
- package/tests/ast-injector.test.ts +50 -0
- package/tests/build-tool.test.ts +3 -5
- package/tests/detect.test.ts +94 -0
- package/tests/doctor.test.ts +224 -0
- package/tests/init.test.ts +333 -0
- package/tests/instructions.test.ts +61 -0
- package/tests/logger.test.ts +100 -0
- package/tests/plan.test.ts +713 -0
- package/tests/workspace-build-tool.test.ts +75 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { buildOnboardingContext } from './context.js'
|
|
2
|
+
import type {
|
|
3
|
+
CommandMessage,
|
|
4
|
+
CommandStatus,
|
|
5
|
+
DetectionResult,
|
|
6
|
+
OnboardingContext,
|
|
7
|
+
PlanResult,
|
|
8
|
+
} from '../types.js'
|
|
9
|
+
|
|
10
|
+
function message(code: string, message: string): CommandMessage {
|
|
11
|
+
return { code, message }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function uniqueMessages(messages: CommandMessage[]): CommandMessage[] {
|
|
15
|
+
const seen = new Set<string>()
|
|
16
|
+
return messages.filter(item => {
|
|
17
|
+
const key = `${item.code}:${item.message}`
|
|
18
|
+
if (seen.has(key)) return false
|
|
19
|
+
seen.add(key)
|
|
20
|
+
return true
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function detectionStatus(warnings: CommandMessage[], blockers: CommandMessage[]): CommandStatus {
|
|
25
|
+
if (blockers.length > 0) return 'blocked'
|
|
26
|
+
if (warnings.length > 0) return 'warning'
|
|
27
|
+
return 'ok'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function planStatus(warnings: CommandMessage[], blockers: CommandMessage[]): CommandStatus {
|
|
31
|
+
if (blockers.length > 0) return 'blocked'
|
|
32
|
+
if (warnings.length > 0) return 'warning'
|
|
33
|
+
return 'ok'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function supportedIde(context: OnboardingContext): string | undefined {
|
|
37
|
+
return context.ides.find(ide => ide.supported)?.ide
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function supportedProvider(context: OnboardingContext): string | undefined {
|
|
41
|
+
return context.providers.find(provider => provider.supported)?.id
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildToolBlockers(context: OnboardingContext): CommandMessage[] {
|
|
45
|
+
if (context.buildTools.unsupported.length > 0) {
|
|
46
|
+
return [
|
|
47
|
+
message(
|
|
48
|
+
'unsupported-build-tool',
|
|
49
|
+
`Detected unsupported build tool(s): ${context.buildTools.unsupported.join(', ')}`,
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (context.buildTools.supported.length > 0) {
|
|
55
|
+
if (context.buildTools.supported.length === 1) {
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const targets = context.buildTools.supported
|
|
60
|
+
.map(target => target.packagePath ?? target.configPath)
|
|
61
|
+
.join(', ')
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
message(
|
|
65
|
+
'multiple-supported-build-targets',
|
|
66
|
+
`Multiple supported build targets detected: ${targets}. Run inspecto apply from a single app/package root until explicit target selection is available.`,
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return [message('missing-build-tool', 'No supported build tool detected')]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function frameworkBlockers(context: OnboardingContext): CommandMessage[] {
|
|
75
|
+
if (context.frameworks.supported.length > 0) {
|
|
76
|
+
return []
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (context.frameworks.unsupported.length > 0) {
|
|
80
|
+
return [
|
|
81
|
+
message(
|
|
82
|
+
'unsupported-framework',
|
|
83
|
+
`Detected unsupported framework(s): ${context.frameworks.unsupported.join(', ')}`,
|
|
84
|
+
),
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return [message('missing-framework', 'No supported frontend framework detected')]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unsupportedEnvironmentWarnings(context: OnboardingContext): CommandMessage[] {
|
|
92
|
+
const warnings: CommandMessage[] = []
|
|
93
|
+
|
|
94
|
+
if (context.frameworks.unsupported.length > 0 && context.frameworks.supported.length > 0) {
|
|
95
|
+
warnings.push(
|
|
96
|
+
message(
|
|
97
|
+
'unsupported-framework-present',
|
|
98
|
+
`Unsupported framework(s) also detected: ${context.frameworks.unsupported.join(', ')}`,
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const unsupportedIdes = context.ides.filter(ide => !ide.supported).map(ide => ide.ide)
|
|
104
|
+
if (unsupportedIdes.length > 0) {
|
|
105
|
+
warnings.push(
|
|
106
|
+
message('unsupported-ide', `Unsupported IDE(s) detected: ${unsupportedIdes.join(', ')}`),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const unsupportedProviders = context.providers
|
|
111
|
+
.filter(provider => !provider.supported)
|
|
112
|
+
.map(provider => provider.label)
|
|
113
|
+
if (unsupportedProviders.length > 0) {
|
|
114
|
+
warnings.push(
|
|
115
|
+
message(
|
|
116
|
+
'unsupported-provider',
|
|
117
|
+
`Unsupported provider(s) detected: ${unsupportedProviders.join(', ')}`,
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return warnings
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function manualBuildToolActions(context: OnboardingContext): PlanResult['actions'] {
|
|
126
|
+
if (context.buildTools.unsupported.length > 0) {
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
type: 'manual_step',
|
|
130
|
+
target: context.buildTools.unsupported.join(', '),
|
|
131
|
+
description:
|
|
132
|
+
'Inspecto cannot auto-configure this build stack yet. Follow the manual setup guide for the detected framework or build tool.',
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (context.buildTools.supported.length > 1) {
|
|
138
|
+
const targets = context.buildTools.supported
|
|
139
|
+
.map(target => target.packagePath ?? target.configPath)
|
|
140
|
+
.join(', ')
|
|
141
|
+
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
type: 'manual_step',
|
|
145
|
+
target: targets,
|
|
146
|
+
description:
|
|
147
|
+
'Run inspecto apply from the target app/package root. Root-level apply is blocked when multiple supported targets are detected.',
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return [
|
|
153
|
+
{
|
|
154
|
+
type: 'manual_step',
|
|
155
|
+
target: context.root,
|
|
156
|
+
description:
|
|
157
|
+
'No supported build tool was detected. Add a supported build config before trying Inspecto again.',
|
|
158
|
+
},
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function manualFrameworkActions(context: OnboardingContext): PlanResult['actions'] {
|
|
163
|
+
if (context.frameworks.unsupported.length > 0) {
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
type: 'manual_step',
|
|
167
|
+
target: context.frameworks.unsupported.join(', '),
|
|
168
|
+
description:
|
|
169
|
+
'Inspecto cannot auto-configure this framework yet. Follow the manual setup guide for the detected framework.',
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return [
|
|
175
|
+
{
|
|
176
|
+
type: 'manual_step',
|
|
177
|
+
target: context.root,
|
|
178
|
+
description:
|
|
179
|
+
'No supported frontend framework was detected. Add a supported React or Vue app before trying Inspecto again.',
|
|
180
|
+
},
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function createDetectionResult(root: string): Promise<DetectionResult> {
|
|
185
|
+
const context = await buildOnboardingContext(root)
|
|
186
|
+
const warnings = uniqueMessages([...unsupportedEnvironmentWarnings(context)])
|
|
187
|
+
|
|
188
|
+
const buildToolResult = buildToolBlockers(context)
|
|
189
|
+
const frameworkResult = frameworkBlockers(context)
|
|
190
|
+
const blockers = uniqueMessages([...buildToolResult, ...frameworkResult])
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
status: detectionStatus(warnings, blockers),
|
|
194
|
+
warnings,
|
|
195
|
+
blockers,
|
|
196
|
+
project: {
|
|
197
|
+
root: context.root,
|
|
198
|
+
packageManager: context.packageManager,
|
|
199
|
+
},
|
|
200
|
+
environment: {
|
|
201
|
+
frameworks: context.frameworks.supported,
|
|
202
|
+
unsupportedFrameworks: context.frameworks.unsupported,
|
|
203
|
+
buildTools: context.buildTools.supported,
|
|
204
|
+
unsupportedBuildTools: context.buildTools.unsupported,
|
|
205
|
+
ides: context.ides,
|
|
206
|
+
providers: context.providers,
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function createPlanResult(context: OnboardingContext): PlanResult {
|
|
212
|
+
const warnings = uniqueMessages(unsupportedEnvironmentWarnings(context))
|
|
213
|
+
const blockers = uniqueMessages([...buildToolBlockers(context), ...frameworkBlockers(context)])
|
|
214
|
+
const actions: PlanResult['actions'] = []
|
|
215
|
+
|
|
216
|
+
let strategy: PlanResult['strategy'] = 'supported'
|
|
217
|
+
|
|
218
|
+
if (blockers.length > 0) {
|
|
219
|
+
strategy = 'manual'
|
|
220
|
+
if (
|
|
221
|
+
context.buildTools.unsupported.length > 0 ||
|
|
222
|
+
context.buildTools.supported.length === 0 ||
|
|
223
|
+
context.buildTools.supported.length > 1
|
|
224
|
+
) {
|
|
225
|
+
actions.push(...manualBuildToolActions(context))
|
|
226
|
+
}
|
|
227
|
+
if (frameworkBlockers(context).length > 0) {
|
|
228
|
+
actions.push(...manualFrameworkActions(context))
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
actions.push({
|
|
232
|
+
type: 'install_dependency',
|
|
233
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
234
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`,
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
for (const buildTool of context.buildTools.supported) {
|
|
238
|
+
actions.push({
|
|
239
|
+
type: 'modify_file',
|
|
240
|
+
target: buildTool.configPath,
|
|
241
|
+
description: `Inject the Inspecto plugin into ${buildTool.label}.`,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const ide = supportedIde(context)
|
|
246
|
+
if (ide === 'vscode') {
|
|
247
|
+
actions.push({
|
|
248
|
+
type: 'install_extension',
|
|
249
|
+
target: 'vscode',
|
|
250
|
+
description: 'Install the Inspecto VS Code extension.',
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const defaults: PlanResult['defaults'] = {
|
|
256
|
+
shared: false,
|
|
257
|
+
extension: supportedIde(context) === 'vscode',
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const provider = supportedProvider(context)
|
|
261
|
+
if (provider) {
|
|
262
|
+
defaults.provider = provider
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const ide = supportedIde(context)
|
|
266
|
+
if (ide) {
|
|
267
|
+
defaults.ide = ide
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
status: planStatus(warnings, blockers),
|
|
272
|
+
warnings,
|
|
273
|
+
blockers,
|
|
274
|
+
strategy,
|
|
275
|
+
actions,
|
|
276
|
+
defaults,
|
|
277
|
+
}
|
|
278
|
+
}
|
package/src/prompts.ts
CHANGED
|
@@ -45,19 +45,21 @@ export async function promptProviderChoice(
|
|
|
45
45
|
type: 'select',
|
|
46
46
|
name: 'choice',
|
|
47
47
|
message: 'Detected multiple providers, please choose one:',
|
|
48
|
-
choices: detections
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
choices: detections
|
|
49
|
+
.map((d, i) => {
|
|
50
|
+
const modeLabels = d.providerModes.map(mode =>
|
|
51
|
+
mode === 'extension' ? 'VS Code Extension' : 'Terminal CLI',
|
|
52
|
+
)
|
|
53
|
+
const modeStr = modeLabels.join(' & ')
|
|
54
|
+
return {
|
|
55
|
+
title: `${d.label} ${d.supported ? `(supported ${modeStr})` : '(unsupported/limited)'}`,
|
|
56
|
+
value: i,
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.concat({ title: 'Skip (configure later)', value: -1 }),
|
|
58
60
|
})
|
|
59
61
|
|
|
60
|
-
if (choice === undefined) return null
|
|
62
|
+
if (choice === undefined || choice === -1) return null
|
|
61
63
|
return detections[choice]!
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -94,6 +96,47 @@ export async function promptConfigChoice(
|
|
|
94
96
|
return detections[choice]!
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Interactive prompt for choosing the target app when init is run from a monorepo root.
|
|
101
|
+
*/
|
|
102
|
+
export async function promptMonorepoPackageChoice(
|
|
103
|
+
detections: BuildToolDetection[],
|
|
104
|
+
): Promise<string | null> {
|
|
105
|
+
const uniquePackages = Array.from(
|
|
106
|
+
new Map(
|
|
107
|
+
detections
|
|
108
|
+
.filter((d): d is BuildToolDetection & { packagePath: string } => !!d.packagePath)
|
|
109
|
+
.map(d => [d.packagePath, d]),
|
|
110
|
+
).values(),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if (uniquePackages.length === 0) {
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!process.stdin.isTTY) {
|
|
118
|
+
log.error('Monorepo root detected, but stdin is not interactive.')
|
|
119
|
+
log.hint(
|
|
120
|
+
'Re-run `inspecto init` inside a specific app directory, or pass --packages <app-path>.',
|
|
121
|
+
)
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { choice } = await prompts({
|
|
126
|
+
type: 'select',
|
|
127
|
+
name: 'choice',
|
|
128
|
+
message: 'Monorepo root detected. Choose the app to initialize:',
|
|
129
|
+
choices: uniquePackages.map((d, i) => ({
|
|
130
|
+
title: `${d.packagePath} (${d.tool})`,
|
|
131
|
+
description: d.configPath,
|
|
132
|
+
value: i,
|
|
133
|
+
})),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
if (choice === undefined) return null
|
|
137
|
+
return uniquePackages[choice]!.packagePath
|
|
138
|
+
}
|
|
139
|
+
|
|
97
140
|
/**
|
|
98
141
|
* Interactive prompt for continuing with unsupported frameworks.
|
|
99
142
|
*/
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,22 @@ export type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm'
|
|
|
8
8
|
/** Supported build tools (v1) */
|
|
9
9
|
export type BuildTool = 'vite' | 'webpack' | 'rspack' | 'rsbuild' | 'esbuild' | 'rollup'
|
|
10
10
|
|
|
11
|
+
/** Machine-readable status for onboarding commands */
|
|
12
|
+
export type CommandStatus = 'ok' | 'warning' | 'blocked' | 'error'
|
|
13
|
+
|
|
14
|
+
/** Structured message emitted by onboarding commands */
|
|
15
|
+
export interface CommandMessage {
|
|
16
|
+
code: string
|
|
17
|
+
message: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OnboardingProvider {
|
|
21
|
+
id: string
|
|
22
|
+
label: string
|
|
23
|
+
supported: boolean
|
|
24
|
+
preferredMode: 'cli' | 'extension'
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
/** Detected build tool with its config path */
|
|
12
28
|
export interface BuildToolDetection {
|
|
13
29
|
tool: BuildTool
|
|
@@ -22,6 +38,85 @@ export interface BuildToolDetection {
|
|
|
22
38
|
packagePath?: string
|
|
23
39
|
}
|
|
24
40
|
|
|
41
|
+
/** Normalized onboarding context shared by detection/planning */
|
|
42
|
+
export interface OnboardingContext {
|
|
43
|
+
root: string
|
|
44
|
+
packageManager: PackageManager
|
|
45
|
+
buildTools: {
|
|
46
|
+
supported: BuildToolDetection[]
|
|
47
|
+
unsupported: string[]
|
|
48
|
+
}
|
|
49
|
+
frameworks: {
|
|
50
|
+
supported: string[]
|
|
51
|
+
unsupported: string[]
|
|
52
|
+
}
|
|
53
|
+
ides: Array<{ ide: string; supported: boolean }>
|
|
54
|
+
providers: OnboardingProvider[]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Machine-readable detection output for skill-first onboarding */
|
|
58
|
+
export interface DetectionResult {
|
|
59
|
+
status: CommandStatus
|
|
60
|
+
warnings: CommandMessage[]
|
|
61
|
+
blockers: CommandMessage[]
|
|
62
|
+
project: {
|
|
63
|
+
root: string
|
|
64
|
+
packageManager: PackageManager
|
|
65
|
+
}
|
|
66
|
+
environment: {
|
|
67
|
+
frameworks: string[]
|
|
68
|
+
unsupportedFrameworks: string[]
|
|
69
|
+
buildTools: BuildToolDetection[]
|
|
70
|
+
unsupportedBuildTools: string[]
|
|
71
|
+
ides: Array<{ ide: string; supported: boolean }>
|
|
72
|
+
providers: OnboardingProvider[]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Machine-readable onboarding plan output */
|
|
77
|
+
export interface PlanResult {
|
|
78
|
+
status: CommandStatus
|
|
79
|
+
warnings: CommandMessage[]
|
|
80
|
+
blockers: CommandMessage[]
|
|
81
|
+
strategy: 'supported' | 'manual' | 'unsupported'
|
|
82
|
+
actions: Array<{
|
|
83
|
+
type: 'install_dependency' | 'modify_file' | 'install_extension' | 'manual_step'
|
|
84
|
+
target: string
|
|
85
|
+
description: string
|
|
86
|
+
}>
|
|
87
|
+
defaults: {
|
|
88
|
+
provider?: string
|
|
89
|
+
ide?: string
|
|
90
|
+
shared: boolean
|
|
91
|
+
extension: boolean
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A single doctor diagnostic check/result */
|
|
96
|
+
export interface DoctorDiagnostic {
|
|
97
|
+
code: string
|
|
98
|
+
status: 'ok' | 'warning' | 'error'
|
|
99
|
+
message: string
|
|
100
|
+
hints: string[]
|
|
101
|
+
details?: Record<string, unknown>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Machine-readable diagnostics output for `inspecto doctor` */
|
|
105
|
+
export interface DoctorResult {
|
|
106
|
+
status: CommandStatus
|
|
107
|
+
summary: {
|
|
108
|
+
errors: number
|
|
109
|
+
warnings: number
|
|
110
|
+
}
|
|
111
|
+
project: {
|
|
112
|
+
root: string
|
|
113
|
+
packageManager?: PackageManager
|
|
114
|
+
}
|
|
115
|
+
errors: DoctorDiagnostic[]
|
|
116
|
+
warnings: DoctorDiagnostic[]
|
|
117
|
+
checks: DoctorDiagnostic[]
|
|
118
|
+
}
|
|
119
|
+
|
|
25
120
|
/** Options passed to `inspecto init` */
|
|
26
121
|
export interface InitOptions {
|
|
27
122
|
shared: boolean
|
package/src/utils/fs.ts
CHANGED
|
@@ -55,7 +55,8 @@ export async function removeDir(dirPath: string): Promise<void> {
|
|
|
55
55
|
/** Read and parse JSON file */
|
|
56
56
|
export async function readJSON<T = unknown>(filePath: string): Promise<T | null> {
|
|
57
57
|
const text = await readFile(filePath)
|
|
58
|
-
if (
|
|
58
|
+
if (text === null) return null
|
|
59
|
+
if (text.trim() === '') return {} as T
|
|
59
60
|
try {
|
|
60
61
|
return JSON.parse(text) as T
|
|
61
62
|
} catch {
|
package/src/utils/logger.ts
CHANGED
|
@@ -57,6 +57,15 @@ export const log = {
|
|
|
57
57
|
console.log()
|
|
58
58
|
},
|
|
59
59
|
|
|
60
|
+
/** Copy-friendly code block without box characters */
|
|
61
|
+
copyableCodeBlock(lines: string[]) {
|
|
62
|
+
console.log()
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
console.log(` ${line}`)
|
|
65
|
+
}
|
|
66
|
+
console.log()
|
|
67
|
+
},
|
|
68
|
+
|
|
60
69
|
/** Dry-run prefix */
|
|
61
70
|
dryRun(text: string) {
|
|
62
71
|
console.log(` ${pc.blue('[dry-run]')} ${text}`)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { log } from './logger.js'
|
|
2
|
+
|
|
3
|
+
interface ReportCommandErrorOptions {
|
|
4
|
+
debug?: boolean
|
|
5
|
+
json?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function writeCommandOutput<T>(result: T, json: boolean, renderText: (value: T) => void): T {
|
|
9
|
+
if (json) {
|
|
10
|
+
console.log(JSON.stringify(result, null, 2))
|
|
11
|
+
return result
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
renderText(result)
|
|
15
|
+
return result
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function reportCommandError(error: unknown, options: ReportCommandErrorOptions = {}): void {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
20
|
+
const stack = error instanceof Error ? error.stack : undefined
|
|
21
|
+
|
|
22
|
+
if (options.json) {
|
|
23
|
+
const payload = {
|
|
24
|
+
status: 'error' as const,
|
|
25
|
+
error: {
|
|
26
|
+
message,
|
|
27
|
+
...(options.debug && stack ? { stack } : {}),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.error(JSON.stringify(payload, null, 2))
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
log.error(message)
|
|
36
|
+
|
|
37
|
+
if (options.debug && stack) {
|
|
38
|
+
console.error(stack)
|
|
39
|
+
}
|
|
40
|
+
}
|