@inspecto-dev/cli 0.3.3 → 0.3.5
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 +7 -7
- package/.turbo/turbo-test.log +10594 -4044
- package/CHANGELOG.md +28 -0
- package/dist/bin.js +36 -2
- package/dist/{chunk-LJOKPCPD.js → chunk-7ABJRH3F.js} +1701 -182
- package/dist/index.d.ts +69 -4
- package/dist/index.js +7 -1
- package/package.json +3 -3
- package/src/bin.ts +49 -1
- package/src/commands/dev-config.ts +109 -0
- package/src/commands/doctor.ts +189 -9
- package/src/commands/init.ts +10 -3
- package/src/commands/integration-automation.ts +2 -0
- package/src/commands/integration-host-ide.ts +18 -15
- package/src/commands/integration-install.ts +100 -5
- package/src/commands/onboard.ts +80 -15
- package/src/detect/build-tool.ts +212 -15
- package/src/detect/framework.ts +3 -0
- package/src/detect/package-manager.ts +1 -1
- package/src/index.ts +1 -0
- package/src/inject/gitignore.ts +13 -2
- package/src/instructions.ts +33 -7
- package/src/onboarding/apply.ts +255 -28
- package/src/onboarding/nextjs-guidance.ts +257 -0
- package/src/onboarding/nuxt-guidance.ts +129 -0
- package/src/onboarding/planner.ts +337 -10
- package/src/onboarding/session.ts +127 -31
- package/src/onboarding/target-resolution.ts +79 -3
- package/src/onboarding/umi-guidance.ts +139 -0
- package/src/types.ts +58 -3
- package/tests/apply.test.ts +553 -0
- package/tests/build-tool.test.ts +199 -0
- package/tests/dev-config.test.ts +73 -0
- package/tests/doctor.test.ts +130 -0
- package/tests/init.test.ts +17 -0
- package/tests/install-wrapper.test.ts +56 -0
- package/tests/instructions.test.ts +10 -6
- package/tests/integration-host-ide.test.ts +20 -0
- package/tests/integration-install.test.ts +193 -0
- package/tests/nextjs-guidance.test.ts +128 -0
- package/tests/nuxt-guidance.test.ts +67 -0
- package/tests/onboard.test.ts +511 -0
- package/tests/plan.test.ts +283 -21
- package/tests/runner-script.test.ts +120 -1
- package/tests/session-resolve.test.ts +116 -0
- package/tests/session.test.ts +120 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import type { OnboardingPatchPlan } from '../types.js'
|
|
4
|
+
|
|
5
|
+
type RouterMode = 'app' | 'pages' | 'mixed' | 'unknown'
|
|
6
|
+
|
|
7
|
+
export interface NextJsGuidance {
|
|
8
|
+
framework: 'react'
|
|
9
|
+
metaFramework: 'Next.js'
|
|
10
|
+
routerMode: RouterMode
|
|
11
|
+
autoApplied: string[]
|
|
12
|
+
pendingSteps: string[]
|
|
13
|
+
assistantPrompt: string
|
|
14
|
+
patches: OnboardingPatchPlan[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const NEXT_CONFIG_CANDIDATES = ['next.config.ts', 'next.config.mjs', 'next.config.js'] as const
|
|
18
|
+
const APP_ROUTER_LAYOUTS = [
|
|
19
|
+
'app/layout.tsx',
|
|
20
|
+
'app/layout.jsx',
|
|
21
|
+
'src/app/layout.tsx',
|
|
22
|
+
'src/app/layout.jsx',
|
|
23
|
+
] as const
|
|
24
|
+
const PAGES_ROUTER_APPS = [
|
|
25
|
+
'pages/_app.tsx',
|
|
26
|
+
'pages/_app.jsx',
|
|
27
|
+
'src/pages/_app.tsx',
|
|
28
|
+
'src/pages/_app.jsx',
|
|
29
|
+
] as const
|
|
30
|
+
const PACKAGE_JSON_PATH = 'package.json'
|
|
31
|
+
|
|
32
|
+
function findFirstExisting(root: string, candidates: readonly string[]): string | undefined {
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (fs.existsSync(path.join(root, candidate))) {
|
|
35
|
+
return candidate
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function detectRouterMode(root: string): RouterMode {
|
|
43
|
+
const hasAppRouter = Boolean(findFirstExisting(root, APP_ROUTER_LAYOUTS))
|
|
44
|
+
const hasPagesRouter = Boolean(findFirstExisting(root, PAGES_ROUTER_APPS))
|
|
45
|
+
|
|
46
|
+
if (hasAppRouter && hasPagesRouter) return 'mixed'
|
|
47
|
+
if (hasAppRouter) return 'app'
|
|
48
|
+
if (hasPagesRouter) return 'pages'
|
|
49
|
+
return 'unknown'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectNextConfigPath(root: string): string | undefined {
|
|
53
|
+
return findFirstExisting(root, NEXT_CONFIG_CANDIDATES)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readConfig(root: string, relativePath?: string): string {
|
|
57
|
+
if (!relativePath) return ''
|
|
58
|
+
const filePath = path.join(root, relativePath)
|
|
59
|
+
if (!fs.existsSync(filePath)) return ''
|
|
60
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function detectPatchShape(source: string): {
|
|
64
|
+
status: OnboardingPatchPlan['status']
|
|
65
|
+
reason: OnboardingPatchPlan['reason']
|
|
66
|
+
confidence: OnboardingPatchPlan['confidence']
|
|
67
|
+
} {
|
|
68
|
+
if (
|
|
69
|
+
/export\s+default\s*\{[\s\S]*\}/m.test(source) ||
|
|
70
|
+
/module\.exports\s*=\s*\{[\s\S]*\}/m.test(source) ||
|
|
71
|
+
/const\s+[A-Za-z0-9_$]+\s*(?::[^=]+)?=\s*\{[\s\S]*\}\s*;?\s*export\s+default\s+[A-Za-z0-9_$]+\s*;?/m.test(
|
|
72
|
+
source,
|
|
73
|
+
) ||
|
|
74
|
+
/\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{[\s\S]*\}/m.test(
|
|
75
|
+
source,
|
|
76
|
+
)
|
|
77
|
+
) {
|
|
78
|
+
return {
|
|
79
|
+
status: 'planned',
|
|
80
|
+
reason: 'next_config_object_export',
|
|
81
|
+
confidence: 'high',
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
/module\.exports\s*=\s*[A-Za-z0-9_$]+\s*\(/m.test(source) ||
|
|
87
|
+
/export\s+default\s+[A-Za-z0-9_$]+\s*\(/m.test(source)
|
|
88
|
+
) {
|
|
89
|
+
return {
|
|
90
|
+
status: 'manual_patch_required',
|
|
91
|
+
reason: 'next_config_wrapped_export',
|
|
92
|
+
confidence: 'medium',
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (source.trim().length === 0) {
|
|
97
|
+
return {
|
|
98
|
+
status: 'manual_patch_required',
|
|
99
|
+
reason: 'next_config_missing',
|
|
100
|
+
confidence: 'low',
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
status: 'manual_patch_required',
|
|
106
|
+
reason: 'next_config_complex_shape',
|
|
107
|
+
confidence: 'medium',
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildPatchSnippet(): string {
|
|
112
|
+
return [
|
|
113
|
+
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
114
|
+
'',
|
|
115
|
+
'webpack(config, { dev, isServer }) {',
|
|
116
|
+
' if (dev) {',
|
|
117
|
+
' config.plugins.push(inspecto())',
|
|
118
|
+
' }',
|
|
119
|
+
' return config',
|
|
120
|
+
'}',
|
|
121
|
+
].join('\n')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function buildAppRouterMountSnippet(): string {
|
|
125
|
+
return [
|
|
126
|
+
"import { useEffect } from 'react'",
|
|
127
|
+
'',
|
|
128
|
+
'function InspectoProvider() {',
|
|
129
|
+
' useEffect(() => {',
|
|
130
|
+
" if (process.env.NODE_ENV !== 'production') {",
|
|
131
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
132
|
+
' mountInspector()',
|
|
133
|
+
' })',
|
|
134
|
+
' }',
|
|
135
|
+
' }, [])',
|
|
136
|
+
'',
|
|
137
|
+
' return null',
|
|
138
|
+
'}',
|
|
139
|
+
'',
|
|
140
|
+
'Render <InspectoProvider /> from app/layout.tsx inside the <body> tree.',
|
|
141
|
+
].join('\n')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildPagesRouterMountSnippet(): string {
|
|
145
|
+
return [
|
|
146
|
+
"import { useEffect } from 'react'",
|
|
147
|
+
'',
|
|
148
|
+
'useEffect(() => {',
|
|
149
|
+
" if (process.env.NODE_ENV !== 'production') {",
|
|
150
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
151
|
+
' mountInspector()',
|
|
152
|
+
' })',
|
|
153
|
+
' }',
|
|
154
|
+
'}, [])',
|
|
155
|
+
'',
|
|
156
|
+
'Add this effect to pages/_app.tsx without changing the existing app wrapper behavior.',
|
|
157
|
+
].join('\n')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function detectWebpackDevScriptPatch(root: string): OnboardingPatchPlan | undefined {
|
|
161
|
+
const packageJsonSource = readConfig(root, PACKAGE_JSON_PATH)
|
|
162
|
+
if (!packageJsonSource) {
|
|
163
|
+
return undefined
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const packageJson = JSON.parse(packageJsonSource) as { scripts?: Record<string, string> }
|
|
168
|
+
const devScript = packageJson.scripts?.dev
|
|
169
|
+
if (!devScript || !/\bnext\s+dev\b/.test(devScript) || /--webpack\b/.test(devScript)) {
|
|
170
|
+
return undefined
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
path: PACKAGE_JSON_PATH,
|
|
175
|
+
status: 'manual_patch_required',
|
|
176
|
+
reason: 'next_dev_script_requires_webpack',
|
|
177
|
+
confidence: 'medium',
|
|
178
|
+
snippet: '"dev": "next dev --webpack"',
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
return undefined
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildPendingSteps(
|
|
186
|
+
routerMode: RouterMode,
|
|
187
|
+
configPath: string,
|
|
188
|
+
needsWebpackDevScript: boolean,
|
|
189
|
+
): string[] {
|
|
190
|
+
const routerHint =
|
|
191
|
+
routerMode === 'app'
|
|
192
|
+
? 'Complete the remaining client-side mount step for your App Router entry.'
|
|
193
|
+
: routerMode === 'pages'
|
|
194
|
+
? 'Complete the remaining client-side mount step for your Pages Router entry.'
|
|
195
|
+
: routerMode === 'mixed'
|
|
196
|
+
? 'Complete the remaining client-side mount step for the router entry your team actually uses in development.'
|
|
197
|
+
: 'Complete the remaining client-side mount step in the appropriate Next.js entry file.'
|
|
198
|
+
|
|
199
|
+
return [
|
|
200
|
+
`Review the generated Next.js patch plan for ${configPath}.`,
|
|
201
|
+
...(needsWebpackDevScript
|
|
202
|
+
? ['Update the Next.js dev script to use webpack mode for Inspecto validation.']
|
|
203
|
+
: []),
|
|
204
|
+
'Keep the Inspecto webpack plugin enabled for both server and client development compilers in App Router projects.',
|
|
205
|
+
routerHint,
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function createNextJsGuidance(root: string): NextJsGuidance {
|
|
210
|
+
const configPath = detectNextConfigPath(root) ?? 'next.config.js'
|
|
211
|
+
const configSource = readConfig(root, detectNextConfigPath(root))
|
|
212
|
+
const patchShape = detectPatchShape(configSource)
|
|
213
|
+
const routerMode = detectRouterMode(root)
|
|
214
|
+
const webpackDevScriptPatch = detectWebpackDevScriptPatch(root)
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
framework: 'react',
|
|
218
|
+
metaFramework: 'Next.js',
|
|
219
|
+
routerMode,
|
|
220
|
+
autoApplied: ['dependencies', 'inspecto_settings'],
|
|
221
|
+
pendingSteps: buildPendingSteps(routerMode, configPath, Boolean(webpackDevScriptPatch)),
|
|
222
|
+
assistantPrompt:
|
|
223
|
+
'Complete the remaining Inspecto onboarding for this Next.js project. Use the generated patches directly, keep existing app behavior unchanged, avoid unrelated documentation searches unless a patch is insufficient, and finish the client-side mount step safely.',
|
|
224
|
+
patches: [
|
|
225
|
+
{
|
|
226
|
+
path: configPath,
|
|
227
|
+
status: patchShape.status,
|
|
228
|
+
reason: patchShape.reason,
|
|
229
|
+
confidence: patchShape.confidence,
|
|
230
|
+
snippet: buildPatchSnippet(),
|
|
231
|
+
},
|
|
232
|
+
...(routerMode === 'app' || routerMode === 'mixed'
|
|
233
|
+
? [
|
|
234
|
+
{
|
|
235
|
+
path: findFirstExisting(root, APP_ROUTER_LAYOUTS) ?? 'app/layout.tsx',
|
|
236
|
+
status: 'manual_patch_required' as const,
|
|
237
|
+
reason: 'next_app_router_mount',
|
|
238
|
+
confidence: 'medium' as const,
|
|
239
|
+
snippet: buildAppRouterMountSnippet(),
|
|
240
|
+
},
|
|
241
|
+
]
|
|
242
|
+
: []),
|
|
243
|
+
...(routerMode === 'pages' || routerMode === 'mixed'
|
|
244
|
+
? [
|
|
245
|
+
{
|
|
246
|
+
path: findFirstExisting(root, PAGES_ROUTER_APPS) ?? 'pages/_app.tsx',
|
|
247
|
+
status: 'manual_patch_required' as const,
|
|
248
|
+
reason: 'next_pages_router_mount',
|
|
249
|
+
confidence: 'medium' as const,
|
|
250
|
+
snippet: buildPagesRouterMountSnippet(),
|
|
251
|
+
},
|
|
252
|
+
]
|
|
253
|
+
: []),
|
|
254
|
+
...(webpackDevScriptPatch ? [webpackDevScriptPatch] : []),
|
|
255
|
+
],
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import type { OnboardingPatchPlan } from '../types.js'
|
|
4
|
+
|
|
5
|
+
export interface NuxtGuidance {
|
|
6
|
+
framework: 'vue'
|
|
7
|
+
metaFramework: 'Nuxt'
|
|
8
|
+
autoApplied: string[]
|
|
9
|
+
pendingSteps: string[]
|
|
10
|
+
assistantPrompt: string
|
|
11
|
+
patches: OnboardingPatchPlan[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const NUXT_CONFIG_CANDIDATES = ['nuxt.config.ts', 'nuxt.config.js'] as const
|
|
15
|
+
|
|
16
|
+
function findFirstExisting(root: string, candidates: readonly string[]): string | undefined {
|
|
17
|
+
for (const candidate of candidates) {
|
|
18
|
+
if (fs.existsSync(path.join(root, candidate))) {
|
|
19
|
+
return candidate
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readConfig(root: string, relativePath?: string): string {
|
|
27
|
+
if (!relativePath) return ''
|
|
28
|
+
const filePath = path.join(root, relativePath)
|
|
29
|
+
if (!fs.existsSync(filePath)) return ''
|
|
30
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function detectPatchShape(source: string): {
|
|
34
|
+
status: OnboardingPatchPlan['status']
|
|
35
|
+
reason: OnboardingPatchPlan['reason']
|
|
36
|
+
confidence: OnboardingPatchPlan['confidence']
|
|
37
|
+
} {
|
|
38
|
+
if (/with[A-Za-z0-9_$]*\s*\(\s*defineNuxtConfig/m.test(source)) {
|
|
39
|
+
return {
|
|
40
|
+
status: 'manual_patch_required',
|
|
41
|
+
reason: 'nuxt_config_wrapped_export',
|
|
42
|
+
confidence: 'medium',
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (/defineNuxtConfig\s*\(\s*\{[\s\S]*\}\s*\)/m.test(source)) {
|
|
47
|
+
return {
|
|
48
|
+
status: 'planned',
|
|
49
|
+
reason: 'nuxt_config_object_export',
|
|
50
|
+
confidence: 'high',
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (source.trim().length === 0) {
|
|
55
|
+
return {
|
|
56
|
+
status: 'manual_patch_required',
|
|
57
|
+
reason: 'nuxt_config_missing',
|
|
58
|
+
confidence: 'low',
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
status: 'manual_patch_required',
|
|
64
|
+
reason: 'nuxt_config_complex_shape',
|
|
65
|
+
confidence: 'medium',
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildNuxtConfigSnippet(): string {
|
|
70
|
+
return [
|
|
71
|
+
"import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
72
|
+
'',
|
|
73
|
+
'export default defineNuxtConfig({',
|
|
74
|
+
' vite: {',
|
|
75
|
+
' plugins: [inspecto()],',
|
|
76
|
+
' },',
|
|
77
|
+
'})',
|
|
78
|
+
].join('\n')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildNuxtPluginSnippet(): string {
|
|
82
|
+
return [
|
|
83
|
+
'export default defineNuxtPlugin(() => {',
|
|
84
|
+
' if (import.meta.dev) {',
|
|
85
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
86
|
+
' mountInspector()',
|
|
87
|
+
' })',
|
|
88
|
+
' }',
|
|
89
|
+
'})',
|
|
90
|
+
].join('\n')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createNuxtGuidance(root: string): NuxtGuidance {
|
|
94
|
+
const configPath = findFirstExisting(root, NUXT_CONFIG_CANDIDATES) ?? 'nuxt.config.ts'
|
|
95
|
+
const configSource = readConfig(root, findFirstExisting(root, NUXT_CONFIG_CANDIDATES))
|
|
96
|
+
const patchShape = detectPatchShape(configSource)
|
|
97
|
+
|
|
98
|
+
const hasSrcDir =
|
|
99
|
+
fs.existsSync(path.join(root, 'src')) && fs.statSync(path.join(root, 'src')).isDirectory()
|
|
100
|
+
const pluginPath = hasSrcDir ? 'src/plugins/inspecto.client.ts' : 'plugins/inspecto.client.ts'
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
framework: 'vue',
|
|
104
|
+
metaFramework: 'Nuxt',
|
|
105
|
+
autoApplied: ['dependencies', 'inspecto_settings'],
|
|
106
|
+
pendingSteps: [
|
|
107
|
+
`Review the generated Nuxt patch plan for ${configPath}.`,
|
|
108
|
+
`Complete the remaining Nuxt client plugin mount step in ${pluginPath}.`,
|
|
109
|
+
],
|
|
110
|
+
assistantPrompt:
|
|
111
|
+
'Complete the remaining Inspecto onboarding for this Nuxt project. Review the generated patch plan, keep existing app behavior unchanged, and finish the client plugin mount step safely.',
|
|
112
|
+
patches: [
|
|
113
|
+
{
|
|
114
|
+
path: configPath,
|
|
115
|
+
status: patchShape.status,
|
|
116
|
+
reason: patchShape.reason,
|
|
117
|
+
confidence: patchShape.confidence,
|
|
118
|
+
snippet: buildNuxtConfigSnippet(),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
path: pluginPath,
|
|
122
|
+
status: 'manual_patch_required',
|
|
123
|
+
reason: 'nuxt_client_plugin_mount',
|
|
124
|
+
confidence: 'medium',
|
|
125
|
+
snippet: buildNuxtPluginSnippet(),
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
}
|