@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
package/src/commands/init.ts
CHANGED
|
@@ -8,27 +8,27 @@
|
|
|
8
8
|
// ============================================================
|
|
9
9
|
import path from 'node:path'
|
|
10
10
|
import { log } from '../utils/logger.js'
|
|
11
|
-
import { exists
|
|
12
|
-
import {
|
|
13
|
-
import { detectPackageManager, getInstallCommand } from '../detect/package-manager.js'
|
|
11
|
+
import { exists } from '../utils/fs.js'
|
|
12
|
+
import { detectPackageManager } from '../detect/package-manager.js'
|
|
14
13
|
import { detectBuildTools, resolveInjectionTarget } from '../detect/build-tool.js'
|
|
15
14
|
import { detectFrameworks } from '../detect/framework.js'
|
|
16
15
|
import { detectIDE } from '../detect/ide.js'
|
|
17
16
|
import { detectProviders, type ProviderDetection } from '../detect/provider.js'
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import type { InitOptions, InstallLock, Mutation, BuildToolDetection } from '../types.js'
|
|
17
|
+
import { applyOnboardingPlan } from '../onboarding/apply.js'
|
|
18
|
+
import type { InitOptions, Mutation, BuildToolDetection } from '../types.js'
|
|
19
|
+
import { resolveOnboardingTarget } from '../onboarding/target-resolution.js'
|
|
22
20
|
import {
|
|
23
21
|
promptIDEChoice,
|
|
24
22
|
promptProviderChoice,
|
|
25
23
|
promptConfigChoice,
|
|
24
|
+
promptMonorepoPackageChoice,
|
|
26
25
|
promptUnsupportedFrameworkContinue,
|
|
27
26
|
} from '../prompts.js'
|
|
28
27
|
import { printNextJsManualInstructions, printNuxtManualInstructions } from '../instructions.js'
|
|
29
28
|
|
|
30
29
|
export async function init(options: InitOptions): Promise<void> {
|
|
31
|
-
const
|
|
30
|
+
const repoRoot = process.cwd()
|
|
31
|
+
let projectRoot = repoRoot
|
|
32
32
|
const mutations: Mutation[] = []
|
|
33
33
|
const normalizedPackages = normalizePackageList(options.packages)
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
40
40
|
continue
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const absolutePath = path.join(
|
|
43
|
+
const absolutePath = path.join(repoRoot, pkg)
|
|
44
44
|
if (await exists(absolutePath)) {
|
|
45
45
|
verifiedPackages.push(pkg)
|
|
46
46
|
} else {
|
|
@@ -57,19 +57,63 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
57
57
|
log.header('Inspecto Setup')
|
|
58
58
|
|
|
59
59
|
// ---- Step 1: Validate project ----
|
|
60
|
-
if (!(await exists(path.join(
|
|
60
|
+
if (!(await exists(path.join(repoRoot, 'package.json')))) {
|
|
61
61
|
log.error('No package.json found in current directory')
|
|
62
62
|
log.hint('Run this command from your project root')
|
|
63
63
|
return
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// ---- Step 2: Detect environment ----
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const pm = await detectPackageManager(repoRoot)
|
|
68
|
+
let buildResult = await detectBuildTools(
|
|
69
|
+
repoRoot,
|
|
70
|
+
verifiedPackages.length > 0 ? verifiedPackages : undefined,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if (verifiedPackages.length === 0) {
|
|
74
|
+
const monorepoCandidates = buildResult.supported.filter(detection => !!detection.packagePath)
|
|
75
|
+
if (monorepoCandidates.length > 0) {
|
|
76
|
+
const frameworkSupportByPackage = await detectFrameworkSupportByPackage(
|
|
77
|
+
repoRoot,
|
|
78
|
+
monorepoCandidates,
|
|
79
|
+
)
|
|
80
|
+
const targetResolution = resolveOnboardingTarget({
|
|
81
|
+
repoRoot,
|
|
82
|
+
buildTools: monorepoCandidates,
|
|
83
|
+
frameworkSupportByPackage,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (targetResolution.status === 'needs_selection') {
|
|
87
|
+
log.warn('Monorepo root detected with multiple candidate apps.')
|
|
88
|
+
const selectedPackage = await promptMonorepoPackageChoice(
|
|
89
|
+
monorepoCandidates.filter(detection =>
|
|
90
|
+
targetResolution.candidates.some(
|
|
91
|
+
candidate => candidate.packagePath === (detection.packagePath ?? ''),
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
if (!selectedPackage) {
|
|
96
|
+
log.hint('Run `inspecto init` inside the target app, or pass --packages <app-path>.')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
projectRoot = path.join(repoRoot, selectedPackage)
|
|
101
|
+
buildResult = await detectBuildTools(repoRoot, [selectedPackage])
|
|
102
|
+
log.info(`Continuing initialization in ${selectedPackage}`)
|
|
103
|
+
} else if (targetResolution.selected?.packagePath) {
|
|
104
|
+
const selectedPackage = targetResolution.selected.packagePath
|
|
105
|
+
projectRoot = path.join(repoRoot, selectedPackage)
|
|
106
|
+
buildResult = await detectBuildTools(repoRoot, [selectedPackage])
|
|
107
|
+
log.warn(`Monorepo root detected. Using the only candidate app: ${selectedPackage}`)
|
|
108
|
+
log.hint('Run `inspecto init` inside that app next time to skip this prompt.')
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const [frameworkResult, ideProbe, providerProbe] = await Promise.all([
|
|
114
|
+
detectFrameworks(projectRoot),
|
|
115
|
+
detectIDE(projectRoot),
|
|
116
|
+
detectProviders(projectRoot),
|
|
73
117
|
])
|
|
74
118
|
|
|
75
119
|
// Package manager
|
|
@@ -77,7 +121,15 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
77
121
|
|
|
78
122
|
// Framework verification
|
|
79
123
|
if (frameworkResult.supported.length > 0) {
|
|
80
|
-
|
|
124
|
+
const frameworks = frameworkResult.supported.join(', ')
|
|
125
|
+
log.success(`Detected framework: ${frameworks}`)
|
|
126
|
+
if (frameworkResult.unsupported.length > 0) {
|
|
127
|
+
log.hint(
|
|
128
|
+
`Other frameworks detected (${frameworkResult.unsupported
|
|
129
|
+
.map(f => f.name)
|
|
130
|
+
.join(', ')}) will be skipped in this setup.`,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
81
133
|
}
|
|
82
134
|
|
|
83
135
|
const isSupported = frameworkResult.supported.length > 0
|
|
@@ -125,6 +177,13 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
125
177
|
manualConfigRequiredFor = buildResult.unsupported[0] || ''
|
|
126
178
|
log.warn(`Detected ${names} — automatic plugin injection is not supported in current version`)
|
|
127
179
|
log.hint('You can still manually configure it by modifying your configuration file')
|
|
180
|
+
|
|
181
|
+
if (buildResult.unsupported.includes('Next.js')) {
|
|
182
|
+
printNextJsManualInstructions()
|
|
183
|
+
}
|
|
184
|
+
if (buildResult.unsupported.includes('Nuxt')) {
|
|
185
|
+
printNuxtManualInstructions()
|
|
186
|
+
}
|
|
128
187
|
}
|
|
129
188
|
if (buildResult.supported.length === 0 && buildResult.unsupported.length === 0) {
|
|
130
189
|
log.warn('No recognized build tool detected')
|
|
@@ -161,6 +220,9 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
161
220
|
|
|
162
221
|
// AI Tool detection
|
|
163
222
|
let selectedProvider: ProviderDetection | null = null
|
|
223
|
+
const explicitProvider = options.provider
|
|
224
|
+
? (providerProbe.detected.find(provider => provider.id === options.provider) ?? null)
|
|
225
|
+
: null
|
|
164
226
|
|
|
165
227
|
if (!options.provider) {
|
|
166
228
|
if (providerProbe.detected.length === 0) {
|
|
@@ -172,40 +234,19 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
172
234
|
log.success(`Detected AI tool: ${selectedProvider.label}`)
|
|
173
235
|
}
|
|
174
236
|
} else {
|
|
237
|
+
log.info('Multiple providers detected, waiting for your selection...')
|
|
175
238
|
selectedProvider = await promptProviderChoice(providerProbe.detected)
|
|
176
239
|
if (selectedProvider) {
|
|
177
240
|
log.success(`Selected provider: ${selectedProvider.label}`)
|
|
241
|
+
} else {
|
|
242
|
+
log.warn('No provider selected. You can set provider.default later in .inspecto/settings.')
|
|
178
243
|
}
|
|
179
244
|
}
|
|
180
245
|
}
|
|
181
246
|
|
|
182
|
-
// ---- Step 3:
|
|
183
|
-
let
|
|
184
|
-
|
|
185
|
-
log.warn('Skipping dependency installation (--skip-install)')
|
|
186
|
-
} else {
|
|
187
|
-
const installCmd = getInstallCommand(pm, '@inspecto-dev/plugin @inspecto-dev/core')
|
|
188
|
-
if (options.dryRun) {
|
|
189
|
-
log.dryRun(`Would run: ${installCmd}`)
|
|
190
|
-
} else {
|
|
191
|
-
try {
|
|
192
|
-
const result = await shell(installCmd, root)
|
|
193
|
-
if (result.stderr && result.stderr.toLowerCase().includes('error')) {
|
|
194
|
-
throw new Error(result.stderr)
|
|
195
|
-
}
|
|
196
|
-
log.success('Installed @inspecto-dev/plugin and @inspecto-dev/core as devDependencies')
|
|
197
|
-
mutations.push({ type: 'dependency_added', name: '@inspecto-dev/plugin', dev: true })
|
|
198
|
-
mutations.push({ type: 'dependency_added', name: '@inspecto-dev/core', dev: true })
|
|
199
|
-
} catch (err: any) {
|
|
200
|
-
installFailed = true
|
|
201
|
-
log.error(`Failed to install dependency: ${err?.message || 'Unknown error'}`)
|
|
202
|
-
log.hint(`Run manually: ${installCmd}`)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ---- Step 4: Inject plugin into build config ----
|
|
208
|
-
let injectionFailed = false
|
|
247
|
+
// ---- Step 3: Resolve injection targets ----
|
|
248
|
+
let injectionSkippedRequiresManualConfig = false
|
|
249
|
+
const supportedBuildTargets: BuildToolDetection[] = []
|
|
209
250
|
if (buildResult.supported.length > 0) {
|
|
210
251
|
if (verifiedPackages.length > 0) {
|
|
211
252
|
const targets = buildResult.supported.filter(detection =>
|
|
@@ -226,17 +267,10 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
226
267
|
}
|
|
227
268
|
|
|
228
269
|
if (targets.length === 0) {
|
|
229
|
-
|
|
270
|
+
injectionSkippedRequiresManualConfig = true
|
|
230
271
|
}
|
|
231
272
|
|
|
232
|
-
|
|
233
|
-
const result = await injectPlugin(root, target, options.dryRun)
|
|
234
|
-
if (result.success) {
|
|
235
|
-
mutations.push(...result.mutations)
|
|
236
|
-
} else {
|
|
237
|
-
injectionFailed = true
|
|
238
|
-
}
|
|
239
|
-
}
|
|
273
|
+
supportedBuildTargets.push(...targets)
|
|
240
274
|
} else {
|
|
241
275
|
let target = resolveInjectionTarget(buildResult.supported)
|
|
242
276
|
|
|
@@ -245,150 +279,57 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
245
279
|
}
|
|
246
280
|
|
|
247
281
|
if (target) {
|
|
248
|
-
|
|
249
|
-
if (result.success) {
|
|
250
|
-
mutations.push(...result.mutations)
|
|
251
|
-
} else {
|
|
252
|
-
injectionFailed = true
|
|
253
|
-
}
|
|
282
|
+
supportedBuildTargets.push(target)
|
|
254
283
|
} else {
|
|
255
|
-
|
|
284
|
+
injectionSkippedRequiresManualConfig = true
|
|
256
285
|
log.warn('Skipping plugin injection (manual configuration required)')
|
|
257
286
|
}
|
|
258
287
|
}
|
|
259
288
|
}
|
|
260
289
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (options.provider) {
|
|
286
|
-
const tool = options.provider
|
|
287
|
-
const mode = tool === 'coco' ? 'cli' : 'extension'
|
|
288
|
-
defaultSettings['provider.default'] = `${tool}.${mode}`
|
|
289
|
-
} else if (selectedProvider) {
|
|
290
|
-
const toolId = selectedProvider.id as string
|
|
291
|
-
const mode = selectedProvider.preferredMode === 'cli' ? 'cli' : 'extension'
|
|
292
|
-
defaultSettings['provider.default'] = `${toolId}.${mode}`
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (options.dryRun) {
|
|
296
|
-
log.dryRun(`Would create .inspecto/${settingsFileName}`)
|
|
297
|
-
} else {
|
|
298
|
-
await writeJSON(settingsPath, defaultSettings)
|
|
299
|
-
log.success(`Created .inspecto/${settingsFileName}`)
|
|
300
|
-
mutations.push({ type: 'file_created', path: `.inspecto/${settingsFileName}` })
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (await exists(promptsPath)) {
|
|
305
|
-
log.success(`.inspecto/${promptsFileName} already exists (skipped)`)
|
|
306
|
-
} else {
|
|
307
|
-
const defaultPrompts: unknown[] = []
|
|
308
|
-
|
|
309
|
-
if (options.dryRun) {
|
|
310
|
-
log.dryRun(`Would create .inspecto/${promptsFileName}`)
|
|
311
|
-
} else {
|
|
312
|
-
await writeJSON(promptsPath, defaultPrompts)
|
|
313
|
-
log.success(`Created .inspecto/${promptsFileName}`)
|
|
314
|
-
mutations.push({ type: 'file_created', path: `.inspecto/${promptsFileName}` })
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// ---- Step 6: Update .gitignore ----
|
|
319
|
-
if (!options.dryRun) {
|
|
320
|
-
await updateGitignore(root, options.shared, options.dryRun)
|
|
321
|
-
mutations.push({
|
|
322
|
-
type: 'file_modified',
|
|
323
|
-
path: '.gitignore',
|
|
324
|
-
description: 'Appended .inspecto/ ignore rules',
|
|
325
|
-
})
|
|
326
|
-
} else {
|
|
327
|
-
log.dryRun('Would update .gitignore')
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ---- Step 7: Write install.lock ----
|
|
331
|
-
if (!options.dryRun && mutations.length > 0) {
|
|
332
|
-
const lock: InstallLock = {
|
|
333
|
-
version: '1.0.0',
|
|
334
|
-
created_at: new Date().toISOString(),
|
|
335
|
-
mutations,
|
|
336
|
-
}
|
|
337
|
-
await writeJSON(path.join(settingsDir, 'install.lock'), lock)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ---- Step 8: Install IDE extension ----
|
|
341
|
-
const shouldInstallExt =
|
|
342
|
-
!options.noExtension && (!selectedIDE || (selectedIDE && selectedIDE.supported))
|
|
343
|
-
let manualExtensionInstallNeeded = false
|
|
344
|
-
|
|
345
|
-
if (options.noExtension) {
|
|
346
|
-
log.warn('Skipping IDE extension (--no-extension)')
|
|
347
|
-
} else if (!shouldInstallExt) {
|
|
348
|
-
// Unsupported IDE detected — skip extension
|
|
349
|
-
} else {
|
|
350
|
-
const extMutation = await installExtension(options.dryRun, selectedIDE?.ide)
|
|
351
|
-
if (extMutation && !options.dryRun) {
|
|
352
|
-
mutations.push(extMutation)
|
|
353
|
-
|
|
354
|
-
if (extMutation.manual_action_required) {
|
|
355
|
-
manualExtensionInstallNeeded = true
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const lockPath = path.join(settingsDir, 'install.lock')
|
|
359
|
-
const lock = await readJSON<InstallLock>(lockPath)
|
|
360
|
-
if (lock) {
|
|
361
|
-
lock.mutations = mutations
|
|
362
|
-
await writeJSON(lockPath, lock)
|
|
363
|
-
}
|
|
364
|
-
} else if (extMutation === null && !options.dryRun) {
|
|
365
|
-
manualExtensionInstallNeeded = true
|
|
366
|
-
}
|
|
367
|
-
}
|
|
290
|
+
const providerDefault = options.provider
|
|
291
|
+
? `${options.provider}.${explicitProvider?.preferredMode ?? (options.provider === 'coco' ? 'cli' : 'extension')}`
|
|
292
|
+
: selectedProvider
|
|
293
|
+
? `${selectedProvider.id}.${selectedProvider.preferredMode === 'cli' ? 'cli' : 'extension'}`
|
|
294
|
+
: undefined
|
|
295
|
+
|
|
296
|
+
const applyResult = await applyOnboardingPlan({
|
|
297
|
+
repoRoot,
|
|
298
|
+
projectRoot,
|
|
299
|
+
packageManager: pm,
|
|
300
|
+
supportedBuildTargets,
|
|
301
|
+
options: {
|
|
302
|
+
shared: options.shared,
|
|
303
|
+
skipInstall: options.skipInstall,
|
|
304
|
+
dryRun: options.dryRun,
|
|
305
|
+
noExtension: options.noExtension,
|
|
306
|
+
},
|
|
307
|
+
selectedIDE,
|
|
308
|
+
providerDefault,
|
|
309
|
+
manualConfigRequiredFor,
|
|
310
|
+
injectionSkippedRequiresManualConfig,
|
|
311
|
+
allowManualPlanApply: true,
|
|
312
|
+
})
|
|
313
|
+
mutations.push(...applyResult.mutations)
|
|
368
314
|
|
|
369
315
|
// ---- Done ----
|
|
370
316
|
if (options.dryRun) {
|
|
371
317
|
log.blank()
|
|
372
318
|
log.warn('Dry run complete. No files were modified.')
|
|
373
|
-
} else
|
|
374
|
-
installFailed ||
|
|
375
|
-
injectionFailed ||
|
|
376
|
-
manualExtensionInstallNeeded ||
|
|
377
|
-
manualConfigRequiredFor
|
|
378
|
-
) {
|
|
319
|
+
} else {
|
|
379
320
|
log.blank()
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
printNextJsManualInstructions()
|
|
321
|
+
if (applyResult.postInstall.nextSteps.length > 0) {
|
|
322
|
+
log.warn('──────── Manual Steps Required ────────')
|
|
323
|
+
applyResult.postInstall.nextSteps.forEach(step => log.error(step))
|
|
324
|
+
log.hint('Complete the items above.')
|
|
325
|
+
log.blank()
|
|
386
326
|
} else {
|
|
387
|
-
log.
|
|
327
|
+
log.ready('Ready! Inspecto is set up.')
|
|
328
|
+
log.info('Next:')
|
|
329
|
+
log.hint('1. Start or restart your dev server.')
|
|
330
|
+
log.hint('2. Open your app in the browser.')
|
|
331
|
+
log.hint('3. Hold Alt + Click any element to inspect.')
|
|
388
332
|
}
|
|
389
|
-
log.blank()
|
|
390
|
-
} else {
|
|
391
|
-
log.ready('Ready! Hold Alt + Click any element to inspect.')
|
|
392
333
|
}
|
|
393
334
|
}
|
|
394
335
|
|
|
@@ -421,3 +362,22 @@ function matchesAnyPackage(detection: BuildToolDetection, packages: string[]): b
|
|
|
421
362
|
if (packages.length === 0) return true
|
|
422
363
|
return packages.some(pkg => matchesPackage(detection, pkg))
|
|
423
364
|
}
|
|
365
|
+
|
|
366
|
+
async function detectFrameworkSupportByPackage(
|
|
367
|
+
repoRoot: string,
|
|
368
|
+
buildTools: BuildToolDetection[],
|
|
369
|
+
): Promise<Record<string, string[]>> {
|
|
370
|
+
const packagePaths = Array.from(
|
|
371
|
+
new Set(buildTools.map(buildTool => buildTool.packagePath).filter((value): value is string => !!value)),
|
|
372
|
+
)
|
|
373
|
+
const supportByPackage: Record<string, string[]> = {}
|
|
374
|
+
|
|
375
|
+
await Promise.all(
|
|
376
|
+
packagePaths.map(async packagePath => {
|
|
377
|
+
const result = await detectFrameworks(path.join(repoRoot, packagePath))
|
|
378
|
+
supportByPackage[packagePath] = result.supported
|
|
379
|
+
}),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return supportByPackage
|
|
383
|
+
}
|