@inspecto-dev/cli 0.2.0-alpha.6 → 0.3.0
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 +16 -0
- package/README.md +44 -9
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +448 -6
- package/dist/{chunk-PDDFPQJS.js → chunk-IBYH7QZM.js} +624 -85
- package/dist/index.d.ts +106 -1
- package/dist/index.js +3 -1
- package/package.json +2 -2
- package/src/bin.ts +148 -0
- package/src/commands/apply.ts +5 -1
- package/src/commands/init.ts +60 -23
- package/src/commands/integration-install.ts +452 -0
- package/src/commands/onboard.ts +62 -0
- package/src/index.ts +4 -0
- package/src/inject/ast-injector.ts +15 -4
- package/src/inject/extension.ts +40 -24
- package/src/inject/gitignore.ts +10 -3
- package/src/onboarding/apply.ts +48 -9
- package/src/onboarding/planner.ts +6 -0
- package/src/onboarding/session.ts +434 -0
- package/src/onboarding/target-resolution.ts +116 -0
- package/src/types.ts +89 -0
- package/tests/apply.test.ts +47 -1
- package/tests/init.test.ts +31 -0
- package/tests/install-wrapper.test.ts +76 -0
- package/tests/integration-install.test.ts +294 -0
- package/tests/onboard.test.ts +352 -0
- package/.turbo/turbo-test.log +0 -16
|
@@ -369,7 +369,8 @@ var STRATEGIES = [
|
|
|
369
369
|
];
|
|
370
370
|
|
|
371
371
|
// src/inject/ast-injector.ts
|
|
372
|
-
function printManualInstructions(strategy, detection, reason) {
|
|
372
|
+
function printManualInstructions(strategy, detection, reason, quiet = false) {
|
|
373
|
+
if (quiet) return;
|
|
373
374
|
log.warn(`Could not automatically configure ${detection.configPath}`);
|
|
374
375
|
log.hint(`(reason: ${reason})`);
|
|
375
376
|
log.blank();
|
|
@@ -398,17 +399,19 @@ function isAlreadyInjected(content) {
|
|
|
398
399
|
}
|
|
399
400
|
return false;
|
|
400
401
|
}
|
|
401
|
-
async function injectPlugin(root, detection, dryRun) {
|
|
402
|
+
async function injectPlugin(root, detection, dryRun, quiet = false) {
|
|
402
403
|
const configPath = path3.join(root, detection.configPath);
|
|
403
404
|
const mutations = [];
|
|
404
405
|
const strategy = STRATEGIES.find((s) => s.supports(detection.tool));
|
|
405
406
|
const content = await readFile(configPath);
|
|
406
407
|
if (!content) {
|
|
407
|
-
printManualInstructions(strategy, detection, "config file not readable");
|
|
408
|
+
printManualInstructions(strategy, detection, "config file not readable", quiet);
|
|
408
409
|
return { success: false, mutations, failureReason: "config file not readable" };
|
|
409
410
|
}
|
|
410
411
|
if (isAlreadyInjected(content)) {
|
|
411
|
-
|
|
412
|
+
if (!quiet) {
|
|
413
|
+
log.success(`Plugin already configured in ${detection.configPath} (skipped)`);
|
|
414
|
+
}
|
|
412
415
|
mutations.push({
|
|
413
416
|
type: "file_modified",
|
|
414
417
|
path: detection.configPath,
|
|
@@ -420,12 +423,15 @@ async function injectPlugin(root, detection, dryRun) {
|
|
|
420
423
|
printManualInstructions(
|
|
421
424
|
strategy,
|
|
422
425
|
detection,
|
|
423
|
-
`No injection strategy found for ${detection.tool}
|
|
426
|
+
`No injection strategy found for ${detection.tool}`,
|
|
427
|
+
quiet
|
|
424
428
|
);
|
|
425
429
|
return { success: false, mutations, failureReason: "No strategy found" };
|
|
426
430
|
}
|
|
427
431
|
if (dryRun) {
|
|
428
|
-
|
|
432
|
+
if (!quiet) {
|
|
433
|
+
log.dryRun(`Would automatically configure plugin in ${detection.configPath}`);
|
|
434
|
+
}
|
|
429
435
|
return { success: true, mutations: [] };
|
|
430
436
|
}
|
|
431
437
|
try {
|
|
@@ -440,13 +446,16 @@ async function injectPlugin(root, detection, dryRun) {
|
|
|
440
446
|
path: detection.configPath,
|
|
441
447
|
description: "Automatically configured inspecto() plugin"
|
|
442
448
|
});
|
|
443
|
-
|
|
449
|
+
if (!quiet) {
|
|
450
|
+
log.success(`Configured plugin in ${detection.configPath}`);
|
|
451
|
+
}
|
|
444
452
|
return { success: true, mutations };
|
|
445
453
|
} catch (err) {
|
|
446
454
|
printManualInstructions(
|
|
447
455
|
strategy,
|
|
448
456
|
detection,
|
|
449
|
-
`Automatic configuration unavailable: ${err instanceof Error ? err.message : String(err)}
|
|
457
|
+
`Automatic configuration unavailable: ${err instanceof Error ? err.message : String(err)}`,
|
|
458
|
+
quiet
|
|
450
459
|
);
|
|
451
460
|
return {
|
|
452
461
|
success: false,
|
|
@@ -529,9 +538,11 @@ async function tryOpenURI(uri) {
|
|
|
529
538
|
return false;
|
|
530
539
|
}
|
|
531
540
|
}
|
|
532
|
-
async function installExtension(dryRun, ide) {
|
|
541
|
+
async function installExtension(dryRun, ide, quiet = false) {
|
|
533
542
|
if (dryRun) {
|
|
534
|
-
|
|
543
|
+
if (!quiet) {
|
|
544
|
+
log.dryRun("Would attempt to install VS Code extension");
|
|
545
|
+
}
|
|
535
546
|
return null;
|
|
536
547
|
}
|
|
537
548
|
const isVSCode = !ide || ide === "vscode";
|
|
@@ -539,7 +550,9 @@ async function installExtension(dryRun, ide) {
|
|
|
539
550
|
if (await which("code")) {
|
|
540
551
|
try {
|
|
541
552
|
await run("code", ["--install-extension", EXTENSION_ID]);
|
|
542
|
-
|
|
553
|
+
if (!quiet) {
|
|
554
|
+
log.success("VS Code extension installed via CLI");
|
|
555
|
+
}
|
|
543
556
|
return { type: "extension_installed", id: EXTENSION_ID };
|
|
544
557
|
} catch {
|
|
545
558
|
}
|
|
@@ -548,35 +561,45 @@ async function installExtension(dryRun, ide) {
|
|
|
548
561
|
if (codePath) {
|
|
549
562
|
try {
|
|
550
563
|
await run(codePath, ["--install-extension", EXTENSION_ID]);
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
564
|
+
if (!quiet) {
|
|
565
|
+
log.success("VS Code extension installed via binary path");
|
|
566
|
+
log.info(
|
|
567
|
+
'Tip: Add "code" to your PATH to help Inspecto detect other AI tools in the future'
|
|
568
|
+
);
|
|
569
|
+
}
|
|
555
570
|
return { type: "extension_installed", id: EXTENSION_ID };
|
|
556
571
|
} catch {
|
|
557
572
|
}
|
|
558
573
|
}
|
|
559
574
|
const uri = `vscode:extension/${EXTENSION_ID}`;
|
|
560
575
|
if (await tryOpenURI(uri)) {
|
|
561
|
-
|
|
562
|
-
|
|
576
|
+
if (!quiet) {
|
|
577
|
+
log.warn("Opened extension page in VS Code");
|
|
578
|
+
log.hint('Please click "Install" in the opened VS Code window to complete setup.');
|
|
579
|
+
}
|
|
563
580
|
return { type: "extension_installed", id: EXTENSION_ID, manual_action_required: true };
|
|
564
581
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
582
|
+
if (!quiet) {
|
|
583
|
+
log.warn("Could not auto-install VS Code extension");
|
|
584
|
+
log.hint("Please install it manually to enable Inspector features:");
|
|
585
|
+
log.hint(" 1. Open VS Code");
|
|
586
|
+
log.hint(" 2. Press Ctrl+Shift+X (or Cmd+Shift+X)");
|
|
587
|
+
log.hint(' 3. Search for "Inspecto"');
|
|
588
|
+
log.hint(` Or visit: https://marketplace.visualstudio.com/items?itemName=${EXTENSION_ID}`);
|
|
589
|
+
}
|
|
571
590
|
return null;
|
|
572
591
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
592
|
+
if (!quiet) {
|
|
593
|
+
log.warn(`Could not auto-install extension for ${ide}`);
|
|
594
|
+
log.hint("Please install it manually to enable Inspector features:");
|
|
595
|
+
log.hint(
|
|
596
|
+
" 1. Download the latest .vsix file (Open VSX: https://open-vsx.org/extension/inspecto/inspecto)"
|
|
597
|
+
);
|
|
598
|
+
log.hint(` 2. Open ${ide}`);
|
|
599
|
+
log.hint(" 3. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)");
|
|
600
|
+
log.hint(' 4. Type and select "Extensions: Install from VSIX..."');
|
|
601
|
+
log.hint(" 5. Select the downloaded .vsix file");
|
|
602
|
+
}
|
|
580
603
|
return null;
|
|
581
604
|
}
|
|
582
605
|
async function isExtensionInstalled() {
|
|
@@ -600,7 +623,7 @@ async function isExtensionInstalled() {
|
|
|
600
623
|
import path4 from "path";
|
|
601
624
|
var DEFAULT_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
|
|
602
625
|
var SHARED_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
|
|
603
|
-
async function updateGitignore(root, shared, dryRun) {
|
|
626
|
+
async function updateGitignore(root, shared, dryRun, quiet = false) {
|
|
604
627
|
const gitignorePath = path4.join(root, ".gitignore");
|
|
605
628
|
let content = await readFile(gitignorePath) ?? "";
|
|
606
629
|
const desiredRules = shared ? SHARED_RULES : DEFAULT_RULES;
|
|
@@ -610,7 +633,9 @@ async function updateGitignore(root, shared, dryRun) {
|
|
|
610
633
|
if (!dryRun) {
|
|
611
634
|
await writeFile(gitignorePath, content);
|
|
612
635
|
}
|
|
613
|
-
|
|
636
|
+
if (!quiet) {
|
|
637
|
+
log.success("Updated .gitignore: .inspecto/ is no longer fully ignored");
|
|
638
|
+
}
|
|
614
639
|
return;
|
|
615
640
|
}
|
|
616
641
|
const missingRules = desiredRules.filter((rule) => !content.includes(rule));
|
|
@@ -620,10 +645,14 @@ async function updateGitignore(root, shared, dryRun) {
|
|
|
620
645
|
const section = "\n# Inspecto\n" + missingRules.join("\n") + "\n";
|
|
621
646
|
content = content.trimEnd() + "\n" + section;
|
|
622
647
|
if (dryRun) {
|
|
623
|
-
|
|
648
|
+
if (!quiet) {
|
|
649
|
+
log.dryRun(`Would update .gitignore with: ${missingRules.join(", ")}`);
|
|
650
|
+
}
|
|
624
651
|
} else {
|
|
625
652
|
await writeFile(gitignorePath, content);
|
|
626
|
-
|
|
653
|
+
if (!quiet) {
|
|
654
|
+
log.success("Updated .gitignore");
|
|
655
|
+
}
|
|
627
656
|
}
|
|
628
657
|
}
|
|
629
658
|
async function cleanGitignore(root) {
|
|
@@ -635,6 +664,26 @@ async function cleanGitignore(root) {
|
|
|
635
664
|
}
|
|
636
665
|
|
|
637
666
|
// src/onboarding/apply.ts
|
|
667
|
+
function shellQuote(value) {
|
|
668
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
669
|
+
}
|
|
670
|
+
function resolveRuntimePackages() {
|
|
671
|
+
const devRepo = process.env.INSPECTO_DEV_REPO;
|
|
672
|
+
if (!devRepo) {
|
|
673
|
+
return {
|
|
674
|
+
installSpec: "@inspecto-dev/plugin @inspecto-dev/core",
|
|
675
|
+
installedDependencyNames: ["@inspecto-dev/plugin", "@inspecto-dev/core"]
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const normalizedRepo = path5.resolve(devRepo);
|
|
679
|
+
return {
|
|
680
|
+
installSpec: [
|
|
681
|
+
shellQuote(path5.join(normalizedRepo, "packages/plugin")),
|
|
682
|
+
shellQuote(path5.join(normalizedRepo, "packages/core"))
|
|
683
|
+
].join(" "),
|
|
684
|
+
installedDependencyNames: ["@inspecto-dev/plugin", "@inspecto-dev/core"]
|
|
685
|
+
};
|
|
686
|
+
}
|
|
638
687
|
function resultStatus(nextSteps) {
|
|
639
688
|
return nextSteps.length > 0 ? "warning" : "ok";
|
|
640
689
|
}
|
|
@@ -724,10 +773,8 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
724
773
|
const promptsFileName = input.options.shared ? "prompts.json" : "prompts.local.json";
|
|
725
774
|
const settingsPath = path5.join(settingsDir, settingsFileName);
|
|
726
775
|
const promptsPath = path5.join(settingsDir, promptsFileName);
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
"@inspecto-dev/plugin @inspecto-dev/core"
|
|
730
|
-
);
|
|
776
|
+
const runtimePackages = resolveRuntimePackages();
|
|
777
|
+
const installCmd = getInstallCommand(input.packageManager, runtimePackages.installSpec);
|
|
731
778
|
const nextSteps = [];
|
|
732
779
|
let installFailed = false;
|
|
733
780
|
if (input.options.skipInstall) {
|
|
@@ -744,8 +791,9 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
744
791
|
await shell(installCmd, input.projectRoot);
|
|
745
792
|
spinner.succeed("Dependencies installed successfully");
|
|
746
793
|
reporter.success("Installed @inspecto-dev/plugin and @inspecto-dev/core as devDependencies");
|
|
747
|
-
|
|
748
|
-
|
|
794
|
+
for (const name of runtimePackages.installedDependencyNames) {
|
|
795
|
+
mutations.push({ type: "dependency_added", name, dev: true });
|
|
796
|
+
}
|
|
749
797
|
} catch (error) {
|
|
750
798
|
spinner.fail("Dependency installation failed");
|
|
751
799
|
installFailed = true;
|
|
@@ -758,7 +806,12 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
758
806
|
}
|
|
759
807
|
let injectionFailed = Boolean(input.injectionSkippedRequiresManualConfig);
|
|
760
808
|
for (const target of input.supportedBuildTargets) {
|
|
761
|
-
const result = await injectPlugin(
|
|
809
|
+
const result = await injectPlugin(
|
|
810
|
+
input.repoRoot,
|
|
811
|
+
target,
|
|
812
|
+
input.options.dryRun,
|
|
813
|
+
input.options.quiet ?? false
|
|
814
|
+
);
|
|
762
815
|
if (result.success) {
|
|
763
816
|
mutations.push(...result.mutations);
|
|
764
817
|
} else {
|
|
@@ -800,7 +853,12 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
800
853
|
mutations.push({ type: "file_created", path: `.inspecto/${promptsFileName}` });
|
|
801
854
|
}
|
|
802
855
|
if (!input.options.dryRun) {
|
|
803
|
-
await updateGitignore(
|
|
856
|
+
await updateGitignore(
|
|
857
|
+
input.projectRoot,
|
|
858
|
+
input.options.shared,
|
|
859
|
+
input.options.dryRun,
|
|
860
|
+
input.options.quiet ?? false
|
|
861
|
+
);
|
|
804
862
|
mutations.push({
|
|
805
863
|
type: "file_modified",
|
|
806
864
|
path: ".gitignore",
|
|
@@ -822,7 +880,11 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
822
880
|
if (input.options.noExtension) {
|
|
823
881
|
reporter.warn("Skipping IDE extension (--no-extension)");
|
|
824
882
|
} else if (shouldInstallExt) {
|
|
825
|
-
const extMutation = await installExtension(
|
|
883
|
+
const extMutation = await installExtension(
|
|
884
|
+
input.options.dryRun,
|
|
885
|
+
input.selectedIDE?.ide,
|
|
886
|
+
input.options.quiet ?? false
|
|
887
|
+
);
|
|
826
888
|
if (extMutation && !input.options.dryRun) {
|
|
827
889
|
mutations.push(extMutation);
|
|
828
890
|
if (extMutation.manual_action_required) {
|
|
@@ -1496,7 +1558,9 @@ function unsupportedEnvironmentWarnings(context) {
|
|
|
1496
1558
|
}
|
|
1497
1559
|
const unsupportedIdes = context.ides.filter((ide) => !ide.supported).map((ide) => ide.ide);
|
|
1498
1560
|
if (unsupportedIdes.length > 0) {
|
|
1499
|
-
warnings.push(
|
|
1561
|
+
warnings.push(
|
|
1562
|
+
message("unsupported-ide", `Unsupported IDE(s) detected: ${unsupportedIdes.join(", ")}`)
|
|
1563
|
+
);
|
|
1500
1564
|
}
|
|
1501
1565
|
const unsupportedProviders = context.providers.filter((provider) => !provider.supported).map((provider) => provider.label);
|
|
1502
1566
|
if (unsupportedProviders.length > 0) {
|
|
@@ -1635,6 +1699,9 @@ function createPlanResult(context) {
|
|
|
1635
1699
|
defaults
|
|
1636
1700
|
};
|
|
1637
1701
|
}
|
|
1702
|
+
function planManualFollowUp(result) {
|
|
1703
|
+
return result.actions.filter((action) => action.type === "manual_step").map((action) => action.description);
|
|
1704
|
+
}
|
|
1638
1705
|
|
|
1639
1706
|
// src/commands/apply.ts
|
|
1640
1707
|
function getProviderDefault(providerId, preferredMode) {
|
|
@@ -1680,7 +1747,11 @@ function printApplyResult(result) {
|
|
|
1680
1747
|
if (result.plan.warnings.length > 0) {
|
|
1681
1748
|
return;
|
|
1682
1749
|
}
|
|
1683
|
-
log.ready("Ready!
|
|
1750
|
+
log.ready("Ready! Inspecto is set up.");
|
|
1751
|
+
log.info("Next:");
|
|
1752
|
+
log.hint("1. Start or restart your dev server.");
|
|
1753
|
+
log.hint("2. Open your app in the browser.");
|
|
1754
|
+
log.hint("3. Hold Alt + Click any element to inspect.");
|
|
1684
1755
|
}
|
|
1685
1756
|
async function apply(options = {}) {
|
|
1686
1757
|
const root = process.cwd();
|
|
@@ -1763,6 +1834,89 @@ async function detect(json = false) {
|
|
|
1763
1834
|
// src/commands/init.ts
|
|
1764
1835
|
import path10 from "path";
|
|
1765
1836
|
|
|
1837
|
+
// src/onboarding/target-resolution.ts
|
|
1838
|
+
function normalizePackagePath(packagePath) {
|
|
1839
|
+
if (!packagePath || packagePath === ".") return "";
|
|
1840
|
+
return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
1841
|
+
}
|
|
1842
|
+
function looksLikeAppPackage(packagePath) {
|
|
1843
|
+
if (!packagePath) return true;
|
|
1844
|
+
return /(^|\/)(app|apps|web|client|frontend|site)(\/|$)/i.test(packagePath);
|
|
1845
|
+
}
|
|
1846
|
+
function looksLikeAuxiliaryPackage(packagePath) {
|
|
1847
|
+
return /(^|\/)(docs?|example|examples|playground|storybook|demo)(\/|$)/i.test(packagePath);
|
|
1848
|
+
}
|
|
1849
|
+
function buildCandidates(input) {
|
|
1850
|
+
return input.buildTools.map((buildTool) => {
|
|
1851
|
+
const packagePath = normalizePackagePath(buildTool.packagePath);
|
|
1852
|
+
return {
|
|
1853
|
+
packagePath,
|
|
1854
|
+
configPath: buildTool.configPath,
|
|
1855
|
+
buildTool: buildTool.tool,
|
|
1856
|
+
frameworks: input.frameworkSupportByPackage[packagePath] ?? [],
|
|
1857
|
+
automaticInjection: true
|
|
1858
|
+
};
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
function rankCandidate(candidate) {
|
|
1862
|
+
let score = 0;
|
|
1863
|
+
if (candidate.frameworks.length > 0) score += 4;
|
|
1864
|
+
if (candidate.automaticInjection) score += 2;
|
|
1865
|
+
if (looksLikeAppPackage(candidate.packagePath)) score += 1;
|
|
1866
|
+
if (looksLikeAuxiliaryPackage(candidate.packagePath)) score -= 2;
|
|
1867
|
+
return score;
|
|
1868
|
+
}
|
|
1869
|
+
function rankCandidates(candidates) {
|
|
1870
|
+
return candidates.map((candidate) => ({
|
|
1871
|
+
candidate,
|
|
1872
|
+
score: rankCandidate(candidate)
|
|
1873
|
+
})).sort((left, right) => right.score - left.score);
|
|
1874
|
+
}
|
|
1875
|
+
function resolveOnboardingTarget(input) {
|
|
1876
|
+
const candidates = buildCandidates(input);
|
|
1877
|
+
if (candidates.length === 0) {
|
|
1878
|
+
return {
|
|
1879
|
+
status: "needs_selection",
|
|
1880
|
+
candidates,
|
|
1881
|
+
reason: "No supported targets were detected."
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
const explicitlySelected = normalizePackagePath(input.selectedPackagePath);
|
|
1885
|
+
if (input.selectedPackagePath !== void 0) {
|
|
1886
|
+
const selected = candidates.find((candidate) => candidate.packagePath === explicitlySelected);
|
|
1887
|
+
if (selected) {
|
|
1888
|
+
return {
|
|
1889
|
+
status: "resolved",
|
|
1890
|
+
selected,
|
|
1891
|
+
candidates,
|
|
1892
|
+
reason: `Using the explicitly selected target: ${selected.packagePath || "."}.`
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
if (candidates.length === 1) {
|
|
1897
|
+
return {
|
|
1898
|
+
status: "resolved",
|
|
1899
|
+
selected: candidates[0],
|
|
1900
|
+
candidates,
|
|
1901
|
+
reason: "Only one supported target was detected."
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
const ranked = rankCandidates(candidates);
|
|
1905
|
+
if (ranked.length > 1 && ranked[0].score === ranked[1].score) {
|
|
1906
|
+
return {
|
|
1907
|
+
status: "needs_selection",
|
|
1908
|
+
candidates,
|
|
1909
|
+
reason: "Multiple supported targets look equally plausible."
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
return {
|
|
1913
|
+
status: "resolved",
|
|
1914
|
+
selected: ranked[0].candidate,
|
|
1915
|
+
candidates,
|
|
1916
|
+
reason: `Preselected ${ranked[0].candidate.packagePath || "."} because it has the strongest supported app signal.`
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1766
1920
|
// src/prompts.ts
|
|
1767
1921
|
import prompts from "prompts";
|
|
1768
1922
|
async function promptIDEChoice(detections) {
|
|
@@ -1841,7 +1995,9 @@ async function promptMonorepoPackageChoice(detections) {
|
|
|
1841
1995
|
}
|
|
1842
1996
|
if (!process.stdin.isTTY) {
|
|
1843
1997
|
log.error("Monorepo root detected, but stdin is not interactive.");
|
|
1844
|
-
log.hint(
|
|
1998
|
+
log.hint(
|
|
1999
|
+
"Re-run `inspecto init` inside a specific app directory, or pass --packages <app-path>."
|
|
2000
|
+
);
|
|
1845
2001
|
return null;
|
|
1846
2002
|
}
|
|
1847
2003
|
const { choice } = await prompts({
|
|
@@ -1976,27 +2132,40 @@ async function init(options) {
|
|
|
1976
2132
|
verifiedPackages.length > 0 ? verifiedPackages : void 0
|
|
1977
2133
|
);
|
|
1978
2134
|
if (verifiedPackages.length === 0) {
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2135
|
+
const monorepoCandidates = buildResult.supported.filter((detection) => !!detection.packagePath);
|
|
2136
|
+
if (monorepoCandidates.length > 0) {
|
|
2137
|
+
const frameworkSupportByPackage = await detectFrameworkSupportByPackage(
|
|
2138
|
+
repoRoot,
|
|
2139
|
+
monorepoCandidates
|
|
2140
|
+
);
|
|
2141
|
+
const targetResolution = resolveOnboardingTarget({
|
|
2142
|
+
repoRoot,
|
|
2143
|
+
buildTools: monorepoCandidates,
|
|
2144
|
+
frameworkSupportByPackage
|
|
2145
|
+
});
|
|
2146
|
+
if (targetResolution.status === "needs_selection") {
|
|
2147
|
+
log.warn("Monorepo root detected with multiple candidate apps.");
|
|
2148
|
+
const selectedPackage = await promptMonorepoPackageChoice(
|
|
2149
|
+
monorepoCandidates.filter(
|
|
2150
|
+
(detection) => targetResolution.candidates.some(
|
|
2151
|
+
(candidate) => candidate.packagePath === (detection.packagePath ?? "")
|
|
2152
|
+
)
|
|
2153
|
+
)
|
|
2154
|
+
);
|
|
2155
|
+
if (!selectedPackage) {
|
|
2156
|
+
log.hint("Run `inspecto init` inside the target app, or pass --packages <app-path>.");
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
projectRoot = path10.join(repoRoot, selectedPackage);
|
|
2160
|
+
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
2161
|
+
log.info(`Continuing initialization in ${selectedPackage}`);
|
|
2162
|
+
} else if (targetResolution.selected?.packagePath) {
|
|
2163
|
+
const selectedPackage = targetResolution.selected.packagePath;
|
|
2164
|
+
projectRoot = path10.join(repoRoot, selectedPackage);
|
|
2165
|
+
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
2166
|
+
log.warn(`Monorepo root detected. Using the only candidate app: ${selectedPackage}`);
|
|
2167
|
+
log.hint("Run `inspecto init` inside that app next time to skip this prompt.");
|
|
1990
2168
|
}
|
|
1991
|
-
projectRoot = path10.join(repoRoot, selectedPackage);
|
|
1992
|
-
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
1993
|
-
log.info(`Continuing initialization in ${selectedPackage}`);
|
|
1994
|
-
} else if (monorepoTargets.length === 1) {
|
|
1995
|
-
const [selectedPackage] = monorepoTargets;
|
|
1996
|
-
projectRoot = path10.join(repoRoot, selectedPackage);
|
|
1997
|
-
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
1998
|
-
log.warn(`Monorepo root detected. Using the only candidate app: ${selectedPackage}`);
|
|
1999
|
-
log.hint("Run `inspecto init` inside that app next time to skip this prompt.");
|
|
2000
2169
|
}
|
|
2001
2170
|
}
|
|
2002
2171
|
const [frameworkResult, ideProbe, providerProbe] = await Promise.all([
|
|
@@ -2173,7 +2342,11 @@ async function init(options) {
|
|
|
2173
2342
|
log.hint("Complete the items above.");
|
|
2174
2343
|
log.blank();
|
|
2175
2344
|
} else {
|
|
2176
|
-
log.ready("Ready!
|
|
2345
|
+
log.ready("Ready! Inspecto is set up.");
|
|
2346
|
+
log.info("Next:");
|
|
2347
|
+
log.hint("1. Start or restart your dev server.");
|
|
2348
|
+
log.hint("2. Open your app in the browser.");
|
|
2349
|
+
log.hint("3. Hold Alt + Click any element to inspect.");
|
|
2177
2350
|
}
|
|
2178
2351
|
}
|
|
2179
2352
|
}
|
|
@@ -2198,6 +2371,19 @@ function matchesAnyPackage(detection, packages) {
|
|
|
2198
2371
|
if (packages.length === 0) return true;
|
|
2199
2372
|
return packages.some((pkg) => matchesPackage(detection, pkg));
|
|
2200
2373
|
}
|
|
2374
|
+
async function detectFrameworkSupportByPackage(repoRoot, buildTools) {
|
|
2375
|
+
const packagePaths = Array.from(
|
|
2376
|
+
new Set(buildTools.map((buildTool) => buildTool.packagePath).filter((value) => !!value))
|
|
2377
|
+
);
|
|
2378
|
+
const supportByPackage = {};
|
|
2379
|
+
await Promise.all(
|
|
2380
|
+
packagePaths.map(async (packagePath) => {
|
|
2381
|
+
const result = await detectFrameworks(path10.join(repoRoot, packagePath));
|
|
2382
|
+
supportByPackage[packagePath] = result.supported;
|
|
2383
|
+
})
|
|
2384
|
+
);
|
|
2385
|
+
return supportByPackage;
|
|
2386
|
+
}
|
|
2201
2387
|
|
|
2202
2388
|
// src/commands/doctor.ts
|
|
2203
2389
|
import path11 from "path";
|
|
@@ -2245,12 +2431,9 @@ function printDoctorResult(result) {
|
|
|
2245
2431
|
async function collectDoctorResult(root = process.cwd()) {
|
|
2246
2432
|
const checks = [];
|
|
2247
2433
|
if (!await exists(path11.join(root, "package.json"))) {
|
|
2248
|
-
const diagnostic = createDiagnostic(
|
|
2249
|
-
"
|
|
2250
|
-
|
|
2251
|
-
"No package.json found",
|
|
2252
|
-
["Run this command from your project root"]
|
|
2253
|
-
);
|
|
2434
|
+
const diagnostic = createDiagnostic("missing-package-json", "error", "No package.json found", [
|
|
2435
|
+
"Run this command from your project root"
|
|
2436
|
+
]);
|
|
2254
2437
|
checks.push(diagnostic);
|
|
2255
2438
|
return {
|
|
2256
2439
|
status: "blocked",
|
|
@@ -2374,20 +2557,28 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2374
2557
|
}
|
|
2375
2558
|
if (!injected) {
|
|
2376
2559
|
checks.push(
|
|
2377
|
-
createDiagnostic(
|
|
2378
|
-
"
|
|
2379
|
-
|
|
2560
|
+
createDiagnostic(
|
|
2561
|
+
"plugin-not-configured",
|
|
2562
|
+
"error",
|
|
2563
|
+
"Plugin not configured in any build config",
|
|
2564
|
+
["Fix: npx @inspecto-dev/cli init"]
|
|
2565
|
+
)
|
|
2380
2566
|
);
|
|
2381
2567
|
}
|
|
2382
2568
|
} else if (buildResult.unsupported.length > 0) {
|
|
2383
2569
|
const names = buildResult.unsupported.join(", ");
|
|
2384
2570
|
checks.push(
|
|
2385
|
-
createDiagnostic(
|
|
2386
|
-
|
|
2387
|
-
|
|
2571
|
+
createDiagnostic(
|
|
2572
|
+
`build-tool-unsupported`,
|
|
2573
|
+
"warning",
|
|
2574
|
+
`Build tool: ${names} (not supported in v1)`,
|
|
2575
|
+
["current version supports: Vite, Webpack, Rspack, esbuild, Rollup"]
|
|
2576
|
+
)
|
|
2388
2577
|
);
|
|
2389
2578
|
} else {
|
|
2390
|
-
checks.push(
|
|
2579
|
+
checks.push(
|
|
2580
|
+
createDiagnostic("build-tool-missing", "warning", "No recognized build config found")
|
|
2581
|
+
);
|
|
2391
2582
|
}
|
|
2392
2583
|
if (extInstalled) {
|
|
2393
2584
|
checks.push(createDiagnostic("extension-installed", "ok", "VS Code extension detected"));
|
|
@@ -2483,6 +2674,350 @@ async function doctor(options = {}) {
|
|
|
2483
2674
|
return writeCommandOutput(result, json, printDoctorResult);
|
|
2484
2675
|
}
|
|
2485
2676
|
|
|
2677
|
+
// src/onboarding/session.ts
|
|
2678
|
+
import path12 from "path";
|
|
2679
|
+
function normalizePackagePath2(packagePath) {
|
|
2680
|
+
if (!packagePath || packagePath === ".") return "";
|
|
2681
|
+
return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
2682
|
+
}
|
|
2683
|
+
function getProviderDefault2(providerId, preferredMode) {
|
|
2684
|
+
if (!providerId) return void 0;
|
|
2685
|
+
const mode = preferredMode ?? (providerId === "coco" ? "cli" : "extension");
|
|
2686
|
+
return `${providerId}.${mode}`;
|
|
2687
|
+
}
|
|
2688
|
+
function getVerificationCommand(packageManager) {
|
|
2689
|
+
switch (packageManager) {
|
|
2690
|
+
case "pnpm":
|
|
2691
|
+
return "pnpm dev";
|
|
2692
|
+
case "yarn":
|
|
2693
|
+
return "yarn dev";
|
|
2694
|
+
case "bun":
|
|
2695
|
+
return "bun run dev";
|
|
2696
|
+
case "npm":
|
|
2697
|
+
default:
|
|
2698
|
+
return "npm run dev";
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
async function buildVerification(projectRoot, packageManager) {
|
|
2702
|
+
const packageJson = await readJSON(
|
|
2703
|
+
path12.join(projectRoot, "package.json")
|
|
2704
|
+
);
|
|
2705
|
+
if (packageJson?.scripts?.dev) {
|
|
2706
|
+
const devCommand = getVerificationCommand(packageManager);
|
|
2707
|
+
return {
|
|
2708
|
+
available: true,
|
|
2709
|
+
devCommand,
|
|
2710
|
+
message: `Start the local dev server with \`${devCommand}\` to verify Inspecto in the browser.`
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
return {
|
|
2714
|
+
available: false,
|
|
2715
|
+
message: "Start your normal local dev server command to verify Inspecto in the browser."
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
function buildIdeExtensionStatus(input) {
|
|
2719
|
+
if (!input.required) {
|
|
2720
|
+
return {
|
|
2721
|
+
required: false,
|
|
2722
|
+
installed: false,
|
|
2723
|
+
manualRequired: false
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
return {
|
|
2727
|
+
required: true,
|
|
2728
|
+
installed: input.installed,
|
|
2729
|
+
manualRequired: input.manualRequired,
|
|
2730
|
+
installCommand: "code --install-extension inspecto.inspecto",
|
|
2731
|
+
marketplaceUrl: "https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto",
|
|
2732
|
+
openVsxUrl: "https://open-vsx.org/extension/inspecto/inspecto"
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
async function detectFrameworkSupportByPackage2(repoRoot, context) {
|
|
2736
|
+
const packagePaths = new Set(
|
|
2737
|
+
context.buildTools.supported.map((item) => normalizePackagePath2(item.packagePath))
|
|
2738
|
+
);
|
|
2739
|
+
const supportByPackage = {};
|
|
2740
|
+
await Promise.all(
|
|
2741
|
+
Array.from(packagePaths).map(async (packagePath) => {
|
|
2742
|
+
const frameworkResult = await detectFrameworks(
|
|
2743
|
+
packagePath ? path12.join(repoRoot, packagePath) : repoRoot
|
|
2744
|
+
);
|
|
2745
|
+
supportByPackage[packagePath] = frameworkResult.supported;
|
|
2746
|
+
})
|
|
2747
|
+
);
|
|
2748
|
+
return supportByPackage;
|
|
2749
|
+
}
|
|
2750
|
+
async function buildTargetedContext(rootContext, packagePath) {
|
|
2751
|
+
const projectRoot = packagePath ? path12.join(rootContext.root, packagePath) : rootContext.root;
|
|
2752
|
+
const [frameworks, ides, providers] = await Promise.all([
|
|
2753
|
+
detectFrameworks(projectRoot),
|
|
2754
|
+
detectIDE(projectRoot),
|
|
2755
|
+
detectProviders(projectRoot)
|
|
2756
|
+
]);
|
|
2757
|
+
return {
|
|
2758
|
+
root: projectRoot,
|
|
2759
|
+
packageManager: rootContext.packageManager,
|
|
2760
|
+
buildTools: {
|
|
2761
|
+
supported: rootContext.buildTools.supported.filter((item) => {
|
|
2762
|
+
return normalizePackagePath2(item.packagePath) === packagePath;
|
|
2763
|
+
}),
|
|
2764
|
+
unsupported: []
|
|
2765
|
+
},
|
|
2766
|
+
frameworks: {
|
|
2767
|
+
supported: frameworks.supported,
|
|
2768
|
+
unsupported: frameworks.unsupported.map((item) => item.name)
|
|
2769
|
+
},
|
|
2770
|
+
ides: ides.detected.map(({ ide, supported }) => ({ ide, supported })),
|
|
2771
|
+
providers: providers.detected.map(({ id, label, supported, preferredMode }) => ({
|
|
2772
|
+
id,
|
|
2773
|
+
label,
|
|
2774
|
+
supported,
|
|
2775
|
+
preferredMode
|
|
2776
|
+
}))
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
function buildOnboardingSummary(plan2, projectRoot) {
|
|
2780
|
+
const changes = plan2.actions.filter((action) => action.type !== "manual_step").map((action) => action.description);
|
|
2781
|
+
const risks = [...plan2.warnings.map((item) => item.message)];
|
|
2782
|
+
const manualFollowUp = planManualFollowUp(plan2);
|
|
2783
|
+
let headline = `Inspecto is ready to onboard ${projectRoot}.`;
|
|
2784
|
+
if (manualFollowUp.length > 0) {
|
|
2785
|
+
headline = `Inspecto can partially onboard ${projectRoot}, but manual follow-up remains.`;
|
|
2786
|
+
} else if (plan2.status === "blocked") {
|
|
2787
|
+
headline = `Inspecto could not build an automatic onboarding path for ${projectRoot}.`;
|
|
2788
|
+
}
|
|
2789
|
+
return {
|
|
2790
|
+
headline,
|
|
2791
|
+
changes,
|
|
2792
|
+
risks,
|
|
2793
|
+
manualFollowUp
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
function buildConfirmation(plan2, summary, session, options) {
|
|
2797
|
+
if (options.yes) {
|
|
2798
|
+
return { required: false };
|
|
2799
|
+
}
|
|
2800
|
+
const reasons = [];
|
|
2801
|
+
if (session.targetWasHeuristic) reasons.push("a monorepo target was preselected");
|
|
2802
|
+
if (summary.manualFollowUp.length > 0) reasons.push("manual follow-up will remain after setup");
|
|
2803
|
+
if (options.noExtension || options.skipInstall) reasons.push("non-core setup steps are being skipped");
|
|
2804
|
+
if (session.supportedIdeCount > 1 || session.supportedProviderCount > 1) {
|
|
2805
|
+
reasons.push("multiple IDE or provider choices are still relevant");
|
|
2806
|
+
}
|
|
2807
|
+
if (plan2.warnings.length > 0) reasons.push("the CLI detected non-blocking risk");
|
|
2808
|
+
if (reasons.length === 0) {
|
|
2809
|
+
return { required: false };
|
|
2810
|
+
}
|
|
2811
|
+
return {
|
|
2812
|
+
required: true,
|
|
2813
|
+
reason: reasons.join("; "),
|
|
2814
|
+
question: "Proceed with Inspecto onboarding using the proposed default target and settings?"
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
function buildPreApplyResult(status, session) {
|
|
2818
|
+
const diagnostics = session.summary.risks.length > 0 || session.summary.manualFollowUp.length > 0 || session.plan.blockers.length > 0 ? {
|
|
2819
|
+
warnings: session.summary.risks,
|
|
2820
|
+
errors: session.plan.blockers.map((item) => item.message),
|
|
2821
|
+
nextSteps: session.summary.manualFollowUp
|
|
2822
|
+
} : void 0;
|
|
2823
|
+
return {
|
|
2824
|
+
status,
|
|
2825
|
+
target: session.target,
|
|
2826
|
+
summary: session.summary,
|
|
2827
|
+
confirmation: session.confirmation,
|
|
2828
|
+
ideExtension: buildIdeExtensionStatus({
|
|
2829
|
+
required: session.plan.defaults.extension,
|
|
2830
|
+
installed: false,
|
|
2831
|
+
manualRequired: session.plan.defaults.extension
|
|
2832
|
+
}),
|
|
2833
|
+
verification: session.verification,
|
|
2834
|
+
diagnostics
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
function buildExecutionResult(session, applyResult) {
|
|
2838
|
+
return {
|
|
2839
|
+
changedFiles: Array.from(
|
|
2840
|
+
new Set(applyResult.mutations.map((item) => item.path).filter((value) => !!value))
|
|
2841
|
+
),
|
|
2842
|
+
installedDependencies: applyResult.mutations.map((item) => item.name).filter((value) => !!value),
|
|
2843
|
+
selectedProviderDefault: session.providerDefault,
|
|
2844
|
+
selectedIDE: session.selectedIDE?.ide,
|
|
2845
|
+
mutations: applyResult.mutations
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
function buildExecutionDiagnostics(session, applyResult) {
|
|
2849
|
+
const warnings = [...session.plan.warnings.map((item) => item.message)];
|
|
2850
|
+
const errors = [];
|
|
2851
|
+
if (applyResult.postInstall.installFailed) {
|
|
2852
|
+
errors.push("Dependency installation failed during onboarding.");
|
|
2853
|
+
}
|
|
2854
|
+
if (applyResult.postInstall.injectionFailed) {
|
|
2855
|
+
warnings.push("Automatic plugin injection did not finish cleanly.");
|
|
2856
|
+
}
|
|
2857
|
+
if (applyResult.postInstall.manualExtensionInstallNeeded) {
|
|
2858
|
+
warnings.push("IDE extension installation still needs manual completion.");
|
|
2859
|
+
}
|
|
2860
|
+
if (warnings.length === 0 && errors.length === 0 && applyResult.postInstall.nextSteps.length === 0) {
|
|
2861
|
+
return void 0;
|
|
2862
|
+
}
|
|
2863
|
+
return {
|
|
2864
|
+
warnings,
|
|
2865
|
+
errors,
|
|
2866
|
+
nextSteps: applyResult.postInstall.nextSteps
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
async function resolveOnboardingSession(root, options = {}) {
|
|
2870
|
+
const rootContext = await buildOnboardingContext(root);
|
|
2871
|
+
const rootVerification = await buildVerification(root, rootContext.packageManager);
|
|
2872
|
+
const frameworkSupportByPackage = await detectFrameworkSupportByPackage2(root, rootContext);
|
|
2873
|
+
const target = resolveOnboardingTarget({
|
|
2874
|
+
repoRoot: root,
|
|
2875
|
+
buildTools: rootContext.buildTools.supported,
|
|
2876
|
+
frameworkSupportByPackage,
|
|
2877
|
+
selectedPackagePath: options.target
|
|
2878
|
+
});
|
|
2879
|
+
if (target.candidates.length === 0) {
|
|
2880
|
+
const plan3 = createPlanResult(rootContext);
|
|
2881
|
+
return {
|
|
2882
|
+
status: "error",
|
|
2883
|
+
target,
|
|
2884
|
+
summary: buildOnboardingSummary(plan3, root),
|
|
2885
|
+
confirmation: { required: false },
|
|
2886
|
+
verification: rootVerification,
|
|
2887
|
+
context: rootContext,
|
|
2888
|
+
plan: plan3,
|
|
2889
|
+
projectRoot: root
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
if (target.status === "needs_selection") {
|
|
2893
|
+
const plan3 = createPlanResult(rootContext);
|
|
2894
|
+
const summary2 = {
|
|
2895
|
+
headline: "Inspecto found multiple plausible app targets and needs one selection.",
|
|
2896
|
+
changes: [],
|
|
2897
|
+
risks: [],
|
|
2898
|
+
manualFollowUp: []
|
|
2899
|
+
};
|
|
2900
|
+
return {
|
|
2901
|
+
status: "needs_target_selection",
|
|
2902
|
+
target,
|
|
2903
|
+
summary: summary2,
|
|
2904
|
+
confirmation: { required: false },
|
|
2905
|
+
verification: rootVerification,
|
|
2906
|
+
context: rootContext,
|
|
2907
|
+
plan: plan3,
|
|
2908
|
+
projectRoot: root
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
const packagePath = normalizePackagePath2(target.selected?.packagePath);
|
|
2912
|
+
const context = await buildTargetedContext(rootContext, packagePath);
|
|
2913
|
+
const verification = await buildVerification(context.root, context.packageManager);
|
|
2914
|
+
const plan2 = createPlanResult(context);
|
|
2915
|
+
const summary = buildOnboardingSummary(plan2, context.root);
|
|
2916
|
+
const confirmation = buildConfirmation(
|
|
2917
|
+
plan2,
|
|
2918
|
+
summary,
|
|
2919
|
+
{
|
|
2920
|
+
targetWasHeuristic: target.candidates.length > 1 && options.target === void 0,
|
|
2921
|
+
supportedIdeCount: context.ides.filter((item) => item.supported).length,
|
|
2922
|
+
supportedProviderCount: context.providers.filter((item) => item.supported).length
|
|
2923
|
+
},
|
|
2924
|
+
options
|
|
2925
|
+
);
|
|
2926
|
+
const selectedProvider = context.providers.find((provider) => provider.id === plan2.defaults.provider) ?? null;
|
|
2927
|
+
const selectedIDE = context.ides.find((ide) => ide.ide === plan2.defaults.ide) ?? context.ides.find((ide) => ide.supported) ?? null;
|
|
2928
|
+
let status = "success";
|
|
2929
|
+
if (plan2.status === "blocked") {
|
|
2930
|
+
status = "error";
|
|
2931
|
+
} else if (confirmation.required) {
|
|
2932
|
+
status = "needs_confirmation";
|
|
2933
|
+
} else if (summary.manualFollowUp.length > 0 || plan2.warnings.length > 0) {
|
|
2934
|
+
status = "partial_success";
|
|
2935
|
+
}
|
|
2936
|
+
return {
|
|
2937
|
+
status,
|
|
2938
|
+
target,
|
|
2939
|
+
summary,
|
|
2940
|
+
confirmation,
|
|
2941
|
+
verification,
|
|
2942
|
+
context,
|
|
2943
|
+
plan: plan2,
|
|
2944
|
+
projectRoot: context.root,
|
|
2945
|
+
selectedIDE,
|
|
2946
|
+
providerDefault: getProviderDefault2(plan2.defaults.provider, selectedProvider?.preferredMode)
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
async function applyResolvedOnboardingSession(session, options = {}) {
|
|
2950
|
+
const verification = await buildVerification(session.projectRoot, session.context.packageManager);
|
|
2951
|
+
const applyResult = await applyOnboardingPlan({
|
|
2952
|
+
repoRoot: process.cwd(),
|
|
2953
|
+
projectRoot: session.projectRoot,
|
|
2954
|
+
packageManager: session.context.packageManager,
|
|
2955
|
+
supportedBuildTargets: session.context.buildTools.supported,
|
|
2956
|
+
options: {
|
|
2957
|
+
shared: options.shared ?? session.plan.defaults.shared,
|
|
2958
|
+
skipInstall: options.skipInstall ?? false,
|
|
2959
|
+
dryRun: options.dryRun ?? false,
|
|
2960
|
+
noExtension: options.noExtension ?? !session.plan.defaults.extension,
|
|
2961
|
+
quiet: options.json ?? false
|
|
2962
|
+
},
|
|
2963
|
+
selectedIDE: session.selectedIDE,
|
|
2964
|
+
providerDefault: session.providerDefault,
|
|
2965
|
+
plan: session.plan
|
|
2966
|
+
});
|
|
2967
|
+
const diagnostics = buildExecutionDiagnostics(session, applyResult);
|
|
2968
|
+
const status = applyResult.postInstall.installFailed && session.context.buildTools.supported.length === 0 ? "error" : diagnostics?.nextSteps.length || diagnostics?.errors.length || diagnostics?.warnings.length ? "partial_success" : "success";
|
|
2969
|
+
return {
|
|
2970
|
+
status,
|
|
2971
|
+
target: session.target,
|
|
2972
|
+
summary: session.summary,
|
|
2973
|
+
confirmation: session.confirmation,
|
|
2974
|
+
ideExtension: buildIdeExtensionStatus({
|
|
2975
|
+
required: session.plan.defaults.extension,
|
|
2976
|
+
installed: session.plan.defaults.extension && !applyResult.postInstall.manualExtensionInstallNeeded,
|
|
2977
|
+
manualRequired: applyResult.postInstall.manualExtensionInstallNeeded
|
|
2978
|
+
}),
|
|
2979
|
+
verification,
|
|
2980
|
+
result: buildExecutionResult(session, applyResult),
|
|
2981
|
+
diagnostics
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
function buildDeferredOnboardResult(session) {
|
|
2985
|
+
return buildPreApplyResult(session.status, session);
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// src/commands/onboard.ts
|
|
2989
|
+
function printOnboardResult(result) {
|
|
2990
|
+
log.header("Inspecto Onboard");
|
|
2991
|
+
log.info(`Status: ${result.status}`);
|
|
2992
|
+
log.info(result.summary.headline);
|
|
2993
|
+
for (const change of result.summary.changes) {
|
|
2994
|
+
log.hint(change);
|
|
2995
|
+
}
|
|
2996
|
+
for (const step of result.diagnostics?.nextSteps ?? []) {
|
|
2997
|
+
log.warn(step);
|
|
2998
|
+
}
|
|
2999
|
+
if (result.confirmation.required && result.confirmation.question) {
|
|
3000
|
+
log.warn(result.confirmation.question);
|
|
3001
|
+
}
|
|
3002
|
+
const extensionReady = !result.ideExtension?.required || result.ideExtension.installed && !result.ideExtension.manualRequired;
|
|
3003
|
+
if (extensionReady && (result.status === "success" || result.status === "partial_success") && result.verification?.message) {
|
|
3004
|
+
log.info(result.verification.message);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
async function onboard(options = {}) {
|
|
3008
|
+
const root = process.cwd();
|
|
3009
|
+
const session = await resolveOnboardingSession(root, options);
|
|
3010
|
+
if (session.status === "error" || session.status === "needs_target_selection" || session.status === "needs_confirmation") {
|
|
3011
|
+
return writeCommandOutput(
|
|
3012
|
+
buildDeferredOnboardResult(session),
|
|
3013
|
+
options.json ?? false,
|
|
3014
|
+
printOnboardResult
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
const result = await applyResolvedOnboardingSession(session, options);
|
|
3018
|
+
return writeCommandOutput(result, options.json ?? false, printOnboardResult);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
2486
3021
|
// src/commands/plan.ts
|
|
2487
3022
|
function printPlanResult(result) {
|
|
2488
3023
|
log.header("Inspecto Plan");
|
|
@@ -2517,11 +3052,11 @@ async function plan(json = false) {
|
|
|
2517
3052
|
}
|
|
2518
3053
|
|
|
2519
3054
|
// src/commands/teardown.ts
|
|
2520
|
-
import
|
|
3055
|
+
import path13 from "path";
|
|
2521
3056
|
async function teardown() {
|
|
2522
3057
|
const root = process.cwd();
|
|
2523
3058
|
log.header("Inspecto Teardown");
|
|
2524
|
-
const lockPath =
|
|
3059
|
+
const lockPath = path13.join(root, ".inspecto", "install.lock");
|
|
2525
3060
|
const lock = await readJSON(lockPath);
|
|
2526
3061
|
if (!lock) {
|
|
2527
3062
|
log.warn("No .inspecto/install.lock found. Running in best-effort mode.");
|
|
@@ -2534,8 +3069,8 @@ async function teardown() {
|
|
|
2534
3069
|
} catch {
|
|
2535
3070
|
log.warn("Could not remove @inspecto-dev/plugin (may not be installed)");
|
|
2536
3071
|
}
|
|
2537
|
-
if (await exists(
|
|
2538
|
-
await removeDir(
|
|
3072
|
+
if (await exists(path13.join(root, ".inspecto"))) {
|
|
3073
|
+
await removeDir(path13.join(root, ".inspecto"));
|
|
2539
3074
|
log.success("Deleted .inspecto/ directory");
|
|
2540
3075
|
}
|
|
2541
3076
|
await cleanGitignore(root);
|
|
@@ -2584,7 +3119,7 @@ async function teardown() {
|
|
|
2584
3119
|
}
|
|
2585
3120
|
}
|
|
2586
3121
|
}
|
|
2587
|
-
await removeDir(
|
|
3122
|
+
await removeDir(path13.join(root, ".inspecto"));
|
|
2588
3123
|
log.success("Deleted .inspecto/ directory");
|
|
2589
3124
|
await cleanGitignore(root);
|
|
2590
3125
|
log.blank();
|
|
@@ -2593,6 +3128,9 @@ async function teardown() {
|
|
|
2593
3128
|
}
|
|
2594
3129
|
|
|
2595
3130
|
export {
|
|
3131
|
+
exists,
|
|
3132
|
+
writeFile,
|
|
3133
|
+
log,
|
|
2596
3134
|
writeCommandOutput,
|
|
2597
3135
|
reportCommandError,
|
|
2598
3136
|
apply,
|
|
@@ -2600,6 +3138,7 @@ export {
|
|
|
2600
3138
|
init,
|
|
2601
3139
|
collectDoctorResult,
|
|
2602
3140
|
doctor,
|
|
3141
|
+
onboard,
|
|
2603
3142
|
plan,
|
|
2604
3143
|
teardown
|
|
2605
3144
|
};
|