@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
|
@@ -12,6 +12,7 @@ export type HostIdeSource = 'explicit' | 'config' | 'env' | 'artifact' | 'ambigu
|
|
|
12
12
|
export interface ResolveIntegrationHostIdeOptions {
|
|
13
13
|
explicitIde?: string
|
|
14
14
|
cwd?: string
|
|
15
|
+
ignoreProjectArtifacts?: boolean
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface ResolvedIntegrationHostIde {
|
|
@@ -51,7 +52,7 @@ export async function resolveIntegrationHostIde(
|
|
|
51
52
|
const envCandidates = detectEnvHostIdes()
|
|
52
53
|
if (envCandidates.length === 1) {
|
|
53
54
|
return {
|
|
54
|
-
ide: envCandidates[0]
|
|
55
|
+
ide: envCandidates[0]!,
|
|
55
56
|
confidence: 'high',
|
|
56
57
|
source: 'env',
|
|
57
58
|
candidates: envCandidates,
|
|
@@ -67,22 +68,24 @@ export async function resolveIntegrationHostIde(
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
if (!options.ignoreProjectArtifacts) {
|
|
72
|
+
const artifactCandidates = await detectArtifactHostIdes(cwd)
|
|
73
|
+
if (artifactCandidates.length === 1) {
|
|
74
|
+
return {
|
|
75
|
+
ide: artifactCandidates[0]!,
|
|
76
|
+
confidence: 'medium',
|
|
77
|
+
source: 'artifact',
|
|
78
|
+
candidates: artifactCandidates,
|
|
79
|
+
}
|
|
77
80
|
}
|
|
78
|
-
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
if (artifactCandidates.length > 1) {
|
|
83
|
+
return {
|
|
84
|
+
ide: null,
|
|
85
|
+
confidence: 'low',
|
|
86
|
+
source: 'ambiguous',
|
|
87
|
+
candidates: artifactCandidates,
|
|
88
|
+
}
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
|
|
@@ -2,10 +2,19 @@ import fs from 'node:fs/promises'
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import { exists, writeFile } from '../utils/fs.js'
|
|
5
|
+
import { exists, readJSON, writeFile, writeJSON } from '../utils/fs.js'
|
|
6
6
|
import { log } from '../utils/logger.js'
|
|
7
7
|
import { writeCommandOutput } from '../utils/output.js'
|
|
8
8
|
import { runIntegrationAutomation } from './integration-automation.js'
|
|
9
|
+
import { resolveIntegrationDispatchMode } from './integration-dispatch-mode.js'
|
|
10
|
+
import { resolveIntegrationHostIde } from './integration-host-ide.js'
|
|
11
|
+
import { isSupportedHostIde, type SupportedHostIde } from '../integrations/capabilities.js'
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_PROVIDER_MODE,
|
|
14
|
+
VALID_MODES,
|
|
15
|
+
type Provider,
|
|
16
|
+
type ProviderMode,
|
|
17
|
+
} from '@inspecto-dev/types'
|
|
9
18
|
|
|
10
19
|
const REPO_RAW_BASE = 'https://raw.githubusercontent.com/inspecto-dev/inspecto/main'
|
|
11
20
|
const TOTAL_STEPS = 6
|
|
@@ -55,6 +64,12 @@ interface InstallPlan {
|
|
|
55
64
|
nextStep: string
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
interface InspectoSettingsShape {
|
|
68
|
+
ide?: string
|
|
69
|
+
'provider.default'?: string
|
|
70
|
+
[key: string]: unknown
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
export interface IntegrationInstallResult {
|
|
59
74
|
status: 'launched' | 'partial' | 'blocked' | 'preview' | 'preview_blocked'
|
|
60
75
|
assistant: string
|
|
@@ -117,7 +132,7 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
|
|
|
117
132
|
{
|
|
118
133
|
assistant: 'coco',
|
|
119
134
|
type: 'native-skill',
|
|
120
|
-
installTarget: '.
|
|
135
|
+
installTarget: '.trae/skills/inspecto-onboarding/',
|
|
121
136
|
preferredInstall:
|
|
122
137
|
'npx @inspecto-dev/cli integrations install coco --host-ide <vscode|cursor|trae|trae-cn>',
|
|
123
138
|
cliSupported: true,
|
|
@@ -194,6 +209,10 @@ export async function installIntegration(
|
|
|
194
209
|
}
|
|
195
210
|
}
|
|
196
211
|
|
|
212
|
+
if (shouldPersistProjectOnboardingDefaults(options)) {
|
|
213
|
+
await persistProjectOnboardingDefaults(assistant as AssistantId, options)
|
|
214
|
+
}
|
|
215
|
+
|
|
197
216
|
const stepOneMessage = options.preview
|
|
198
217
|
? formatIntegrationStep(1, `Previewing ${getAssistantLabel(assistant)} integration assets`)
|
|
199
218
|
: formatIntegrationStep(1, `Installed ${getAssistantLabel(assistant)} integration assets`)
|
|
@@ -247,7 +266,7 @@ export async function installIntegration(
|
|
|
247
266
|
|
|
248
267
|
const automationResult = await runIntegrationAutomation(
|
|
249
268
|
assistant,
|
|
250
|
-
{ ...options, silent },
|
|
269
|
+
{ ...options, silent, ignoreProjectArtifacts: true },
|
|
251
270
|
process.cwd(),
|
|
252
271
|
)
|
|
253
272
|
const result: IntegrationInstallResult = {
|
|
@@ -292,6 +311,70 @@ export async function installIntegration(
|
|
|
292
311
|
return result
|
|
293
312
|
}
|
|
294
313
|
|
|
314
|
+
function shouldPersistProjectOnboardingDefaults(
|
|
315
|
+
options: InstallIntegrationOptions,
|
|
316
|
+
): options is InstallIntegrationOptions & { ide: SupportedHostIde } {
|
|
317
|
+
return (
|
|
318
|
+
!options.preview && !shouldSkipAutomationForInstall(options) && isSupportedHostIde(options.ide)
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function isProviderAssistant(value: string): value is Provider {
|
|
323
|
+
return Object.prototype.hasOwnProperty.call(VALID_MODES, value)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function resolveProviderDefaultForAssistant(
|
|
327
|
+
assistant: AssistantId,
|
|
328
|
+
ide: SupportedHostIde,
|
|
329
|
+
): Promise<string | undefined> {
|
|
330
|
+
if (!isProviderAssistant(assistant)) {
|
|
331
|
+
return undefined
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let mode: ProviderMode | undefined
|
|
335
|
+
if (assistant === 'codex' || assistant === 'claude-code' || assistant === 'gemini') {
|
|
336
|
+
const dispatchMode = await resolveIntegrationDispatchMode({ assistant, hostIde: ide })
|
|
337
|
+
mode = dispatchMode.mode ?? DEFAULT_PROVIDER_MODE[assistant]
|
|
338
|
+
} else {
|
|
339
|
+
mode = DEFAULT_PROVIDER_MODE[assistant]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!mode || !VALID_MODES[assistant].includes(mode)) {
|
|
343
|
+
return undefined
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return `${assistant}.${mode}`
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function persistProjectOnboardingDefaults(
|
|
350
|
+
assistant: AssistantId,
|
|
351
|
+
options: InstallIntegrationOptions & { ide: SupportedHostIde },
|
|
352
|
+
): Promise<void> {
|
|
353
|
+
const settingsPath = path.join(process.cwd(), '.inspecto', 'settings.local.json')
|
|
354
|
+
const existingSettings = await readJSON<InspectoSettingsShape>(settingsPath)
|
|
355
|
+
const resolvedHostIde = await resolveIntegrationHostIde({
|
|
356
|
+
explicitIde: options.ide,
|
|
357
|
+
cwd: process.cwd(),
|
|
358
|
+
})
|
|
359
|
+
const providerDefault =
|
|
360
|
+
resolvedHostIde.ide && resolvedHostIde.confidence !== 'low'
|
|
361
|
+
? await resolveProviderDefaultForAssistant(assistant, resolvedHostIde.ide)
|
|
362
|
+
: undefined
|
|
363
|
+
const mergedSettings =
|
|
364
|
+
existingSettings && typeof existingSettings === 'object'
|
|
365
|
+
? {
|
|
366
|
+
...existingSettings,
|
|
367
|
+
ide: options.ide,
|
|
368
|
+
...(providerDefault ? { 'provider.default': providerDefault } : {}),
|
|
369
|
+
}
|
|
370
|
+
: {
|
|
371
|
+
ide: options.ide,
|
|
372
|
+
...(providerDefault ? { 'provider.default': providerDefault } : {}),
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
await writeJSON(settingsPath, mergedSettings)
|
|
376
|
+
}
|
|
377
|
+
|
|
295
378
|
function shouldSkipAutomationForInstall(options: InstallIntegrationOptions): boolean {
|
|
296
379
|
return options.scope === 'user' && !options.preview
|
|
297
380
|
}
|
|
@@ -375,6 +458,12 @@ function resolveInstallPlan(assistant: string, options: InstallIntegrationOption
|
|
|
375
458
|
target: '.trae/skills/inspecto-onboarding/SKILL.md',
|
|
376
459
|
localSource: 'skills/inspecto-onboarding-trae/SKILL.md',
|
|
377
460
|
},
|
|
461
|
+
{
|
|
462
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/scripts/run-inspecto.sh`,
|
|
463
|
+
target: '.trae/skills/inspecto-onboarding/scripts/run-inspecto.sh',
|
|
464
|
+
localSource: 'skills/inspecto-onboarding-trae/scripts/run-inspecto.sh',
|
|
465
|
+
executable: true,
|
|
466
|
+
},
|
|
378
467
|
],
|
|
379
468
|
successMessage: 'Installed Trae skill to .trae/skills/inspecto-onboarding/SKILL.md',
|
|
380
469
|
nextStep: 'Open a new Trae chat and verify the inspecto-onboarding skill is available.',
|
|
@@ -384,11 +473,17 @@ function resolveInstallPlan(assistant: string, options: InstallIntegrationOption
|
|
|
384
473
|
assets: [
|
|
385
474
|
{
|
|
386
475
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
|
|
387
|
-
target: '.
|
|
476
|
+
target: '.trae/skills/inspecto-onboarding/SKILL.md',
|
|
388
477
|
localSource: 'skills/inspecto-onboarding-trae/SKILL.md',
|
|
389
478
|
},
|
|
479
|
+
{
|
|
480
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/scripts/run-inspecto.sh`,
|
|
481
|
+
target: '.trae/skills/inspecto-onboarding/scripts/run-inspecto.sh',
|
|
482
|
+
localSource: 'skills/inspecto-onboarding-trae/scripts/run-inspecto.sh',
|
|
483
|
+
executable: true,
|
|
484
|
+
},
|
|
390
485
|
],
|
|
391
|
-
successMessage: 'Installed Coco skill to .
|
|
486
|
+
successMessage: 'Installed Coco skill to .trae/skills/inspecto-onboarding/SKILL.md',
|
|
392
487
|
nextStep: 'Start a new Coco session.',
|
|
393
488
|
}
|
|
394
489
|
default:
|
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,33 +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
|
-
|
|
87
|
+
if (normalized.status === 'needs_target_selection') {
|
|
88
|
+
if (normalized.target.selectionPurpose) {
|
|
89
|
+
log.warn(normalized.target.selectionPurpose)
|
|
90
|
+
}
|
|
91
|
+
for (const candidate of normalized.target.candidates) {
|
|
92
|
+
const identifier = candidate.candidateId ?? candidate.id ?? candidate.configPath
|
|
93
|
+
const label = candidate.label ?? candidate.configPath
|
|
94
|
+
log.hint(`${identifier}: ${label}`)
|
|
95
|
+
}
|
|
96
|
+
if (normalized.target.selectionInstructions) {
|
|
97
|
+
log.hint(normalized.target.selectionInstructions)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const change of normalized.summary.changes) {
|
|
43
102
|
log.hint(change)
|
|
44
103
|
}
|
|
45
|
-
for (const step of
|
|
104
|
+
for (const step of collectDisplayNextSteps(normalized)) {
|
|
46
105
|
log.warn(step)
|
|
47
106
|
}
|
|
48
|
-
|
|
49
|
-
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)
|
|
50
115
|
}
|
|
51
116
|
|
|
52
|
-
printManualExtensionGuidance(
|
|
117
|
+
printManualExtensionGuidance(normalized)
|
|
53
118
|
|
|
54
119
|
const extensionReady =
|
|
55
|
-
!
|
|
56
|
-
(
|
|
120
|
+
!normalized.ideExtension?.required ||
|
|
121
|
+
(normalized.ideExtension.installed && !normalized.ideExtension.manualRequired)
|
|
57
122
|
|
|
58
123
|
if (
|
|
59
124
|
extensionReady &&
|
|
60
|
-
(
|
|
61
|
-
|
|
125
|
+
(normalized.status === 'success' || normalized.status === 'partial_success') &&
|
|
126
|
+
normalized.verification?.message
|
|
62
127
|
) {
|
|
63
|
-
log.info(
|
|
128
|
+
log.info(normalized.verification.message)
|
|
64
129
|
}
|
|
65
130
|
}
|
|
66
131
|
|
|
@@ -74,12 +139,12 @@ export async function onboard(options: OnboardCommandOptions = {}): Promise<Onbo
|
|
|
74
139
|
session.status === 'needs_confirmation'
|
|
75
140
|
) {
|
|
76
141
|
return writeCommandOutput(
|
|
77
|
-
buildDeferredOnboardResult(session),
|
|
142
|
+
normalizeOnboardResult(buildDeferredOnboardResult(session)),
|
|
78
143
|
options.json ?? false,
|
|
79
144
|
printOnboardResult,
|
|
80
145
|
)
|
|
81
146
|
}
|
|
82
147
|
|
|
83
|
-
const result = await applyResolvedOnboardingSession(session, options)
|
|
148
|
+
const result = normalizeOnboardResult(await applyResolvedOnboardingSession(session, options))
|
|
84
149
|
return writeCommandOutput(result, options.json ?? false, printOnboardResult)
|
|
85
150
|
}
|
package/src/detect/build-tool.ts
CHANGED
|
@@ -50,6 +50,37 @@ async function getResolvedPackageVersion(pkgName: string, root: string): Promise
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function parseFirstSemver(
|
|
54
|
+
version: string | null,
|
|
55
|
+
): { major: number; minor: number; patch: number } | null {
|
|
56
|
+
if (!version) return null
|
|
57
|
+
|
|
58
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/)
|
|
59
|
+
if (!match) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
major: Number(match[1]),
|
|
65
|
+
minor: Number(match[2]),
|
|
66
|
+
patch: Number(match[3]),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isLegacyRspackVersion(version: string | null): boolean {
|
|
71
|
+
const parsed = parseFirstSemver(version)
|
|
72
|
+
if (!parsed) return false
|
|
73
|
+
|
|
74
|
+
return parsed.major === 0 && parsed.minor < 4
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isLegacyWebpackVersion(version: string | null): boolean {
|
|
78
|
+
const parsed = parseFirstSemver(version)
|
|
79
|
+
if (!parsed) return false
|
|
80
|
+
|
|
81
|
+
return parsed.major === 4
|
|
82
|
+
}
|
|
83
|
+
|
|
53
84
|
/** Supported build tools in v1 */
|
|
54
85
|
const SUPPORTED_PATTERNS: { tool: BuildTool; files: string[]; label: string }[] = [
|
|
55
86
|
{
|
|
@@ -76,7 +107,22 @@ const SUPPORTED_PATTERNS: { tool: BuildTool; files: string[]; label: string }[]
|
|
|
76
107
|
},
|
|
77
108
|
{
|
|
78
109
|
tool: 'webpack',
|
|
79
|
-
files: [
|
|
110
|
+
files: [
|
|
111
|
+
'webpack.config.js',
|
|
112
|
+
'webpack.config.ts',
|
|
113
|
+
'webpack.config.mjs',
|
|
114
|
+
'webpack.config.cjs',
|
|
115
|
+
'webpack.config.common.js',
|
|
116
|
+
'webpack.config.common.ts',
|
|
117
|
+
'webpack.config.dev.js',
|
|
118
|
+
'webpack.config.dev.ts',
|
|
119
|
+
'webpack.config.prod.js',
|
|
120
|
+
'webpack.config.prod.ts',
|
|
121
|
+
'webpack.config.esbuild.js',
|
|
122
|
+
'webpack.config.esbuild.ts',
|
|
123
|
+
'webpack.config.build-pre.js',
|
|
124
|
+
'webpack.config.build-pre.ts',
|
|
125
|
+
],
|
|
80
126
|
label: 'Webpack',
|
|
81
127
|
},
|
|
82
128
|
{
|
|
@@ -93,6 +139,11 @@ const SUPPORTED_PATTERNS: { tool: BuildTool; files: string[]; label: string }[]
|
|
|
93
139
|
|
|
94
140
|
/** Recognized but unsupported meta-frameworks — detect via dep + config file */
|
|
95
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
|
+
},
|
|
96
147
|
{ name: 'Next.js', dep: 'next', files: ['next.config.mjs', 'next.config.js', 'next.config.ts'] },
|
|
97
148
|
{ name: 'Nuxt', dep: 'nuxt', files: ['nuxt.config.ts', 'nuxt.config.js'] },
|
|
98
149
|
{ name: 'Remix', dep: '@remix-run/dev', files: ['remix.config.js', 'remix.config.ts'] },
|
|
@@ -251,7 +302,9 @@ export async function detectBuildTools(
|
|
|
251
302
|
}
|
|
252
303
|
|
|
253
304
|
const unsupportedChecks = UNSUPPORTED_META.map(async meta => {
|
|
254
|
-
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
|
|
255
308
|
for (const file of meta.files) {
|
|
256
309
|
if (await exists(path.join(target.absolutePath, file))) {
|
|
257
310
|
return meta.name
|
|
@@ -280,6 +333,119 @@ interface PatternContext {
|
|
|
280
333
|
scripts: Record<string, string>
|
|
281
334
|
}
|
|
282
335
|
|
|
336
|
+
function rankScriptCommand(name: string, command: string): number {
|
|
337
|
+
const haystack = `${name} ${command}`.toLowerCase()
|
|
338
|
+
let score = 0
|
|
339
|
+
|
|
340
|
+
if (/(^|[\s:_-])(start|dev|serve|watch)([\s:_-]|$)/.test(haystack)) score += 8
|
|
341
|
+
if (/(^|[\s:_-])(prod|build|release|stats)([\s:_-]|$)/.test(haystack)) score -= 3
|
|
342
|
+
if (/(^|[\s:_-])(dll|vendor)([\s:_-]|$)/.test(haystack)) score -= 6
|
|
343
|
+
if (haystack.includes('webpack-dev-server')) score += 3
|
|
344
|
+
if (haystack.includes('webpack')) score += 1
|
|
345
|
+
if (haystack.includes('rspack')) score += 1
|
|
346
|
+
|
|
347
|
+
return score
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function extractConfigArgs(scriptContent: string): string[] {
|
|
351
|
+
return Array.from(scriptContent.matchAll(/(?:-c|--config)\s+([^\s'"`;]+)/g))
|
|
352
|
+
.map(match => match[1])
|
|
353
|
+
.filter((value): value is string => Boolean(value))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function resolveScriptRelativeCandidate(
|
|
357
|
+
targetRoot: string,
|
|
358
|
+
scriptPath: string,
|
|
359
|
+
candidate: string,
|
|
360
|
+
): Promise<string | null> {
|
|
361
|
+
const normalizedCandidate = candidate.replace(/^['"`]|['"`]$/g, '')
|
|
362
|
+
const normalizedRelativeCandidate = path.normalize(normalizedCandidate)
|
|
363
|
+
const possiblePaths: string[] = []
|
|
364
|
+
|
|
365
|
+
if (normalizedRelativeCandidate.startsWith('..')) {
|
|
366
|
+
possiblePaths.push(
|
|
367
|
+
path.normalize(path.join(path.dirname(scriptPath), normalizedRelativeCandidate)),
|
|
368
|
+
)
|
|
369
|
+
} else {
|
|
370
|
+
possiblePaths.push(normalizedRelativeCandidate)
|
|
371
|
+
possiblePaths.push(
|
|
372
|
+
path.normalize(path.join(path.dirname(scriptPath), normalizedRelativeCandidate)),
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for (const possiblePath of possiblePaths) {
|
|
377
|
+
if (await exists(path.join(targetRoot, possiblePath))) {
|
|
378
|
+
return possiblePath.split(path.sep).join('/')
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return null
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function resolveRspackConfigFromScript(
|
|
386
|
+
targetRoot: string,
|
|
387
|
+
scriptPath: string,
|
|
388
|
+
): Promise<string | null> {
|
|
389
|
+
const scriptContent = await readFile(path.join(targetRoot, scriptPath))
|
|
390
|
+
if (!scriptContent) {
|
|
391
|
+
return null
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
for (const candidate of extractConfigArgs(scriptContent)) {
|
|
395
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate)
|
|
396
|
+
if (resolved) {
|
|
397
|
+
return resolved
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const matches = scriptContent.matchAll(
|
|
402
|
+
/['"`]([^'"`\n]*rspack[^'"`\n]*config[^'"`\n]*\.(?:js|ts|mjs|cjs))['"`]/g,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
for (const match of matches) {
|
|
406
|
+
const candidate = match[1]
|
|
407
|
+
if (!candidate) continue
|
|
408
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate)
|
|
409
|
+
if (resolved) {
|
|
410
|
+
return resolved
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return null
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function resolveWebpackBaseConfigFromFile(
|
|
418
|
+
targetRoot: string,
|
|
419
|
+
configPath: string,
|
|
420
|
+
): Promise<string | null> {
|
|
421
|
+
const configContent = await readFile(path.join(targetRoot, configPath))
|
|
422
|
+
if (!configContent) {
|
|
423
|
+
return null
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const candidate of extractConfigArgs(configContent)) {
|
|
427
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, configPath, candidate)
|
|
428
|
+
if (resolved) {
|
|
429
|
+
return resolved
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const matches = configContent.matchAll(
|
|
434
|
+
/(?:configPath\s*=\s*|require\()\s*['"`]([^'"`\n]*webpack[^'"`\n]*config[^'"`\n]*\.(?:js|ts|mjs|cjs))['"`]/g,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
for (const match of matches) {
|
|
438
|
+
const candidate = match[1]
|
|
439
|
+
if (!candidate) continue
|
|
440
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, configPath, candidate)
|
|
441
|
+
if (resolved) {
|
|
442
|
+
return resolved
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return null
|
|
447
|
+
}
|
|
448
|
+
|
|
283
449
|
async function detectPattern({
|
|
284
450
|
pattern,
|
|
285
451
|
workspaceRoot,
|
|
@@ -312,6 +478,8 @@ async function detectPattern({
|
|
|
312
478
|
}
|
|
313
479
|
} else if (pattern.tool === 'rsbuild') {
|
|
314
480
|
hasDep = !!allDeps['@rsbuild/core'] || isPackageResolvable('@rsbuild/core', targetRoot)
|
|
481
|
+
} else if (pattern.tool === 'vite') {
|
|
482
|
+
hasDep = !!allDeps['vite'] || isPackageResolvable('vite', targetRoot)
|
|
315
483
|
} else {
|
|
316
484
|
hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, targetRoot)
|
|
317
485
|
}
|
|
@@ -339,13 +507,47 @@ async function detectPattern({
|
|
|
339
507
|
pattern.tool === 'rspack' ||
|
|
340
508
|
pattern.tool === 'rsbuild')
|
|
341
509
|
) {
|
|
342
|
-
|
|
510
|
+
const rankedScripts = Object.entries(scripts).sort(
|
|
511
|
+
([leftName, leftCommand], [rightName, rightCommand]) =>
|
|
512
|
+
rankScriptCommand(rightName, rightCommand) - rankScriptCommand(leftName, leftCommand),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
for (const [, cmd] of rankedScripts) {
|
|
516
|
+
if (pattern.tool === 'webpack' || pattern.tool === 'rspack') {
|
|
517
|
+
for (const configArg of extractConfigArgs(cmd)) {
|
|
518
|
+
const resolvedConfig = await resolveScriptRelativeCandidate(targetRoot, '', configArg)
|
|
519
|
+
if (resolvedConfig && (cmd.includes(pattern.tool) || cmd.includes(`${pattern.tool}-`))) {
|
|
520
|
+
if (pattern.tool === 'webpack') {
|
|
521
|
+
detectedFile =
|
|
522
|
+
(await resolveWebpackBaseConfigFromFile(targetRoot, resolvedConfig)) ??
|
|
523
|
+
resolvedConfig
|
|
524
|
+
} else {
|
|
525
|
+
detectedFile =
|
|
526
|
+
(await resolveRspackConfigFromScript(targetRoot, resolvedConfig)) ?? resolvedConfig
|
|
527
|
+
}
|
|
528
|
+
break
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (detectedFile) {
|
|
533
|
+
break
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
343
537
|
if (cmd.includes('node ')) {
|
|
344
538
|
const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/)
|
|
345
539
|
if (match && match[1]) {
|
|
346
540
|
if (await exists(path.join(targetRoot, match[1]))) {
|
|
347
541
|
if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
|
|
348
|
-
|
|
542
|
+
if (pattern.tool === 'rspack') {
|
|
543
|
+
detectedFile =
|
|
544
|
+
(await resolveRspackConfigFromScript(targetRoot, match[1])) ?? match[1]
|
|
545
|
+
} else if (pattern.tool === 'webpack') {
|
|
546
|
+
detectedFile =
|
|
547
|
+
(await resolveWebpackBaseConfigFromFile(targetRoot, match[1])) ?? match[1]
|
|
548
|
+
} else {
|
|
549
|
+
detectedFile = match[1]
|
|
550
|
+
}
|
|
349
551
|
break
|
|
350
552
|
}
|
|
351
553
|
}
|
|
@@ -383,7 +585,7 @@ async function detectPattern({
|
|
|
383
585
|
tool: pattern.tool,
|
|
384
586
|
configPath: 'package.json (dependency)',
|
|
385
587
|
label: `${pattern.label} (detected via dependency)`,
|
|
386
|
-
packagePath
|
|
588
|
+
...(packagePath ? { packagePath } : {}),
|
|
387
589
|
}
|
|
388
590
|
}
|
|
389
591
|
return null
|
|
@@ -393,16 +595,11 @@ async function detectPattern({
|
|
|
393
595
|
let isLegacyWebpack = false
|
|
394
596
|
|
|
395
597
|
if (pattern.tool === 'rspack') {
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
version &&
|
|
399
|
-
(version.includes('0.3.') || version.includes('0.2.') || version.includes('0.1.'))
|
|
400
|
-
) {
|
|
598
|
+
if (isLegacyRspackVersion(resolvedVersion)) {
|
|
401
599
|
isLegacyRspack = true
|
|
402
600
|
}
|
|
403
601
|
} else if (pattern.tool === 'webpack') {
|
|
404
|
-
|
|
405
|
-
if ((version && version.includes('^4')) || version?.startsWith('4.')) {
|
|
602
|
+
if (isLegacyWebpackVersion(resolvedVersion)) {
|
|
406
603
|
isLegacyWebpack = true
|
|
407
604
|
}
|
|
408
605
|
}
|
|
@@ -416,9 +613,9 @@ async function detectPattern({
|
|
|
416
613
|
label: `${pattern.label} (${relativeConfig})${isLegacyRspack ? ' [Legacy]' : ''}${
|
|
417
614
|
isLegacyWebpack ? ' [Webpack 4]' : ''
|
|
418
615
|
}${inferredFromScripts ? ' [Scripts Detected]' : ''}`,
|
|
419
|
-
isLegacyRspack,
|
|
420
|
-
isLegacyWebpack,
|
|
421
|
-
packagePath
|
|
616
|
+
...(isLegacyRspack ? { isLegacyRspack: true } : {}),
|
|
617
|
+
...(isLegacyWebpack ? { isLegacyWebpack: true } : {}),
|
|
618
|
+
...(packagePath ? { packagePath } : {}),
|
|
422
619
|
}
|
|
423
620
|
}
|
|
424
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'
|