@inspecto-dev/cli 0.2.0-alpha.5 → 0.3.0-alpha.1
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 +19 -20
- package/CHANGELOG.md +22 -0
- package/README.md +93 -11
- package/bin/inspecto.js +5 -1
- package/dist/bin.d.ts +5 -1
- package/dist/bin.js +530 -49
- package/dist/chunk-FZS2TLXQ.js +3140 -0
- package/dist/index.d.ts +233 -2
- package/dist/index.js +17 -3
- package/package.json +3 -2
- package/src/bin.ts +286 -66
- package/src/commands/apply.ts +118 -0
- package/src/commands/detect.ts +59 -0
- package/src/commands/doctor.ts +225 -72
- package/src/commands/init.ts +143 -183
- package/src/commands/integration-install.ts +452 -0
- package/src/commands/onboard.ts +50 -0
- package/src/commands/plan.ts +41 -0
- package/src/detect/build-tool.ts +107 -3
- package/src/index.ts +17 -2
- package/src/inject/ast-injector.ts +17 -6
- package/src/inject/extension.ts +40 -22
- package/src/inject/gitignore.ts +10 -3
- package/src/instructions.ts +60 -46
- package/src/onboarding/apply.ts +364 -0
- package/src/onboarding/context.ts +36 -0
- package/src/onboarding/planner.ts +284 -0
- package/src/onboarding/session.ts +434 -0
- package/src/onboarding/target-resolution.ts +116 -0
- package/src/prompts.ts +54 -11
- package/src/types.ts +184 -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 +583 -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 +364 -0
- package/tests/install-wrapper.test.ts +76 -0
- package/tests/instructions.test.ts +61 -0
- package/tests/integration-install.test.ts +294 -0
- package/tests/logger.test.ts +100 -0
- package/tests/onboard.test.ts +258 -0
- package/tests/plan.test.ts +713 -0
- package/tests/workspace-build-tool.test.ts +75 -0
- package/.turbo/turbo-test.log +0 -16
- package/dist/chunk-MIHQGC3L.js +0 -1720
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { applyOnboardingPlan, type ApplyOnboardingResult } from '../onboarding/apply.js'
|
|
2
|
+
import { buildOnboardingContext } from '../onboarding/context.js'
|
|
3
|
+
import { createPlanResult } from '../onboarding/planner.js'
|
|
4
|
+
import { log } from '../utils/logger.js'
|
|
5
|
+
import { writeCommandOutput } from '../utils/output.js'
|
|
6
|
+
import type { PlanResult } from '../types.js'
|
|
7
|
+
|
|
8
|
+
export interface ApplyCommandOptions {
|
|
9
|
+
json?: boolean
|
|
10
|
+
shared?: boolean
|
|
11
|
+
skipInstall?: boolean
|
|
12
|
+
dryRun?: boolean
|
|
13
|
+
noExtension?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ApplyCommandResult extends ApplyOnboardingResult {
|
|
17
|
+
plan: PlanResult
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getProviderDefault(
|
|
21
|
+
providerId?: string,
|
|
22
|
+
preferredMode?: 'cli' | 'extension',
|
|
23
|
+
): string | undefined {
|
|
24
|
+
if (!providerId) return undefined
|
|
25
|
+
const mode = preferredMode ?? (providerId === 'coco' ? 'cli' : 'extension')
|
|
26
|
+
return `${providerId}.${mode}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function statusRank(status: ApplyCommandResult['status']): number {
|
|
30
|
+
switch (status) {
|
|
31
|
+
case 'error':
|
|
32
|
+
return 3
|
|
33
|
+
case 'blocked':
|
|
34
|
+
return 2
|
|
35
|
+
case 'warning':
|
|
36
|
+
return 1
|
|
37
|
+
case 'ok':
|
|
38
|
+
default:
|
|
39
|
+
return 0
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mergeStatus(
|
|
44
|
+
planStatus: PlanResult['status'],
|
|
45
|
+
applyStatus: ApplyOnboardingResult['status'],
|
|
46
|
+
): ApplyCommandResult['status'] {
|
|
47
|
+
return statusRank(planStatus) >= statusRank(applyStatus) ? planStatus : applyStatus
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function printApplyResult(result: ApplyCommandResult): void {
|
|
51
|
+
const manualSteps = result.postInstall.nextSteps.filter(
|
|
52
|
+
step => !result.plan.blockers.some(blocker => blocker.message === step),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
log.header('Inspecto Apply')
|
|
56
|
+
log.info(`Status: ${result.status}`)
|
|
57
|
+
log.info(`Strategy: ${result.plan.strategy}`)
|
|
58
|
+
|
|
59
|
+
for (const blocker of result.plan.blockers) {
|
|
60
|
+
log.error(blocker.message)
|
|
61
|
+
}
|
|
62
|
+
for (const warning of result.plan.warnings) {
|
|
63
|
+
log.warn(warning.message)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (manualSteps.length > 0 || result.plan.blockers.length > 0) {
|
|
67
|
+
log.blank()
|
|
68
|
+
log.warn('──────── Manual Steps Required ────────')
|
|
69
|
+
manualSteps.forEach(step => log.error(step))
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (result.plan.warnings.length > 0) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
log.ready('Ready! Inspecto is set up.')
|
|
78
|
+
log.info('Next:')
|
|
79
|
+
log.hint('1. Start or restart your dev server.')
|
|
80
|
+
log.hint('2. Open your app in the browser.')
|
|
81
|
+
log.hint('3. Hold Alt + Click any element to inspect.')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function apply(options: ApplyCommandOptions = {}): Promise<ApplyCommandResult> {
|
|
85
|
+
const root = process.cwd()
|
|
86
|
+
const context = await buildOnboardingContext(root)
|
|
87
|
+
const plan = createPlanResult(context)
|
|
88
|
+
const selectedProvider =
|
|
89
|
+
context.providers.find(provider => provider.id === plan.defaults.provider) ?? null
|
|
90
|
+
const selectedIDE =
|
|
91
|
+
context.ides.find(ide => ide.ide === plan.defaults.ide) ??
|
|
92
|
+
context.ides.find(ide => ide.supported) ??
|
|
93
|
+
null
|
|
94
|
+
|
|
95
|
+
const applyResult = await applyOnboardingPlan({
|
|
96
|
+
repoRoot: root,
|
|
97
|
+
projectRoot: root,
|
|
98
|
+
packageManager: context.packageManager,
|
|
99
|
+
supportedBuildTargets: context.buildTools.supported,
|
|
100
|
+
options: {
|
|
101
|
+
shared: options.shared ?? plan.defaults.shared,
|
|
102
|
+
skipInstall: options.skipInstall ?? false,
|
|
103
|
+
dryRun: options.dryRun ?? false,
|
|
104
|
+
noExtension: options.noExtension ?? !plan.defaults.extension,
|
|
105
|
+
quiet: options.json ?? false,
|
|
106
|
+
},
|
|
107
|
+
selectedIDE,
|
|
108
|
+
providerDefault: getProviderDefault(plan.defaults.provider, selectedProvider?.preferredMode),
|
|
109
|
+
plan,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const result: ApplyCommandResult = {
|
|
113
|
+
...applyResult,
|
|
114
|
+
status: mergeStatus(plan.status, applyResult.status),
|
|
115
|
+
plan,
|
|
116
|
+
}
|
|
117
|
+
return writeCommandOutput(result, options.json ?? false, printApplyResult)
|
|
118
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { log } from '../utils/logger.js'
|
|
2
|
+
import { writeCommandOutput } from '../utils/output.js'
|
|
3
|
+
import { createDetectionResult } from '../onboarding/planner.js'
|
|
4
|
+
import type { DetectionResult } from '../types.js'
|
|
5
|
+
|
|
6
|
+
function printDetectionResult(result: DetectionResult): void {
|
|
7
|
+
const suppressedCodes = new Set([
|
|
8
|
+
'unsupported-build-tool',
|
|
9
|
+
'unsupported-build-tool-present',
|
|
10
|
+
'unsupported-framework',
|
|
11
|
+
'unsupported-framework-present',
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
log.header('Inspecto Detect')
|
|
15
|
+
log.info(`Status: ${result.status}`)
|
|
16
|
+
log.info(`Root: ${result.project.root}`)
|
|
17
|
+
log.info(`Package manager: ${result.project.packageManager}`)
|
|
18
|
+
|
|
19
|
+
if (result.environment.frameworks.length > 0) {
|
|
20
|
+
log.success(`Supported frameworks: ${result.environment.frameworks.join(', ')}`)
|
|
21
|
+
}
|
|
22
|
+
if (result.environment.unsupportedFrameworks.length > 0) {
|
|
23
|
+
log.warn(`Unsupported frameworks: ${result.environment.unsupportedFrameworks.join(', ')}`)
|
|
24
|
+
}
|
|
25
|
+
if (result.environment.buildTools.length > 0) {
|
|
26
|
+
log.success(
|
|
27
|
+
`Supported build tools: ${result.environment.buildTools.map(tool => tool.label).join(', ')}`,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
if (result.environment.unsupportedBuildTools.length > 0) {
|
|
31
|
+
log.warn(`Unsupported build tools: ${result.environment.unsupportedBuildTools.join(', ')}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const supportedIdes = result.environment.ides.filter(ide => ide.supported).map(ide => ide.ide)
|
|
35
|
+
if (supportedIdes.length > 0) {
|
|
36
|
+
log.success(`Supported IDEs: ${supportedIdes.join(', ')}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const supportedProviders = result.environment.providers
|
|
40
|
+
.filter(provider => provider.supported)
|
|
41
|
+
.map(provider => provider.label)
|
|
42
|
+
if (supportedProviders.length > 0) {
|
|
43
|
+
log.success(`Supported providers: ${supportedProviders.join(', ')}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const blocker of result.blockers) {
|
|
47
|
+
if (suppressedCodes.has(blocker.code)) continue
|
|
48
|
+
log.error(blocker.message)
|
|
49
|
+
}
|
|
50
|
+
for (const warning of result.warnings) {
|
|
51
|
+
if (suppressedCodes.has(warning.code)) continue
|
|
52
|
+
log.warn(warning.message)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function detect(json = false): Promise<DetectionResult> {
|
|
57
|
+
const result = await createDetectionResult(process.cwd())
|
|
58
|
+
return writeCommandOutput(result, json, printDetectionResult)
|
|
59
|
+
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -10,23 +10,83 @@ import { detectFrameworks } from '../detect/framework.js'
|
|
|
10
10
|
import { detectIDE } from '../detect/ide.js'
|
|
11
11
|
import { detectProviders } from '../detect/provider.js'
|
|
12
12
|
import { isExtensionInstalled } from '../inject/extension.js'
|
|
13
|
+
import { writeCommandOutput } from '../utils/output.js'
|
|
14
|
+
import type { CommandStatus, DoctorDiagnostic, DoctorResult } from '../types.js'
|
|
13
15
|
|
|
14
|
-
interface
|
|
15
|
-
|
|
16
|
-
warnings: number
|
|
16
|
+
export interface DoctorCommandOptions {
|
|
17
|
+
json?: boolean
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
function createDiagnostic(
|
|
21
|
+
code: string,
|
|
22
|
+
status: DoctorDiagnostic['status'],
|
|
23
|
+
message: string,
|
|
24
|
+
hints: string[] = [],
|
|
25
|
+
details?: Record<string, unknown>,
|
|
26
|
+
): DoctorDiagnostic {
|
|
27
|
+
return {
|
|
28
|
+
code,
|
|
29
|
+
status,
|
|
30
|
+
message,
|
|
31
|
+
hints,
|
|
32
|
+
...(details ? { details } : {}),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function doctorStatus(errors: number, warnings: number): CommandStatus {
|
|
37
|
+
if (errors > 0) return 'blocked'
|
|
38
|
+
if (warnings > 0) return 'warning'
|
|
39
|
+
return 'ok'
|
|
40
|
+
}
|
|
22
41
|
|
|
42
|
+
function printDoctorResult(result: DoctorResult): void {
|
|
23
43
|
log.header('Inspecto Doctor')
|
|
24
44
|
|
|
45
|
+
for (const check of result.checks) {
|
|
46
|
+
if (check.status === 'ok') {
|
|
47
|
+
log.success(check.message)
|
|
48
|
+
} else if (check.status === 'warning') {
|
|
49
|
+
log.warn(check.message)
|
|
50
|
+
} else {
|
|
51
|
+
log.error(check.message)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const hint of check.hints) {
|
|
55
|
+
log.hint(hint)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
log.blank()
|
|
60
|
+
if (result.summary.errors === 0 && result.summary.warnings === 0) {
|
|
61
|
+
log.success('All checks passed. Hold Alt + Click to start!')
|
|
62
|
+
} else {
|
|
63
|
+
const parts: string[] = []
|
|
64
|
+
if (result.summary.errors > 0) parts.push(`${result.summary.errors} error(s)`)
|
|
65
|
+
if (result.summary.warnings > 0) parts.push(`${result.summary.warnings} warning(s)`)
|
|
66
|
+
console.log(
|
|
67
|
+
` ${parts.join(', ')}. ${result.summary.errors > 0 ? 'Fix the errors above to get started.' : ''}`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
log.blank()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function collectDoctorResult(root = process.cwd()): Promise<DoctorResult> {
|
|
74
|
+
const checks: DoctorDiagnostic[] = []
|
|
75
|
+
|
|
25
76
|
// Check 1: package.json exists
|
|
26
77
|
if (!(await exists(path.join(root, 'package.json')))) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
78
|
+
const diagnostic = createDiagnostic('missing-package-json', 'error', 'No package.json found', [
|
|
79
|
+
'Run this command from your project root',
|
|
80
|
+
])
|
|
81
|
+
checks.push(diagnostic)
|
|
82
|
+
return {
|
|
83
|
+
status: 'blocked',
|
|
84
|
+
summary: { errors: 1, warnings: 0 },
|
|
85
|
+
project: { root },
|
|
86
|
+
errors: [diagnostic],
|
|
87
|
+
warnings: [],
|
|
88
|
+
checks,
|
|
89
|
+
}
|
|
30
90
|
}
|
|
31
91
|
|
|
32
92
|
// Run detections concurrently
|
|
@@ -42,42 +102,76 @@ export async function doctor(): Promise<void> {
|
|
|
42
102
|
|
|
43
103
|
// Check 2: IDE
|
|
44
104
|
if (ideProbe.detected.length === 0) {
|
|
45
|
-
|
|
46
|
-
result.warnings++
|
|
105
|
+
checks.push(createDiagnostic('ide-not-detected', 'warning', 'IDE: not detected'))
|
|
47
106
|
} else {
|
|
48
107
|
// If we have at least one supported IDE, it's a pass
|
|
49
108
|
const hasSupported = ideProbe.detected.some(d => d.supported)
|
|
50
109
|
if (hasSupported) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.
|
|
110
|
+
checks.push(
|
|
111
|
+
createDiagnostic(
|
|
112
|
+
'ide-supported',
|
|
113
|
+
'ok',
|
|
114
|
+
`IDE: ${ideProbe.detected
|
|
115
|
+
.filter(d => d.supported)
|
|
116
|
+
.map(d => d.ide)
|
|
117
|
+
.join(', ')}`,
|
|
118
|
+
[],
|
|
119
|
+
{
|
|
120
|
+
detected: ideProbe.detected,
|
|
121
|
+
},
|
|
122
|
+
),
|
|
56
123
|
)
|
|
57
124
|
} else {
|
|
58
125
|
const names = ideProbe.detected.map(d => d.ide).join(', ')
|
|
59
|
-
|
|
60
|
-
|
|
126
|
+
checks.push(
|
|
127
|
+
createDiagnostic(
|
|
128
|
+
'ide-unsupported',
|
|
129
|
+
'warning',
|
|
130
|
+
`IDE: ${names} (not supported in v1, VS Code, Cursor, Trae only)`,
|
|
131
|
+
[],
|
|
132
|
+
{
|
|
133
|
+
detected: ideProbe.detected,
|
|
134
|
+
},
|
|
135
|
+
),
|
|
136
|
+
)
|
|
61
137
|
}
|
|
62
138
|
}
|
|
63
139
|
|
|
64
140
|
// Check 3: Supported framework
|
|
65
141
|
if (frameworkResult.supported.length > 0) {
|
|
66
|
-
|
|
142
|
+
checks.push(
|
|
143
|
+
createDiagnostic(
|
|
144
|
+
'framework-supported',
|
|
145
|
+
'ok',
|
|
146
|
+
`Framework: ${frameworkResult.supported.join(', ')}`,
|
|
147
|
+
),
|
|
148
|
+
)
|
|
67
149
|
} else if (frameworkResult.unsupported.length > 0) {
|
|
68
150
|
const names = frameworkResult.unsupported.map(f => f.name).join(', ')
|
|
69
|
-
|
|
70
|
-
|
|
151
|
+
checks.push(
|
|
152
|
+
createDiagnostic(
|
|
153
|
+
'framework-unsupported',
|
|
154
|
+
'warning',
|
|
155
|
+
`Framework: ${names} (not supported in v1, React/Vue only)`,
|
|
156
|
+
),
|
|
157
|
+
)
|
|
71
158
|
} else {
|
|
72
|
-
|
|
73
|
-
|
|
159
|
+
checks.push(
|
|
160
|
+
createDiagnostic(
|
|
161
|
+
'framework-not-detected',
|
|
162
|
+
'warning',
|
|
163
|
+
'Framework: not detected (React / Vue expected)',
|
|
164
|
+
),
|
|
165
|
+
)
|
|
74
166
|
}
|
|
75
167
|
|
|
76
168
|
// Check 3.5: Providers
|
|
77
169
|
if (providerProbe.detected.length === 0) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
170
|
+
checks.push(
|
|
171
|
+
createDiagnostic('provider-missing', 'warning', 'Provider: none detected', [
|
|
172
|
+
'Inspecto works best with Claude Code, Trae CLI, or GitHub Copilot',
|
|
173
|
+
]),
|
|
174
|
+
)
|
|
81
175
|
} else {
|
|
82
176
|
const aiNames = providerProbe.detected
|
|
83
177
|
.map(d => {
|
|
@@ -87,7 +181,7 @@ export async function doctor(): Promise<void> {
|
|
|
87
181
|
return `${d.label} (${modeLabels.join(' & ')})`
|
|
88
182
|
})
|
|
89
183
|
.join(', ')
|
|
90
|
-
|
|
184
|
+
checks.push(createDiagnostic('provider-detected', 'ok', `Provider: ${aiNames}`))
|
|
91
185
|
}
|
|
92
186
|
|
|
93
187
|
// Check 4: @inspecto-dev/plugin installed
|
|
@@ -95,12 +189,17 @@ export async function doctor(): Promise<void> {
|
|
|
95
189
|
if (await exists(pluginPath)) {
|
|
96
190
|
const pkgJson = await readJSON<{ version?: string }>(path.join(pluginPath, 'package.json'))
|
|
97
191
|
const version = pkgJson?.version ?? 'unknown'
|
|
98
|
-
|
|
192
|
+
checks.push(
|
|
193
|
+
createDiagnostic('plugin-installed', 'ok', `@inspecto-dev/plugin@${version} installed`, [], {
|
|
194
|
+
version,
|
|
195
|
+
}),
|
|
196
|
+
)
|
|
99
197
|
} else {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
198
|
+
checks.push(
|
|
199
|
+
createDiagnostic('plugin-missing', 'error', '@inspecto-dev/plugin not installed', [
|
|
200
|
+
`Fix: ${getInstallCommand(pm, '@inspecto-dev/plugin')}`,
|
|
201
|
+
]),
|
|
202
|
+
)
|
|
104
203
|
}
|
|
105
204
|
|
|
106
205
|
// Check 5: Plugin injected in build config
|
|
@@ -109,38 +208,64 @@ export async function doctor(): Promise<void> {
|
|
|
109
208
|
for (const bt of buildResult.supported) {
|
|
110
209
|
const content = await readFile(path.join(root, bt.configPath))
|
|
111
210
|
if (content && content.includes('@inspecto-dev/plugin')) {
|
|
112
|
-
|
|
211
|
+
checks.push(
|
|
212
|
+
createDiagnostic('plugin-configured', 'ok', `Plugin configured in ${bt.configPath}`, [], {
|
|
213
|
+
configPath: bt.configPath,
|
|
214
|
+
buildTool: bt.tool,
|
|
215
|
+
}),
|
|
216
|
+
)
|
|
113
217
|
injected = true
|
|
114
218
|
break
|
|
115
219
|
}
|
|
116
220
|
}
|
|
117
221
|
if (!injected) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
222
|
+
checks.push(
|
|
223
|
+
createDiagnostic(
|
|
224
|
+
'plugin-not-configured',
|
|
225
|
+
'error',
|
|
226
|
+
'Plugin not configured in any build config',
|
|
227
|
+
['Fix: npx @inspecto-dev/cli init'],
|
|
228
|
+
),
|
|
229
|
+
)
|
|
121
230
|
}
|
|
122
231
|
} else if (buildResult.unsupported.length > 0) {
|
|
123
232
|
const names = buildResult.unsupported.join(', ')
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
233
|
+
checks.push(
|
|
234
|
+
createDiagnostic(
|
|
235
|
+
`build-tool-unsupported`,
|
|
236
|
+
'warning',
|
|
237
|
+
`Build tool: ${names} (not supported in v1)`,
|
|
238
|
+
['current version supports: Vite, Webpack, Rspack, esbuild, Rollup'],
|
|
239
|
+
),
|
|
240
|
+
)
|
|
127
241
|
} else {
|
|
128
|
-
|
|
129
|
-
|
|
242
|
+
checks.push(
|
|
243
|
+
createDiagnostic('build-tool-missing', 'warning', 'No recognized build config found'),
|
|
244
|
+
)
|
|
130
245
|
}
|
|
131
246
|
|
|
132
247
|
// Check 6: VS Code extension
|
|
133
248
|
if (extInstalled) {
|
|
134
|
-
|
|
249
|
+
checks.push(createDiagnostic('extension-installed', 'ok', 'VS Code extension detected'))
|
|
135
250
|
} else {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
251
|
+
const hasVSCode = ideProbe.detected.some(d => d.supported && d.ide === 'vscode')
|
|
252
|
+
const hasSupportedNonVSCode = ideProbe.detected.some(d => d.supported && d.ide !== 'vscode')
|
|
253
|
+
|
|
254
|
+
if (hasSupportedNonVSCode && !hasVSCode) {
|
|
255
|
+
checks.push(
|
|
256
|
+
createDiagnostic(
|
|
257
|
+
'extension-not-applicable',
|
|
258
|
+
'warning',
|
|
259
|
+
'VS Code extension not applicable (non-VS Code IDE)',
|
|
260
|
+
),
|
|
261
|
+
)
|
|
139
262
|
} else {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
263
|
+
checks.push(
|
|
264
|
+
createDiagnostic('extension-missing', 'error', 'VS Code extension not found', [
|
|
265
|
+
'Fix: code --install-extension inspecto.inspecto',
|
|
266
|
+
'Or: https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto',
|
|
267
|
+
]),
|
|
268
|
+
)
|
|
144
269
|
}
|
|
145
270
|
}
|
|
146
271
|
|
|
@@ -156,18 +281,31 @@ export async function doctor(): Promise<void> {
|
|
|
156
281
|
const fileName = hasSettingsLocal ? 'settings.local.json' : 'settings.json'
|
|
157
282
|
const settings = await readJSON(targetPath)
|
|
158
283
|
if (settings) {
|
|
159
|
-
|
|
284
|
+
checks.push(createDiagnostic('settings-valid', 'ok', `.inspecto/${fileName} valid`))
|
|
160
285
|
} else {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
286
|
+
checks.push(
|
|
287
|
+
createDiagnostic(
|
|
288
|
+
'settings-invalid-json',
|
|
289
|
+
'error',
|
|
290
|
+
`.inspecto/${fileName} has invalid JSON`,
|
|
291
|
+
[
|
|
292
|
+
'Fix: Manually correct the syntax errors, or delete the file and re-run npx @inspecto-dev/cli init',
|
|
293
|
+
],
|
|
294
|
+
{
|
|
295
|
+
fileName,
|
|
296
|
+
},
|
|
297
|
+
),
|
|
164
298
|
)
|
|
165
|
-
result.errors++
|
|
166
299
|
}
|
|
167
300
|
} else {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
301
|
+
checks.push(
|
|
302
|
+
createDiagnostic(
|
|
303
|
+
'settings-missing',
|
|
304
|
+
'warning',
|
|
305
|
+
'No .inspecto/settings.json or settings.local.json found (using defaults)',
|
|
306
|
+
['Optional: npx @inspecto-dev/cli init'],
|
|
307
|
+
),
|
|
308
|
+
)
|
|
171
309
|
}
|
|
172
310
|
|
|
173
311
|
// Check 8: .gitignore status
|
|
@@ -176,23 +314,38 @@ export async function doctor(): Promise<void> {
|
|
|
176
314
|
const hasLockIgnore =
|
|
177
315
|
gitignoreContent.includes('.inspecto/install.lock') || gitignoreContent.includes('.inspecto/')
|
|
178
316
|
if (!hasLockIgnore) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
317
|
+
checks.push(
|
|
318
|
+
createDiagnostic(
|
|
319
|
+
'gitignore-missing-install-lock',
|
|
320
|
+
'warning',
|
|
321
|
+
'.inspecto/install.lock not in .gitignore',
|
|
322
|
+
['install.lock contains local machine state and should not be committed'],
|
|
323
|
+
),
|
|
324
|
+
)
|
|
182
325
|
}
|
|
183
326
|
}
|
|
184
327
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
328
|
+
const errors = checks.filter(check => check.status === 'error')
|
|
329
|
+
const warnings = checks.filter(check => check.status === 'warning')
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
status: doctorStatus(errors.length, warnings.length),
|
|
333
|
+
summary: {
|
|
334
|
+
errors: errors.length,
|
|
335
|
+
warnings: warnings.length,
|
|
336
|
+
},
|
|
337
|
+
project: {
|
|
338
|
+
root,
|
|
339
|
+
packageManager: pm,
|
|
340
|
+
},
|
|
341
|
+
errors,
|
|
342
|
+
warnings,
|
|
343
|
+
checks,
|
|
196
344
|
}
|
|
197
|
-
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export async function doctor(options: DoctorCommandOptions | boolean = {}): Promise<DoctorResult> {
|
|
348
|
+
const json = typeof options === 'boolean' ? options : (options.json ?? false)
|
|
349
|
+
const result = await collectDoctorResult(process.cwd())
|
|
350
|
+
return writeCommandOutput(result, json, printDoctorResult)
|
|
198
351
|
}
|