@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,94 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { buildOnboardingContext } from '../src/onboarding/context.js'
|
|
3
|
+
import * as buildToolDetector from '../src/detect/build-tool.js'
|
|
4
|
+
import * as frameworkDetector from '../src/detect/framework.js'
|
|
5
|
+
import * as ideDetector from '../src/detect/ide.js'
|
|
6
|
+
import * as packageManagerDetector from '../src/detect/package-manager.js'
|
|
7
|
+
import * as providerDetector from '../src/detect/provider.js'
|
|
8
|
+
|
|
9
|
+
vi.mock('../src/detect/build-tool.js', () => ({
|
|
10
|
+
detectBuildTools: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock('../src/detect/framework.js', () => ({
|
|
14
|
+
detectFrameworks: vi.fn(),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
vi.mock('../src/detect/ide.js', () => ({
|
|
18
|
+
detectIDE: vi.fn(),
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
vi.mock('../src/detect/package-manager.js', () => ({
|
|
22
|
+
detectPackageManager: vi.fn(),
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
vi.mock('../src/detect/provider.js', () => ({
|
|
26
|
+
detectProviders: vi.fn(),
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
describe('buildOnboardingContext', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.resetAllMocks()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('normalizes detector output into a shared onboarding context', async () => {
|
|
35
|
+
vi.mocked(packageManagerDetector.detectPackageManager).mockResolvedValue('pnpm')
|
|
36
|
+
vi.mocked(buildToolDetector.detectBuildTools).mockResolvedValue({
|
|
37
|
+
supported: [
|
|
38
|
+
{
|
|
39
|
+
tool: 'vite',
|
|
40
|
+
configPath: 'vite.config.ts',
|
|
41
|
+
label: 'Vite (vite.config.ts)',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
unsupported: ['Next.js'],
|
|
45
|
+
})
|
|
46
|
+
vi.mocked(frameworkDetector.detectFrameworks).mockResolvedValue({
|
|
47
|
+
supported: ['react'],
|
|
48
|
+
unsupported: [{ name: 'Svelte', dep: 'svelte' }],
|
|
49
|
+
})
|
|
50
|
+
vi.mocked(ideDetector.detectIDE).mockResolvedValue({
|
|
51
|
+
detected: [{ ide: 'vscode', supported: true }],
|
|
52
|
+
})
|
|
53
|
+
vi.mocked(providerDetector.detectProviders).mockResolvedValue({
|
|
54
|
+
detected: [
|
|
55
|
+
{
|
|
56
|
+
id: 'codex',
|
|
57
|
+
label: 'Codex CLI',
|
|
58
|
+
supported: true,
|
|
59
|
+
providerModes: ['cli'],
|
|
60
|
+
preferredMode: 'cli',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const result = await buildOnboardingContext('/repo')
|
|
66
|
+
|
|
67
|
+
expect(result).toEqual({
|
|
68
|
+
root: '/repo',
|
|
69
|
+
packageManager: 'pnpm',
|
|
70
|
+
buildTools: {
|
|
71
|
+
supported: [
|
|
72
|
+
{
|
|
73
|
+
tool: 'vite',
|
|
74
|
+
configPath: 'vite.config.ts',
|
|
75
|
+
label: 'Vite (vite.config.ts)',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
unsupported: ['Next.js'],
|
|
79
|
+
},
|
|
80
|
+
frameworks: {
|
|
81
|
+
supported: ['react'],
|
|
82
|
+
unsupported: ['Svelte'],
|
|
83
|
+
},
|
|
84
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
85
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(packageManagerDetector.detectPackageManager).toHaveBeenCalledWith('/repo')
|
|
89
|
+
expect(buildToolDetector.detectBuildTools).toHaveBeenCalledWith('/repo')
|
|
90
|
+
expect(frameworkDetector.detectFrameworks).toHaveBeenCalledWith('/repo')
|
|
91
|
+
expect(ideDetector.detectIDE).toHaveBeenCalledWith('/repo')
|
|
92
|
+
expect(providerDetector.detectProviders).toHaveBeenCalledWith('/repo')
|
|
93
|
+
})
|
|
94
|
+
})
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
vi.mock('../src/utils/fs.js', () => ({
|
|
4
|
+
exists: vi.fn(),
|
|
5
|
+
readJSON: vi.fn(),
|
|
6
|
+
readFile: vi.fn(),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
vi.mock('../src/detect/package-manager.js', () => ({
|
|
10
|
+
detectPackageManager: vi.fn(),
|
|
11
|
+
getInstallCommand: vi.fn(),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
vi.mock('../src/detect/build-tool.js', () => ({
|
|
15
|
+
detectBuildTools: vi.fn(),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
vi.mock('../src/detect/framework.js', () => ({
|
|
19
|
+
detectFrameworks: vi.fn(),
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
vi.mock('../src/detect/ide.js', () => ({
|
|
23
|
+
detectIDE: vi.fn(),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
vi.mock('../src/detect/provider.js', () => ({
|
|
27
|
+
detectProviders: vi.fn(),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
vi.mock('../src/inject/extension.js', () => ({
|
|
31
|
+
isExtensionInstalled: vi.fn(),
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
import { collectDoctorResult, doctor } from '../src/commands/doctor.js'
|
|
35
|
+
import * as buildTool from '../src/detect/build-tool.js'
|
|
36
|
+
import * as framework from '../src/detect/framework.js'
|
|
37
|
+
import * as ide from '../src/detect/ide.js'
|
|
38
|
+
import * as packageManager from '../src/detect/package-manager.js'
|
|
39
|
+
import * as provider from '../src/detect/provider.js'
|
|
40
|
+
import * as extension from '../src/inject/extension.js'
|
|
41
|
+
import * as fsUtils from '../src/utils/fs.js'
|
|
42
|
+
import { log } from '../src/utils/logger.js'
|
|
43
|
+
|
|
44
|
+
function mockFailingInstall(): void {
|
|
45
|
+
vi.mocked(fsUtils.exists).mockImplementation(async (filePath: string) => {
|
|
46
|
+
const existingPaths = new Set([
|
|
47
|
+
'/repo/package.json',
|
|
48
|
+
'/repo/vite.config.ts',
|
|
49
|
+
'/repo/node_modules/@inspecto-dev/plugin',
|
|
50
|
+
'/repo/.inspecto/settings.json',
|
|
51
|
+
'/repo/.gitignore',
|
|
52
|
+
])
|
|
53
|
+
return existingPaths.has(filePath)
|
|
54
|
+
})
|
|
55
|
+
vi.mocked(fsUtils.readJSON).mockImplementation(async (filePath: string) => {
|
|
56
|
+
if (filePath === '/repo/node_modules/@inspecto-dev/plugin/package.json') {
|
|
57
|
+
return { version: '1.2.3' }
|
|
58
|
+
}
|
|
59
|
+
if (filePath === '/repo/.inspecto/settings.json') {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
return null
|
|
63
|
+
})
|
|
64
|
+
vi.mocked(fsUtils.readFile).mockImplementation(async (filePath: string) => {
|
|
65
|
+
if (filePath === '/repo/vite.config.ts') {
|
|
66
|
+
return 'export default {}'
|
|
67
|
+
}
|
|
68
|
+
if (filePath === '/repo/.gitignore') {
|
|
69
|
+
return 'node_modules/\n'
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
})
|
|
73
|
+
vi.mocked(packageManager.detectPackageManager).mockResolvedValue('pnpm')
|
|
74
|
+
vi.mocked(packageManager.getInstallCommand).mockReturnValue('pnpm add -D @inspecto-dev/plugin')
|
|
75
|
+
vi.mocked(ide.detectIDE).mockResolvedValue({
|
|
76
|
+
detected: [{ ide: 'vscode', supported: true }],
|
|
77
|
+
})
|
|
78
|
+
vi.mocked(framework.detectFrameworks).mockResolvedValue({
|
|
79
|
+
supported: ['react'],
|
|
80
|
+
unsupported: [],
|
|
81
|
+
})
|
|
82
|
+
vi.mocked(provider.detectProviders).mockResolvedValue({
|
|
83
|
+
detected: [],
|
|
84
|
+
})
|
|
85
|
+
vi.mocked(buildTool.detectBuildTools).mockResolvedValue({
|
|
86
|
+
supported: [{ tool: 'vite', configPath: 'vite.config.ts', label: 'Vite (vite.config.ts)' }],
|
|
87
|
+
unsupported: [],
|
|
88
|
+
})
|
|
89
|
+
vi.mocked(extension.isExtensionInstalled).mockResolvedValue(false)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe('doctor command', () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
vi.restoreAllMocks()
|
|
95
|
+
vi.clearAllMocks()
|
|
96
|
+
vi.spyOn(process, 'cwd').mockReturnValue('/repo')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('returns an early blocked result when package.json is missing', async () => {
|
|
100
|
+
vi.mocked(fsUtils.exists).mockResolvedValue(false)
|
|
101
|
+
const detectIdeSpy = vi.mocked(ide.detectIDE)
|
|
102
|
+
|
|
103
|
+
const result = await collectDoctorResult('/repo')
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual({
|
|
106
|
+
status: 'blocked',
|
|
107
|
+
summary: { errors: 1, warnings: 0 },
|
|
108
|
+
project: { root: '/repo' },
|
|
109
|
+
errors: [
|
|
110
|
+
{
|
|
111
|
+
code: 'missing-package-json',
|
|
112
|
+
status: 'error',
|
|
113
|
+
message: 'No package.json found',
|
|
114
|
+
hints: ['Run this command from your project root'],
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
warnings: [],
|
|
118
|
+
checks: [
|
|
119
|
+
{
|
|
120
|
+
code: 'missing-package-json',
|
|
121
|
+
status: 'error',
|
|
122
|
+
message: 'No package.json found',
|
|
123
|
+
hints: ['Run this command from your project root'],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
})
|
|
127
|
+
expect(detectIdeSpy).not.toHaveBeenCalled()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('returns structured diagnostics in json mode for a failing installation', async () => {
|
|
131
|
+
mockFailingInstall()
|
|
132
|
+
|
|
133
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
|
134
|
+
const headerSpy = vi.spyOn(log, 'header')
|
|
135
|
+
|
|
136
|
+
const result = await doctor({ json: true })
|
|
137
|
+
|
|
138
|
+
expect(result.status).toBe('blocked')
|
|
139
|
+
expect(result.summary).toEqual({
|
|
140
|
+
errors: 3,
|
|
141
|
+
warnings: 2,
|
|
142
|
+
})
|
|
143
|
+
expect(result.errors).toEqual([
|
|
144
|
+
expect.objectContaining({
|
|
145
|
+
code: 'plugin-not-configured',
|
|
146
|
+
message: 'Plugin not configured in any build config',
|
|
147
|
+
}),
|
|
148
|
+
expect.objectContaining({
|
|
149
|
+
code: 'extension-missing',
|
|
150
|
+
message: 'VS Code extension not found',
|
|
151
|
+
hints: [
|
|
152
|
+
'Fix: code --install-extension inspecto.inspecto',
|
|
153
|
+
'Or: https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto',
|
|
154
|
+
],
|
|
155
|
+
}),
|
|
156
|
+
expect.objectContaining({
|
|
157
|
+
code: 'settings-invalid-json',
|
|
158
|
+
message: '.inspecto/settings.json has invalid JSON',
|
|
159
|
+
}),
|
|
160
|
+
])
|
|
161
|
+
expect(result.warnings).toEqual([
|
|
162
|
+
expect.objectContaining({
|
|
163
|
+
code: 'provider-missing',
|
|
164
|
+
message: 'Provider: none detected',
|
|
165
|
+
}),
|
|
166
|
+
expect.objectContaining({
|
|
167
|
+
code: 'gitignore-missing-install-lock',
|
|
168
|
+
message: '.inspecto/install.lock not in .gitignore',
|
|
169
|
+
}),
|
|
170
|
+
])
|
|
171
|
+
expect(result.checks).toEqual(
|
|
172
|
+
expect.arrayContaining([
|
|
173
|
+
expect.objectContaining({ code: 'plugin-installed', status: 'ok' }),
|
|
174
|
+
expect.objectContaining({ code: 'plugin-not-configured', status: 'error' }),
|
|
175
|
+
expect.objectContaining({ code: 'provider-missing', status: 'warning' }),
|
|
176
|
+
]),
|
|
177
|
+
)
|
|
178
|
+
expect(headerSpy).not.toHaveBeenCalled()
|
|
179
|
+
expect(consoleSpy).toHaveBeenCalledWith(JSON.stringify(result, null, 2))
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('does not report a VS Code extension error for supported non-VS Code IDEs', async () => {
|
|
183
|
+
mockFailingInstall()
|
|
184
|
+
vi.mocked(ide.detectIDE).mockResolvedValue({
|
|
185
|
+
detected: [{ ide: 'cursor', supported: true }],
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const result = await collectDoctorResult('/repo')
|
|
189
|
+
|
|
190
|
+
expect(result.errors).toEqual(
|
|
191
|
+
expect.not.arrayContaining([expect.objectContaining({ code: 'extension-missing' })]),
|
|
192
|
+
)
|
|
193
|
+
expect(result.warnings).toEqual(
|
|
194
|
+
expect.arrayContaining([
|
|
195
|
+
expect.objectContaining({
|
|
196
|
+
code: 'extension-not-applicable',
|
|
197
|
+
message: 'VS Code extension not applicable (non-VS Code IDE)',
|
|
198
|
+
}),
|
|
199
|
+
]),
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('preserves the human-readable doctor output in text mode', async () => {
|
|
204
|
+
mockFailingInstall()
|
|
205
|
+
|
|
206
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
|
207
|
+
|
|
208
|
+
const result = await doctor()
|
|
209
|
+
const output = consoleSpy.mock.calls.map(call => call.join(' ')).join('\n')
|
|
210
|
+
|
|
211
|
+
expect(result.status).toBe('blocked')
|
|
212
|
+
expect(output).toContain('Inspecto Doctor')
|
|
213
|
+
expect(output).toContain('Provider: none detected')
|
|
214
|
+
expect(output).toContain('Inspecto works best with Claude Code, Trae CLI, or GitHub Copilot')
|
|
215
|
+
expect(output).toContain('Plugin not configured in any build config')
|
|
216
|
+
expect(output).toContain('Fix: npx @inspecto-dev/cli init')
|
|
217
|
+
expect(output).toContain('VS Code extension not found')
|
|
218
|
+
expect(output).toContain('Fix: code --install-extension inspecto.inspecto')
|
|
219
|
+
expect(output).toContain('.inspecto/settings.json has invalid JSON')
|
|
220
|
+
expect(output).toContain('3 error(s), 2 warning(s). Fix the errors above to get started.')
|
|
221
|
+
expect(output).not.toContain('"status"')
|
|
222
|
+
expect(output).not.toContain('"summary"')
|
|
223
|
+
})
|
|
224
|
+
})
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { init } from '../src/commands/init.js'
|
|
3
|
+
import * as fsUtils from '../src/utils/fs.js'
|
|
4
|
+
import * as execUtils from '../src/utils/exec.js'
|
|
5
|
+
import * as promptUtils from '../src/prompts.js'
|
|
6
|
+
import * as buildToolUtils from '../src/detect/build-tool.js'
|
|
7
|
+
import * as frameworkUtils from '../src/detect/framework.js'
|
|
8
|
+
import * as ideUtils from '../src/detect/ide.js'
|
|
9
|
+
import * as packageManagerUtils from '../src/detect/package-manager.js'
|
|
10
|
+
import * as providerUtils from '../src/detect/provider.js'
|
|
11
|
+
import * as astInjectorUtils from '../src/inject/ast-injector.js'
|
|
12
|
+
import * as instructionUtils from '../src/instructions.js'
|
|
13
|
+
import * as onboardingApply from '../src/onboarding/apply.js'
|
|
14
|
+
|
|
15
|
+
vi.mock('../src/utils/fs.js', () => ({
|
|
16
|
+
exists: vi.fn(),
|
|
17
|
+
readJSON: vi.fn(),
|
|
18
|
+
writeJSON: vi.fn(),
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
vi.mock('../src/utils/exec.js', () => ({
|
|
22
|
+
shell: vi.fn(),
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
vi.mock('../src/detect/package-manager.js', async () => {
|
|
26
|
+
const actual = await vi.importActual('../src/detect/package-manager.js')
|
|
27
|
+
return {
|
|
28
|
+
...actual,
|
|
29
|
+
detectPackageManager: vi.fn(),
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
vi.mock('../src/detect/build-tool.js', async () => {
|
|
34
|
+
const actual = await vi.importActual('../src/detect/build-tool.js')
|
|
35
|
+
return {
|
|
36
|
+
...actual,
|
|
37
|
+
detectBuildTools: vi.fn(),
|
|
38
|
+
resolveInjectionTarget: vi.fn(),
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
vi.mock('../src/detect/framework.js', () => ({
|
|
43
|
+
detectFrameworks: vi.fn(),
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
vi.mock('../src/detect/ide.js', () => ({
|
|
47
|
+
detectIDE: vi.fn(),
|
|
48
|
+
}))
|
|
49
|
+
|
|
50
|
+
vi.mock('../src/detect/provider.js', () => ({
|
|
51
|
+
detectProviders: vi.fn(),
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
vi.mock('../src/prompts.js', async () => {
|
|
55
|
+
const actual = await vi.importActual('../src/prompts.js')
|
|
56
|
+
return {
|
|
57
|
+
...actual,
|
|
58
|
+
promptMonorepoPackageChoice: vi.fn(),
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
vi.mock('../src/inject/ast-injector.js', () => ({
|
|
63
|
+
injectPlugin: vi.fn().mockResolvedValue({ success: true, mutations: [] }),
|
|
64
|
+
}))
|
|
65
|
+
|
|
66
|
+
vi.mock('../src/inject/gitignore.js', () => ({
|
|
67
|
+
updateGitignore: vi.fn(),
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
vi.mock('../src/inject/extension.js', () => ({
|
|
71
|
+
installExtension: vi.fn().mockResolvedValue(null),
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
vi.mock('../src/instructions.js', () => ({
|
|
75
|
+
printNextJsManualInstructions: vi.fn(),
|
|
76
|
+
printNuxtManualInstructions: vi.fn(),
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
vi.mock('../src/onboarding/apply.js', async () => {
|
|
80
|
+
const actual = await vi.importActual('../src/onboarding/apply.js')
|
|
81
|
+
return {
|
|
82
|
+
...actual,
|
|
83
|
+
applyOnboardingPlan: vi.fn().mockResolvedValue({
|
|
84
|
+
status: 'ok',
|
|
85
|
+
mutations: [],
|
|
86
|
+
postInstall: {
|
|
87
|
+
installFailed: false,
|
|
88
|
+
injectionFailed: false,
|
|
89
|
+
manualExtensionInstallNeeded: false,
|
|
90
|
+
nextSteps: [],
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('init in monorepo roots', () => {
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
vi.resetAllMocks()
|
|
99
|
+
vi.spyOn(process, 'cwd').mockReturnValue('/repo')
|
|
100
|
+
vi.mocked(onboardingApply.applyOnboardingPlan).mockResolvedValue({
|
|
101
|
+
status: 'ok',
|
|
102
|
+
mutations: [],
|
|
103
|
+
postInstall: {
|
|
104
|
+
installFailed: false,
|
|
105
|
+
injectionFailed: false,
|
|
106
|
+
manualExtensionInstallNeeded: false,
|
|
107
|
+
nextSteps: [],
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
|
|
112
|
+
return (
|
|
113
|
+
filePath === '/repo/package.json' ||
|
|
114
|
+
filePath === '/repo/apps/web' ||
|
|
115
|
+
filePath === '/repo/apps/admin'
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
vi.mocked(fsUtils.readJSON).mockResolvedValue({})
|
|
120
|
+
vi.mocked(execUtils.shell).mockResolvedValue({ stdout: '', stderr: '' })
|
|
121
|
+
vi.mocked(packageManagerUtils.detectPackageManager).mockResolvedValue('pnpm')
|
|
122
|
+
vi.mocked(frameworkUtils.detectFrameworks).mockResolvedValue({
|
|
123
|
+
supported: ['react'],
|
|
124
|
+
unsupported: [],
|
|
125
|
+
})
|
|
126
|
+
vi.mocked(ideUtils.detectIDE).mockResolvedValue({
|
|
127
|
+
detected: [{ ide: 'vscode', supported: true }],
|
|
128
|
+
})
|
|
129
|
+
vi.mocked(providerUtils.detectProviders).mockResolvedValue({
|
|
130
|
+
detected: [],
|
|
131
|
+
})
|
|
132
|
+
vi.mocked(buildToolUtils.resolveInjectionTarget).mockReturnValue({
|
|
133
|
+
tool: 'vite',
|
|
134
|
+
configPath: 'apps/web/vite.config.ts',
|
|
135
|
+
label: 'Vite (apps/web/vite.config.ts)',
|
|
136
|
+
packagePath: 'apps/web',
|
|
137
|
+
})
|
|
138
|
+
vi.mocked(astInjectorUtils.injectPlugin).mockResolvedValue({
|
|
139
|
+
success: true,
|
|
140
|
+
mutations: [],
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('routes installation and config generation into the selected app instead of the monorepo root', async () => {
|
|
145
|
+
vi.mocked(buildToolUtils.detectBuildTools)
|
|
146
|
+
.mockResolvedValueOnce({
|
|
147
|
+
supported: [
|
|
148
|
+
{
|
|
149
|
+
tool: 'vite',
|
|
150
|
+
configPath: 'apps/web/vite.config.ts',
|
|
151
|
+
label: 'Vite (apps/web/vite.config.ts)',
|
|
152
|
+
packagePath: 'apps/web',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
tool: 'vite',
|
|
156
|
+
configPath: 'apps/admin/vite.config.ts',
|
|
157
|
+
label: 'Vite (apps/admin/vite.config.ts)',
|
|
158
|
+
packagePath: 'apps/admin',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
unsupported: [],
|
|
162
|
+
})
|
|
163
|
+
.mockResolvedValueOnce({
|
|
164
|
+
supported: [
|
|
165
|
+
{
|
|
166
|
+
tool: 'vite',
|
|
167
|
+
configPath: 'apps/web/vite.config.ts',
|
|
168
|
+
label: 'Vite (apps/web/vite.config.ts)',
|
|
169
|
+
packagePath: 'apps/web',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
unsupported: [],
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
vi.mocked(promptUtils.promptMonorepoPackageChoice).mockResolvedValue('apps/web')
|
|
176
|
+
|
|
177
|
+
await init({
|
|
178
|
+
shared: false,
|
|
179
|
+
skipInstall: false,
|
|
180
|
+
dryRun: false,
|
|
181
|
+
noExtension: false,
|
|
182
|
+
force: false,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
expect(promptUtils.promptMonorepoPackageChoice).toHaveBeenCalledWith([
|
|
186
|
+
{
|
|
187
|
+
tool: 'vite',
|
|
188
|
+
configPath: 'apps/web/vite.config.ts',
|
|
189
|
+
label: 'Vite (apps/web/vite.config.ts)',
|
|
190
|
+
packagePath: 'apps/web',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
tool: 'vite',
|
|
194
|
+
configPath: 'apps/admin/vite.config.ts',
|
|
195
|
+
label: 'Vite (apps/admin/vite.config.ts)',
|
|
196
|
+
packagePath: 'apps/admin',
|
|
197
|
+
},
|
|
198
|
+
])
|
|
199
|
+
expect(buildToolUtils.detectBuildTools).toHaveBeenNthCalledWith(2, '/repo', ['apps/web'])
|
|
200
|
+
expect(onboardingApply.applyOnboardingPlan).toHaveBeenCalledWith({
|
|
201
|
+
repoRoot: '/repo',
|
|
202
|
+
projectRoot: '/repo/apps/web',
|
|
203
|
+
packageManager: 'pnpm',
|
|
204
|
+
supportedBuildTargets: [
|
|
205
|
+
{
|
|
206
|
+
tool: 'vite',
|
|
207
|
+
configPath: 'apps/web/vite.config.ts',
|
|
208
|
+
label: 'Vite (apps/web/vite.config.ts)',
|
|
209
|
+
packagePath: 'apps/web',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
options: {
|
|
213
|
+
shared: false,
|
|
214
|
+
skipInstall: false,
|
|
215
|
+
dryRun: false,
|
|
216
|
+
noExtension: false,
|
|
217
|
+
},
|
|
218
|
+
selectedIDE: {
|
|
219
|
+
ide: 'vscode',
|
|
220
|
+
supported: true,
|
|
221
|
+
},
|
|
222
|
+
providerDefault: undefined,
|
|
223
|
+
manualConfigRequiredFor: '',
|
|
224
|
+
injectionSkippedRequiresManualConfig: false,
|
|
225
|
+
allowManualPlanApply: true,
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('prints detailed Next.js manual instructions when Next.js is detected', async () => {
|
|
230
|
+
vi.mocked(buildToolUtils.detectBuildTools).mockResolvedValue({
|
|
231
|
+
supported: [],
|
|
232
|
+
unsupported: ['Next.js'],
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
await init({
|
|
236
|
+
shared: false,
|
|
237
|
+
skipInstall: true,
|
|
238
|
+
dryRun: true,
|
|
239
|
+
noExtension: true,
|
|
240
|
+
force: false,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
expect(instructionUtils.printNextJsManualInstructions).toHaveBeenCalled()
|
|
244
|
+
expect(instructionUtils.printNuxtManualInstructions).not.toHaveBeenCalled()
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('preserves detected preferred mode for an explicit provider override', async () => {
|
|
248
|
+
vi.mocked(buildToolUtils.detectBuildTools).mockResolvedValue({
|
|
249
|
+
supported: [
|
|
250
|
+
{
|
|
251
|
+
tool: 'vite',
|
|
252
|
+
configPath: 'vite.config.ts',
|
|
253
|
+
label: 'Vite (vite.config.ts)',
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
unsupported: [],
|
|
257
|
+
})
|
|
258
|
+
vi.mocked(providerUtils.detectProviders).mockResolvedValue({
|
|
259
|
+
detected: [
|
|
260
|
+
{
|
|
261
|
+
id: 'codex',
|
|
262
|
+
label: 'Codex CLI',
|
|
263
|
+
supported: true,
|
|
264
|
+
providerModes: ['cli'],
|
|
265
|
+
preferredMode: 'cli',
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
await init({
|
|
271
|
+
shared: false,
|
|
272
|
+
skipInstall: false,
|
|
273
|
+
dryRun: false,
|
|
274
|
+
provider: 'codex',
|
|
275
|
+
noExtension: false,
|
|
276
|
+
force: false,
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
expect(onboardingApply.applyOnboardingPlan).toHaveBeenCalledWith(
|
|
280
|
+
expect.objectContaining({
|
|
281
|
+
providerDefault: 'codex.cli',
|
|
282
|
+
}),
|
|
283
|
+
)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('prints delegated manual steps when the shared apply flow returns a warning', async () => {
|
|
287
|
+
vi.mocked(buildToolUtils.detectBuildTools).mockResolvedValue({
|
|
288
|
+
supported: [
|
|
289
|
+
{
|
|
290
|
+
tool: 'vite',
|
|
291
|
+
configPath: 'vite.config.ts',
|
|
292
|
+
label: 'Vite (vite.config.ts)',
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
unsupported: [],
|
|
296
|
+
})
|
|
297
|
+
vi.mocked(onboardingApply.applyOnboardingPlan).mockResolvedValue({
|
|
298
|
+
status: 'warning',
|
|
299
|
+
mutations: [],
|
|
300
|
+
postInstall: {
|
|
301
|
+
installFailed: false,
|
|
302
|
+
injectionFailed: false,
|
|
303
|
+
manualExtensionInstallNeeded: false,
|
|
304
|
+
nextSteps: [
|
|
305
|
+
'Install dependencies manually in /repo: pnpm add -D @inspecto-dev/plugin @inspecto-dev/core',
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
311
|
+
|
|
312
|
+
await init({
|
|
313
|
+
shared: false,
|
|
314
|
+
skipInstall: false,
|
|
315
|
+
dryRun: false,
|
|
316
|
+
noExtension: false,
|
|
317
|
+
force: false,
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const output = consoleSpy.mock.calls.flatMap(call => call.map(value => String(value)))
|
|
321
|
+
expect(output.some(line => line.includes('Manual Steps Required'))).toBe(true)
|
|
322
|
+
expect(
|
|
323
|
+
output.some(line =>
|
|
324
|
+
line.includes(
|
|
325
|
+
'Install dependencies manually in /repo: pnpm add -D @inspecto-dev/plugin @inspecto-dev/core',
|
|
326
|
+
),
|
|
327
|
+
),
|
|
328
|
+
).toBe(true)
|
|
329
|
+
expect(
|
|
330
|
+
output.some(line => line.includes('Ready! Hold Alt + Click any element to inspect.')),
|
|
331
|
+
).toBe(false)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('prints a short 3-step success guide after automatic setup succeeds', async () => {
|
|
335
|
+
vi.mocked(buildToolUtils.detectBuildTools).mockResolvedValue({
|
|
336
|
+
supported: [
|
|
337
|
+
{
|
|
338
|
+
tool: 'vite',
|
|
339
|
+
configPath: 'vite.config.ts',
|
|
340
|
+
label: 'Vite (vite.config.ts)',
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
unsupported: [],
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
347
|
+
|
|
348
|
+
await init({
|
|
349
|
+
shared: false,
|
|
350
|
+
skipInstall: false,
|
|
351
|
+
dryRun: false,
|
|
352
|
+
noExtension: false,
|
|
353
|
+
force: false,
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
const output = consoleSpy.mock.calls.flatMap(call => call.map(value => String(value)))
|
|
357
|
+
expect(output.some(line => line.includes('Ready! Inspecto is set up.'))).toBe(true)
|
|
358
|
+
expect(output.some(line => line.includes('1. Start or restart your dev server.'))).toBe(true)
|
|
359
|
+
expect(output.some(line => line.includes('2. Open your app in the browser.'))).toBe(true)
|
|
360
|
+
expect(output.some(line => line.includes('3. Hold Alt + Click any element to inspect.'))).toBe(
|
|
361
|
+
true,
|
|
362
|
+
)
|
|
363
|
+
})
|
|
364
|
+
})
|