@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
|
@@ -118,6 +118,12 @@ async function writeFile(filePath, content) {
|
|
|
118
118
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
119
119
|
await fs.writeFile(filePath, content, "utf-8");
|
|
120
120
|
}
|
|
121
|
+
async function removeFile(filePath) {
|
|
122
|
+
try {
|
|
123
|
+
await fs.unlink(filePath);
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
121
127
|
async function removeDir(dirPath) {
|
|
122
128
|
try {
|
|
123
129
|
await fs.rm(dirPath, { recursive: true, force: true });
|
|
@@ -144,8 +150,8 @@ async function detectPackageManager(root) {
|
|
|
144
150
|
["bun.lockb", "bun"],
|
|
145
151
|
["bun.lock", "bun"],
|
|
146
152
|
["pnpm-lock.yaml", "pnpm"],
|
|
147
|
-
["
|
|
148
|
-
["
|
|
153
|
+
["package-lock.json", "npm"],
|
|
154
|
+
["yarn.lock", "yarn"]
|
|
149
155
|
];
|
|
150
156
|
const results = await Promise.all(
|
|
151
157
|
checks.map(async ([file, pm]) => {
|
|
@@ -826,8 +832,18 @@ async function isExtensionInstalled() {
|
|
|
826
832
|
|
|
827
833
|
// src/inject/gitignore.ts
|
|
828
834
|
import path5 from "path";
|
|
829
|
-
var DEFAULT_RULES = [
|
|
830
|
-
|
|
835
|
+
var DEFAULT_RULES = [
|
|
836
|
+
".inspecto/install.lock",
|
|
837
|
+
".inspecto/cache.json",
|
|
838
|
+
".inspecto/*.local.json",
|
|
839
|
+
".inspecto/dev.json"
|
|
840
|
+
];
|
|
841
|
+
var SHARED_RULES = [
|
|
842
|
+
".inspecto/install.lock",
|
|
843
|
+
".inspecto/cache.json",
|
|
844
|
+
".inspecto/*.local.json",
|
|
845
|
+
".inspecto/dev.json"
|
|
846
|
+
];
|
|
831
847
|
async function updateGitignore(root, shared, dryRun, quiet = false) {
|
|
832
848
|
const gitignorePath = path5.join(root, ".gitignore");
|
|
833
849
|
let content = await readFile(gitignorePath) ?? "";
|
|
@@ -864,11 +880,39 @@ async function cleanGitignore(root) {
|
|
|
864
880
|
const gitignorePath = path5.join(root, ".gitignore");
|
|
865
881
|
const content = await readFile(gitignorePath);
|
|
866
882
|
if (!content) return;
|
|
867
|
-
const cleaned = content.replace(/^# Inspecto\s*$/m, "").replace(/^\.inspecto\/?\s*$/gm, "").replace(/^\.inspecto\/install\.lock\s*$/gm, "").replace(/^\.inspecto\/cache\.json\s*$/gm, "").replace(/^\.inspecto\/\*\.local\.json\s*$/gm, "").replace(/\n{3,}/g, "\n\n");
|
|
883
|
+
const cleaned = content.replace(/^# Inspecto\s*$/m, "").replace(/^\.inspecto\/?\s*$/gm, "").replace(/^\.inspecto\/install\.lock\s*$/gm, "").replace(/^\.inspecto\/cache\.json\s*$/gm, "").replace(/^\.inspecto\/\*\.local\.json\s*$/gm, "").replace(/^\.inspecto\/dev\.json\s*$/gm, "").replace(/\n{3,}/g, "\n\n");
|
|
868
884
|
await writeFile(gitignorePath, cleaned);
|
|
869
885
|
}
|
|
870
886
|
|
|
871
887
|
// src/onboarding/apply.ts
|
|
888
|
+
function normalizeSupportedIde(ide) {
|
|
889
|
+
if (!ide) return void 0;
|
|
890
|
+
return ide.toLowerCase() === "vscode" ? "vscode" : ide.toLowerCase();
|
|
891
|
+
}
|
|
892
|
+
async function readInheritedSettingsDefaults(repoRoot, projectRoot, settingsFileName) {
|
|
893
|
+
if (path6.resolve(repoRoot) === path6.resolve(projectRoot)) {
|
|
894
|
+
return {};
|
|
895
|
+
}
|
|
896
|
+
const inheritedSettingsPath = path6.join(repoRoot, ".inspecto", settingsFileName);
|
|
897
|
+
if (!await exists(inheritedSettingsPath)) {
|
|
898
|
+
return {};
|
|
899
|
+
}
|
|
900
|
+
const inheritedSettings = await readJSON(inheritedSettingsPath);
|
|
901
|
+
if (!inheritedSettings || typeof inheritedSettings !== "object") {
|
|
902
|
+
return {};
|
|
903
|
+
}
|
|
904
|
+
const inheritedDefaults = {};
|
|
905
|
+
if (typeof inheritedSettings.ide === "string") {
|
|
906
|
+
const normalizedIde = normalizeSupportedIde(inheritedSettings.ide);
|
|
907
|
+
if (normalizedIde) {
|
|
908
|
+
inheritedDefaults.ide = normalizedIde;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (typeof inheritedSettings["provider.default"] === "string") {
|
|
912
|
+
inheritedDefaults.providerDefault = inheritedSettings["provider.default"];
|
|
913
|
+
}
|
|
914
|
+
return inheritedDefaults;
|
|
915
|
+
}
|
|
872
916
|
function shellQuote(value) {
|
|
873
917
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
874
918
|
}
|
|
@@ -892,11 +936,120 @@ function resolveRuntimePackages() {
|
|
|
892
936
|
function resultStatus(nextSteps) {
|
|
893
937
|
return nextSteps.length > 0 ? "warning" : "ok";
|
|
894
938
|
}
|
|
895
|
-
function manualPlanSteps(plan2) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
939
|
+
function manualPlanSteps(plan2, includeBlockers = true) {
|
|
940
|
+
const steps = plan2.actions.filter(
|
|
941
|
+
(action) => ["manual_step", "generate_patch_plan", "generate_file", "manual_confirmation"].includes(
|
|
942
|
+
action.type
|
|
943
|
+
)
|
|
944
|
+
).map((action) => action.description);
|
|
945
|
+
if (!includeBlockers) {
|
|
946
|
+
return steps;
|
|
947
|
+
}
|
|
948
|
+
return [...plan2.blockers.map((blocker) => blocker.message), ...steps];
|
|
949
|
+
}
|
|
950
|
+
function applyGuidedPatchContent(source, snippet) {
|
|
951
|
+
if (source.includes("import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'")) {
|
|
952
|
+
return source;
|
|
953
|
+
}
|
|
954
|
+
if (source.includes("import { vitePlugin as inspecto } from '@inspecto-dev/plugin'")) {
|
|
955
|
+
return source;
|
|
956
|
+
}
|
|
957
|
+
const trimmedSource = source.trimEnd();
|
|
958
|
+
if (/export\s+default\s*\{/m.test(source)) {
|
|
959
|
+
const nextSource = trimmedSource.replace(
|
|
960
|
+
/export\s+default\s*\{/m,
|
|
961
|
+
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\nexport default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },"
|
|
962
|
+
);
|
|
963
|
+
return `${nextSource}
|
|
964
|
+
`;
|
|
965
|
+
}
|
|
966
|
+
if (/module\.exports\s*=\s*\{/m.test(source)) {
|
|
967
|
+
const nextSource = trimmedSource.replace(
|
|
968
|
+
/module\.exports\s*=\s*\{/m,
|
|
969
|
+
"const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\nmodule.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },"
|
|
970
|
+
);
|
|
971
|
+
return `${nextSource}
|
|
972
|
+
`;
|
|
973
|
+
}
|
|
974
|
+
const objectExportVariableMatch = source.match(
|
|
975
|
+
/(const\s+([A-Za-z0-9_$]+)\s*(?::[^=]+)?=\s*\{[\s\S]*?\}\s*;?\s*)export\s+default\s+\2\s*;?/m
|
|
976
|
+
);
|
|
977
|
+
const variableDeclaration = objectExportVariableMatch?.[1];
|
|
978
|
+
const variableName = objectExportVariableMatch?.[2];
|
|
979
|
+
if (variableDeclaration && variableName) {
|
|
980
|
+
const nextDeclaration = variableDeclaration.replace(
|
|
981
|
+
/=\s*\{/m,
|
|
982
|
+
"= {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },"
|
|
983
|
+
);
|
|
984
|
+
const nextSource = source.replace(
|
|
985
|
+
objectExportVariableMatch[0],
|
|
986
|
+
`import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'
|
|
987
|
+
|
|
988
|
+
${nextDeclaration}export default ${variableName}`
|
|
989
|
+
);
|
|
990
|
+
return `${nextSource.trimEnd()}
|
|
991
|
+
`;
|
|
992
|
+
}
|
|
993
|
+
if (/defineNuxtConfig\s*\(\s*\{/m.test(source)) {
|
|
994
|
+
const trimmedSource2 = source.trimEnd();
|
|
995
|
+
const nextSource = trimmedSource2.replace(
|
|
996
|
+
/defineNuxtConfig\s*\(\s*\{/m,
|
|
997
|
+
"defineNuxtConfig({\n vite: {\n plugins: [inspecto()],\n },"
|
|
998
|
+
);
|
|
999
|
+
return `import { vitePlugin as inspecto } from '@inspecto-dev/plugin'
|
|
1000
|
+
|
|
1001
|
+
${nextSource}
|
|
1002
|
+
`;
|
|
1003
|
+
}
|
|
1004
|
+
const jsdocMatch = source.match(
|
|
1005
|
+
/\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{/m
|
|
1006
|
+
);
|
|
1007
|
+
if (jsdocMatch) {
|
|
1008
|
+
const isEsm = jsdocMatch[1] === "export default";
|
|
1009
|
+
const importStatement = isEsm ? "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\n" : "const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\n";
|
|
1010
|
+
const replacementPattern = isEsm ? /export\s+default\s*\{/m : /module\.exports\s*=\s*\{/m;
|
|
1011
|
+
const injectConfig = isEsm ? "export default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n }," : "module.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },";
|
|
1012
|
+
const nextSource = source.replace(replacementPattern, injectConfig);
|
|
1013
|
+
return `${importStatement}${nextSource.trimEnd()}
|
|
1014
|
+
`;
|
|
1015
|
+
}
|
|
1016
|
+
return `${trimmedSource}
|
|
1017
|
+
|
|
1018
|
+
${snippet}
|
|
1019
|
+
`;
|
|
1020
|
+
}
|
|
1021
|
+
async function applyGuidedPlanPatches(input, mutations, reporter) {
|
|
1022
|
+
const nextSteps = [];
|
|
1023
|
+
if (!input.plan || input.plan.strategy !== "guided" || !input.plan.patches?.length) {
|
|
1024
|
+
return nextSteps;
|
|
1025
|
+
}
|
|
1026
|
+
for (const patch of input.plan.patches) {
|
|
1027
|
+
if (patch.status !== "planned" || !patch.reason.startsWith("next_config_") && !patch.reason.startsWith("nuxt_config_")) {
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
const patchPath = path6.join(input.projectRoot, patch.path);
|
|
1031
|
+
const existingContent = await readFile(patchPath);
|
|
1032
|
+
if (existingContent === null) {
|
|
1033
|
+
nextSteps.push(`Could not auto-apply the guided patch for ${patch.path}.`);
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
const nextContent = applyGuidedPatchContent(existingContent, patch.snippet);
|
|
1037
|
+
if (nextContent === existingContent) {
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
if (input.options.dryRun) {
|
|
1041
|
+
reporter.dryRun(`Would apply guided patch to ${patch.path}`);
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
await writeFile(patchPath, nextContent);
|
|
1045
|
+
mutations.push({
|
|
1046
|
+
type: "file_modified",
|
|
1047
|
+
path: patch.path,
|
|
1048
|
+
description: "Automatically configured Inspecto guided Next.js patch"
|
|
1049
|
+
});
|
|
1050
|
+
reporter.success(`Applied guided patch to ${patch.path}`);
|
|
1051
|
+
}
|
|
1052
|
+
return nextSteps;
|
|
900
1053
|
}
|
|
901
1054
|
async function applyOnboardingPlan(input) {
|
|
902
1055
|
return applyOnboardingPlanInternal(input);
|
|
@@ -960,6 +1113,7 @@ function createSpinner(text, quiet = false) {
|
|
|
960
1113
|
}
|
|
961
1114
|
async function applyOnboardingPlanInternal(input) {
|
|
962
1115
|
const reporter = createReporter(input.options.quiet);
|
|
1116
|
+
const additiveManualPlan = (input.plan?.strategy === "manual" || input.plan?.strategy === "guided") && input.allowManualPlanApply && input.plan.blockers.length === 0;
|
|
963
1117
|
if (input.plan && input.plan.strategy !== "supported" && !input.allowManualPlanApply) {
|
|
964
1118
|
return {
|
|
965
1119
|
status: input.plan.status,
|
|
@@ -968,7 +1122,7 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
968
1122
|
installFailed: false,
|
|
969
1123
|
injectionFailed: false,
|
|
970
1124
|
manualExtensionInstallNeeded: false,
|
|
971
|
-
nextSteps: manualPlanSteps(input.plan)
|
|
1125
|
+
nextSteps: manualPlanSteps(input.plan, true)
|
|
972
1126
|
}
|
|
973
1127
|
};
|
|
974
1128
|
}
|
|
@@ -978,6 +1132,11 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
978
1132
|
const promptsFileName = input.options.shared ? "prompts.json" : "prompts.local.json";
|
|
979
1133
|
const settingsPath = path6.join(settingsDir, settingsFileName);
|
|
980
1134
|
const promptsPath = path6.join(settingsDir, promptsFileName);
|
|
1135
|
+
const inheritedDefaults = await readInheritedSettingsDefaults(
|
|
1136
|
+
input.repoRoot,
|
|
1137
|
+
input.projectRoot,
|
|
1138
|
+
settingsFileName
|
|
1139
|
+
);
|
|
981
1140
|
const runtimePackages = resolveRuntimePackages();
|
|
982
1141
|
const installCmd = getInstallCommand(input.packageManager, runtimePackages.installSpec);
|
|
983
1142
|
const nextSteps = [];
|
|
@@ -1003,6 +1162,11 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
1003
1162
|
spinner.fail("Dependency installation failed");
|
|
1004
1163
|
installFailed = true;
|
|
1005
1164
|
reporter.error(`Failed to install dependency: ${error?.message || "Unknown error"}`);
|
|
1165
|
+
if (error?.stderr) {
|
|
1166
|
+
reporter.error(`Details: ${error.stderr}`);
|
|
1167
|
+
} else if (error?.stdout) {
|
|
1168
|
+
reporter.error(`Details: ${error.stdout}`);
|
|
1169
|
+
}
|
|
1006
1170
|
reporter.hint(`Run manually in ${input.projectRoot}: ${installCmd}`);
|
|
1007
1171
|
reporter.hint(
|
|
1008
1172
|
"Setup will continue without dependencies, but Inspecto may not run until installation succeeds."
|
|
@@ -1010,19 +1174,24 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
1010
1174
|
}
|
|
1011
1175
|
}
|
|
1012
1176
|
let injectionFailed = Boolean(input.injectionSkippedRequiresManualConfig);
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1177
|
+
if (!additiveManualPlan) {
|
|
1178
|
+
for (const target of input.supportedBuildTargets) {
|
|
1179
|
+
const result = await injectPlugin(
|
|
1180
|
+
input.repoRoot,
|
|
1181
|
+
target,
|
|
1182
|
+
input.options.dryRun,
|
|
1183
|
+
input.options.quiet ?? false
|
|
1184
|
+
);
|
|
1185
|
+
if (result.success) {
|
|
1186
|
+
mutations.push(...result.mutations);
|
|
1187
|
+
} else {
|
|
1188
|
+
injectionFailed = true;
|
|
1189
|
+
}
|
|
1024
1190
|
}
|
|
1025
1191
|
}
|
|
1192
|
+
if (additiveManualPlan) {
|
|
1193
|
+
nextSteps.push(...await applyGuidedPlanPatches(input, mutations, reporter));
|
|
1194
|
+
}
|
|
1026
1195
|
if (await exists(settingsPath)) {
|
|
1027
1196
|
const existingSettings = await readJSON(settingsPath);
|
|
1028
1197
|
if (existingSettings === null) {
|
|
@@ -1030,15 +1199,43 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
1030
1199
|
reporter.hint("Please fix the syntax errors manually, or delete it and re-run init");
|
|
1031
1200
|
nextSteps.push(`Fix .inspecto/${settingsFileName} or delete it and rerun Inspecto setup.`);
|
|
1032
1201
|
} else {
|
|
1033
|
-
|
|
1202
|
+
const mergedSettings = existingSettings && typeof existingSettings === "object" ? { ...existingSettings } : {};
|
|
1203
|
+
let settingsChanged = false;
|
|
1204
|
+
const desiredIde = inheritedDefaults.ide ?? (input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : void 0);
|
|
1205
|
+
if (desiredIde && !mergedSettings.ide) {
|
|
1206
|
+
mergedSettings.ide = desiredIde;
|
|
1207
|
+
settingsChanged = true;
|
|
1208
|
+
}
|
|
1209
|
+
const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault;
|
|
1210
|
+
if (desiredProviderDefault && !mergedSettings["provider.default"]) {
|
|
1211
|
+
mergedSettings["provider.default"] = desiredProviderDefault;
|
|
1212
|
+
settingsChanged = true;
|
|
1213
|
+
}
|
|
1214
|
+
if (settingsChanged) {
|
|
1215
|
+
if (input.options.dryRun) {
|
|
1216
|
+
reporter.dryRun(`Would update .inspecto/${settingsFileName}`);
|
|
1217
|
+
} else {
|
|
1218
|
+
await writeJSON(settingsPath, mergedSettings);
|
|
1219
|
+
reporter.success(`Updated .inspecto/${settingsFileName} with missing defaults`);
|
|
1220
|
+
mutations.push({
|
|
1221
|
+
type: "file_modified",
|
|
1222
|
+
path: `.inspecto/${settingsFileName}`,
|
|
1223
|
+
description: "Merged missing Inspecto defaults into existing settings"
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
} else {
|
|
1227
|
+
reporter.success(`.inspecto/${settingsFileName} already exists (skipped)`);
|
|
1228
|
+
}
|
|
1034
1229
|
}
|
|
1035
1230
|
} else {
|
|
1036
1231
|
const defaultSettings = {};
|
|
1037
|
-
|
|
1038
|
-
|
|
1232
|
+
const desiredIde = inheritedDefaults.ide ?? (input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : void 0);
|
|
1233
|
+
const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault;
|
|
1234
|
+
if (desiredIde) {
|
|
1235
|
+
defaultSettings.ide = desiredIde;
|
|
1039
1236
|
}
|
|
1040
|
-
if (
|
|
1041
|
-
defaultSettings["provider.default"] =
|
|
1237
|
+
if (desiredProviderDefault) {
|
|
1238
|
+
defaultSettings["provider.default"] = desiredProviderDefault;
|
|
1042
1239
|
}
|
|
1043
1240
|
if (input.options.dryRun) {
|
|
1044
1241
|
reporter.dryRun(`Would create .inspecto/${settingsFileName}`);
|
|
@@ -1117,6 +1314,9 @@ async function applyOnboardingPlanInternal(input) {
|
|
|
1117
1314
|
if (manualExtensionInstallNeeded) {
|
|
1118
1315
|
nextSteps.push("Install the Inspecto IDE extension manually");
|
|
1119
1316
|
}
|
|
1317
|
+
if (additiveManualPlan && input.plan) {
|
|
1318
|
+
nextSteps.push(...manualPlanSteps(input.plan, false));
|
|
1319
|
+
}
|
|
1120
1320
|
if (input.manualConfigRequiredFor === "Nuxt") {
|
|
1121
1321
|
nextSteps.push(
|
|
1122
1322
|
"Nuxt detected\u2014please follow the Nuxt instructions printed above to finish setup."
|
|
@@ -1167,6 +1367,28 @@ async function getResolvedPackageVersion(pkgName, root) {
|
|
|
1167
1367
|
return null;
|
|
1168
1368
|
}
|
|
1169
1369
|
}
|
|
1370
|
+
function parseFirstSemver(version) {
|
|
1371
|
+
if (!version) return null;
|
|
1372
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
1373
|
+
if (!match) {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
major: Number(match[1]),
|
|
1378
|
+
minor: Number(match[2]),
|
|
1379
|
+
patch: Number(match[3])
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function isLegacyRspackVersion(version) {
|
|
1383
|
+
const parsed = parseFirstSemver(version);
|
|
1384
|
+
if (!parsed) return false;
|
|
1385
|
+
return parsed.major === 0 && parsed.minor < 4;
|
|
1386
|
+
}
|
|
1387
|
+
function isLegacyWebpackVersion(version) {
|
|
1388
|
+
const parsed = parseFirstSemver(version);
|
|
1389
|
+
if (!parsed) return false;
|
|
1390
|
+
return parsed.major === 4;
|
|
1391
|
+
}
|
|
1170
1392
|
var SUPPORTED_PATTERNS = [
|
|
1171
1393
|
{
|
|
1172
1394
|
tool: "vite",
|
|
@@ -1192,7 +1414,22 @@ var SUPPORTED_PATTERNS = [
|
|
|
1192
1414
|
},
|
|
1193
1415
|
{
|
|
1194
1416
|
tool: "webpack",
|
|
1195
|
-
files: [
|
|
1417
|
+
files: [
|
|
1418
|
+
"webpack.config.js",
|
|
1419
|
+
"webpack.config.ts",
|
|
1420
|
+
"webpack.config.mjs",
|
|
1421
|
+
"webpack.config.cjs",
|
|
1422
|
+
"webpack.config.common.js",
|
|
1423
|
+
"webpack.config.common.ts",
|
|
1424
|
+
"webpack.config.dev.js",
|
|
1425
|
+
"webpack.config.dev.ts",
|
|
1426
|
+
"webpack.config.prod.js",
|
|
1427
|
+
"webpack.config.prod.ts",
|
|
1428
|
+
"webpack.config.esbuild.js",
|
|
1429
|
+
"webpack.config.esbuild.ts",
|
|
1430
|
+
"webpack.config.build-pre.js",
|
|
1431
|
+
"webpack.config.build-pre.ts"
|
|
1432
|
+
],
|
|
1196
1433
|
label: "Webpack"
|
|
1197
1434
|
},
|
|
1198
1435
|
{
|
|
@@ -1207,6 +1444,11 @@ var SUPPORTED_PATTERNS = [
|
|
|
1207
1444
|
}
|
|
1208
1445
|
];
|
|
1209
1446
|
var UNSUPPORTED_META = [
|
|
1447
|
+
{
|
|
1448
|
+
name: "Umi",
|
|
1449
|
+
dep: "umi",
|
|
1450
|
+
files: [".umirc.ts", ".umirc.js", "config/config.ts", "config/config.js"]
|
|
1451
|
+
},
|
|
1210
1452
|
{ name: "Next.js", dep: "next", files: ["next.config.mjs", "next.config.js", "next.config.ts"] },
|
|
1211
1453
|
{ name: "Nuxt", dep: "nuxt", files: ["nuxt.config.ts", "nuxt.config.js"] },
|
|
1212
1454
|
{ name: "Remix", dep: "@remix-run/dev", files: ["remix.config.js", "remix.config.ts"] },
|
|
@@ -1320,7 +1562,8 @@ async function detectBuildTools(root, packagePaths) {
|
|
|
1320
1562
|
}
|
|
1321
1563
|
}
|
|
1322
1564
|
const unsupportedChecks = UNSUPPORTED_META.map(async (meta) => {
|
|
1323
|
-
|
|
1565
|
+
const hasDep = meta.dep in allDeps || Object.keys(allDeps).some((dep) => dep.includes(meta.dep));
|
|
1566
|
+
if (!hasDep) return null;
|
|
1324
1567
|
for (const file of meta.files) {
|
|
1325
1568
|
if (await exists(path7.join(target.absolutePath, file))) {
|
|
1326
1569
|
return meta.name;
|
|
@@ -1337,6 +1580,89 @@ async function detectBuildTools(root, packagePaths) {
|
|
|
1337
1580
|
}
|
|
1338
1581
|
return { supported, unsupported: Array.from(unsupported) };
|
|
1339
1582
|
}
|
|
1583
|
+
function rankScriptCommand(name, command) {
|
|
1584
|
+
const haystack = `${name} ${command}`.toLowerCase();
|
|
1585
|
+
let score = 0;
|
|
1586
|
+
if (/(^|[\s:_-])(start|dev|serve|watch)([\s:_-]|$)/.test(haystack)) score += 8;
|
|
1587
|
+
if (/(^|[\s:_-])(prod|build|release|stats)([\s:_-]|$)/.test(haystack)) score -= 3;
|
|
1588
|
+
if (/(^|[\s:_-])(dll|vendor)([\s:_-]|$)/.test(haystack)) score -= 6;
|
|
1589
|
+
if (haystack.includes("webpack-dev-server")) score += 3;
|
|
1590
|
+
if (haystack.includes("webpack")) score += 1;
|
|
1591
|
+
if (haystack.includes("rspack")) score += 1;
|
|
1592
|
+
return score;
|
|
1593
|
+
}
|
|
1594
|
+
function extractConfigArgs(scriptContent) {
|
|
1595
|
+
return Array.from(scriptContent.matchAll(/(?:-c|--config)\s+([^\s'"`;]+)/g)).map((match) => match[1]).filter((value) => Boolean(value));
|
|
1596
|
+
}
|
|
1597
|
+
async function resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate) {
|
|
1598
|
+
const normalizedCandidate = candidate.replace(/^['"`]|['"`]$/g, "");
|
|
1599
|
+
const normalizedRelativeCandidate = path7.normalize(normalizedCandidate);
|
|
1600
|
+
const possiblePaths = [];
|
|
1601
|
+
if (normalizedRelativeCandidate.startsWith("..")) {
|
|
1602
|
+
possiblePaths.push(
|
|
1603
|
+
path7.normalize(path7.join(path7.dirname(scriptPath), normalizedRelativeCandidate))
|
|
1604
|
+
);
|
|
1605
|
+
} else {
|
|
1606
|
+
possiblePaths.push(normalizedRelativeCandidate);
|
|
1607
|
+
possiblePaths.push(
|
|
1608
|
+
path7.normalize(path7.join(path7.dirname(scriptPath), normalizedRelativeCandidate))
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
for (const possiblePath of possiblePaths) {
|
|
1612
|
+
if (await exists(path7.join(targetRoot, possiblePath))) {
|
|
1613
|
+
return possiblePath.split(path7.sep).join("/");
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
async function resolveRspackConfigFromScript(targetRoot, scriptPath) {
|
|
1619
|
+
const scriptContent = await readFile(path7.join(targetRoot, scriptPath));
|
|
1620
|
+
if (!scriptContent) {
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1623
|
+
for (const candidate of extractConfigArgs(scriptContent)) {
|
|
1624
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate);
|
|
1625
|
+
if (resolved) {
|
|
1626
|
+
return resolved;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
const matches = scriptContent.matchAll(
|
|
1630
|
+
/['"`]([^'"`\n]*rspack[^'"`\n]*config[^'"`\n]*\.(?:js|ts|mjs|cjs))['"`]/g
|
|
1631
|
+
);
|
|
1632
|
+
for (const match of matches) {
|
|
1633
|
+
const candidate = match[1];
|
|
1634
|
+
if (!candidate) continue;
|
|
1635
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate);
|
|
1636
|
+
if (resolved) {
|
|
1637
|
+
return resolved;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
async function resolveWebpackBaseConfigFromFile(targetRoot, configPath) {
|
|
1643
|
+
const configContent = await readFile(path7.join(targetRoot, configPath));
|
|
1644
|
+
if (!configContent) {
|
|
1645
|
+
return null;
|
|
1646
|
+
}
|
|
1647
|
+
for (const candidate of extractConfigArgs(configContent)) {
|
|
1648
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, configPath, candidate);
|
|
1649
|
+
if (resolved) {
|
|
1650
|
+
return resolved;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
const matches = configContent.matchAll(
|
|
1654
|
+
/(?:configPath\s*=\s*|require\()\s*['"`]([^'"`\n]*webpack[^'"`\n]*config[^'"`\n]*\.(?:js|ts|mjs|cjs))['"`]/g
|
|
1655
|
+
);
|
|
1656
|
+
for (const match of matches) {
|
|
1657
|
+
const candidate = match[1];
|
|
1658
|
+
if (!candidate) continue;
|
|
1659
|
+
const resolved = await resolveScriptRelativeCandidate(targetRoot, configPath, candidate);
|
|
1660
|
+
if (resolved) {
|
|
1661
|
+
return resolved;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1340
1666
|
async function detectPattern({
|
|
1341
1667
|
pattern,
|
|
1342
1668
|
workspaceRoot,
|
|
@@ -1361,6 +1687,8 @@ async function detectPattern({
|
|
|
1361
1687
|
}
|
|
1362
1688
|
} else if (pattern.tool === "rsbuild") {
|
|
1363
1689
|
hasDep = !!allDeps["@rsbuild/core"] || isPackageResolvable("@rsbuild/core", targetRoot);
|
|
1690
|
+
} else if (pattern.tool === "vite") {
|
|
1691
|
+
hasDep = !!allDeps["vite"] || isPackageResolvable("vite", targetRoot);
|
|
1364
1692
|
} else {
|
|
1365
1693
|
hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, targetRoot);
|
|
1366
1694
|
}
|
|
@@ -1376,13 +1704,38 @@ async function detectPattern({
|
|
|
1376
1704
|
}
|
|
1377
1705
|
}
|
|
1378
1706
|
if (hasDep && !detectedFile && (pattern.tool === "esbuild" || pattern.tool === "rollup" || pattern.tool === "webpack" || pattern.tool === "rspack" || pattern.tool === "rsbuild")) {
|
|
1379
|
-
|
|
1707
|
+
const rankedScripts = Object.entries(scripts).sort(
|
|
1708
|
+
([leftName, leftCommand], [rightName, rightCommand]) => rankScriptCommand(rightName, rightCommand) - rankScriptCommand(leftName, leftCommand)
|
|
1709
|
+
);
|
|
1710
|
+
for (const [, cmd] of rankedScripts) {
|
|
1711
|
+
if (pattern.tool === "webpack" || pattern.tool === "rspack") {
|
|
1712
|
+
for (const configArg of extractConfigArgs(cmd)) {
|
|
1713
|
+
const resolvedConfig = await resolveScriptRelativeCandidate(targetRoot, "", configArg);
|
|
1714
|
+
if (resolvedConfig && (cmd.includes(pattern.tool) || cmd.includes(`${pattern.tool}-`))) {
|
|
1715
|
+
if (pattern.tool === "webpack") {
|
|
1716
|
+
detectedFile = await resolveWebpackBaseConfigFromFile(targetRoot, resolvedConfig) ?? resolvedConfig;
|
|
1717
|
+
} else {
|
|
1718
|
+
detectedFile = await resolveRspackConfigFromScript(targetRoot, resolvedConfig) ?? resolvedConfig;
|
|
1719
|
+
}
|
|
1720
|
+
break;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
if (detectedFile) {
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1380
1727
|
if (cmd.includes("node ")) {
|
|
1381
1728
|
const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/);
|
|
1382
1729
|
if (match && match[1]) {
|
|
1383
1730
|
if (await exists(path7.join(targetRoot, match[1]))) {
|
|
1384
1731
|
if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
|
|
1385
|
-
|
|
1732
|
+
if (pattern.tool === "rspack") {
|
|
1733
|
+
detectedFile = await resolveRspackConfigFromScript(targetRoot, match[1]) ?? match[1];
|
|
1734
|
+
} else if (pattern.tool === "webpack") {
|
|
1735
|
+
detectedFile = await resolveWebpackBaseConfigFromFile(targetRoot, match[1]) ?? match[1];
|
|
1736
|
+
} else {
|
|
1737
|
+
detectedFile = match[1];
|
|
1738
|
+
}
|
|
1386
1739
|
break;
|
|
1387
1740
|
}
|
|
1388
1741
|
}
|
|
@@ -1411,7 +1764,7 @@ async function detectPattern({
|
|
|
1411
1764
|
tool: pattern.tool,
|
|
1412
1765
|
configPath: "package.json (dependency)",
|
|
1413
1766
|
label: `${pattern.label} (detected via dependency)`,
|
|
1414
|
-
packagePath
|
|
1767
|
+
...packagePath ? { packagePath } : {}
|
|
1415
1768
|
};
|
|
1416
1769
|
}
|
|
1417
1770
|
return null;
|
|
@@ -1419,13 +1772,11 @@ async function detectPattern({
|
|
|
1419
1772
|
let isLegacyRspack = false;
|
|
1420
1773
|
let isLegacyWebpack = false;
|
|
1421
1774
|
if (pattern.tool === "rspack") {
|
|
1422
|
-
|
|
1423
|
-
if (version && (version.includes("0.3.") || version.includes("0.2.") || version.includes("0.1."))) {
|
|
1775
|
+
if (isLegacyRspackVersion(resolvedVersion)) {
|
|
1424
1776
|
isLegacyRspack = true;
|
|
1425
1777
|
}
|
|
1426
1778
|
} else if (pattern.tool === "webpack") {
|
|
1427
|
-
|
|
1428
|
-
if (version && version.includes("^4") || version?.startsWith("4.")) {
|
|
1779
|
+
if (isLegacyWebpackVersion(resolvedVersion)) {
|
|
1429
1780
|
isLegacyWebpack = true;
|
|
1430
1781
|
}
|
|
1431
1782
|
}
|
|
@@ -1435,9 +1786,9 @@ async function detectPattern({
|
|
|
1435
1786
|
tool: pattern.tool,
|
|
1436
1787
|
configPath: relativeConfig,
|
|
1437
1788
|
label: `${pattern.label} (${relativeConfig})${isLegacyRspack ? " [Legacy]" : ""}${isLegacyWebpack ? " [Webpack 4]" : ""}${inferredFromScripts ? " [Scripts Detected]" : ""}`,
|
|
1438
|
-
isLegacyRspack,
|
|
1439
|
-
isLegacyWebpack,
|
|
1440
|
-
packagePath
|
|
1789
|
+
...isLegacyRspack ? { isLegacyRspack: true } : {},
|
|
1790
|
+
...isLegacyWebpack ? { isLegacyWebpack: true } : {},
|
|
1791
|
+
...packagePath ? { packagePath } : {}
|
|
1441
1792
|
};
|
|
1442
1793
|
}
|
|
1443
1794
|
function resolveInjectionTarget(detections) {
|
|
@@ -1467,9 +1818,12 @@ var SUPPORTED_FRAMEWORKS = [
|
|
|
1467
1818
|
var UNSUPPORTED_FRAMEWORKS = [
|
|
1468
1819
|
{ name: "Solid", dep: "solid-js" },
|
|
1469
1820
|
{ name: "Svelte", dep: "svelte" },
|
|
1821
|
+
{ name: "SvelteKit", dep: "@sveltejs/kit" },
|
|
1470
1822
|
{ name: "Angular", dep: "@angular/core" },
|
|
1471
1823
|
{ name: "Preact", dep: "preact" },
|
|
1472
|
-
{ name: "Lit", dep: "lit" }
|
|
1824
|
+
{ name: "Lit", dep: "lit" },
|
|
1825
|
+
{ name: "Qwik", dep: "qwik" },
|
|
1826
|
+
{ name: "Alpine", dep: "lit-html" }
|
|
1473
1827
|
];
|
|
1474
1828
|
function isPackageResolvable2(pkgName, root) {
|
|
1475
1829
|
try {
|
|
@@ -1685,6 +2039,418 @@ async function buildOnboardingContext(root) {
|
|
|
1685
2039
|
};
|
|
1686
2040
|
}
|
|
1687
2041
|
|
|
2042
|
+
// src/onboarding/nextjs-guidance.ts
|
|
2043
|
+
import fs3 from "fs";
|
|
2044
|
+
import path11 from "path";
|
|
2045
|
+
var NEXT_CONFIG_CANDIDATES = ["next.config.ts", "next.config.mjs", "next.config.js"];
|
|
2046
|
+
var APP_ROUTER_LAYOUTS = [
|
|
2047
|
+
"app/layout.tsx",
|
|
2048
|
+
"app/layout.jsx",
|
|
2049
|
+
"src/app/layout.tsx",
|
|
2050
|
+
"src/app/layout.jsx"
|
|
2051
|
+
];
|
|
2052
|
+
var PAGES_ROUTER_APPS = [
|
|
2053
|
+
"pages/_app.tsx",
|
|
2054
|
+
"pages/_app.jsx",
|
|
2055
|
+
"src/pages/_app.tsx",
|
|
2056
|
+
"src/pages/_app.jsx"
|
|
2057
|
+
];
|
|
2058
|
+
var PACKAGE_JSON_PATH = "package.json";
|
|
2059
|
+
function findFirstExisting(root, candidates) {
|
|
2060
|
+
for (const candidate of candidates) {
|
|
2061
|
+
if (fs3.existsSync(path11.join(root, candidate))) {
|
|
2062
|
+
return candidate;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return void 0;
|
|
2066
|
+
}
|
|
2067
|
+
function detectRouterMode(root) {
|
|
2068
|
+
const hasAppRouter = Boolean(findFirstExisting(root, APP_ROUTER_LAYOUTS));
|
|
2069
|
+
const hasPagesRouter = Boolean(findFirstExisting(root, PAGES_ROUTER_APPS));
|
|
2070
|
+
if (hasAppRouter && hasPagesRouter) return "mixed";
|
|
2071
|
+
if (hasAppRouter) return "app";
|
|
2072
|
+
if (hasPagesRouter) return "pages";
|
|
2073
|
+
return "unknown";
|
|
2074
|
+
}
|
|
2075
|
+
function detectNextConfigPath(root) {
|
|
2076
|
+
return findFirstExisting(root, NEXT_CONFIG_CANDIDATES);
|
|
2077
|
+
}
|
|
2078
|
+
function readConfig(root, relativePath) {
|
|
2079
|
+
if (!relativePath) return "";
|
|
2080
|
+
const filePath = path11.join(root, relativePath);
|
|
2081
|
+
if (!fs3.existsSync(filePath)) return "";
|
|
2082
|
+
return fs3.readFileSync(filePath, "utf8");
|
|
2083
|
+
}
|
|
2084
|
+
function detectPatchShape(source) {
|
|
2085
|
+
if (/export\s+default\s*\{[\s\S]*\}/m.test(source) || /module\.exports\s*=\s*\{[\s\S]*\}/m.test(source) || /const\s+[A-Za-z0-9_$]+\s*(?::[^=]+)?=\s*\{[\s\S]*\}\s*;?\s*export\s+default\s+[A-Za-z0-9_$]+\s*;?/m.test(
|
|
2086
|
+
source
|
|
2087
|
+
) || /\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{[\s\S]*\}/m.test(
|
|
2088
|
+
source
|
|
2089
|
+
)) {
|
|
2090
|
+
return {
|
|
2091
|
+
status: "planned",
|
|
2092
|
+
reason: "next_config_object_export",
|
|
2093
|
+
confidence: "high"
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
if (/module\.exports\s*=\s*[A-Za-z0-9_$]+\s*\(/m.test(source) || /export\s+default\s+[A-Za-z0-9_$]+\s*\(/m.test(source)) {
|
|
2097
|
+
return {
|
|
2098
|
+
status: "manual_patch_required",
|
|
2099
|
+
reason: "next_config_wrapped_export",
|
|
2100
|
+
confidence: "medium"
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
if (source.trim().length === 0) {
|
|
2104
|
+
return {
|
|
2105
|
+
status: "manual_patch_required",
|
|
2106
|
+
reason: "next_config_missing",
|
|
2107
|
+
confidence: "low"
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
return {
|
|
2111
|
+
status: "manual_patch_required",
|
|
2112
|
+
reason: "next_config_complex_shape",
|
|
2113
|
+
confidence: "medium"
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
function buildPatchSnippet() {
|
|
2117
|
+
return [
|
|
2118
|
+
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
2119
|
+
"",
|
|
2120
|
+
"webpack(config, { dev, isServer }) {",
|
|
2121
|
+
" if (dev) {",
|
|
2122
|
+
" config.plugins.push(inspecto())",
|
|
2123
|
+
" }",
|
|
2124
|
+
" return config",
|
|
2125
|
+
"}"
|
|
2126
|
+
].join("\n");
|
|
2127
|
+
}
|
|
2128
|
+
function buildAppRouterMountSnippet() {
|
|
2129
|
+
return [
|
|
2130
|
+
"import { useEffect } from 'react'",
|
|
2131
|
+
"",
|
|
2132
|
+
"function InspectoProvider() {",
|
|
2133
|
+
" useEffect(() => {",
|
|
2134
|
+
" if (process.env.NODE_ENV !== 'production') {",
|
|
2135
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
2136
|
+
" mountInspector()",
|
|
2137
|
+
" })",
|
|
2138
|
+
" }",
|
|
2139
|
+
" }, [])",
|
|
2140
|
+
"",
|
|
2141
|
+
" return null",
|
|
2142
|
+
"}",
|
|
2143
|
+
"",
|
|
2144
|
+
"Render <InspectoProvider /> from app/layout.tsx inside the <body> tree."
|
|
2145
|
+
].join("\n");
|
|
2146
|
+
}
|
|
2147
|
+
function buildPagesRouterMountSnippet() {
|
|
2148
|
+
return [
|
|
2149
|
+
"import { useEffect } from 'react'",
|
|
2150
|
+
"",
|
|
2151
|
+
"useEffect(() => {",
|
|
2152
|
+
" if (process.env.NODE_ENV !== 'production') {",
|
|
2153
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
2154
|
+
" mountInspector()",
|
|
2155
|
+
" })",
|
|
2156
|
+
" }",
|
|
2157
|
+
"}, [])",
|
|
2158
|
+
"",
|
|
2159
|
+
"Add this effect to pages/_app.tsx without changing the existing app wrapper behavior."
|
|
2160
|
+
].join("\n");
|
|
2161
|
+
}
|
|
2162
|
+
function detectWebpackDevScriptPatch(root) {
|
|
2163
|
+
const packageJsonSource = readConfig(root, PACKAGE_JSON_PATH);
|
|
2164
|
+
if (!packageJsonSource) {
|
|
2165
|
+
return void 0;
|
|
2166
|
+
}
|
|
2167
|
+
try {
|
|
2168
|
+
const packageJson = JSON.parse(packageJsonSource);
|
|
2169
|
+
const devScript = packageJson.scripts?.dev;
|
|
2170
|
+
if (!devScript || !/\bnext\s+dev\b/.test(devScript) || /--webpack\b/.test(devScript)) {
|
|
2171
|
+
return void 0;
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
path: PACKAGE_JSON_PATH,
|
|
2175
|
+
status: "manual_patch_required",
|
|
2176
|
+
reason: "next_dev_script_requires_webpack",
|
|
2177
|
+
confidence: "medium",
|
|
2178
|
+
snippet: '"dev": "next dev --webpack"'
|
|
2179
|
+
};
|
|
2180
|
+
} catch {
|
|
2181
|
+
return void 0;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
function buildPendingSteps(routerMode, configPath, needsWebpackDevScript) {
|
|
2185
|
+
const routerHint = routerMode === "app" ? "Complete the remaining client-side mount step for your App Router entry." : routerMode === "pages" ? "Complete the remaining client-side mount step for your Pages Router entry." : routerMode === "mixed" ? "Complete the remaining client-side mount step for the router entry your team actually uses in development." : "Complete the remaining client-side mount step in the appropriate Next.js entry file.";
|
|
2186
|
+
return [
|
|
2187
|
+
`Review the generated Next.js patch plan for ${configPath}.`,
|
|
2188
|
+
...needsWebpackDevScript ? ["Update the Next.js dev script to use webpack mode for Inspecto validation."] : [],
|
|
2189
|
+
"Keep the Inspecto webpack plugin enabled for both server and client development compilers in App Router projects.",
|
|
2190
|
+
routerHint
|
|
2191
|
+
];
|
|
2192
|
+
}
|
|
2193
|
+
function createNextJsGuidance(root) {
|
|
2194
|
+
const configPath = detectNextConfigPath(root) ?? "next.config.js";
|
|
2195
|
+
const configSource = readConfig(root, detectNextConfigPath(root));
|
|
2196
|
+
const patchShape = detectPatchShape(configSource);
|
|
2197
|
+
const routerMode = detectRouterMode(root);
|
|
2198
|
+
const webpackDevScriptPatch = detectWebpackDevScriptPatch(root);
|
|
2199
|
+
return {
|
|
2200
|
+
framework: "react",
|
|
2201
|
+
metaFramework: "Next.js",
|
|
2202
|
+
routerMode,
|
|
2203
|
+
autoApplied: ["dependencies", "inspecto_settings"],
|
|
2204
|
+
pendingSteps: buildPendingSteps(routerMode, configPath, Boolean(webpackDevScriptPatch)),
|
|
2205
|
+
assistantPrompt: "Complete the remaining Inspecto onboarding for this Next.js project. Use the generated patches directly, keep existing app behavior unchanged, avoid unrelated documentation searches unless a patch is insufficient, and finish the client-side mount step safely.",
|
|
2206
|
+
patches: [
|
|
2207
|
+
{
|
|
2208
|
+
path: configPath,
|
|
2209
|
+
status: patchShape.status,
|
|
2210
|
+
reason: patchShape.reason,
|
|
2211
|
+
confidence: patchShape.confidence,
|
|
2212
|
+
snippet: buildPatchSnippet()
|
|
2213
|
+
},
|
|
2214
|
+
...routerMode === "app" || routerMode === "mixed" ? [
|
|
2215
|
+
{
|
|
2216
|
+
path: findFirstExisting(root, APP_ROUTER_LAYOUTS) ?? "app/layout.tsx",
|
|
2217
|
+
status: "manual_patch_required",
|
|
2218
|
+
reason: "next_app_router_mount",
|
|
2219
|
+
confidence: "medium",
|
|
2220
|
+
snippet: buildAppRouterMountSnippet()
|
|
2221
|
+
}
|
|
2222
|
+
] : [],
|
|
2223
|
+
...routerMode === "pages" || routerMode === "mixed" ? [
|
|
2224
|
+
{
|
|
2225
|
+
path: findFirstExisting(root, PAGES_ROUTER_APPS) ?? "pages/_app.tsx",
|
|
2226
|
+
status: "manual_patch_required",
|
|
2227
|
+
reason: "next_pages_router_mount",
|
|
2228
|
+
confidence: "medium",
|
|
2229
|
+
snippet: buildPagesRouterMountSnippet()
|
|
2230
|
+
}
|
|
2231
|
+
] : [],
|
|
2232
|
+
...webpackDevScriptPatch ? [webpackDevScriptPatch] : []
|
|
2233
|
+
]
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/onboarding/nuxt-guidance.ts
|
|
2238
|
+
import fs4 from "fs";
|
|
2239
|
+
import path12 from "path";
|
|
2240
|
+
var NUXT_CONFIG_CANDIDATES = ["nuxt.config.ts", "nuxt.config.js"];
|
|
2241
|
+
function findFirstExisting2(root, candidates) {
|
|
2242
|
+
for (const candidate of candidates) {
|
|
2243
|
+
if (fs4.existsSync(path12.join(root, candidate))) {
|
|
2244
|
+
return candidate;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
return void 0;
|
|
2248
|
+
}
|
|
2249
|
+
function readConfig2(root, relativePath) {
|
|
2250
|
+
if (!relativePath) return "";
|
|
2251
|
+
const filePath = path12.join(root, relativePath);
|
|
2252
|
+
if (!fs4.existsSync(filePath)) return "";
|
|
2253
|
+
return fs4.readFileSync(filePath, "utf8");
|
|
2254
|
+
}
|
|
2255
|
+
function detectPatchShape2(source) {
|
|
2256
|
+
if (/with[A-Za-z0-9_$]*\s*\(\s*defineNuxtConfig/m.test(source)) {
|
|
2257
|
+
return {
|
|
2258
|
+
status: "manual_patch_required",
|
|
2259
|
+
reason: "nuxt_config_wrapped_export",
|
|
2260
|
+
confidence: "medium"
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
if (/defineNuxtConfig\s*\(\s*\{[\s\S]*\}\s*\)/m.test(source)) {
|
|
2264
|
+
return {
|
|
2265
|
+
status: "planned",
|
|
2266
|
+
reason: "nuxt_config_object_export",
|
|
2267
|
+
confidence: "high"
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
if (source.trim().length === 0) {
|
|
2271
|
+
return {
|
|
2272
|
+
status: "manual_patch_required",
|
|
2273
|
+
reason: "nuxt_config_missing",
|
|
2274
|
+
confidence: "low"
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
return {
|
|
2278
|
+
status: "manual_patch_required",
|
|
2279
|
+
reason: "nuxt_config_complex_shape",
|
|
2280
|
+
confidence: "medium"
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
function buildNuxtConfigSnippet() {
|
|
2284
|
+
return [
|
|
2285
|
+
"import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
2286
|
+
"",
|
|
2287
|
+
"export default defineNuxtConfig({",
|
|
2288
|
+
" vite: {",
|
|
2289
|
+
" plugins: [inspecto()],",
|
|
2290
|
+
" },",
|
|
2291
|
+
"})"
|
|
2292
|
+
].join("\n");
|
|
2293
|
+
}
|
|
2294
|
+
function buildNuxtPluginSnippet() {
|
|
2295
|
+
return [
|
|
2296
|
+
"export default defineNuxtPlugin(() => {",
|
|
2297
|
+
" if (import.meta.dev) {",
|
|
2298
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
2299
|
+
" mountInspector()",
|
|
2300
|
+
" })",
|
|
2301
|
+
" }",
|
|
2302
|
+
"})"
|
|
2303
|
+
].join("\n");
|
|
2304
|
+
}
|
|
2305
|
+
function createNuxtGuidance(root) {
|
|
2306
|
+
const configPath = findFirstExisting2(root, NUXT_CONFIG_CANDIDATES) ?? "nuxt.config.ts";
|
|
2307
|
+
const configSource = readConfig2(root, findFirstExisting2(root, NUXT_CONFIG_CANDIDATES));
|
|
2308
|
+
const patchShape = detectPatchShape2(configSource);
|
|
2309
|
+
const hasSrcDir = fs4.existsSync(path12.join(root, "src")) && fs4.statSync(path12.join(root, "src")).isDirectory();
|
|
2310
|
+
const pluginPath = hasSrcDir ? "src/plugins/inspecto.client.ts" : "plugins/inspecto.client.ts";
|
|
2311
|
+
return {
|
|
2312
|
+
framework: "vue",
|
|
2313
|
+
metaFramework: "Nuxt",
|
|
2314
|
+
autoApplied: ["dependencies", "inspecto_settings"],
|
|
2315
|
+
pendingSteps: [
|
|
2316
|
+
`Review the generated Nuxt patch plan for ${configPath}.`,
|
|
2317
|
+
`Complete the remaining Nuxt client plugin mount step in ${pluginPath}.`
|
|
2318
|
+
],
|
|
2319
|
+
assistantPrompt: "Complete the remaining Inspecto onboarding for this Nuxt project. Review the generated patch plan, keep existing app behavior unchanged, and finish the client plugin mount step safely.",
|
|
2320
|
+
patches: [
|
|
2321
|
+
{
|
|
2322
|
+
path: configPath,
|
|
2323
|
+
status: patchShape.status,
|
|
2324
|
+
reason: patchShape.reason,
|
|
2325
|
+
confidence: patchShape.confidence,
|
|
2326
|
+
snippet: buildNuxtConfigSnippet()
|
|
2327
|
+
},
|
|
2328
|
+
{
|
|
2329
|
+
path: pluginPath,
|
|
2330
|
+
status: "manual_patch_required",
|
|
2331
|
+
reason: "nuxt_client_plugin_mount",
|
|
2332
|
+
confidence: "medium",
|
|
2333
|
+
snippet: buildNuxtPluginSnippet()
|
|
2334
|
+
}
|
|
2335
|
+
]
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/onboarding/umi-guidance.ts
|
|
2340
|
+
import fs5 from "fs";
|
|
2341
|
+
import path13 from "path";
|
|
2342
|
+
var UMI_CONFIG_CANDIDATES = [
|
|
2343
|
+
".umirc.ts",
|
|
2344
|
+
".umirc.js",
|
|
2345
|
+
"config/config.ts",
|
|
2346
|
+
"config/config.js"
|
|
2347
|
+
];
|
|
2348
|
+
function findFirstExisting3(root, candidates) {
|
|
2349
|
+
for (const candidate of candidates) {
|
|
2350
|
+
if (fs5.existsSync(path13.join(root, candidate))) {
|
|
2351
|
+
return candidate;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return void 0;
|
|
2355
|
+
}
|
|
2356
|
+
function readConfig3(root, relativePath) {
|
|
2357
|
+
if (!relativePath) return "";
|
|
2358
|
+
const filePath = path13.join(root, relativePath);
|
|
2359
|
+
if (!fs5.existsSync(filePath)) return "";
|
|
2360
|
+
return fs5.readFileSync(filePath, "utf8");
|
|
2361
|
+
}
|
|
2362
|
+
function detectPatchShape3(source) {
|
|
2363
|
+
if (/defineConfig\s*\(\s*\{[\s\S]*\}\s*\)/m.test(source)) {
|
|
2364
|
+
return {
|
|
2365
|
+
status: "planned",
|
|
2366
|
+
reason: "umi_config_object_export",
|
|
2367
|
+
confidence: "high"
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
if (source.trim().length === 0) {
|
|
2371
|
+
return {
|
|
2372
|
+
status: "manual_patch_required",
|
|
2373
|
+
reason: "umi_config_missing",
|
|
2374
|
+
confidence: "low"
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
return {
|
|
2378
|
+
status: "manual_patch_required",
|
|
2379
|
+
reason: "umi_config_complex_shape",
|
|
2380
|
+
confidence: "medium"
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
function buildUmiConfigSnippet() {
|
|
2384
|
+
return [
|
|
2385
|
+
"import { defineConfig } from 'umi'",
|
|
2386
|
+
"import { webpack4Plugin } from '@inspecto-dev/plugin/legacy/webpack4'",
|
|
2387
|
+
"",
|
|
2388
|
+
"export default defineConfig({",
|
|
2389
|
+
" chainWebpack(memo) {",
|
|
2390
|
+
" if (process.env.NODE_ENV === 'development') {",
|
|
2391
|
+
" memo.plugin('inspecto').use(webpack4Plugin())",
|
|
2392
|
+
" }",
|
|
2393
|
+
" },",
|
|
2394
|
+
"})"
|
|
2395
|
+
].join("\n");
|
|
2396
|
+
}
|
|
2397
|
+
function buildUmiMountSnippet() {
|
|
2398
|
+
return [
|
|
2399
|
+
"import { useEffect } from 'react'",
|
|
2400
|
+
"",
|
|
2401
|
+
"export function rootContainer(container: React.ReactNode) {",
|
|
2402
|
+
" return (",
|
|
2403
|
+
" <InspectoWrapper>{container}</InspectoWrapper>",
|
|
2404
|
+
" )",
|
|
2405
|
+
"}",
|
|
2406
|
+
"",
|
|
2407
|
+
"function InspectoWrapper({ children }: { children: React.ReactNode }) {",
|
|
2408
|
+
" useEffect(() => {",
|
|
2409
|
+
" if (process.env.NODE_ENV !== 'production') {",
|
|
2410
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
2411
|
+
" mountInspector({",
|
|
2412
|
+
" serverUrl: 'http://127.0.0.1:' + ((window as any).__AI_INSPECTOR_PORT__ || 5678),",
|
|
2413
|
+
" })",
|
|
2414
|
+
" })",
|
|
2415
|
+
" }",
|
|
2416
|
+
" }, [])",
|
|
2417
|
+
"",
|
|
2418
|
+
" return <>{children}</>",
|
|
2419
|
+
"}"
|
|
2420
|
+
].join("\n");
|
|
2421
|
+
}
|
|
2422
|
+
function createUmiGuidance(root) {
|
|
2423
|
+
const configPath = findFirstExisting3(root, UMI_CONFIG_CANDIDATES) ?? ".umirc.ts";
|
|
2424
|
+
const configSource = readConfig3(root, findFirstExisting3(root, UMI_CONFIG_CANDIDATES));
|
|
2425
|
+
const patchShape = detectPatchShape3(configSource);
|
|
2426
|
+
return {
|
|
2427
|
+
framework: "react",
|
|
2428
|
+
metaFramework: "Umi",
|
|
2429
|
+
autoApplied: ["dependencies", "inspecto_settings"],
|
|
2430
|
+
pendingSteps: [
|
|
2431
|
+
`Review the generated Umi patch plan for ${configPath}.`,
|
|
2432
|
+
"Complete the remaining client-side mount step in src/app.tsx."
|
|
2433
|
+
],
|
|
2434
|
+
assistantPrompt: "Complete the remaining Inspecto onboarding for this Umi project. Review the generated patch plan, keep existing app behavior unchanged, and finish the client-side mount step safely.",
|
|
2435
|
+
patches: [
|
|
2436
|
+
{
|
|
2437
|
+
path: configPath,
|
|
2438
|
+
status: patchShape.status,
|
|
2439
|
+
reason: patchShape.reason,
|
|
2440
|
+
confidence: patchShape.confidence,
|
|
2441
|
+
snippet: buildUmiConfigSnippet()
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
path: "src/app.tsx",
|
|
2445
|
+
status: "manual_patch_required",
|
|
2446
|
+
reason: "umi_app_mount",
|
|
2447
|
+
confidence: "medium",
|
|
2448
|
+
snippet: buildUmiMountSnippet()
|
|
2449
|
+
}
|
|
2450
|
+
]
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
|
|
1688
2454
|
// src/onboarding/planner.ts
|
|
1689
2455
|
function message(code, message2) {
|
|
1690
2456
|
return { code, message: message2 };
|
|
@@ -1711,10 +2477,16 @@ function planStatus(warnings, blockers) {
|
|
|
1711
2477
|
function supportedIde(context) {
|
|
1712
2478
|
return context.ides.find((ide) => ide.supported)?.ide;
|
|
1713
2479
|
}
|
|
2480
|
+
function shouldInstallInspectoExtension(ide) {
|
|
2481
|
+
return Boolean(ide && isSupportedHostIde(ide));
|
|
2482
|
+
}
|
|
1714
2483
|
function supportedProvider(context) {
|
|
1715
2484
|
return context.providers.find((provider) => provider.supported)?.id;
|
|
1716
2485
|
}
|
|
1717
2486
|
function buildToolBlockers(context) {
|
|
2487
|
+
if (isGuidedMetaFrameworkScenario(context)) {
|
|
2488
|
+
return [];
|
|
2489
|
+
}
|
|
1718
2490
|
if (context.buildTools.unsupported.length > 0) {
|
|
1719
2491
|
return [
|
|
1720
2492
|
message(
|
|
@@ -1737,6 +2509,28 @@ function buildToolBlockers(context) {
|
|
|
1737
2509
|
}
|
|
1738
2510
|
return [message("missing-build-tool", "No supported build tool detected")];
|
|
1739
2511
|
}
|
|
2512
|
+
function buildToolWarnings(context) {
|
|
2513
|
+
const warnings = [];
|
|
2514
|
+
if (context.buildTools.supported.length === 1) {
|
|
2515
|
+
const buildTool = context.buildTools.supported[0];
|
|
2516
|
+
if (buildTool.tool === "rspack" && buildTool.isLegacyRspack) {
|
|
2517
|
+
warnings.push(
|
|
2518
|
+
message(
|
|
2519
|
+
"legacy-rspack-requires-manual-config",
|
|
2520
|
+
`Legacy Rspack detected at ${buildTool.configPath}. Inspecto must use the legacy Rspack plugin entry and manual config steps.`
|
|
2521
|
+
)
|
|
2522
|
+
);
|
|
2523
|
+
} else if (buildTool.tool === "webpack" && buildTool.isLegacyWebpack) {
|
|
2524
|
+
warnings.push(
|
|
2525
|
+
message(
|
|
2526
|
+
"legacy-webpack4-requires-manual-config",
|
|
2527
|
+
`Webpack 4 detected at ${buildTool.configPath}. Inspecto must use the legacy Webpack 4 plugin entry and manual config steps.`
|
|
2528
|
+
)
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
return warnings;
|
|
2533
|
+
}
|
|
1740
2534
|
function frameworkBlockers(context) {
|
|
1741
2535
|
if (context.frameworks.supported.length > 0) {
|
|
1742
2536
|
return [];
|
|
@@ -1798,6 +2592,35 @@ function manualBuildToolActions(context) {
|
|
|
1798
2592
|
}
|
|
1799
2593
|
];
|
|
1800
2594
|
}
|
|
2595
|
+
const buildTool = context.buildTools.supported[0];
|
|
2596
|
+
if (buildTool?.tool === "rspack" && buildTool.isLegacyRspack) {
|
|
2597
|
+
return [
|
|
2598
|
+
{
|
|
2599
|
+
type: "install_dependency",
|
|
2600
|
+
target: "@inspecto-dev/plugin @inspecto-dev/core",
|
|
2601
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`
|
|
2602
|
+
},
|
|
2603
|
+
{
|
|
2604
|
+
type: "manual_step",
|
|
2605
|
+
target: buildTool.configPath,
|
|
2606
|
+
description: `Update ${buildTool.configPath} to import \`rspackPlugin\` from \`@inspecto-dev/plugin/legacy/rspack\` and add it to the Rspack plugins array.`
|
|
2607
|
+
}
|
|
2608
|
+
];
|
|
2609
|
+
}
|
|
2610
|
+
if (buildTool?.tool === "webpack" && buildTool.isLegacyWebpack) {
|
|
2611
|
+
return [
|
|
2612
|
+
{
|
|
2613
|
+
type: "install_dependency",
|
|
2614
|
+
target: "@inspecto-dev/plugin @inspecto-dev/core",
|
|
2615
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`
|
|
2616
|
+
},
|
|
2617
|
+
{
|
|
2618
|
+
type: "manual_step",
|
|
2619
|
+
target: buildTool.configPath,
|
|
2620
|
+
description: `Update ${buildTool.configPath} to import \`webpackPlugin\` from \`@inspecto-dev/plugin/legacy/webpack4\` and add it to the Webpack plugins array.`
|
|
2621
|
+
}
|
|
2622
|
+
];
|
|
2623
|
+
}
|
|
1801
2624
|
return [
|
|
1802
2625
|
{
|
|
1803
2626
|
type: "manual_step",
|
|
@@ -1806,6 +2629,46 @@ function manualBuildToolActions(context) {
|
|
|
1806
2629
|
}
|
|
1807
2630
|
];
|
|
1808
2631
|
}
|
|
2632
|
+
function hasUnsupportedBuildTool(context, buildTool) {
|
|
2633
|
+
return context.buildTools.unsupported.includes(buildTool);
|
|
2634
|
+
}
|
|
2635
|
+
function isGuidedNextJsScenario(context) {
|
|
2636
|
+
return context.buildTools.supported.length === 0 && hasUnsupportedBuildTool(context, "Next.js") && context.frameworks.supported.includes("react");
|
|
2637
|
+
}
|
|
2638
|
+
function isGuidedNuxtScenario(context) {
|
|
2639
|
+
return context.buildTools.supported.length === 0 && hasUnsupportedBuildTool(context, "Nuxt") && context.frameworks.supported.includes("vue");
|
|
2640
|
+
}
|
|
2641
|
+
function isGuidedUmiScenario(context) {
|
|
2642
|
+
return hasUnsupportedBuildTool(context, "Umi") && context.frameworks.supported.includes("react");
|
|
2643
|
+
}
|
|
2644
|
+
function isGuidedMetaFrameworkScenario(context) {
|
|
2645
|
+
return isGuidedNextJsScenario(context) || isGuidedNuxtScenario(context) || isGuidedUmiScenario(context);
|
|
2646
|
+
}
|
|
2647
|
+
function guidedBuildToolWarnings(context, guidedBuildTool) {
|
|
2648
|
+
return context.buildTools.unsupported.filter((buildTool) => buildTool !== guidedBuildTool).map(
|
|
2649
|
+
(buildTool) => message(
|
|
2650
|
+
"additional-unsupported-build-tool",
|
|
2651
|
+
`Additional unsupported build tool also detected: ${buildTool}`
|
|
2652
|
+
)
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
function guidedFrameworkWarnings(context, framework) {
|
|
2656
|
+
return context.frameworks.unsupported.filter((item) => item !== framework).map(
|
|
2657
|
+
(item) => message(
|
|
2658
|
+
"additional-unsupported-framework",
|
|
2659
|
+
`Additional unsupported framework also detected: ${item}`
|
|
2660
|
+
)
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2663
|
+
function buildGuidedWarnings(context, guidedBuildTool, guidedFramework) {
|
|
2664
|
+
return uniqueMessages([
|
|
2665
|
+
...guidedBuildToolWarnings(context, guidedBuildTool),
|
|
2666
|
+
...guidedFrameworkWarnings(context, guidedFramework),
|
|
2667
|
+
...unsupportedEnvironmentWarnings(context)
|
|
2668
|
+
]).filter(
|
|
2669
|
+
(warning) => warning.message !== `Unsupported framework(s) also detected: ${context.frameworks.unsupported.join(", ")}`
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
1809
2672
|
function manualFrameworkActions(context) {
|
|
1810
2673
|
if (context.frameworks.unsupported.length > 0) {
|
|
1811
2674
|
return [
|
|
@@ -1826,7 +2689,10 @@ function manualFrameworkActions(context) {
|
|
|
1826
2689
|
}
|
|
1827
2690
|
async function createDetectionResult(root) {
|
|
1828
2691
|
const context = await buildOnboardingContext(root);
|
|
1829
|
-
const warnings = uniqueMessages([
|
|
2692
|
+
const warnings = uniqueMessages([
|
|
2693
|
+
...unsupportedEnvironmentWarnings(context),
|
|
2694
|
+
...buildToolWarnings(context)
|
|
2695
|
+
]);
|
|
1830
2696
|
const buildToolResult = buildToolBlockers(context);
|
|
1831
2697
|
const frameworkResult = frameworkBlockers(context);
|
|
1832
2698
|
const blockers = uniqueMessages([...buildToolResult, ...frameworkResult]);
|
|
@@ -1849,13 +2715,169 @@ async function createDetectionResult(root) {
|
|
|
1849
2715
|
};
|
|
1850
2716
|
}
|
|
1851
2717
|
function createPlanResult(context) {
|
|
1852
|
-
|
|
2718
|
+
if (isGuidedNextJsScenario(context)) {
|
|
2719
|
+
const ide2 = supportedIde(context);
|
|
2720
|
+
const provider2 = supportedProvider(context);
|
|
2721
|
+
const guidance = createNextJsGuidance(context.root);
|
|
2722
|
+
const actions2 = [
|
|
2723
|
+
{
|
|
2724
|
+
type: "install_dependency",
|
|
2725
|
+
target: "@inspecto-dev/plugin @inspecto-dev/core",
|
|
2726
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`
|
|
2727
|
+
}
|
|
2728
|
+
];
|
|
2729
|
+
if (shouldInstallInspectoExtension(ide2) && ide2) {
|
|
2730
|
+
actions2.push({
|
|
2731
|
+
type: "install_extension",
|
|
2732
|
+
target: ide2,
|
|
2733
|
+
description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
actions2.push(
|
|
2737
|
+
{
|
|
2738
|
+
type: "generate_patch_plan",
|
|
2739
|
+
target: "next.config",
|
|
2740
|
+
description: "Generate a guided patch plan for the Next.js Inspecto webpack integration."
|
|
2741
|
+
},
|
|
2742
|
+
{
|
|
2743
|
+
type: "manual_confirmation",
|
|
2744
|
+
target: context.root,
|
|
2745
|
+
description: "Complete the remaining client-side Inspecto mount step in your assistant or editor."
|
|
2746
|
+
}
|
|
2747
|
+
);
|
|
2748
|
+
const defaults2 = {
|
|
2749
|
+
shared: false,
|
|
2750
|
+
extension: shouldInstallInspectoExtension(ide2),
|
|
2751
|
+
...provider2 ? { provider: provider2 } : {},
|
|
2752
|
+
...ide2 ? { ide: ide2 } : {}
|
|
2753
|
+
};
|
|
2754
|
+
return {
|
|
2755
|
+
status: "warning",
|
|
2756
|
+
warnings: buildGuidedWarnings(context, "Next.js", "react"),
|
|
2757
|
+
blockers: [],
|
|
2758
|
+
strategy: "guided",
|
|
2759
|
+
actions: actions2,
|
|
2760
|
+
defaults: defaults2,
|
|
2761
|
+
framework: guidance.framework,
|
|
2762
|
+
metaFramework: guidance.metaFramework,
|
|
2763
|
+
routerMode: guidance.routerMode,
|
|
2764
|
+
autoApplied: guidance.autoApplied,
|
|
2765
|
+
pendingSteps: guidance.pendingSteps,
|
|
2766
|
+
assistantPrompt: guidance.assistantPrompt,
|
|
2767
|
+
patches: guidance.patches
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
if (isGuidedNuxtScenario(context)) {
|
|
2771
|
+
const ide2 = supportedIde(context);
|
|
2772
|
+
const provider2 = supportedProvider(context);
|
|
2773
|
+
const guidance = createNuxtGuidance(context.root);
|
|
2774
|
+
const actions2 = [
|
|
2775
|
+
{
|
|
2776
|
+
type: "install_dependency",
|
|
2777
|
+
target: "@inspecto-dev/plugin @inspecto-dev/core",
|
|
2778
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`
|
|
2779
|
+
}
|
|
2780
|
+
];
|
|
2781
|
+
if (shouldInstallInspectoExtension(ide2) && ide2) {
|
|
2782
|
+
actions2.push({
|
|
2783
|
+
type: "install_extension",
|
|
2784
|
+
target: ide2,
|
|
2785
|
+
description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
actions2.push(
|
|
2789
|
+
{
|
|
2790
|
+
type: "generate_patch_plan",
|
|
2791
|
+
target: "nuxt.config",
|
|
2792
|
+
description: "Generate a guided patch plan for the Nuxt Inspecto Vite integration."
|
|
2793
|
+
},
|
|
2794
|
+
{
|
|
2795
|
+
type: "manual_confirmation",
|
|
2796
|
+
target: context.root,
|
|
2797
|
+
description: "Complete the remaining Nuxt client plugin mount step in your assistant or editor."
|
|
2798
|
+
}
|
|
2799
|
+
);
|
|
2800
|
+
const defaults2 = {
|
|
2801
|
+
shared: false,
|
|
2802
|
+
extension: shouldInstallInspectoExtension(ide2),
|
|
2803
|
+
...provider2 ? { provider: provider2 } : {},
|
|
2804
|
+
...ide2 ? { ide: ide2 } : {}
|
|
2805
|
+
};
|
|
2806
|
+
return {
|
|
2807
|
+
status: "warning",
|
|
2808
|
+
warnings: buildGuidedWarnings(context, "Nuxt", "vue"),
|
|
2809
|
+
blockers: [],
|
|
2810
|
+
strategy: "guided",
|
|
2811
|
+
actions: actions2,
|
|
2812
|
+
defaults: defaults2,
|
|
2813
|
+
framework: guidance.framework,
|
|
2814
|
+
metaFramework: guidance.metaFramework,
|
|
2815
|
+
autoApplied: guidance.autoApplied,
|
|
2816
|
+
pendingSteps: guidance.pendingSteps,
|
|
2817
|
+
assistantPrompt: guidance.assistantPrompt,
|
|
2818
|
+
patches: guidance.patches
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
if (isGuidedUmiScenario(context)) {
|
|
2822
|
+
const ide2 = supportedIde(context);
|
|
2823
|
+
const provider2 = supportedProvider(context);
|
|
2824
|
+
const guidance = createUmiGuidance(context.root);
|
|
2825
|
+
const actions2 = [
|
|
2826
|
+
{
|
|
2827
|
+
type: "install_dependency",
|
|
2828
|
+
target: "@inspecto-dev/plugin @inspecto-dev/core",
|
|
2829
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`
|
|
2830
|
+
}
|
|
2831
|
+
];
|
|
2832
|
+
if (shouldInstallInspectoExtension(ide2) && ide2) {
|
|
2833
|
+
actions2.push({
|
|
2834
|
+
type: "install_extension",
|
|
2835
|
+
target: ide2,
|
|
2836
|
+
description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
actions2.push({
|
|
2840
|
+
type: "generate_patch_plan",
|
|
2841
|
+
target: "umi.config",
|
|
2842
|
+
description: "Generate a guided patch plan for the Umi Inspecto webpack integration."
|
|
2843
|
+
});
|
|
2844
|
+
const defaults2 = {
|
|
2845
|
+
shared: false,
|
|
2846
|
+
extension: shouldInstallInspectoExtension(ide2),
|
|
2847
|
+
...provider2 ? { provider: provider2 } : {},
|
|
2848
|
+
...ide2 ? { ide: ide2 } : {}
|
|
2849
|
+
};
|
|
2850
|
+
return {
|
|
2851
|
+
status: "warning",
|
|
2852
|
+
warnings: buildGuidedWarnings(context, "Umi", "react"),
|
|
2853
|
+
blockers: [],
|
|
2854
|
+
strategy: "guided",
|
|
2855
|
+
actions: actions2,
|
|
2856
|
+
defaults: defaults2,
|
|
2857
|
+
framework: guidance.framework,
|
|
2858
|
+
metaFramework: guidance.metaFramework,
|
|
2859
|
+
autoApplied: guidance.autoApplied,
|
|
2860
|
+
pendingSteps: guidance.pendingSteps,
|
|
2861
|
+
assistantPrompt: guidance.assistantPrompt,
|
|
2862
|
+
patches: guidance.patches
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
const warnings = uniqueMessages([
|
|
2866
|
+
...unsupportedEnvironmentWarnings(context),
|
|
2867
|
+
...buildToolWarnings(context)
|
|
2868
|
+
]);
|
|
1853
2869
|
const blockers = uniqueMessages([...buildToolBlockers(context), ...frameworkBlockers(context)]);
|
|
1854
2870
|
const actions = [];
|
|
1855
2871
|
let strategy = "supported";
|
|
1856
|
-
|
|
2872
|
+
const hasLegacyRspackManualPlan = context.buildTools.supported.length === 1 && context.buildTools.supported[0]?.tool === "rspack" && context.buildTools.supported[0]?.isLegacyRspack;
|
|
2873
|
+
const hasLegacyWebpackManualPlan = context.buildTools.supported.length === 1 && context.buildTools.supported[0]?.tool === "webpack" && context.buildTools.supported[0]?.isLegacyWebpack;
|
|
2874
|
+
if (blockers.length > 0 || hasLegacyRspackManualPlan || hasLegacyWebpackManualPlan) {
|
|
1857
2875
|
strategy = "manual";
|
|
1858
|
-
if (context.buildTools.unsupported.length > 0 || context.buildTools.supported.length === 0 || context.buildTools.supported.length > 1
|
|
2876
|
+
if (context.buildTools.unsupported.length > 0 || context.buildTools.supported.length === 0 || context.buildTools.supported.length > 1 || context.buildTools.supported.some(
|
|
2877
|
+
(buildTool) => buildTool.tool === "rspack" && buildTool.isLegacyRspack
|
|
2878
|
+
) || context.buildTools.supported.some(
|
|
2879
|
+
(buildTool) => buildTool.tool === "webpack" && buildTool.isLegacyWebpack
|
|
2880
|
+
)) {
|
|
1859
2881
|
actions.push(...manualBuildToolActions(context));
|
|
1860
2882
|
}
|
|
1861
2883
|
if (frameworkBlockers(context).length > 0) {
|
|
@@ -1875,23 +2897,23 @@ function createPlanResult(context) {
|
|
|
1875
2897
|
});
|
|
1876
2898
|
}
|
|
1877
2899
|
const ide2 = supportedIde(context);
|
|
1878
|
-
if (ide2
|
|
2900
|
+
if (shouldInstallInspectoExtension(ide2) && ide2) {
|
|
1879
2901
|
actions.push({
|
|
1880
2902
|
type: "install_extension",
|
|
1881
|
-
target:
|
|
1882
|
-
description:
|
|
2903
|
+
target: ide2,
|
|
2904
|
+
description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
|
|
1883
2905
|
});
|
|
1884
2906
|
}
|
|
1885
2907
|
}
|
|
2908
|
+
const ide = supportedIde(context);
|
|
1886
2909
|
const defaults = {
|
|
1887
2910
|
shared: false,
|
|
1888
|
-
extension:
|
|
2911
|
+
extension: shouldInstallInspectoExtension(ide)
|
|
1889
2912
|
};
|
|
1890
2913
|
const provider = supportedProvider(context);
|
|
1891
2914
|
if (provider) {
|
|
1892
2915
|
defaults.provider = provider;
|
|
1893
2916
|
}
|
|
1894
|
-
const ide = supportedIde(context);
|
|
1895
2917
|
if (ide) {
|
|
1896
2918
|
defaults.ide = ide;
|
|
1897
2919
|
}
|
|
@@ -1905,7 +2927,11 @@ function createPlanResult(context) {
|
|
|
1905
2927
|
};
|
|
1906
2928
|
}
|
|
1907
2929
|
function planManualFollowUp(result) {
|
|
1908
|
-
return result.actions.filter(
|
|
2930
|
+
return result.actions.filter(
|
|
2931
|
+
(action) => ["manual_step", "generate_patch_plan", "generate_file", "manual_confirmation"].includes(
|
|
2932
|
+
action.type
|
|
2933
|
+
)
|
|
2934
|
+
).map((action) => action.description);
|
|
1909
2935
|
}
|
|
1910
2936
|
|
|
1911
2937
|
// src/commands/apply.ts
|
|
@@ -2036,14 +3062,107 @@ async function detect(json = false) {
|
|
|
2036
3062
|
return writeCommandOutput(result, json, printDetectionResult);
|
|
2037
3063
|
}
|
|
2038
3064
|
|
|
3065
|
+
// src/commands/dev-config.ts
|
|
3066
|
+
import path14 from "path";
|
|
3067
|
+
var DEV_CONFIG_PATH = path14.join(".inspecto", "dev.json");
|
|
3068
|
+
function absoluteDevConfigPath(root) {
|
|
3069
|
+
return path14.join(root, DEV_CONFIG_PATH);
|
|
3070
|
+
}
|
|
3071
|
+
function printDevConfigResult(result) {
|
|
3072
|
+
log.header("Inspecto Dev");
|
|
3073
|
+
log.info(`Config: ${result.configPath}`);
|
|
3074
|
+
if (result.config.cliBin) {
|
|
3075
|
+
log.hint(`cliBin: ${result.config.cliBin}`);
|
|
3076
|
+
}
|
|
3077
|
+
if (result.config.devRepo) {
|
|
3078
|
+
log.hint(`devRepo: ${result.config.devRepo}`);
|
|
3079
|
+
}
|
|
3080
|
+
if (!result.config.cliBin && !result.config.devRepo) {
|
|
3081
|
+
log.hint("No local dev overrides are configured.");
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
async function readExistingConfig(root) {
|
|
3085
|
+
const configPath = absoluteDevConfigPath(root);
|
|
3086
|
+
const config = await readJSON(configPath);
|
|
3087
|
+
if (!config || typeof config !== "object") {
|
|
3088
|
+
return {};
|
|
3089
|
+
}
|
|
3090
|
+
return config;
|
|
3091
|
+
}
|
|
3092
|
+
async function devLink(options) {
|
|
3093
|
+
const root = process.cwd();
|
|
3094
|
+
const configPath = absoluteDevConfigPath(root);
|
|
3095
|
+
const existing = await readExistingConfig(root);
|
|
3096
|
+
const nextConfig = {
|
|
3097
|
+
...existing,
|
|
3098
|
+
...options.cliBin ? { cliBin: options.cliBin } : {},
|
|
3099
|
+
...options.devRepo ? { devRepo: options.devRepo } : {}
|
|
3100
|
+
};
|
|
3101
|
+
await writeJSON(configPath, nextConfig);
|
|
3102
|
+
await updateGitignore(root, false, false, true);
|
|
3103
|
+
return writeCommandOutput(
|
|
3104
|
+
{
|
|
3105
|
+
status: "ok",
|
|
3106
|
+
configPath,
|
|
3107
|
+
config: nextConfig
|
|
3108
|
+
},
|
|
3109
|
+
options.json ?? false,
|
|
3110
|
+
printDevConfigResult
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
async function devStatus(json = false) {
|
|
3114
|
+
const root = process.cwd();
|
|
3115
|
+
const configPath = absoluteDevConfigPath(root);
|
|
3116
|
+
const config = await readExistingConfig(root);
|
|
3117
|
+
return writeCommandOutput(
|
|
3118
|
+
{
|
|
3119
|
+
status: "ok",
|
|
3120
|
+
configPath,
|
|
3121
|
+
config
|
|
3122
|
+
},
|
|
3123
|
+
json,
|
|
3124
|
+
printDevConfigResult
|
|
3125
|
+
);
|
|
3126
|
+
}
|
|
3127
|
+
async function devUnlink(json = false) {
|
|
3128
|
+
const root = process.cwd();
|
|
3129
|
+
const configPath = absoluteDevConfigPath(root);
|
|
3130
|
+
await removeFile(configPath);
|
|
3131
|
+
return writeCommandOutput(
|
|
3132
|
+
{
|
|
3133
|
+
status: "ok",
|
|
3134
|
+
configPath,
|
|
3135
|
+
config: {}
|
|
3136
|
+
},
|
|
3137
|
+
json,
|
|
3138
|
+
printDevConfigResult
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
|
|
2039
3142
|
// src/commands/init.ts
|
|
2040
|
-
import
|
|
3143
|
+
import path15 from "path";
|
|
2041
3144
|
|
|
2042
3145
|
// src/onboarding/target-resolution.ts
|
|
3146
|
+
function buildCandidateId(candidate) {
|
|
3147
|
+
return [candidate.packagePath || ".", candidate.buildTool, candidate.configPath].join(":");
|
|
3148
|
+
}
|
|
2043
3149
|
function normalizePackagePath(packagePath) {
|
|
2044
3150
|
if (!packagePath || packagePath === ".") return "";
|
|
2045
3151
|
return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
2046
3152
|
}
|
|
3153
|
+
function normalizeTargetValue(target) {
|
|
3154
|
+
if (!target) return "";
|
|
3155
|
+
return target.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
3156
|
+
}
|
|
3157
|
+
function buildSelectionPurpose() {
|
|
3158
|
+
return "Choose the build target that runs your local development build so Inspecto can attach the right plugin and settings.";
|
|
3159
|
+
}
|
|
3160
|
+
function buildSelectionInstructions(hasCandidates) {
|
|
3161
|
+
if (!hasCandidates) {
|
|
3162
|
+
return "If auto-detection missed your build entrypoint, rerun with --target <configPath> using the config file or wrapper script your dev command actually starts.";
|
|
3163
|
+
}
|
|
3164
|
+
return "Rerun with --target <candidateId> using one returned candidateId. The CLI also accepts an exact configPath from the candidate list as a compatibility fallback.";
|
|
3165
|
+
}
|
|
2047
3166
|
function looksLikeAppPackage(packagePath) {
|
|
2048
3167
|
if (!packagePath) return true;
|
|
2049
3168
|
return /(^|\/)(app|apps|web|client|frontend|site)(\/|$)/i.test(packagePath);
|
|
@@ -2054,13 +3173,19 @@ function looksLikeAuxiliaryPackage(packagePath) {
|
|
|
2054
3173
|
function buildCandidates(input) {
|
|
2055
3174
|
return input.buildTools.map((buildTool) => {
|
|
2056
3175
|
const packagePath = normalizePackagePath(buildTool.packagePath);
|
|
2057
|
-
|
|
3176
|
+
const candidate = {
|
|
2058
3177
|
packagePath,
|
|
2059
3178
|
configPath: buildTool.configPath,
|
|
3179
|
+
label: buildTool.label,
|
|
2060
3180
|
buildTool: buildTool.tool,
|
|
3181
|
+
...buildTool.isLegacyRspack ? { isLegacyRspack: true } : {},
|
|
3182
|
+
...buildTool.isLegacyWebpack ? { isLegacyWebpack: true } : {},
|
|
2061
3183
|
frameworks: input.frameworkSupportByPackage[packagePath] ?? [],
|
|
2062
3184
|
automaticInjection: true
|
|
2063
3185
|
};
|
|
3186
|
+
candidate.id = buildCandidateId(candidate);
|
|
3187
|
+
candidate.candidateId = candidate.id;
|
|
3188
|
+
return candidate;
|
|
2064
3189
|
});
|
|
2065
3190
|
}
|
|
2066
3191
|
function rankCandidate(candidate) {
|
|
@@ -2083,18 +3208,52 @@ function resolveOnboardingTarget(input) {
|
|
|
2083
3208
|
return {
|
|
2084
3209
|
status: "needs_selection",
|
|
2085
3210
|
candidates,
|
|
2086
|
-
reason: "No supported targets were detected."
|
|
3211
|
+
reason: "No supported targets were detected.",
|
|
3212
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
3213
|
+
selectionInstructions: buildSelectionInstructions(false)
|
|
2087
3214
|
};
|
|
2088
3215
|
}
|
|
2089
3216
|
const explicitlySelected = normalizePackagePath(input.selectedPackagePath);
|
|
3217
|
+
const explicitlySelectedValue = normalizeTargetValue(input.selectedPackagePath);
|
|
2090
3218
|
if (input.selectedPackagePath !== void 0) {
|
|
2091
|
-
const
|
|
3219
|
+
const selectedById = candidates.find(
|
|
3220
|
+
(candidate) => candidate.id === input.selectedPackagePath || candidate.candidateId === input.selectedPackagePath
|
|
3221
|
+
);
|
|
3222
|
+
if (selectedById) {
|
|
3223
|
+
return {
|
|
3224
|
+
status: "resolved",
|
|
3225
|
+
selected: selectedById,
|
|
3226
|
+
candidates,
|
|
3227
|
+
reason: `Using the explicitly selected target: ${selectedById.configPath}.`,
|
|
3228
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
3229
|
+
selectionInstructions: buildSelectionInstructions(true)
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
const selectedByConfigPath = candidates.find(
|
|
3233
|
+
(candidate) => normalizeTargetValue(candidate.configPath) === explicitlySelectedValue
|
|
3234
|
+
);
|
|
3235
|
+
if (selectedByConfigPath) {
|
|
3236
|
+
return {
|
|
3237
|
+
status: "resolved",
|
|
3238
|
+
selected: selectedByConfigPath,
|
|
3239
|
+
candidates,
|
|
3240
|
+
reason: `Using the explicitly selected config path: ${selectedByConfigPath.configPath}.`,
|
|
3241
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
3242
|
+
selectionInstructions: buildSelectionInstructions(true)
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
const matchingPackageCandidates = candidates.filter(
|
|
3246
|
+
(candidate) => candidate.packagePath === explicitlySelected
|
|
3247
|
+
);
|
|
3248
|
+
const selected = matchingPackageCandidates.length === 1 ? matchingPackageCandidates[0] : void 0;
|
|
2092
3249
|
if (selected) {
|
|
2093
3250
|
return {
|
|
2094
3251
|
status: "resolved",
|
|
2095
3252
|
selected,
|
|
2096
3253
|
candidates,
|
|
2097
|
-
reason: `Using the explicitly selected target: ${selected.packagePath || "."}
|
|
3254
|
+
reason: `Using the explicitly selected target: ${selected.packagePath || "."}.`,
|
|
3255
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
3256
|
+
selectionInstructions: buildSelectionInstructions(true)
|
|
2098
3257
|
};
|
|
2099
3258
|
}
|
|
2100
3259
|
}
|
|
@@ -2103,7 +3262,9 @@ function resolveOnboardingTarget(input) {
|
|
|
2103
3262
|
status: "resolved",
|
|
2104
3263
|
selected: candidates[0],
|
|
2105
3264
|
candidates,
|
|
2106
|
-
reason: "Only one supported target was detected."
|
|
3265
|
+
reason: "Only one supported target was detected.",
|
|
3266
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
3267
|
+
selectionInstructions: buildSelectionInstructions(true)
|
|
2107
3268
|
};
|
|
2108
3269
|
}
|
|
2109
3270
|
const ranked = rankCandidates(candidates);
|
|
@@ -2111,14 +3272,18 @@ function resolveOnboardingTarget(input) {
|
|
|
2111
3272
|
return {
|
|
2112
3273
|
status: "needs_selection",
|
|
2113
3274
|
candidates,
|
|
2114
|
-
reason: "Multiple supported targets look equally plausible."
|
|
3275
|
+
reason: "Multiple supported targets look equally plausible.",
|
|
3276
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
3277
|
+
selectionInstructions: buildSelectionInstructions(true)
|
|
2115
3278
|
};
|
|
2116
3279
|
}
|
|
2117
3280
|
return {
|
|
2118
3281
|
status: "resolved",
|
|
2119
3282
|
selected: ranked[0].candidate,
|
|
2120
3283
|
candidates,
|
|
2121
|
-
reason: `Preselected ${ranked[0].candidate.packagePath || "."} because it has the strongest supported app signal
|
|
3284
|
+
reason: `Preselected ${ranked[0].candidate.packagePath || "."} because it has the strongest supported app signal.`,
|
|
3285
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
3286
|
+
selectionInstructions: buildSelectionInstructions(true)
|
|
2122
3287
|
};
|
|
2123
3288
|
}
|
|
2124
3289
|
|
|
@@ -2236,7 +3401,9 @@ async function promptUnsupportedFrameworkContinue() {
|
|
|
2236
3401
|
// src/instructions.ts
|
|
2237
3402
|
function printNuxtManualInstructions() {
|
|
2238
3403
|
log.blank();
|
|
2239
|
-
log.hint(
|
|
3404
|
+
log.hint(
|
|
3405
|
+
"Nuxt supports guided setup in the current version. Inspecto can prepare the config patch, but the client plugin mount step still needs review."
|
|
3406
|
+
);
|
|
2240
3407
|
log.hint("1. Update `nuxt.config.ts` to register the Inspecto Vite plugin:");
|
|
2241
3408
|
log.copyableCodeBlock([
|
|
2242
3409
|
"import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
@@ -2247,7 +3414,7 @@ function printNuxtManualInstructions() {
|
|
|
2247
3414
|
" },",
|
|
2248
3415
|
"})"
|
|
2249
3416
|
]);
|
|
2250
|
-
log.hint("2.
|
|
3417
|
+
log.hint("2. Complete the remaining client plugin mount step in `plugins/inspecto.client.ts`:");
|
|
2251
3418
|
log.copyableCodeBlock([
|
|
2252
3419
|
"export default defineNuxtPlugin(() => {",
|
|
2253
3420
|
" if (import.meta.dev) {",
|
|
@@ -2257,11 +3424,31 @@ function printNuxtManualInstructions() {
|
|
|
2257
3424
|
" }",
|
|
2258
3425
|
"})"
|
|
2259
3426
|
]);
|
|
2260
|
-
log.hint("3. Restart your Nuxt dev server after
|
|
3427
|
+
log.hint("3. Restart your Nuxt dev server after applying the guided patches.");
|
|
3428
|
+
}
|
|
3429
|
+
function printUmiManualInstructions() {
|
|
3430
|
+
log.blank();
|
|
3431
|
+
log.hint("Umi supports guided setup in the current version.");
|
|
3432
|
+
log.hint("1. Update `config/config.ts` or `.umirc.ts` to register the Inspecto webpack plugin:");
|
|
3433
|
+
log.copyableCodeBlock([
|
|
3434
|
+
"import { defineConfig } from 'umi'",
|
|
3435
|
+
"import { webpack4Plugin } from '@inspecto-dev/plugin/legacy/webpack4'",
|
|
3436
|
+
"",
|
|
3437
|
+
"export default defineConfig({",
|
|
3438
|
+
" chainWebpack(memo) {",
|
|
3439
|
+
" if (process.env.NODE_ENV === 'development') {",
|
|
3440
|
+
" memo.plugin('inspecto').use(webpack4Plugin())",
|
|
3441
|
+
" }",
|
|
3442
|
+
" },",
|
|
3443
|
+
"})"
|
|
3444
|
+
]);
|
|
3445
|
+
log.hint("2. Restart your Umi dev server after applying the guided patches.");
|
|
2261
3446
|
}
|
|
2262
3447
|
function printNextJsManualInstructions() {
|
|
2263
3448
|
log.blank();
|
|
2264
|
-
log.hint(
|
|
3449
|
+
log.hint(
|
|
3450
|
+
"Next.js supports guided setup in the current version. Inspecto can prepare the config patch, but the client-side mount step still needs review."
|
|
3451
|
+
);
|
|
2265
3452
|
log.hint("1. Update `next.config.mjs` to register the Inspecto webpack plugin:");
|
|
2266
3453
|
log.copyableCodeBlock([
|
|
2267
3454
|
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
@@ -2269,7 +3456,7 @@ function printNextJsManualInstructions() {
|
|
|
2269
3456
|
"/** @type {import('next').NextConfig} */",
|
|
2270
3457
|
"const nextConfig = {",
|
|
2271
3458
|
" webpack: (config, { dev, isServer }) => {",
|
|
2272
|
-
" if (dev
|
|
3459
|
+
" if (dev) {",
|
|
2273
3460
|
" config.plugins.push(inspecto())",
|
|
2274
3461
|
" }",
|
|
2275
3462
|
" return config",
|
|
@@ -2279,7 +3466,10 @@ function printNextJsManualInstructions() {
|
|
|
2279
3466
|
"export default nextConfig"
|
|
2280
3467
|
]);
|
|
2281
3468
|
log.hint(
|
|
2282
|
-
"
|
|
3469
|
+
"Keep the plugin enabled for both server and client development compilers so App Router server components also receive Inspecto transforms."
|
|
3470
|
+
);
|
|
3471
|
+
log.hint(
|
|
3472
|
+
"2. Complete the remaining client-side mount step in `app/layout.tsx` or `pages/_app.tsx`:"
|
|
2283
3473
|
);
|
|
2284
3474
|
log.copyableCodeBlock([
|
|
2285
3475
|
"'use client'",
|
|
@@ -2298,7 +3488,7 @@ function printNextJsManualInstructions() {
|
|
|
2298
3488
|
" return <html><body>{children}</body></html>",
|
|
2299
3489
|
"}"
|
|
2300
3490
|
]);
|
|
2301
|
-
log.hint("3. Restart your Next.js dev server after
|
|
3491
|
+
log.hint("3. Restart your Next.js dev server after applying the guided patches.");
|
|
2302
3492
|
}
|
|
2303
3493
|
|
|
2304
3494
|
// src/commands/init.ts
|
|
@@ -2313,7 +3503,7 @@ async function init(options) {
|
|
|
2313
3503
|
verifiedPackages.push(pkg);
|
|
2314
3504
|
continue;
|
|
2315
3505
|
}
|
|
2316
|
-
const absolutePath =
|
|
3506
|
+
const absolutePath = path15.join(repoRoot, pkg);
|
|
2317
3507
|
if (await exists(absolutePath)) {
|
|
2318
3508
|
verifiedPackages.push(pkg);
|
|
2319
3509
|
} else {
|
|
@@ -2326,7 +3516,7 @@ async function init(options) {
|
|
|
2326
3516
|
return;
|
|
2327
3517
|
}
|
|
2328
3518
|
log.header("Inspecto Setup");
|
|
2329
|
-
if (!await exists(
|
|
3519
|
+
if (!await exists(path15.join(repoRoot, "package.json"))) {
|
|
2330
3520
|
log.error("No package.json found in current directory");
|
|
2331
3521
|
log.hint("Run this command from your project root");
|
|
2332
3522
|
return;
|
|
@@ -2361,12 +3551,12 @@ async function init(options) {
|
|
|
2361
3551
|
log.hint("Run `inspecto init` inside the target app, or pass --packages <app-path>.");
|
|
2362
3552
|
return;
|
|
2363
3553
|
}
|
|
2364
|
-
projectRoot =
|
|
3554
|
+
projectRoot = path15.join(repoRoot, selectedPackage);
|
|
2365
3555
|
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
2366
3556
|
log.info(`Continuing initialization in ${selectedPackage}`);
|
|
2367
3557
|
} else if (targetResolution.selected?.packagePath) {
|
|
2368
3558
|
const selectedPackage = targetResolution.selected.packagePath;
|
|
2369
|
-
projectRoot =
|
|
3559
|
+
projectRoot = path15.join(repoRoot, selectedPackage);
|
|
2370
3560
|
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
2371
3561
|
log.warn(`Monorepo root detected. Using the only candidate app: ${selectedPackage}`);
|
|
2372
3562
|
log.hint("Run `inspecto init` inside that app next time to skip this prompt.");
|
|
@@ -2426,14 +3616,17 @@ async function init(options) {
|
|
|
2426
3616
|
if (buildResult.unsupported.length > 0) {
|
|
2427
3617
|
const names = buildResult.unsupported.join(", ");
|
|
2428
3618
|
manualConfigRequiredFor = buildResult.unsupported[0] || "";
|
|
2429
|
-
log.warn(`Detected ${names} \u2014
|
|
2430
|
-
log.hint("
|
|
3619
|
+
log.warn(`Detected ${names} \u2014 guided onboarding is available in the current version`);
|
|
3620
|
+
log.hint("Inspecto can prepare the remaining patch plan and assistant handoff for this stack.");
|
|
2431
3621
|
if (buildResult.unsupported.includes("Next.js")) {
|
|
2432
3622
|
printNextJsManualInstructions();
|
|
2433
3623
|
}
|
|
2434
3624
|
if (buildResult.unsupported.includes("Nuxt")) {
|
|
2435
3625
|
printNuxtManualInstructions();
|
|
2436
3626
|
}
|
|
3627
|
+
if (buildResult.unsupported.includes("Umi")) {
|
|
3628
|
+
printUmiManualInstructions();
|
|
3629
|
+
}
|
|
2437
3630
|
}
|
|
2438
3631
|
if (buildResult.supported.length === 0 && buildResult.unsupported.length === 0) {
|
|
2439
3632
|
log.warn("No recognized build tool detected");
|
|
@@ -2585,7 +3778,7 @@ async function detectFrameworkSupportByPackage(repoRoot, buildTools) {
|
|
|
2585
3778
|
const supportByPackage = {};
|
|
2586
3779
|
await Promise.all(
|
|
2587
3780
|
packagePaths.map(async (packagePath) => {
|
|
2588
|
-
const result = await detectFrameworks(
|
|
3781
|
+
const result = await detectFrameworks(path15.join(repoRoot, packagePath));
|
|
2589
3782
|
supportByPackage[packagePath] = result.supported;
|
|
2590
3783
|
})
|
|
2591
3784
|
);
|
|
@@ -2593,7 +3786,7 @@ async function detectFrameworkSupportByPackage(repoRoot, buildTools) {
|
|
|
2593
3786
|
}
|
|
2594
3787
|
|
|
2595
3788
|
// src/commands/doctor.ts
|
|
2596
|
-
import
|
|
3789
|
+
import path16 from "path";
|
|
2597
3790
|
function createDiagnostic(code, status, message2, hints = [], details) {
|
|
2598
3791
|
return {
|
|
2599
3792
|
code,
|
|
@@ -2608,6 +3801,94 @@ function doctorStatus(errors, warnings) {
|
|
|
2608
3801
|
if (warnings > 0) return "warning";
|
|
2609
3802
|
return "ok";
|
|
2610
3803
|
}
|
|
3804
|
+
function isGuidedMetaFramework(buildTool) {
|
|
3805
|
+
return buildTool === "Next.js" || buildTool === "Nuxt";
|
|
3806
|
+
}
|
|
3807
|
+
function buildDoctorOnboardingContext(input) {
|
|
3808
|
+
return {
|
|
3809
|
+
root: input.root,
|
|
3810
|
+
packageManager: input.packageManager,
|
|
3811
|
+
buildTools: input.buildTools,
|
|
3812
|
+
frameworks: {
|
|
3813
|
+
supported: input.frameworks.supported,
|
|
3814
|
+
unsupported: input.frameworks.unsupported.map((item) => item.name)
|
|
3815
|
+
},
|
|
3816
|
+
ides: input.ides.detected.map(({ ide, supported }) => ({ ide, supported })),
|
|
3817
|
+
providers: input.providers.detected.map(({ id, label, supported, preferredMode }) => ({
|
|
3818
|
+
id,
|
|
3819
|
+
label,
|
|
3820
|
+
supported,
|
|
3821
|
+
preferredMode
|
|
3822
|
+
}))
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
function isConfigPatchReason(reason) {
|
|
3826
|
+
return reason.startsWith("next_config_") || reason.startsWith("nuxt_config_");
|
|
3827
|
+
}
|
|
3828
|
+
function isNextWebpackDevPatchReason(reason) {
|
|
3829
|
+
return reason === "next_dev_script_requires_webpack";
|
|
3830
|
+
}
|
|
3831
|
+
async function collectGuidedPatchDiagnostics(root, plan2) {
|
|
3832
|
+
if (plan2.strategy !== "guided" || !plan2.patches?.length) {
|
|
3833
|
+
return [];
|
|
3834
|
+
}
|
|
3835
|
+
const diagnostics = [];
|
|
3836
|
+
for (const patch of plan2.patches) {
|
|
3837
|
+
if (isConfigPatchReason(patch.reason)) {
|
|
3838
|
+
const content = await readFile(path16.join(root, patch.path));
|
|
3839
|
+
if (content?.includes("@inspecto-dev/plugin")) {
|
|
3840
|
+
diagnostics.push(
|
|
3841
|
+
createDiagnostic(
|
|
3842
|
+
"guided-config-patch-detected",
|
|
3843
|
+
"ok",
|
|
3844
|
+
`Guided config patch appears to be applied in ${patch.path}`,
|
|
3845
|
+
[],
|
|
3846
|
+
{ path: patch.path, reason: patch.reason }
|
|
3847
|
+
)
|
|
3848
|
+
);
|
|
3849
|
+
} else {
|
|
3850
|
+
diagnostics.push(
|
|
3851
|
+
createDiagnostic(
|
|
3852
|
+
"guided-config-patch-pending",
|
|
3853
|
+
"warning",
|
|
3854
|
+
`Guided config patch still needs review in ${patch.path}`,
|
|
3855
|
+
["Run `inspecto onboard --json` to review the generated patch details."],
|
|
3856
|
+
{ path: patch.path, reason: patch.reason }
|
|
3857
|
+
)
|
|
3858
|
+
);
|
|
3859
|
+
}
|
|
3860
|
+
continue;
|
|
3861
|
+
}
|
|
3862
|
+
if (isNextWebpackDevPatchReason(patch.reason)) {
|
|
3863
|
+
const packageJson = await readJSON(
|
|
3864
|
+
path16.join(root, "package.json")
|
|
3865
|
+
);
|
|
3866
|
+
const devScript = packageJson?.scripts?.dev ?? "";
|
|
3867
|
+
if (/next\s+dev\b/.test(devScript) && /--webpack\b/.test(devScript)) {
|
|
3868
|
+
diagnostics.push(
|
|
3869
|
+
createDiagnostic(
|
|
3870
|
+
"guided-dev-script-configured",
|
|
3871
|
+
"ok",
|
|
3872
|
+
"Next.js dev script is configured for webpack mode",
|
|
3873
|
+
[],
|
|
3874
|
+
{ script: devScript }
|
|
3875
|
+
)
|
|
3876
|
+
);
|
|
3877
|
+
} else {
|
|
3878
|
+
diagnostics.push(
|
|
3879
|
+
createDiagnostic(
|
|
3880
|
+
"guided-dev-script-pending",
|
|
3881
|
+
"warning",
|
|
3882
|
+
"Next.js dev script still needs webpack mode for Inspecto validation",
|
|
3883
|
+
["Update the `dev` script to `next dev --webpack` before browser validation."],
|
|
3884
|
+
{ script: devScript }
|
|
3885
|
+
)
|
|
3886
|
+
);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
return diagnostics;
|
|
3891
|
+
}
|
|
2611
3892
|
function printDoctorResult(result) {
|
|
2612
3893
|
log.header("Inspecto Doctor");
|
|
2613
3894
|
for (const check of result.checks) {
|
|
@@ -2637,7 +3918,7 @@ function printDoctorResult(result) {
|
|
|
2637
3918
|
}
|
|
2638
3919
|
async function collectDoctorResult(root = process.cwd()) {
|
|
2639
3920
|
const checks = [];
|
|
2640
|
-
if (!await exists(
|
|
3921
|
+
if (!await exists(path16.join(root, "package.json"))) {
|
|
2641
3922
|
const diagnostic = createDiagnostic("missing-package-json", "error", "No package.json found", [
|
|
2642
3923
|
"Run this command from your project root"
|
|
2643
3924
|
]);
|
|
@@ -2659,6 +3940,15 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2659
3940
|
detectBuildTools(root),
|
|
2660
3941
|
isExtensionInstalled()
|
|
2661
3942
|
]);
|
|
3943
|
+
const onboardingContext = buildDoctorOnboardingContext({
|
|
3944
|
+
root,
|
|
3945
|
+
packageManager: pm,
|
|
3946
|
+
buildTools: buildResult,
|
|
3947
|
+
frameworks: frameworkResult,
|
|
3948
|
+
ides: ideProbe,
|
|
3949
|
+
providers: providerProbe
|
|
3950
|
+
});
|
|
3951
|
+
const onboardingPlan = createPlanResult(onboardingContext);
|
|
2662
3952
|
if (ideProbe.detected.length === 0) {
|
|
2663
3953
|
checks.push(createDiagnostic("ide-not-detected", "warning", "IDE: not detected"));
|
|
2664
3954
|
} else {
|
|
@@ -2731,9 +4021,9 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2731
4021
|
}).join(", ");
|
|
2732
4022
|
checks.push(createDiagnostic("provider-detected", "ok", `Provider: ${aiNames}`));
|
|
2733
4023
|
}
|
|
2734
|
-
const pluginPath =
|
|
4024
|
+
const pluginPath = path16.join(root, "node_modules", "@inspecto-dev", "plugin");
|
|
2735
4025
|
if (await exists(pluginPath)) {
|
|
2736
|
-
const pkgJson = await readJSON(
|
|
4026
|
+
const pkgJson = await readJSON(path16.join(pluginPath, "package.json"));
|
|
2737
4027
|
const version = pkgJson?.version ?? "unknown";
|
|
2738
4028
|
checks.push(
|
|
2739
4029
|
createDiagnostic("plugin-installed", "ok", `@inspecto-dev/plugin@${version} installed`, [], {
|
|
@@ -2750,7 +4040,7 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2750
4040
|
if (buildResult.supported.length > 0) {
|
|
2751
4041
|
let injected = false;
|
|
2752
4042
|
for (const bt of buildResult.supported) {
|
|
2753
|
-
const content = await readFile(
|
|
4043
|
+
const content = await readFile(path16.join(root, bt.configPath));
|
|
2754
4044
|
if (content && content.includes("@inspecto-dev/plugin")) {
|
|
2755
4045
|
checks.push(
|
|
2756
4046
|
createDiagnostic("plugin-configured", "ok", `Plugin configured in ${bt.configPath}`, [], {
|
|
@@ -2773,15 +4063,40 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2773
4063
|
);
|
|
2774
4064
|
}
|
|
2775
4065
|
} else if (buildResult.unsupported.length > 0) {
|
|
2776
|
-
const
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
`build-tool-unsupported`,
|
|
2780
|
-
"warning",
|
|
2781
|
-
`Build tool: ${names} (not supported in v1)`,
|
|
2782
|
-
["current version supports: Vite, Webpack, Rspack, esbuild, Rollup"]
|
|
2783
|
-
)
|
|
4066
|
+
const guidedBuildTools = buildResult.unsupported.filter(isGuidedMetaFramework);
|
|
4067
|
+
const trulyUnsupportedBuildTools = buildResult.unsupported.filter(
|
|
4068
|
+
(buildTool) => !isGuidedMetaFramework(buildTool)
|
|
2784
4069
|
);
|
|
4070
|
+
if (guidedBuildTools.length > 0) {
|
|
4071
|
+
checks.push(
|
|
4072
|
+
createDiagnostic(
|
|
4073
|
+
"build-tool-guided",
|
|
4074
|
+
"warning",
|
|
4075
|
+
`Build tool: ${guidedBuildTools.join(", ")} (guided onboarding available)`,
|
|
4076
|
+
[
|
|
4077
|
+
"Run `inspecto onboard --json` to generate the remaining patch plan and assistant handoff.",
|
|
4078
|
+
...onboardingPlan.pendingSteps ?? []
|
|
4079
|
+
],
|
|
4080
|
+
{
|
|
4081
|
+
metaFrameworks: guidedBuildTools,
|
|
4082
|
+
...onboardingPlan.pendingSteps ? { pendingSteps: onboardingPlan.pendingSteps } : {},
|
|
4083
|
+
...onboardingPlan.assistantPrompt ? { assistantPrompt: onboardingPlan.assistantPrompt } : {},
|
|
4084
|
+
...onboardingPlan.patches ? { patchCount: onboardingPlan.patches.length } : {}
|
|
4085
|
+
}
|
|
4086
|
+
)
|
|
4087
|
+
);
|
|
4088
|
+
}
|
|
4089
|
+
checks.push(...await collectGuidedPatchDiagnostics(root, onboardingPlan));
|
|
4090
|
+
if (trulyUnsupportedBuildTools.length > 0) {
|
|
4091
|
+
checks.push(
|
|
4092
|
+
createDiagnostic(
|
|
4093
|
+
"build-tool-unsupported",
|
|
4094
|
+
"warning",
|
|
4095
|
+
`Build tool: ${trulyUnsupportedBuildTools.join(", ")} (not supported in v1)`,
|
|
4096
|
+
["current version supports: Vite, Webpack, Rspack, esbuild, Rollup"]
|
|
4097
|
+
)
|
|
4098
|
+
);
|
|
4099
|
+
}
|
|
2785
4100
|
} else {
|
|
2786
4101
|
checks.push(
|
|
2787
4102
|
createDiagnostic("build-tool-missing", "warning", "No recognized build config found")
|
|
@@ -2809,8 +4124,8 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2809
4124
|
);
|
|
2810
4125
|
}
|
|
2811
4126
|
}
|
|
2812
|
-
const settingsJsonPath =
|
|
2813
|
-
const settingsLocalPath =
|
|
4127
|
+
const settingsJsonPath = path16.join(root, ".inspecto", "settings.json");
|
|
4128
|
+
const settingsLocalPath = path16.join(root, ".inspecto", "settings.local.json");
|
|
2814
4129
|
const hasSettingsJson = await exists(settingsJsonPath);
|
|
2815
4130
|
const hasSettingsLocal = await exists(settingsLocalPath);
|
|
2816
4131
|
if (hasSettingsJson || hasSettingsLocal) {
|
|
@@ -2819,6 +4134,25 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2819
4134
|
const settings = await readJSON(targetPath);
|
|
2820
4135
|
if (settings) {
|
|
2821
4136
|
checks.push(createDiagnostic("settings-valid", "ok", `.inspecto/${fileName} valid`));
|
|
4137
|
+
const configuredIde = typeof settings.ide === "string" ? settings.ide : void 0;
|
|
4138
|
+
const detectedIdeCandidates = ideProbe.detected.map((item) => item.ide);
|
|
4139
|
+
if (configuredIde && detectedIdeCandidates.length > 0 && !detectedIdeCandidates.includes(configuredIde)) {
|
|
4140
|
+
checks.push(
|
|
4141
|
+
createDiagnostic(
|
|
4142
|
+
"settings-ide-mismatch",
|
|
4143
|
+
"warning",
|
|
4144
|
+
`.inspecto/${fileName} sets ide=${configuredIde}, but the current environment looks like ${detectedIdeCandidates.join(", ")}. Inspecto will use the configured IDE from ${fileName}.`,
|
|
4145
|
+
[
|
|
4146
|
+
`Update .inspecto/${fileName} if you want Inspecto to target the currently detected IDE instead.`
|
|
4147
|
+
],
|
|
4148
|
+
{
|
|
4149
|
+
configuredIde,
|
|
4150
|
+
detectedIdeCandidates,
|
|
4151
|
+
precedence: `configured ide from ${fileName}`
|
|
4152
|
+
}
|
|
4153
|
+
)
|
|
4154
|
+
);
|
|
4155
|
+
}
|
|
2822
4156
|
} else {
|
|
2823
4157
|
checks.push(
|
|
2824
4158
|
createDiagnostic(
|
|
@@ -2844,7 +4178,7 @@ async function collectDoctorResult(root = process.cwd()) {
|
|
|
2844
4178
|
)
|
|
2845
4179
|
);
|
|
2846
4180
|
}
|
|
2847
|
-
const gitignoreContent = await readFile(
|
|
4181
|
+
const gitignoreContent = await readFile(path16.join(root, ".gitignore"));
|
|
2848
4182
|
if (gitignoreContent) {
|
|
2849
4183
|
const hasLockIgnore = gitignoreContent.includes(".inspecto/install.lock") || gitignoreContent.includes(".inspecto/");
|
|
2850
4184
|
if (!hasLockIgnore) {
|
|
@@ -2882,7 +4216,7 @@ async function doctor(options = {}) {
|
|
|
2882
4216
|
}
|
|
2883
4217
|
|
|
2884
4218
|
// src/onboarding/session.ts
|
|
2885
|
-
import
|
|
4219
|
+
import path17 from "path";
|
|
2886
4220
|
function normalizePackagePath2(packagePath) {
|
|
2887
4221
|
if (!packagePath || packagePath === ".") return "";
|
|
2888
4222
|
return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
@@ -2906,9 +4240,7 @@ function getVerificationCommand(packageManager) {
|
|
|
2906
4240
|
}
|
|
2907
4241
|
}
|
|
2908
4242
|
async function buildVerification(projectRoot, packageManager) {
|
|
2909
|
-
const packageJson = await readJSON(
|
|
2910
|
-
path13.join(projectRoot, "package.json")
|
|
2911
|
-
);
|
|
4243
|
+
const packageJson = await readJSON(path17.join(projectRoot, "package.json"));
|
|
2912
4244
|
if (packageJson?.scripts?.dev) {
|
|
2913
4245
|
const devCommand = getVerificationCommand(packageManager);
|
|
2914
4246
|
return {
|
|
@@ -2917,11 +4249,28 @@ async function buildVerification(projectRoot, packageManager) {
|
|
|
2917
4249
|
message: `Start the local dev server with \`${devCommand}\` to verify Inspecto in the browser.`
|
|
2918
4250
|
};
|
|
2919
4251
|
}
|
|
4252
|
+
if (packageJson?.scripts?.start && packageJson?.dependencies?.next) {
|
|
4253
|
+
const devCommand = packageManager === "bun" ? "bunx next dev" : "npx next dev";
|
|
4254
|
+
return {
|
|
4255
|
+
available: true,
|
|
4256
|
+
devCommand,
|
|
4257
|
+
message: `Start the local dev server with \`${devCommand}\` to verify Inspecto in the browser.`
|
|
4258
|
+
};
|
|
4259
|
+
}
|
|
2920
4260
|
return {
|
|
2921
4261
|
available: false,
|
|
2922
4262
|
message: "Start your normal local dev server command to verify Inspecto in the browser."
|
|
2923
4263
|
};
|
|
2924
4264
|
}
|
|
4265
|
+
function buildExtensionInstallCommand(ide) {
|
|
4266
|
+
if (ide && isSupportedHostIde(ide)) {
|
|
4267
|
+
const binaryName = getHostIdeBinaryName(ide);
|
|
4268
|
+
if (binaryName) {
|
|
4269
|
+
return `${binaryName} --install-extension inspecto.inspecto`;
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
return "code --install-extension inspecto.inspecto";
|
|
4273
|
+
}
|
|
2925
4274
|
function buildIdeExtensionStatus(input) {
|
|
2926
4275
|
if (!input.required) {
|
|
2927
4276
|
return {
|
|
@@ -2934,8 +4283,10 @@ function buildIdeExtensionStatus(input) {
|
|
|
2934
4283
|
required: true,
|
|
2935
4284
|
installed: input.installed,
|
|
2936
4285
|
manualRequired: input.manualRequired,
|
|
2937
|
-
installCommand:
|
|
2938
|
-
|
|
4286
|
+
installCommand: buildExtensionInstallCommand(input.ide),
|
|
4287
|
+
...input.ide === "vscode" ? {
|
|
4288
|
+
marketplaceUrl: "https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto"
|
|
4289
|
+
} : {},
|
|
2939
4290
|
openVsxUrl: "https://open-vsx.org/extension/inspecto/inspecto"
|
|
2940
4291
|
};
|
|
2941
4292
|
}
|
|
@@ -2947,15 +4298,16 @@ async function detectFrameworkSupportByPackage2(repoRoot, context) {
|
|
|
2947
4298
|
await Promise.all(
|
|
2948
4299
|
Array.from(packagePaths).map(async (packagePath) => {
|
|
2949
4300
|
const frameworkResult = await detectFrameworks(
|
|
2950
|
-
packagePath ?
|
|
4301
|
+
packagePath ? path17.join(repoRoot, packagePath) : repoRoot
|
|
2951
4302
|
);
|
|
2952
4303
|
supportByPackage[packagePath] = frameworkResult.supported;
|
|
2953
4304
|
})
|
|
2954
4305
|
);
|
|
2955
4306
|
return supportByPackage;
|
|
2956
4307
|
}
|
|
2957
|
-
async function buildTargetedContext(rootContext,
|
|
2958
|
-
const
|
|
4308
|
+
async function buildTargetedContext(rootContext, target) {
|
|
4309
|
+
const packagePath = normalizePackagePath2(target.packagePath);
|
|
4310
|
+
const projectRoot = packagePath ? path17.join(rootContext.root, packagePath) : rootContext.root;
|
|
2959
4311
|
const [frameworks, ides, providers] = await Promise.all([
|
|
2960
4312
|
detectFrameworks(projectRoot),
|
|
2961
4313
|
detectIDE(projectRoot),
|
|
@@ -2966,7 +4318,11 @@ async function buildTargetedContext(rootContext, packagePath) {
|
|
|
2966
4318
|
packageManager: rootContext.packageManager,
|
|
2967
4319
|
buildTools: {
|
|
2968
4320
|
supported: rootContext.buildTools.supported.filter((item) => {
|
|
2969
|
-
|
|
4321
|
+
const itemPackagePath = normalizePackagePath2(item.packagePath);
|
|
4322
|
+
if (target.id) {
|
|
4323
|
+
return `${itemPackagePath || "."}:${item.tool}:${item.configPath}` === target.id;
|
|
4324
|
+
}
|
|
4325
|
+
return itemPackagePath === packagePath;
|
|
2970
4326
|
}),
|
|
2971
4327
|
unsupported: []
|
|
2972
4328
|
},
|
|
@@ -2984,7 +4340,11 @@ async function buildTargetedContext(rootContext, packagePath) {
|
|
|
2984
4340
|
};
|
|
2985
4341
|
}
|
|
2986
4342
|
function buildOnboardingSummary(plan2, projectRoot) {
|
|
2987
|
-
const changes = plan2.actions.filter(
|
|
4343
|
+
const changes = plan2.actions.filter(
|
|
4344
|
+
(action) => !["manual_step", "generate_patch_plan", "generate_file", "manual_confirmation"].includes(
|
|
4345
|
+
action.type
|
|
4346
|
+
)
|
|
4347
|
+
).map((action) => action.description);
|
|
2988
4348
|
const risks = [...plan2.warnings.map((item) => item.message)];
|
|
2989
4349
|
const manualFollowUp = planManualFollowUp(plan2);
|
|
2990
4350
|
let headline = `Inspecto is ready to onboard ${projectRoot}.`;
|
|
@@ -3028,18 +4388,27 @@ function buildPreApplyResult(status, session) {
|
|
|
3028
4388
|
errors: session.plan.blockers.map((item) => item.message),
|
|
3029
4389
|
nextSteps: session.summary.manualFollowUp
|
|
3030
4390
|
} : void 0;
|
|
4391
|
+
const ideExtension = buildIdeExtensionStatus({
|
|
4392
|
+
...session.selectedIDE?.ide ? { ide: session.selectedIDE.ide } : {},
|
|
4393
|
+
required: session.plan.defaults.extension,
|
|
4394
|
+
installed: false,
|
|
4395
|
+
manualRequired: session.plan.defaults.extension
|
|
4396
|
+
});
|
|
3031
4397
|
return {
|
|
3032
4398
|
status,
|
|
3033
4399
|
target: session.target,
|
|
3034
4400
|
summary: session.summary,
|
|
3035
4401
|
confirmation: session.confirmation,
|
|
3036
|
-
ideExtension
|
|
3037
|
-
required: session.plan.defaults.extension,
|
|
3038
|
-
installed: false,
|
|
3039
|
-
manualRequired: session.plan.defaults.extension
|
|
3040
|
-
}),
|
|
4402
|
+
ideExtension,
|
|
3041
4403
|
verification: session.verification,
|
|
3042
|
-
|
|
4404
|
+
...session.framework ? { framework: session.framework } : {},
|
|
4405
|
+
...session.metaFramework ? { metaFramework: session.metaFramework } : {},
|
|
4406
|
+
...session.routerMode ? { routerMode: session.routerMode } : {},
|
|
4407
|
+
...session.autoApplied ? { autoApplied: session.autoApplied } : {},
|
|
4408
|
+
...session.pendingSteps ? { pendingSteps: session.pendingSteps } : {},
|
|
4409
|
+
...session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {},
|
|
4410
|
+
...session.patches ? { patches: session.patches } : {},
|
|
4411
|
+
...diagnostics ? { diagnostics } : {}
|
|
3043
4412
|
};
|
|
3044
4413
|
}
|
|
3045
4414
|
function buildExecutionResult(session, applyResult) {
|
|
@@ -3050,8 +4419,8 @@ function buildExecutionResult(session, applyResult) {
|
|
|
3050
4419
|
)
|
|
3051
4420
|
),
|
|
3052
4421
|
installedDependencies: applyResult.mutations.map((item) => item.name).filter((value) => !!value),
|
|
3053
|
-
selectedProviderDefault: session.providerDefault,
|
|
3054
|
-
selectedIDE: session.selectedIDE
|
|
4422
|
+
...session.providerDefault ? { selectedProviderDefault: session.providerDefault } : {},
|
|
4423
|
+
...session.selectedIDE?.ide ? { selectedIDE: session.selectedIDE.ide } : {},
|
|
3055
4424
|
mutations: applyResult.mutations
|
|
3056
4425
|
};
|
|
3057
4426
|
}
|
|
@@ -3084,25 +4453,41 @@ async function resolveOnboardingSession(root, options = {}) {
|
|
|
3084
4453
|
repoRoot: root,
|
|
3085
4454
|
buildTools: rootContext.buildTools.supported,
|
|
3086
4455
|
frameworkSupportByPackage,
|
|
3087
|
-
selectedPackagePath: options.target
|
|
4456
|
+
...options.target ? { selectedPackagePath: options.target } : {}
|
|
3088
4457
|
});
|
|
3089
|
-
|
|
4458
|
+
const isGuided = rootContext.buildTools.unsupported.some(
|
|
4459
|
+
(t) => ["Next.js", "Nuxt", "Umi"].includes(t)
|
|
4460
|
+
);
|
|
4461
|
+
if (target.candidates.length === 0 || isGuided) {
|
|
3090
4462
|
const plan3 = createPlanResult(rootContext);
|
|
4463
|
+
const guidedStatus = plan3.strategy === "guided" ? "partial_success" : "error";
|
|
4464
|
+
const resolvedTarget = plan3.strategy === "guided" ? {
|
|
4465
|
+
status: "guided",
|
|
4466
|
+
candidates: [],
|
|
4467
|
+
reason: `Guided onboarding is available for ${plan3.metaFramework ?? "this project"}.`
|
|
4468
|
+
} : target;
|
|
3091
4469
|
return {
|
|
3092
|
-
status:
|
|
3093
|
-
target,
|
|
4470
|
+
status: guidedStatus,
|
|
4471
|
+
target: resolvedTarget,
|
|
3094
4472
|
summary: buildOnboardingSummary(plan3, root),
|
|
3095
4473
|
confirmation: { required: false },
|
|
3096
4474
|
verification: rootVerification,
|
|
3097
4475
|
context: rootContext,
|
|
3098
4476
|
plan: plan3,
|
|
3099
|
-
projectRoot: root
|
|
4477
|
+
projectRoot: root,
|
|
4478
|
+
...plan3.framework ? { framework: plan3.framework } : {},
|
|
4479
|
+
...plan3.metaFramework ? { metaFramework: plan3.metaFramework } : {},
|
|
4480
|
+
...plan3.routerMode ? { routerMode: plan3.routerMode } : {},
|
|
4481
|
+
...plan3.autoApplied ? { autoApplied: plan3.autoApplied } : {},
|
|
4482
|
+
...plan3.pendingSteps ? { pendingSteps: plan3.pendingSteps } : {},
|
|
4483
|
+
...plan3.assistantPrompt ? { assistantPrompt: plan3.assistantPrompt } : {},
|
|
4484
|
+
...plan3.patches ? { patches: plan3.patches } : {}
|
|
3100
4485
|
};
|
|
3101
4486
|
}
|
|
3102
4487
|
if (target.status === "needs_selection") {
|
|
3103
4488
|
const plan3 = createPlanResult(rootContext);
|
|
3104
4489
|
const summary2 = {
|
|
3105
|
-
headline: "Inspecto
|
|
4490
|
+
headline: "Inspecto needs one build target selection before setup so it knows which local dev build should receive the plugin and settings.",
|
|
3106
4491
|
changes: [],
|
|
3107
4492
|
risks: [],
|
|
3108
4493
|
manualFollowUp: []
|
|
@@ -3118,8 +4503,7 @@ async function resolveOnboardingSession(root, options = {}) {
|
|
|
3118
4503
|
projectRoot: root
|
|
3119
4504
|
};
|
|
3120
4505
|
}
|
|
3121
|
-
const
|
|
3122
|
-
const context = await buildTargetedContext(rootContext, packagePath);
|
|
4506
|
+
const context = await buildTargetedContext(rootContext, target.selected);
|
|
3123
4507
|
const verification = await buildVerification(context.root, context.packageManager);
|
|
3124
4508
|
const plan2 = createPlanResult(context);
|
|
3125
4509
|
const summary = buildOnboardingSummary(plan2, context.root);
|
|
@@ -3143,6 +4527,10 @@ async function resolveOnboardingSession(root, options = {}) {
|
|
|
3143
4527
|
} else if (summary.manualFollowUp.length > 0 || plan2.warnings.length > 0) {
|
|
3144
4528
|
status = "partial_success";
|
|
3145
4529
|
}
|
|
4530
|
+
const providerDefault = getProviderDefault2(
|
|
4531
|
+
plan2.defaults.provider,
|
|
4532
|
+
selectedProvider?.preferredMode
|
|
4533
|
+
);
|
|
3146
4534
|
return {
|
|
3147
4535
|
status,
|
|
3148
4536
|
target,
|
|
@@ -3153,7 +4541,14 @@ async function resolveOnboardingSession(root, options = {}) {
|
|
|
3153
4541
|
plan: plan2,
|
|
3154
4542
|
projectRoot: context.root,
|
|
3155
4543
|
selectedIDE,
|
|
3156
|
-
providerDefault:
|
|
4544
|
+
...providerDefault ? { providerDefault } : {},
|
|
4545
|
+
...plan2.framework ? { framework: plan2.framework } : {},
|
|
4546
|
+
...plan2.metaFramework ? { metaFramework: plan2.metaFramework } : {},
|
|
4547
|
+
...plan2.routerMode ? { routerMode: plan2.routerMode } : {},
|
|
4548
|
+
...plan2.autoApplied ? { autoApplied: plan2.autoApplied } : {},
|
|
4549
|
+
...plan2.pendingSteps ? { pendingSteps: plan2.pendingSteps } : {},
|
|
4550
|
+
...plan2.assistantPrompt ? { assistantPrompt: plan2.assistantPrompt } : {},
|
|
4551
|
+
...plan2.patches ? { patches: plan2.patches } : {}
|
|
3157
4552
|
};
|
|
3158
4553
|
}
|
|
3159
4554
|
async function applyResolvedOnboardingSession(session, options = {}) {
|
|
@@ -3172,23 +4567,33 @@ async function applyResolvedOnboardingSession(session, options = {}) {
|
|
|
3172
4567
|
},
|
|
3173
4568
|
selectedIDE: session.selectedIDE,
|
|
3174
4569
|
providerDefault: session.providerDefault,
|
|
3175
|
-
plan: session.plan
|
|
4570
|
+
plan: session.plan,
|
|
4571
|
+
allowManualPlanApply: (session.plan.strategy === "manual" || session.plan.strategy === "guided") && session.plan.blockers.length === 0
|
|
3176
4572
|
});
|
|
3177
4573
|
const diagnostics = buildExecutionDiagnostics(session, applyResult);
|
|
3178
4574
|
const status = applyResult.postInstall.installFailed && session.context.buildTools.supported.length === 0 ? "error" : diagnostics?.nextSteps.length || diagnostics?.errors.length || diagnostics?.warnings.length ? "partial_success" : "success";
|
|
4575
|
+
const ideExtension = buildIdeExtensionStatus({
|
|
4576
|
+
...session.selectedIDE?.ide ? { ide: session.selectedIDE.ide } : {},
|
|
4577
|
+
required: session.plan.defaults.extension,
|
|
4578
|
+
installed: session.plan.defaults.extension && !applyResult.postInstall.manualExtensionInstallNeeded,
|
|
4579
|
+
manualRequired: applyResult.postInstall.manualExtensionInstallNeeded
|
|
4580
|
+
});
|
|
3179
4581
|
return {
|
|
3180
4582
|
status,
|
|
3181
4583
|
target: session.target,
|
|
3182
4584
|
summary: session.summary,
|
|
3183
4585
|
confirmation: session.confirmation,
|
|
3184
|
-
ideExtension
|
|
3185
|
-
required: session.plan.defaults.extension,
|
|
3186
|
-
installed: session.plan.defaults.extension && !applyResult.postInstall.manualExtensionInstallNeeded,
|
|
3187
|
-
manualRequired: applyResult.postInstall.manualExtensionInstallNeeded
|
|
3188
|
-
}),
|
|
4586
|
+
ideExtension,
|
|
3189
4587
|
verification,
|
|
3190
4588
|
result: buildExecutionResult(session, applyResult),
|
|
3191
|
-
|
|
4589
|
+
...session.framework ? { framework: session.framework } : {},
|
|
4590
|
+
...session.metaFramework ? { metaFramework: session.metaFramework } : {},
|
|
4591
|
+
...session.routerMode ? { routerMode: session.routerMode } : {},
|
|
4592
|
+
...session.autoApplied ? { autoApplied: session.autoApplied } : {},
|
|
4593
|
+
...session.pendingSteps ? { pendingSteps: session.pendingSteps } : {},
|
|
4594
|
+
...session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {},
|
|
4595
|
+
...session.patches ? { patches: session.patches } : {},
|
|
4596
|
+
...diagnostics ? { diagnostics } : {}
|
|
3192
4597
|
};
|
|
3193
4598
|
}
|
|
3194
4599
|
function buildDeferredOnboardResult(session) {
|
|
@@ -3211,23 +4616,72 @@ function printManualExtensionGuidance(result) {
|
|
|
3211
4616
|
log.hint(result.ideExtension.openVsxUrl);
|
|
3212
4617
|
}
|
|
3213
4618
|
}
|
|
4619
|
+
function buildAssistantHandoff(result) {
|
|
4620
|
+
if (!result.framework && !result.metaFramework && !result.routerMode && !result.autoApplied && !result.pendingSteps && !result.assistantPrompt && !result.patches) {
|
|
4621
|
+
return result.handoff;
|
|
4622
|
+
}
|
|
4623
|
+
return {
|
|
4624
|
+
...result.framework ? { framework: result.framework } : {},
|
|
4625
|
+
...result.metaFramework ? { metaFramework: result.metaFramework } : {},
|
|
4626
|
+
...result.routerMode ? { routerMode: result.routerMode } : {},
|
|
4627
|
+
...result.autoApplied ? { autoApplied: result.autoApplied } : {},
|
|
4628
|
+
...result.pendingSteps ? { pendingSteps: result.pendingSteps } : {},
|
|
4629
|
+
...result.assistantPrompt ? { assistantPrompt: result.assistantPrompt } : {},
|
|
4630
|
+
...result.patches ? { patches: result.patches } : {}
|
|
4631
|
+
};
|
|
4632
|
+
}
|
|
4633
|
+
function normalizeOnboardResult(result) {
|
|
4634
|
+
const handoff = buildAssistantHandoff(result);
|
|
4635
|
+
if (!handoff) {
|
|
4636
|
+
return result;
|
|
4637
|
+
}
|
|
4638
|
+
return {
|
|
4639
|
+
...result,
|
|
4640
|
+
handoff
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4643
|
+
function collectDisplayNextSteps(result) {
|
|
4644
|
+
return Array.from(
|
|
4645
|
+
/* @__PURE__ */ new Set([...result.diagnostics?.nextSteps ?? [], ...result.handoff?.pendingSteps ?? []])
|
|
4646
|
+
);
|
|
4647
|
+
}
|
|
3214
4648
|
function printOnboardResult(result) {
|
|
4649
|
+
const normalized = normalizeOnboardResult(result);
|
|
3215
4650
|
log.header("Inspecto Onboard");
|
|
3216
|
-
log.info(`Status: ${
|
|
3217
|
-
log.info(
|
|
3218
|
-
|
|
4651
|
+
log.info(`Status: ${normalized.status}`);
|
|
4652
|
+
log.info(normalized.summary.headline);
|
|
4653
|
+
if (normalized.status === "needs_target_selection") {
|
|
4654
|
+
if (normalized.target.selectionPurpose) {
|
|
4655
|
+
log.warn(normalized.target.selectionPurpose);
|
|
4656
|
+
}
|
|
4657
|
+
for (const candidate of normalized.target.candidates) {
|
|
4658
|
+
const identifier = candidate.candidateId ?? candidate.id ?? candidate.configPath;
|
|
4659
|
+
const label = candidate.label ?? candidate.configPath;
|
|
4660
|
+
log.hint(`${identifier}: ${label}`);
|
|
4661
|
+
}
|
|
4662
|
+
if (normalized.target.selectionInstructions) {
|
|
4663
|
+
log.hint(normalized.target.selectionInstructions);
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
for (const change of normalized.summary.changes) {
|
|
3219
4667
|
log.hint(change);
|
|
3220
4668
|
}
|
|
3221
|
-
for (const step of
|
|
4669
|
+
for (const step of collectDisplayNextSteps(normalized)) {
|
|
3222
4670
|
log.warn(step);
|
|
3223
4671
|
}
|
|
3224
|
-
|
|
3225
|
-
log.
|
|
4672
|
+
for (const patch of normalized.handoff?.patches ?? []) {
|
|
4673
|
+
log.hint(`Patch target: ${patch.path} (${patch.reason})`);
|
|
4674
|
+
}
|
|
4675
|
+
if (normalized.handoff?.assistantPrompt) {
|
|
4676
|
+
log.hint(normalized.handoff.assistantPrompt);
|
|
3226
4677
|
}
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
4678
|
+
if (normalized.confirmation.required && normalized.confirmation.question) {
|
|
4679
|
+
log.warn(normalized.confirmation.question);
|
|
4680
|
+
}
|
|
4681
|
+
printManualExtensionGuidance(normalized);
|
|
4682
|
+
const extensionReady = !normalized.ideExtension?.required || normalized.ideExtension.installed && !normalized.ideExtension.manualRequired;
|
|
4683
|
+
if (extensionReady && (normalized.status === "success" || normalized.status === "partial_success") && normalized.verification?.message) {
|
|
4684
|
+
log.info(normalized.verification.message);
|
|
3231
4685
|
}
|
|
3232
4686
|
}
|
|
3233
4687
|
async function onboard(options = {}) {
|
|
@@ -3235,12 +4689,12 @@ async function onboard(options = {}) {
|
|
|
3235
4689
|
const session = await resolveOnboardingSession(root, options);
|
|
3236
4690
|
if (session.status === "error" || session.status === "needs_target_selection" || session.status === "needs_confirmation") {
|
|
3237
4691
|
return writeCommandOutput(
|
|
3238
|
-
buildDeferredOnboardResult(session),
|
|
4692
|
+
normalizeOnboardResult(buildDeferredOnboardResult(session)),
|
|
3239
4693
|
options.json ?? false,
|
|
3240
4694
|
printOnboardResult
|
|
3241
4695
|
);
|
|
3242
4696
|
}
|
|
3243
|
-
const result = await applyResolvedOnboardingSession(session, options);
|
|
4697
|
+
const result = normalizeOnboardResult(await applyResolvedOnboardingSession(session, options));
|
|
3244
4698
|
return writeCommandOutput(result, options.json ?? false, printOnboardResult);
|
|
3245
4699
|
}
|
|
3246
4700
|
|
|
@@ -3278,11 +4732,11 @@ async function plan(json = false) {
|
|
|
3278
4732
|
}
|
|
3279
4733
|
|
|
3280
4734
|
// src/commands/teardown.ts
|
|
3281
|
-
import
|
|
4735
|
+
import path18 from "path";
|
|
3282
4736
|
async function teardown() {
|
|
3283
4737
|
const root = process.cwd();
|
|
3284
4738
|
log.header("Inspecto Teardown");
|
|
3285
|
-
const lockPath =
|
|
4739
|
+
const lockPath = path18.join(root, ".inspecto", "install.lock");
|
|
3286
4740
|
const lock = await readJSON(lockPath);
|
|
3287
4741
|
if (!lock) {
|
|
3288
4742
|
log.warn("No .inspecto/install.lock found. Running in best-effort mode.");
|
|
@@ -3295,8 +4749,8 @@ async function teardown() {
|
|
|
3295
4749
|
} catch {
|
|
3296
4750
|
log.warn("Could not remove @inspecto-dev/plugin (may not be installed)");
|
|
3297
4751
|
}
|
|
3298
|
-
if (await exists(
|
|
3299
|
-
await removeDir(
|
|
4752
|
+
if (await exists(path18.join(root, ".inspecto"))) {
|
|
4753
|
+
await removeDir(path18.join(root, ".inspecto"));
|
|
3300
4754
|
log.success("Deleted .inspecto/ directory");
|
|
3301
4755
|
}
|
|
3302
4756
|
await cleanGitignore(root);
|
|
@@ -3345,7 +4799,7 @@ async function teardown() {
|
|
|
3345
4799
|
}
|
|
3346
4800
|
}
|
|
3347
4801
|
}
|
|
3348
|
-
await removeDir(
|
|
4802
|
+
await removeDir(path18.join(root, ".inspecto"));
|
|
3349
4803
|
log.success("Deleted .inspecto/ directory");
|
|
3350
4804
|
await cleanGitignore(root);
|
|
3351
4805
|
log.blank();
|
|
@@ -3354,13 +4808,13 @@ async function teardown() {
|
|
|
3354
4808
|
}
|
|
3355
4809
|
|
|
3356
4810
|
// src/commands/integration-install.ts
|
|
3357
|
-
import
|
|
4811
|
+
import fs6 from "fs/promises";
|
|
3358
4812
|
import { homedir as homedir2 } from "os";
|
|
3359
|
-
import
|
|
4813
|
+
import path21 from "path";
|
|
3360
4814
|
import { fileURLToPath } from "url";
|
|
3361
4815
|
|
|
3362
4816
|
// src/commands/integration-host-ide.ts
|
|
3363
|
-
import
|
|
4817
|
+
import path19 from "path";
|
|
3364
4818
|
async function resolveIntegrationHostIde(options = {}) {
|
|
3365
4819
|
if (isSupportedHostIde(options.explicitIde)) {
|
|
3366
4820
|
return {
|
|
@@ -3397,22 +4851,24 @@ async function resolveIntegrationHostIde(options = {}) {
|
|
|
3397
4851
|
candidates: envCandidates
|
|
3398
4852
|
};
|
|
3399
4853
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
4854
|
+
if (!options.ignoreProjectArtifacts) {
|
|
4855
|
+
const artifactCandidates = await detectArtifactHostIdes(cwd);
|
|
4856
|
+
if (artifactCandidates.length === 1) {
|
|
4857
|
+
return {
|
|
4858
|
+
ide: artifactCandidates[0],
|
|
4859
|
+
confidence: "medium",
|
|
4860
|
+
source: "artifact",
|
|
4861
|
+
candidates: artifactCandidates
|
|
4862
|
+
};
|
|
4863
|
+
}
|
|
4864
|
+
if (artifactCandidates.length > 1) {
|
|
4865
|
+
return {
|
|
4866
|
+
ide: null,
|
|
4867
|
+
confidence: "low",
|
|
4868
|
+
source: "ambiguous",
|
|
4869
|
+
candidates: artifactCandidates
|
|
4870
|
+
};
|
|
4871
|
+
}
|
|
3416
4872
|
}
|
|
3417
4873
|
return {
|
|
3418
4874
|
ide: null,
|
|
@@ -3423,8 +4879,8 @@ async function resolveIntegrationHostIde(options = {}) {
|
|
|
3423
4879
|
}
|
|
3424
4880
|
async function resolveConfiguredIde(cwd) {
|
|
3425
4881
|
const settingsPaths = [
|
|
3426
|
-
|
|
3427
|
-
|
|
4882
|
+
path19.join(cwd, ".inspecto", "settings.local.json"),
|
|
4883
|
+
path19.join(cwd, ".inspecto", "settings.json")
|
|
3428
4884
|
];
|
|
3429
4885
|
for (const settingsPath of settingsPaths) {
|
|
3430
4886
|
const settings = await readJSON(settingsPath);
|
|
@@ -3464,7 +4920,7 @@ async function detectArtifactHostIdes(cwd) {
|
|
|
3464
4920
|
|
|
3465
4921
|
// src/commands/integration-dispatch-mode.ts
|
|
3466
4922
|
import { homedir } from "os";
|
|
3467
|
-
import
|
|
4923
|
+
import path20 from "path";
|
|
3468
4924
|
async function resolveIntegrationDispatchMode(options) {
|
|
3469
4925
|
const assistantRule = getDualModeAssistantCapability(options.assistant);
|
|
3470
4926
|
const home = options.homeDir ?? homedir();
|
|
@@ -3507,7 +4963,7 @@ async function isIdeExtensionInstalled(extensionId, extensionsDir) {
|
|
|
3507
4963
|
} catch {
|
|
3508
4964
|
return false;
|
|
3509
4965
|
}
|
|
3510
|
-
const obsoletePath =
|
|
4966
|
+
const obsoletePath = path20.join(extensionsDir, ".obsolete");
|
|
3511
4967
|
let obsoleteFolders = /* @__PURE__ */ new Set();
|
|
3512
4968
|
if (await exists(obsoletePath)) {
|
|
3513
4969
|
const obsolete = await readJSON(obsoletePath);
|
|
@@ -3546,7 +5002,8 @@ async function runIntegrationAutomation(assistant, options = {}, cwd) {
|
|
|
3546
5002
|
const silent = options.silent ?? false;
|
|
3547
5003
|
const resolvedHostIde = await resolveIntegrationHostIde({
|
|
3548
5004
|
...options.ide ? { explicitIde: options.ide } : {},
|
|
3549
|
-
...cwd ? { cwd } : {}
|
|
5005
|
+
...cwd ? { cwd } : {},
|
|
5006
|
+
...options.ignoreProjectArtifacts ? { ignoreProjectArtifacts: true } : {}
|
|
3550
5007
|
});
|
|
3551
5008
|
const details = {
|
|
3552
5009
|
hostIde: {
|
|
@@ -3889,6 +5346,10 @@ function getDispatchModeLabel(assistant, ide, mode) {
|
|
|
3889
5346
|
}
|
|
3890
5347
|
|
|
3891
5348
|
// src/commands/integration-install.ts
|
|
5349
|
+
import {
|
|
5350
|
+
DEFAULT_PROVIDER_MODE,
|
|
5351
|
+
VALID_MODES
|
|
5352
|
+
} from "@inspecto-dev/types";
|
|
3892
5353
|
var REPO_RAW_BASE = "https://raw.githubusercontent.com/inspecto-dev/inspecto/main";
|
|
3893
5354
|
var TOTAL_STEPS2 = 6;
|
|
3894
5355
|
var INTEGRATION_MANIFESTS = [
|
|
@@ -3937,7 +5398,7 @@ var INTEGRATION_MANIFESTS = [
|
|
|
3937
5398
|
{
|
|
3938
5399
|
assistant: "coco",
|
|
3939
5400
|
type: "native-skill",
|
|
3940
|
-
installTarget: ".
|
|
5401
|
+
installTarget: ".trae/skills/inspecto-onboarding/",
|
|
3941
5402
|
preferredInstall: "npx @inspecto-dev/cli integrations install coco --host-ide <vscode|cursor|trae|trae-cn>",
|
|
3942
5403
|
cliSupported: true
|
|
3943
5404
|
}
|
|
@@ -3955,7 +5416,7 @@ async function installIntegration(assistant, options = {}) {
|
|
|
3955
5416
|
if (await exists(asset.target)) {
|
|
3956
5417
|
if (options.force) {
|
|
3957
5418
|
} else if (manifest.type === "context-template") {
|
|
3958
|
-
const originalContent = await
|
|
5419
|
+
const originalContent = await fs6.readFile(asset.target, "utf-8");
|
|
3959
5420
|
existingFiles.set(asset.target, originalContent);
|
|
3960
5421
|
if (!silent) {
|
|
3961
5422
|
log.info(`File ${asset.target} already exists. Content will be appended safely.`);
|
|
@@ -3992,10 +5453,13 @@ ${content}`;
|
|
|
3992
5453
|
for (const { asset, content } of downloadedAssets) {
|
|
3993
5454
|
await writeFile(asset.target, content);
|
|
3994
5455
|
if (asset.executable) {
|
|
3995
|
-
await
|
|
5456
|
+
await fs6.chmod(asset.target, 493);
|
|
3996
5457
|
}
|
|
3997
5458
|
}
|
|
3998
5459
|
}
|
|
5460
|
+
if (shouldPersistProjectOnboardingDefaults(options)) {
|
|
5461
|
+
await persistProjectOnboardingDefaults(assistant, options);
|
|
5462
|
+
}
|
|
3999
5463
|
const stepOneMessage = options.preview ? formatIntegrationStep2(1, `Previewing ${getAssistantLabel2(assistant)} integration assets`) : formatIntegrationStep2(1, `Installed ${getAssistantLabel2(assistant)} integration assets`);
|
|
4000
5464
|
if (!silent) {
|
|
4001
5465
|
if (options.preview) {
|
|
@@ -4041,7 +5505,7 @@ ${content}`;
|
|
|
4041
5505
|
}
|
|
4042
5506
|
const automationResult = await runIntegrationAutomation(
|
|
4043
5507
|
assistant,
|
|
4044
|
-
{ ...options, silent },
|
|
5508
|
+
{ ...options, silent, ignoreProjectArtifacts: true },
|
|
4045
5509
|
process.cwd()
|
|
4046
5510
|
);
|
|
4047
5511
|
const result = {
|
|
@@ -4081,6 +5545,46 @@ ${content}`;
|
|
|
4081
5545
|
}
|
|
4082
5546
|
return result;
|
|
4083
5547
|
}
|
|
5548
|
+
function shouldPersistProjectOnboardingDefaults(options) {
|
|
5549
|
+
return !options.preview && !shouldSkipAutomationForInstall(options) && isSupportedHostIde(options.ide);
|
|
5550
|
+
}
|
|
5551
|
+
function isProviderAssistant(value) {
|
|
5552
|
+
return Object.prototype.hasOwnProperty.call(VALID_MODES, value);
|
|
5553
|
+
}
|
|
5554
|
+
async function resolveProviderDefaultForAssistant(assistant, ide) {
|
|
5555
|
+
if (!isProviderAssistant(assistant)) {
|
|
5556
|
+
return void 0;
|
|
5557
|
+
}
|
|
5558
|
+
let mode;
|
|
5559
|
+
if (assistant === "codex" || assistant === "claude-code" || assistant === "gemini") {
|
|
5560
|
+
const dispatchMode = await resolveIntegrationDispatchMode({ assistant, hostIde: ide });
|
|
5561
|
+
mode = dispatchMode.mode ?? DEFAULT_PROVIDER_MODE[assistant];
|
|
5562
|
+
} else {
|
|
5563
|
+
mode = DEFAULT_PROVIDER_MODE[assistant];
|
|
5564
|
+
}
|
|
5565
|
+
if (!mode || !VALID_MODES[assistant].includes(mode)) {
|
|
5566
|
+
return void 0;
|
|
5567
|
+
}
|
|
5568
|
+
return `${assistant}.${mode}`;
|
|
5569
|
+
}
|
|
5570
|
+
async function persistProjectOnboardingDefaults(assistant, options) {
|
|
5571
|
+
const settingsPath = path21.join(process.cwd(), ".inspecto", "settings.local.json");
|
|
5572
|
+
const existingSettings = await readJSON(settingsPath);
|
|
5573
|
+
const resolvedHostIde = await resolveIntegrationHostIde({
|
|
5574
|
+
explicitIde: options.ide,
|
|
5575
|
+
cwd: process.cwd()
|
|
5576
|
+
});
|
|
5577
|
+
const providerDefault = resolvedHostIde.ide && resolvedHostIde.confidence !== "low" ? await resolveProviderDefaultForAssistant(assistant, resolvedHostIde.ide) : void 0;
|
|
5578
|
+
const mergedSettings = existingSettings && typeof existingSettings === "object" ? {
|
|
5579
|
+
...existingSettings,
|
|
5580
|
+
ide: options.ide,
|
|
5581
|
+
...providerDefault ? { "provider.default": providerDefault } : {}
|
|
5582
|
+
} : {
|
|
5583
|
+
ide: options.ide,
|
|
5584
|
+
...providerDefault ? { "provider.default": providerDefault } : {}
|
|
5585
|
+
};
|
|
5586
|
+
await writeJSON(settingsPath, mergedSettings);
|
|
5587
|
+
}
|
|
4084
5588
|
function shouldSkipAutomationForInstall(options) {
|
|
4085
5589
|
return options.scope === "user" && !options.preview;
|
|
4086
5590
|
}
|
|
@@ -4143,6 +5647,12 @@ function resolveInstallPlan(assistant, options) {
|
|
|
4143
5647
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
|
|
4144
5648
|
target: ".trae/skills/inspecto-onboarding/SKILL.md",
|
|
4145
5649
|
localSource: "skills/inspecto-onboarding-trae/SKILL.md"
|
|
5650
|
+
},
|
|
5651
|
+
{
|
|
5652
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/scripts/run-inspecto.sh`,
|
|
5653
|
+
target: ".trae/skills/inspecto-onboarding/scripts/run-inspecto.sh",
|
|
5654
|
+
localSource: "skills/inspecto-onboarding-trae/scripts/run-inspecto.sh",
|
|
5655
|
+
executable: true
|
|
4146
5656
|
}
|
|
4147
5657
|
],
|
|
4148
5658
|
successMessage: "Installed Trae skill to .trae/skills/inspecto-onboarding/SKILL.md",
|
|
@@ -4153,11 +5663,17 @@ function resolveInstallPlan(assistant, options) {
|
|
|
4153
5663
|
assets: [
|
|
4154
5664
|
{
|
|
4155
5665
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
|
|
4156
|
-
target: ".
|
|
5666
|
+
target: ".trae/skills/inspecto-onboarding/SKILL.md",
|
|
4157
5667
|
localSource: "skills/inspecto-onboarding-trae/SKILL.md"
|
|
5668
|
+
},
|
|
5669
|
+
{
|
|
5670
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/scripts/run-inspecto.sh`,
|
|
5671
|
+
target: ".trae/skills/inspecto-onboarding/scripts/run-inspecto.sh",
|
|
5672
|
+
localSource: "skills/inspecto-onboarding-trae/scripts/run-inspecto.sh",
|
|
5673
|
+
executable: true
|
|
4158
5674
|
}
|
|
4159
5675
|
],
|
|
4160
|
-
successMessage: "Installed Coco skill to .
|
|
5676
|
+
successMessage: "Installed Coco skill to .trae/skills/inspecto-onboarding/SKILL.md",
|
|
4161
5677
|
nextStep: "Start a new Coco session."
|
|
4162
5678
|
};
|
|
4163
5679
|
default:
|
|
@@ -4198,22 +5714,22 @@ function resolveCodexPlan(options) {
|
|
|
4198
5714
|
if (options.mode !== void 0) {
|
|
4199
5715
|
throw new Error("`--mode` is not supported for codex.");
|
|
4200
5716
|
}
|
|
4201
|
-
const baseDir = scope === "user" ?
|
|
5717
|
+
const baseDir = scope === "user" ? path21.join(homedir2(), ".agents/skills/inspecto-onboarding-codex") : ".agents/skills/inspecto-onboarding-codex";
|
|
4202
5718
|
return {
|
|
4203
5719
|
assets: [
|
|
4204
5720
|
{
|
|
4205
5721
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/SKILL.md`,
|
|
4206
|
-
target:
|
|
5722
|
+
target: path21.join(baseDir, "SKILL.md"),
|
|
4207
5723
|
localSource: "skills/inspecto-onboarding-codex/SKILL.md"
|
|
4208
5724
|
},
|
|
4209
5725
|
{
|
|
4210
5726
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/agents/openai.yaml`,
|
|
4211
|
-
target:
|
|
5727
|
+
target: path21.join(baseDir, "agents/openai.yaml"),
|
|
4212
5728
|
localSource: "skills/inspecto-onboarding-codex/agents/openai.yaml"
|
|
4213
5729
|
},
|
|
4214
5730
|
{
|
|
4215
5731
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh`,
|
|
4216
|
-
target:
|
|
5732
|
+
target: path21.join(baseDir, "scripts/run-inspecto.sh"),
|
|
4217
5733
|
executable: true,
|
|
4218
5734
|
localSource: "skills/inspecto-onboarding-codex/scripts/run-inspecto.sh"
|
|
4219
5735
|
}
|
|
@@ -4233,22 +5749,22 @@ function resolveClaudeCodePlan(options) {
|
|
|
4233
5749
|
if (scope !== "project" && scope !== "user") {
|
|
4234
5750
|
throw new Error(`Unknown Claude Code scope: ${scope}`);
|
|
4235
5751
|
}
|
|
4236
|
-
const baseDir = scope === "user" ?
|
|
5752
|
+
const baseDir = scope === "user" ? path21.join(homedir2(), ".claude/skills/inspecto-onboarding-claude-code") : ".claude/skills/inspecto-onboarding-claude-code";
|
|
4237
5753
|
return {
|
|
4238
5754
|
assets: [
|
|
4239
5755
|
{
|
|
4240
5756
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/SKILL.md`,
|
|
4241
|
-
target:
|
|
5757
|
+
target: path21.join(baseDir, "SKILL.md"),
|
|
4242
5758
|
localSource: "skills/inspecto-onboarding-claude-code/SKILL.md"
|
|
4243
5759
|
},
|
|
4244
5760
|
{
|
|
4245
5761
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/agents/openai.yaml`,
|
|
4246
|
-
target:
|
|
5762
|
+
target: path21.join(baseDir, "agents/openai.yaml"),
|
|
4247
5763
|
localSource: "skills/inspecto-onboarding-claude-code/agents/openai.yaml"
|
|
4248
5764
|
},
|
|
4249
5765
|
{
|
|
4250
5766
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh`,
|
|
4251
|
-
target:
|
|
5767
|
+
target: path21.join(baseDir, "scripts/run-inspecto.sh"),
|
|
4252
5768
|
executable: true,
|
|
4253
5769
|
localSource: "skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh"
|
|
4254
5770
|
}
|
|
@@ -4313,20 +5829,20 @@ async function loadAsset(asset) {
|
|
|
4313
5829
|
if (asset.localSource) {
|
|
4314
5830
|
const localPath = await resolveBundledAssetPath(asset.localSource);
|
|
4315
5831
|
if (localPath) {
|
|
4316
|
-
return await
|
|
5832
|
+
return await fs6.readFile(localPath, "utf-8");
|
|
4317
5833
|
}
|
|
4318
5834
|
}
|
|
4319
5835
|
return await downloadAsset(asset.source);
|
|
4320
5836
|
}
|
|
4321
5837
|
async function resolveBundledAssetPath(relativePath) {
|
|
4322
|
-
const startDir =
|
|
5838
|
+
const startDir = path21.dirname(fileURLToPath(import.meta.url));
|
|
4323
5839
|
let currentDir = startDir;
|
|
4324
5840
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
4325
|
-
const candidate =
|
|
5841
|
+
const candidate = path21.join(currentDir, relativePath);
|
|
4326
5842
|
if (await exists(candidate)) {
|
|
4327
5843
|
return candidate;
|
|
4328
5844
|
}
|
|
4329
|
-
const parent =
|
|
5845
|
+
const parent = path21.dirname(currentDir);
|
|
4330
5846
|
if (parent === currentDir) break;
|
|
4331
5847
|
currentDir = parent;
|
|
4332
5848
|
}
|
|
@@ -4434,6 +5950,9 @@ export {
|
|
|
4434
5950
|
reportCommandError,
|
|
4435
5951
|
apply,
|
|
4436
5952
|
detect,
|
|
5953
|
+
devLink,
|
|
5954
|
+
devStatus,
|
|
5955
|
+
devUnlink,
|
|
4437
5956
|
init,
|
|
4438
5957
|
collectDoctorResult,
|
|
4439
5958
|
doctor,
|