@inspecto-dev/cli 0.3.4 → 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 +10036 -5601
- package/CHANGELOG.md +8 -0
- package/dist/bin.js +32 -1
- package/dist/{chunk-2MOEVONN.js → chunk-7ABJRH3F.js} +1232 -145
- package/dist/index.d.ts +62 -4
- package/dist/index.js +7 -1
- package/package.json +2 -2
- package/src/bin.ts +45 -0
- package/src/commands/dev-config.ts +109 -0
- package/src/commands/doctor.ts +162 -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 +1 -1
- package/src/commands/onboard.ts +72 -21
- package/src/detect/build-tool.ts +14 -5
- 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 +137 -3
- package/src/onboarding/nextjs-guidance.ts +257 -0
- package/src/onboarding/nuxt-guidance.ts +129 -0
- package/src/onboarding/planner.ts +257 -6
- package/src/onboarding/session.ts +117 -27
- package/src/onboarding/target-resolution.ts +3 -3
- package/src/onboarding/umi-guidance.ts +139 -0
- package/src/types.ts +51 -3
- package/tests/apply.test.ts +319 -0
- package/tests/dev-config.test.ts +73 -0
- package/tests/doctor.test.ts +89 -0
- package/tests/init.test.ts +17 -0
- package/tests/instructions.test.ts +10 -6
- package/tests/integration-host-ide.test.ts +20 -0
- package/tests/integration-install.test.ts +65 -0
- package/tests/nextjs-guidance.test.ts +128 -0
- package/tests/nuxt-guidance.test.ts +67 -0
- package/tests/onboard.test.ts +416 -0
- package/tests/plan.test.ts +181 -21
- package/tests/runner-script.test.ts +120 -1
- package/tests/session-resolve.test.ts +116 -0
- package/tests/session.test.ts +120 -0
package/src/commands/onboard.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from '../onboarding/session.js'
|
|
6
6
|
import { log } from '../utils/logger.js'
|
|
7
7
|
import { writeCommandOutput } from '../utils/output.js'
|
|
8
|
-
import type { OnboardCommandResult } from '../types.js'
|
|
8
|
+
import type { OnboardCommandResult, OnboardingAssistantHandoff } from '../types.js'
|
|
9
9
|
|
|
10
10
|
export interface OnboardCommandOptions {
|
|
11
11
|
json?: boolean
|
|
@@ -34,47 +34,98 @@ function printManualExtensionGuidance(result: OnboardCommandResult): void {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function buildAssistantHandoff(
|
|
38
|
+
result: OnboardCommandResult,
|
|
39
|
+
): OnboardingAssistantHandoff | undefined {
|
|
40
|
+
if (
|
|
41
|
+
!result.framework &&
|
|
42
|
+
!result.metaFramework &&
|
|
43
|
+
!result.routerMode &&
|
|
44
|
+
!result.autoApplied &&
|
|
45
|
+
!result.pendingSteps &&
|
|
46
|
+
!result.assistantPrompt &&
|
|
47
|
+
!result.patches
|
|
48
|
+
) {
|
|
49
|
+
return result.handoff
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...(result.framework ? { framework: result.framework } : {}),
|
|
54
|
+
...(result.metaFramework ? { metaFramework: result.metaFramework } : {}),
|
|
55
|
+
...(result.routerMode ? { routerMode: result.routerMode } : {}),
|
|
56
|
+
...(result.autoApplied ? { autoApplied: result.autoApplied } : {}),
|
|
57
|
+
...(result.pendingSteps ? { pendingSteps: result.pendingSteps } : {}),
|
|
58
|
+
...(result.assistantPrompt ? { assistantPrompt: result.assistantPrompt } : {}),
|
|
59
|
+
...(result.patches ? { patches: result.patches } : {}),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeOnboardResult(result: OnboardCommandResult): OnboardCommandResult {
|
|
64
|
+
const handoff = buildAssistantHandoff(result)
|
|
65
|
+
if (!handoff) {
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...result,
|
|
71
|
+
handoff,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function collectDisplayNextSteps(result: OnboardCommandResult): string[] {
|
|
76
|
+
return Array.from(
|
|
77
|
+
new Set([...(result.diagnostics?.nextSteps ?? []), ...(result.handoff?.pendingSteps ?? [])]),
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
37
81
|
function printOnboardResult(result: OnboardCommandResult): void {
|
|
82
|
+
const normalized = normalizeOnboardResult(result)
|
|
38
83
|
log.header('Inspecto Onboard')
|
|
39
|
-
log.info(`Status: ${
|
|
40
|
-
log.info(
|
|
84
|
+
log.info(`Status: ${normalized.status}`)
|
|
85
|
+
log.info(normalized.summary.headline)
|
|
41
86
|
|
|
42
|
-
if (
|
|
43
|
-
if (
|
|
44
|
-
log.warn(
|
|
87
|
+
if (normalized.status === 'needs_target_selection') {
|
|
88
|
+
if (normalized.target.selectionPurpose) {
|
|
89
|
+
log.warn(normalized.target.selectionPurpose)
|
|
45
90
|
}
|
|
46
|
-
for (const candidate of
|
|
91
|
+
for (const candidate of normalized.target.candidates) {
|
|
47
92
|
const identifier = candidate.candidateId ?? candidate.id ?? candidate.configPath
|
|
48
93
|
const label = candidate.label ?? candidate.configPath
|
|
49
94
|
log.hint(`${identifier}: ${label}`)
|
|
50
95
|
}
|
|
51
|
-
if (
|
|
52
|
-
log.hint(
|
|
96
|
+
if (normalized.target.selectionInstructions) {
|
|
97
|
+
log.hint(normalized.target.selectionInstructions)
|
|
53
98
|
}
|
|
54
99
|
}
|
|
55
100
|
|
|
56
|
-
for (const change of
|
|
101
|
+
for (const change of normalized.summary.changes) {
|
|
57
102
|
log.hint(change)
|
|
58
103
|
}
|
|
59
|
-
for (const step of
|
|
104
|
+
for (const step of collectDisplayNextSteps(normalized)) {
|
|
60
105
|
log.warn(step)
|
|
61
106
|
}
|
|
62
|
-
|
|
63
|
-
log.
|
|
107
|
+
for (const patch of normalized.handoff?.patches ?? []) {
|
|
108
|
+
log.hint(`Patch target: ${patch.path} (${patch.reason})`)
|
|
109
|
+
}
|
|
110
|
+
if (normalized.handoff?.assistantPrompt) {
|
|
111
|
+
log.hint(normalized.handoff.assistantPrompt)
|
|
112
|
+
}
|
|
113
|
+
if (normalized.confirmation.required && normalized.confirmation.question) {
|
|
114
|
+
log.warn(normalized.confirmation.question)
|
|
64
115
|
}
|
|
65
116
|
|
|
66
|
-
printManualExtensionGuidance(
|
|
117
|
+
printManualExtensionGuidance(normalized)
|
|
67
118
|
|
|
68
119
|
const extensionReady =
|
|
69
|
-
!
|
|
70
|
-
(
|
|
120
|
+
!normalized.ideExtension?.required ||
|
|
121
|
+
(normalized.ideExtension.installed && !normalized.ideExtension.manualRequired)
|
|
71
122
|
|
|
72
123
|
if (
|
|
73
124
|
extensionReady &&
|
|
74
|
-
(
|
|
75
|
-
|
|
125
|
+
(normalized.status === 'success' || normalized.status === 'partial_success') &&
|
|
126
|
+
normalized.verification?.message
|
|
76
127
|
) {
|
|
77
|
-
log.info(
|
|
128
|
+
log.info(normalized.verification.message)
|
|
78
129
|
}
|
|
79
130
|
}
|
|
80
131
|
|
|
@@ -88,12 +139,12 @@ export async function onboard(options: OnboardCommandOptions = {}): Promise<Onbo
|
|
|
88
139
|
session.status === 'needs_confirmation'
|
|
89
140
|
) {
|
|
90
141
|
return writeCommandOutput(
|
|
91
|
-
buildDeferredOnboardResult(session),
|
|
142
|
+
normalizeOnboardResult(buildDeferredOnboardResult(session)),
|
|
92
143
|
options.json ?? false,
|
|
93
144
|
printOnboardResult,
|
|
94
145
|
)
|
|
95
146
|
}
|
|
96
147
|
|
|
97
|
-
const result = await applyResolvedOnboardingSession(session, options)
|
|
148
|
+
const result = normalizeOnboardResult(await applyResolvedOnboardingSession(session, options))
|
|
98
149
|
return writeCommandOutput(result, options.json ?? false, printOnboardResult)
|
|
99
150
|
}
|
package/src/detect/build-tool.ts
CHANGED
|
@@ -139,6 +139,11 @@ const SUPPORTED_PATTERNS: { tool: BuildTool; files: string[]; label: string }[]
|
|
|
139
139
|
|
|
140
140
|
/** Recognized but unsupported meta-frameworks — detect via dep + config file */
|
|
141
141
|
const UNSUPPORTED_META: { name: string; dep: string; files: string[] }[] = [
|
|
142
|
+
{
|
|
143
|
+
name: 'Umi',
|
|
144
|
+
dep: 'umi',
|
|
145
|
+
files: ['.umirc.ts', '.umirc.js', 'config/config.ts', 'config/config.js'],
|
|
146
|
+
},
|
|
142
147
|
{ name: 'Next.js', dep: 'next', files: ['next.config.mjs', 'next.config.js', 'next.config.ts'] },
|
|
143
148
|
{ name: 'Nuxt', dep: 'nuxt', files: ['nuxt.config.ts', 'nuxt.config.js'] },
|
|
144
149
|
{ name: 'Remix', dep: '@remix-run/dev', files: ['remix.config.js', 'remix.config.ts'] },
|
|
@@ -297,7 +302,9 @@ export async function detectBuildTools(
|
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
const unsupportedChecks = UNSUPPORTED_META.map(async meta => {
|
|
300
|
-
if (
|
|
305
|
+
// Check if any dependency matches (supports exact match or scoped packages if necessary, but here we do exact match)
|
|
306
|
+
const hasDep = meta.dep in allDeps || Object.keys(allDeps).some(dep => dep.includes(meta.dep))
|
|
307
|
+
if (!hasDep) return null
|
|
301
308
|
for (const file of meta.files) {
|
|
302
309
|
if (await exists(path.join(target.absolutePath, file))) {
|
|
303
310
|
return meta.name
|
|
@@ -471,6 +478,8 @@ async function detectPattern({
|
|
|
471
478
|
}
|
|
472
479
|
} else if (pattern.tool === 'rsbuild') {
|
|
473
480
|
hasDep = !!allDeps['@rsbuild/core'] || isPackageResolvable('@rsbuild/core', targetRoot)
|
|
481
|
+
} else if (pattern.tool === 'vite') {
|
|
482
|
+
hasDep = !!allDeps['vite'] || isPackageResolvable('vite', targetRoot)
|
|
474
483
|
} else {
|
|
475
484
|
hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, targetRoot)
|
|
476
485
|
}
|
|
@@ -576,7 +585,7 @@ async function detectPattern({
|
|
|
576
585
|
tool: pattern.tool,
|
|
577
586
|
configPath: 'package.json (dependency)',
|
|
578
587
|
label: `${pattern.label} (detected via dependency)`,
|
|
579
|
-
packagePath
|
|
588
|
+
...(packagePath ? { packagePath } : {}),
|
|
580
589
|
}
|
|
581
590
|
}
|
|
582
591
|
return null
|
|
@@ -604,9 +613,9 @@ async function detectPattern({
|
|
|
604
613
|
label: `${pattern.label} (${relativeConfig})${isLegacyRspack ? ' [Legacy]' : ''}${
|
|
605
614
|
isLegacyWebpack ? ' [Webpack 4]' : ''
|
|
606
615
|
}${inferredFromScripts ? ' [Scripts Detected]' : ''}`,
|
|
607
|
-
isLegacyRspack,
|
|
608
|
-
isLegacyWebpack,
|
|
609
|
-
packagePath
|
|
616
|
+
...(isLegacyRspack ? { isLegacyRspack: true } : {}),
|
|
617
|
+
...(isLegacyWebpack ? { isLegacyWebpack: true } : {}),
|
|
618
|
+
...(packagePath ? { packagePath } : {}),
|
|
610
619
|
}
|
|
611
620
|
}
|
|
612
621
|
|
package/src/detect/framework.ts
CHANGED
|
@@ -44,9 +44,12 @@ const SUPPORTED_FRAMEWORKS: { framework: Framework; deps: string[] }[] = [
|
|
|
44
44
|
const UNSUPPORTED_FRAMEWORKS: { name: string; dep: string }[] = [
|
|
45
45
|
{ name: 'Solid', dep: 'solid-js' },
|
|
46
46
|
{ name: 'Svelte', dep: 'svelte' },
|
|
47
|
+
{ name: 'SvelteKit', dep: '@sveltejs/kit' },
|
|
47
48
|
{ name: 'Angular', dep: '@angular/core' },
|
|
48
49
|
{ name: 'Preact', dep: 'preact' },
|
|
49
50
|
{ name: 'Lit', dep: 'lit' },
|
|
51
|
+
{ name: 'Qwik', dep: 'qwik' },
|
|
52
|
+
{ name: 'Alpine', dep: 'lit-html' },
|
|
50
53
|
]
|
|
51
54
|
|
|
52
55
|
/**
|
|
@@ -14,8 +14,8 @@ export async function detectPackageManager(root: string): Promise<PackageManager
|
|
|
14
14
|
['bun.lockb', 'bun'],
|
|
15
15
|
['bun.lock', 'bun'],
|
|
16
16
|
['pnpm-lock.yaml', 'pnpm'],
|
|
17
|
-
['yarn.lock', 'yarn'],
|
|
18
17
|
['package-lock.json', 'npm'],
|
|
18
|
+
['yarn.lock', 'yarn'],
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
const results = await Promise.all(
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { apply } from './commands/apply.js'
|
|
2
2
|
export { detect } from './commands/detect.js'
|
|
3
|
+
export { devLink, devStatus, devUnlink } from './commands/dev-config.js'
|
|
3
4
|
export { init } from './commands/init.js'
|
|
4
5
|
export { collectDoctorResult, doctor } from './commands/doctor.js'
|
|
5
6
|
export { integrationDoctor } from './commands/integration-doctor.js'
|
package/src/inject/gitignore.ts
CHANGED
|
@@ -6,10 +6,20 @@ import { readFile, writeFile } from '../utils/fs.js'
|
|
|
6
6
|
import { log } from '../utils/logger.js'
|
|
7
7
|
|
|
8
8
|
/** Rules for default mode: fine-grained rules only */
|
|
9
|
-
const DEFAULT_RULES = [
|
|
9
|
+
const DEFAULT_RULES = [
|
|
10
|
+
'.inspecto/install.lock',
|
|
11
|
+
'.inspecto/cache.json',
|
|
12
|
+
'.inspecto/*.local.json',
|
|
13
|
+
'.inspecto/dev.json',
|
|
14
|
+
]
|
|
10
15
|
|
|
11
16
|
/** Rules for shared mode: same as default in current design to allow settings.json */
|
|
12
|
-
const SHARED_RULES = [
|
|
17
|
+
const SHARED_RULES = [
|
|
18
|
+
'.inspecto/install.lock',
|
|
19
|
+
'.inspecto/cache.json',
|
|
20
|
+
'.inspecto/*.local.json',
|
|
21
|
+
'.inspecto/dev.json',
|
|
22
|
+
]
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* Update .gitignore based on the init mode.
|
|
@@ -77,6 +87,7 @@ export async function cleanGitignore(root: string): Promise<void> {
|
|
|
77
87
|
.replace(/^\.inspecto\/install\.lock\s*$/gm, '')
|
|
78
88
|
.replace(/^\.inspecto\/cache\.json\s*$/gm, '')
|
|
79
89
|
.replace(/^\.inspecto\/\*\.local\.json\s*$/gm, '')
|
|
90
|
+
.replace(/^\.inspecto\/dev\.json\s*$/gm, '')
|
|
80
91
|
.replace(/\n{3,}/g, '\n\n') // Collapse excess blank lines
|
|
81
92
|
|
|
82
93
|
await writeFile(gitignorePath, cleaned)
|
package/src/instructions.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { log } from './utils/logger.js'
|
|
|
2
2
|
|
|
3
3
|
export function printNuxtManualInstructions() {
|
|
4
4
|
log.blank()
|
|
5
|
-
log.hint(
|
|
5
|
+
log.hint(
|
|
6
|
+
'Nuxt supports guided setup in the current version. Inspecto can prepare the config patch, but the client plugin mount step still needs review.',
|
|
7
|
+
)
|
|
6
8
|
log.hint('1. Update `nuxt.config.ts` to register the Inspecto Vite plugin:')
|
|
7
9
|
log.copyableCodeBlock([
|
|
8
10
|
"import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
@@ -13,7 +15,7 @@ export function printNuxtManualInstructions() {
|
|
|
13
15
|
' },',
|
|
14
16
|
'})',
|
|
15
17
|
])
|
|
16
|
-
log.hint('2.
|
|
18
|
+
log.hint('2. Complete the remaining client plugin mount step in `plugins/inspecto.client.ts`:')
|
|
17
19
|
log.copyableCodeBlock([
|
|
18
20
|
'export default defineNuxtPlugin(() => {',
|
|
19
21
|
' if (import.meta.dev) {',
|
|
@@ -23,12 +25,33 @@ export function printNuxtManualInstructions() {
|
|
|
23
25
|
' }',
|
|
24
26
|
'})',
|
|
25
27
|
])
|
|
26
|
-
log.hint('3. Restart your Nuxt dev server after
|
|
28
|
+
log.hint('3. Restart your Nuxt dev server after applying the guided patches.')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function printUmiManualInstructions() {
|
|
32
|
+
log.blank()
|
|
33
|
+
log.hint('Umi supports guided setup in the current version.')
|
|
34
|
+
log.hint('1. Update `config/config.ts` or `.umirc.ts` to register the Inspecto webpack plugin:')
|
|
35
|
+
log.copyableCodeBlock([
|
|
36
|
+
"import { defineConfig } from 'umi'",
|
|
37
|
+
"import { webpack4Plugin } from '@inspecto-dev/plugin/legacy/webpack4'",
|
|
38
|
+
'',
|
|
39
|
+
'export default defineConfig({',
|
|
40
|
+
' chainWebpack(memo) {',
|
|
41
|
+
" if (process.env.NODE_ENV === 'development') {",
|
|
42
|
+
" memo.plugin('inspecto').use(webpack4Plugin())",
|
|
43
|
+
' }',
|
|
44
|
+
' },',
|
|
45
|
+
'})',
|
|
46
|
+
])
|
|
47
|
+
log.hint('2. Restart your Umi dev server after applying the guided patches.')
|
|
27
48
|
}
|
|
28
49
|
|
|
29
50
|
export function printNextJsManualInstructions() {
|
|
30
51
|
log.blank()
|
|
31
|
-
log.hint(
|
|
52
|
+
log.hint(
|
|
53
|
+
'Next.js supports guided setup in the current version. Inspecto can prepare the config patch, but the client-side mount step still needs review.',
|
|
54
|
+
)
|
|
32
55
|
log.hint('1. Update `next.config.mjs` to register the Inspecto webpack plugin:')
|
|
33
56
|
log.copyableCodeBlock([
|
|
34
57
|
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
@@ -36,7 +59,7 @@ export function printNextJsManualInstructions() {
|
|
|
36
59
|
"/** @type {import('next').NextConfig} */",
|
|
37
60
|
'const nextConfig = {',
|
|
38
61
|
' webpack: (config, { dev, isServer }) => {',
|
|
39
|
-
' if (dev
|
|
62
|
+
' if (dev) {',
|
|
40
63
|
' config.plugins.push(inspecto())',
|
|
41
64
|
' }',
|
|
42
65
|
' return config',
|
|
@@ -46,7 +69,10 @@ export function printNextJsManualInstructions() {
|
|
|
46
69
|
'export default nextConfig',
|
|
47
70
|
])
|
|
48
71
|
log.hint(
|
|
49
|
-
'
|
|
72
|
+
'Keep the plugin enabled for both server and client development compilers so App Router server components also receive Inspecto transforms.',
|
|
73
|
+
)
|
|
74
|
+
log.hint(
|
|
75
|
+
'2. Complete the remaining client-side mount step in `app/layout.tsx` or `pages/_app.tsx`:',
|
|
50
76
|
)
|
|
51
77
|
log.copyableCodeBlock([
|
|
52
78
|
"'use client'",
|
|
@@ -65,5 +91,5 @@ export function printNextJsManualInstructions() {
|
|
|
65
91
|
' return <html><body>{children}</body></html>',
|
|
66
92
|
'}',
|
|
67
93
|
])
|
|
68
|
-
log.hint('3. Restart your Next.js dev server after
|
|
94
|
+
log.hint('3. Restart your Next.js dev server after applying the guided patches.')
|
|
69
95
|
}
|
package/src/onboarding/apply.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { injectPlugin } from '../inject/ast-injector.js'
|
|
|
5
5
|
import { installExtension } from '../inject/extension.js'
|
|
6
6
|
import { updateGitignore } from '../inject/gitignore.js'
|
|
7
7
|
import { shell } from '../utils/exec.js'
|
|
8
|
-
import { exists, readJSON, writeJSON } from '../utils/fs.js'
|
|
8
|
+
import { exists, readFile, readJSON, writeFile, writeJSON } from '../utils/fs.js'
|
|
9
9
|
import { log } from '../utils/logger.js'
|
|
10
10
|
import type {
|
|
11
11
|
BuildToolDetection,
|
|
@@ -133,7 +133,11 @@ function resultStatus(nextSteps: string[]): CommandStatus {
|
|
|
133
133
|
|
|
134
134
|
function manualPlanSteps(plan: PlanResult, includeBlockers = true): string[] {
|
|
135
135
|
const steps = plan.actions
|
|
136
|
-
.filter(action =>
|
|
136
|
+
.filter(action =>
|
|
137
|
+
['manual_step', 'generate_patch_plan', 'generate_file', 'manual_confirmation'].includes(
|
|
138
|
+
action.type,
|
|
139
|
+
),
|
|
140
|
+
)
|
|
137
141
|
.map(action => action.description)
|
|
138
142
|
|
|
139
143
|
if (!includeBlockers) {
|
|
@@ -143,6 +147,127 @@ function manualPlanSteps(plan: PlanResult, includeBlockers = true): string[] {
|
|
|
143
147
|
return [...plan.blockers.map(blocker => blocker.message), ...steps]
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
function applyGuidedPatchContent(source: string, snippet: string): string {
|
|
151
|
+
if (source.includes("import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'")) {
|
|
152
|
+
return source
|
|
153
|
+
}
|
|
154
|
+
if (source.includes("import { vitePlugin as inspecto } from '@inspecto-dev/plugin'")) {
|
|
155
|
+
return source
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const trimmedSource = source.trimEnd()
|
|
159
|
+
if (/export\s+default\s*\{/m.test(source)) {
|
|
160
|
+
const nextSource = trimmedSource.replace(
|
|
161
|
+
/export\s+default\s*\{/m,
|
|
162
|
+
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\nexport default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },",
|
|
163
|
+
)
|
|
164
|
+
return `${nextSource}\n`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (/module\.exports\s*=\s*\{/m.test(source)) {
|
|
168
|
+
const nextSource = trimmedSource.replace(
|
|
169
|
+
/module\.exports\s*=\s*\{/m,
|
|
170
|
+
"const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\nmodule.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },",
|
|
171
|
+
)
|
|
172
|
+
return `${nextSource}\n`
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const objectExportVariableMatch = source.match(
|
|
176
|
+
/(const\s+([A-Za-z0-9_$]+)\s*(?::[^=]+)?=\s*\{[\s\S]*?\}\s*;?\s*)export\s+default\s+\2\s*;?/m,
|
|
177
|
+
)
|
|
178
|
+
const variableDeclaration = objectExportVariableMatch?.[1]
|
|
179
|
+
const variableName = objectExportVariableMatch?.[2]
|
|
180
|
+
if (variableDeclaration && variableName) {
|
|
181
|
+
const nextDeclaration = variableDeclaration.replace(
|
|
182
|
+
/=\s*\{/m,
|
|
183
|
+
'= {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },',
|
|
184
|
+
)
|
|
185
|
+
const nextSource = source.replace(
|
|
186
|
+
objectExportVariableMatch[0],
|
|
187
|
+
`import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\n${nextDeclaration}export default ${variableName}`,
|
|
188
|
+
)
|
|
189
|
+
return `${nextSource.trimEnd()}\n`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (/defineNuxtConfig\s*\(\s*\{/m.test(source)) {
|
|
193
|
+
const trimmedSource = source.trimEnd()
|
|
194
|
+
const nextSource = trimmedSource.replace(
|
|
195
|
+
/defineNuxtConfig\s*\(\s*\{/m,
|
|
196
|
+
'defineNuxtConfig({\n vite: {\n plugins: [inspecto()],\n },',
|
|
197
|
+
)
|
|
198
|
+
return `import { vitePlugin as inspecto } from '@inspecto-dev/plugin'\n\n${nextSource}\n`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const jsdocMatch = source.match(
|
|
202
|
+
/\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{/m,
|
|
203
|
+
)
|
|
204
|
+
if (jsdocMatch) {
|
|
205
|
+
const isEsm = jsdocMatch[1] === 'export default'
|
|
206
|
+
const importStatement = isEsm
|
|
207
|
+
? "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\n"
|
|
208
|
+
: "const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\n"
|
|
209
|
+
|
|
210
|
+
const replacementPattern = isEsm ? /export\s+default\s*\{/m : /module\.exports\s*=\s*\{/m
|
|
211
|
+
|
|
212
|
+
const injectConfig = isEsm
|
|
213
|
+
? 'export default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },'
|
|
214
|
+
: 'module.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },'
|
|
215
|
+
|
|
216
|
+
const nextSource = source.replace(replacementPattern, injectConfig)
|
|
217
|
+
return `${importStatement}${nextSource.trimEnd()}\n`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return `${trimmedSource}\n\n${snippet}\n`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function applyGuidedPlanPatches(
|
|
224
|
+
input: ApplyOnboardingInput,
|
|
225
|
+
mutations: Mutation[],
|
|
226
|
+
reporter: ApplyReporter,
|
|
227
|
+
): Promise<string[]> {
|
|
228
|
+
const nextSteps: string[] = []
|
|
229
|
+
|
|
230
|
+
if (!input.plan || input.plan.strategy !== 'guided' || !input.plan.patches?.length) {
|
|
231
|
+
return nextSteps
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const patch of input.plan.patches) {
|
|
235
|
+
if (
|
|
236
|
+
patch.status !== 'planned' ||
|
|
237
|
+
(!patch.reason.startsWith('next_config_') && !patch.reason.startsWith('nuxt_config_'))
|
|
238
|
+
) {
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const patchPath = path.join(input.projectRoot, patch.path)
|
|
243
|
+
const existingContent = await readFile(patchPath)
|
|
244
|
+
if (existingContent === null) {
|
|
245
|
+
nextSteps.push(`Could not auto-apply the guided patch for ${patch.path}.`)
|
|
246
|
+
continue
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const nextContent = applyGuidedPatchContent(existingContent, patch.snippet)
|
|
250
|
+
if (nextContent === existingContent) {
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (input.options.dryRun) {
|
|
255
|
+
reporter.dryRun(`Would apply guided patch to ${patch.path}`)
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await writeFile(patchPath, nextContent)
|
|
260
|
+
mutations.push({
|
|
261
|
+
type: 'file_modified',
|
|
262
|
+
path: patch.path,
|
|
263
|
+
description: 'Automatically configured Inspecto guided Next.js patch',
|
|
264
|
+
})
|
|
265
|
+
reporter.success(`Applied guided patch to ${patch.path}`)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return nextSteps
|
|
269
|
+
}
|
|
270
|
+
|
|
146
271
|
export async function applyOnboardingPlan(
|
|
147
272
|
input: ApplyOnboardingInput,
|
|
148
273
|
): Promise<ApplyOnboardingResult> {
|
|
@@ -207,7 +332,7 @@ async function applyOnboardingPlanInternal(
|
|
|
207
332
|
): Promise<ApplyOnboardingResult> {
|
|
208
333
|
const reporter = createReporter(input.options.quiet)
|
|
209
334
|
const additiveManualPlan =
|
|
210
|
-
input.plan?.strategy === 'manual' &&
|
|
335
|
+
(input.plan?.strategy === 'manual' || input.plan?.strategy === 'guided') &&
|
|
211
336
|
input.allowManualPlanApply &&
|
|
212
337
|
input.plan.blockers.length === 0
|
|
213
338
|
|
|
@@ -261,6 +386,11 @@ async function applyOnboardingPlanInternal(
|
|
|
261
386
|
spinner.fail('Dependency installation failed')
|
|
262
387
|
installFailed = true
|
|
263
388
|
reporter.error(`Failed to install dependency: ${error?.message || 'Unknown error'}`)
|
|
389
|
+
if (error?.stderr) {
|
|
390
|
+
reporter.error(`Details: ${error.stderr}`)
|
|
391
|
+
} else if (error?.stdout) {
|
|
392
|
+
reporter.error(`Details: ${error.stdout}`)
|
|
393
|
+
}
|
|
264
394
|
reporter.hint(`Run manually in ${input.projectRoot}: ${installCmd}`)
|
|
265
395
|
reporter.hint(
|
|
266
396
|
'Setup will continue without dependencies, but Inspecto may not run until installation succeeds.',
|
|
@@ -285,6 +415,10 @@ async function applyOnboardingPlanInternal(
|
|
|
285
415
|
}
|
|
286
416
|
}
|
|
287
417
|
|
|
418
|
+
if (additiveManualPlan) {
|
|
419
|
+
nextSteps.push(...(await applyGuidedPlanPatches(input, mutations, reporter)))
|
|
420
|
+
}
|
|
421
|
+
|
|
288
422
|
if (await exists(settingsPath)) {
|
|
289
423
|
const existingSettings = await readJSON(settingsPath)
|
|
290
424
|
if (existingSettings === null) {
|