@geoql/doctor-core 1.3.3 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +278 -73
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -12,8 +12,8 @@ import { fileURLToPath } from "node:url";
|
|
|
12
12
|
import { babelParse, parse } from "@vue/compiler-sfc";
|
|
13
13
|
import { loadConfig } from "c12";
|
|
14
14
|
import { z } from "zod";
|
|
15
|
-
import { promisify } from "node:util";
|
|
16
15
|
import process$1 from "node:process";
|
|
16
|
+
import { promisify } from "node:util";
|
|
17
17
|
import pc from "picocolors";
|
|
18
18
|
//#region \0rolldown/runtime.js
|
|
19
19
|
var __defProp = Object.defineProperty;
|
|
@@ -337,7 +337,7 @@ async function buildKnipConfig(projectInfo, doctorConfig) {
|
|
|
337
337
|
"!**/knip.config.mjs"
|
|
338
338
|
],
|
|
339
339
|
ignoreFiles: [...doctorConfig.exclude, "knip.config.mjs"],
|
|
340
|
-
ignoreDependencies: [
|
|
340
|
+
ignoreDependencies: [.../* @__PURE__ */ new Set([
|
|
341
341
|
"vite-plus",
|
|
342
342
|
"@geoql/vue-doctor",
|
|
343
343
|
"@geoql/nuxt-doctor",
|
|
@@ -808,9 +808,10 @@ const RULE_ID$17 = "nuxt-doctor/nitro/compatibilityDate-set";
|
|
|
808
808
|
function checkCompatibilityDateSet(projectInfo, nuxtConfig) {
|
|
809
809
|
if (projectInfo.packageJsonPath === null) return [];
|
|
810
810
|
if (nuxtConfig?.compatibilityDate !== void 0) return [];
|
|
811
|
+
const file = projectInfo.nuxtConfigPath ?? projectInfo.packageJsonPath;
|
|
811
812
|
return [{
|
|
812
813
|
ruleId: RULE_ID$17,
|
|
813
|
-
file
|
|
814
|
+
file,
|
|
814
815
|
line: 1,
|
|
815
816
|
column: 1,
|
|
816
817
|
severity: "error",
|
|
@@ -824,9 +825,10 @@ const RULE_ID$16 = "nuxt-doctor/seo/lang-on-html";
|
|
|
824
825
|
function checkLangOnHtml(projectInfo, nuxtConfig) {
|
|
825
826
|
if (projectInfo.packageJsonPath === null) return [];
|
|
826
827
|
if (nuxtConfig?.htmlLang !== void 0) return [];
|
|
828
|
+
const file = projectInfo.nuxtConfigPath ?? projectInfo.packageJsonPath;
|
|
827
829
|
return [{
|
|
828
830
|
ruleId: RULE_ID$16,
|
|
829
|
-
file
|
|
831
|
+
file,
|
|
830
832
|
line: 1,
|
|
831
833
|
column: 1,
|
|
832
834
|
severity: "warn",
|
|
@@ -837,7 +839,7 @@ function checkLangOnHtml(projectInfo, nuxtConfig) {
|
|
|
837
839
|
//#endregion
|
|
838
840
|
//#region src/nuxt/post-checks/nitro-cloudflare-preset.ts
|
|
839
841
|
const RULE_ID$15 = "nuxt-doctor/cloudflare/nitro-cloudflare-preset";
|
|
840
|
-
const CLOUDFLARE_PRESETS = new Set([
|
|
842
|
+
const CLOUDFLARE_PRESETS = /* @__PURE__ */ new Set([
|
|
841
843
|
"cloudflare-pages",
|
|
842
844
|
"cloudflare-module",
|
|
843
845
|
"cloudflare"
|
|
@@ -847,9 +849,10 @@ function checkNitroCloudflarePreset(projectInfo) {
|
|
|
847
849
|
if (!projectInfo.hasWranglerConfig) return [];
|
|
848
850
|
if (projectInfo.nitroPreset === null) return [];
|
|
849
851
|
if (CLOUDFLARE_PRESETS.has(projectInfo.nitroPreset)) return [];
|
|
852
|
+
const file = projectInfo.nuxtConfigPath ?? projectInfo.packageJsonPath;
|
|
850
853
|
return [{
|
|
851
854
|
ruleId: RULE_ID$15,
|
|
852
|
-
file
|
|
855
|
+
file,
|
|
853
856
|
line: 1,
|
|
854
857
|
column: 1,
|
|
855
858
|
severity: "warn",
|
|
@@ -860,7 +863,7 @@ function checkNitroCloudflarePreset(projectInfo) {
|
|
|
860
863
|
//#endregion
|
|
861
864
|
//#region src/nuxt/post-checks/no-modules-incompatible-with-nuxt-4.ts
|
|
862
865
|
const RULE_ID$14 = "nuxt-doctor/modules-deps/no-modules-incompatible-with-nuxt-4";
|
|
863
|
-
const INCOMPATIBLE_MODULES = new Set([
|
|
866
|
+
const INCOMPATIBLE_MODULES = /* @__PURE__ */ new Set([
|
|
864
867
|
"@nuxtjs/composition-api",
|
|
865
868
|
"@nuxt/bridge",
|
|
866
869
|
"nuxt-property-decorator"
|
|
@@ -884,7 +887,7 @@ async function checkNoModulesIncompatibleWithNuxt4(projectInfo) {
|
|
|
884
887
|
//#endregion
|
|
885
888
|
//#region src/nuxt/post-checks/no-node-only-modules.ts
|
|
886
889
|
const RULE_ID$13 = "nuxt-doctor/cloudflare/no-node-only-modules";
|
|
887
|
-
const NODE_ONLY_MODULES = new Set(["fs-extra", "sharp"]);
|
|
890
|
+
const NODE_ONLY_MODULES = /* @__PURE__ */ new Set(["fs-extra", "sharp"]);
|
|
888
891
|
async function checkNoNodeOnlyModules(projectInfo) {
|
|
889
892
|
if (projectInfo.packageJsonPath === null) return [];
|
|
890
893
|
if (!projectInfo.hasWranglerConfig) return [];
|
|
@@ -1030,7 +1033,7 @@ async function checkOgImageViaSatori(projectInfo) {
|
|
|
1030
1033
|
if (!projectInfo.hasWranglerConfig) return [];
|
|
1031
1034
|
const deps = await readDeps(projectInfo.packageJsonPath);
|
|
1032
1035
|
if (deps === null) return [];
|
|
1033
|
-
const installed = new Set([...Object.keys(deps.dependencies), ...Object.keys(deps.devDependencies)]);
|
|
1036
|
+
const installed = /* @__PURE__ */ new Set([...Object.keys(deps.dependencies), ...Object.keys(deps.devDependencies)]);
|
|
1034
1037
|
if (OG_IMAGE_MODULES.some((name) => installed.has(name))) return [];
|
|
1035
1038
|
return [{
|
|
1036
1039
|
ruleId: RULE_ID$10,
|
|
@@ -1049,9 +1052,10 @@ function checkPresetDefinedForDeployTarget(projectInfo) {
|
|
|
1049
1052
|
if (projectInfo.packageJsonPath === null) return [];
|
|
1050
1053
|
if (!projectInfo.hasWranglerConfig) return [];
|
|
1051
1054
|
if (projectInfo.nitroPreset !== null) return [];
|
|
1055
|
+
const file = projectInfo.nuxtConfigPath ?? projectInfo.packageJsonPath;
|
|
1052
1056
|
return [{
|
|
1053
1057
|
ruleId: RULE_ID$9,
|
|
1054
|
-
file
|
|
1058
|
+
file,
|
|
1055
1059
|
line: 1,
|
|
1056
1060
|
column: 1,
|
|
1057
1061
|
severity: "warn",
|
|
@@ -1080,7 +1084,7 @@ async function checkRecommendedModulesInstalled(projectInfo) {
|
|
|
1080
1084
|
if (projectInfo.packageJsonPath === null) return [];
|
|
1081
1085
|
const deps = await readDeps(projectInfo.packageJsonPath);
|
|
1082
1086
|
if (deps === null) return [];
|
|
1083
|
-
const installed = new Set([...Object.keys(deps.dependencies), ...Object.keys(deps.devDependencies)]);
|
|
1087
|
+
const installed = /* @__PURE__ */ new Set([...Object.keys(deps.dependencies), ...Object.keys(deps.devDependencies)]);
|
|
1084
1088
|
return RECOMMENDED_GROUPS.filter((group) => !group.candidates.some((name) => installed.has(name))).map((group, index) => ({
|
|
1085
1089
|
ruleId: RULE_ID$8,
|
|
1086
1090
|
file: projectInfo.packageJsonPath,
|
|
@@ -1462,6 +1466,7 @@ async function detectProject(rootDirectory) {
|
|
|
1462
1466
|
});
|
|
1463
1467
|
return {
|
|
1464
1468
|
framework,
|
|
1469
|
+
frameworkDetected: framework === "vue" || framework === "nuxt",
|
|
1465
1470
|
rootDirectory,
|
|
1466
1471
|
packageJsonPath,
|
|
1467
1472
|
vueVersion,
|
|
@@ -1592,7 +1597,7 @@ const NUXT_PLUGIN_RULES = {
|
|
|
1592
1597
|
"nuxt-doctor/hydration/clientOnly-for-browser-apis": "error",
|
|
1593
1598
|
"nuxt-doctor/security/no-user-input-in-fetch-url": "warn"
|
|
1594
1599
|
};
|
|
1595
|
-
const VUE_OXLINT_RULE_IDS = new Set([
|
|
1600
|
+
const VUE_OXLINT_RULE_IDS = /* @__PURE__ */ new Set([
|
|
1596
1601
|
"vue/no-export-in-script-setup",
|
|
1597
1602
|
"vue/require-typed-ref",
|
|
1598
1603
|
"vue/no-arrow-functions-in-watch",
|
|
@@ -1640,7 +1645,7 @@ const VUE_OXLINT_RULE_IDS = new Set([
|
|
|
1640
1645
|
const NUXT_OXLINT_RULE_IDS = new Set(Object.keys(NUXT_PLUGIN_RULES));
|
|
1641
1646
|
function oxlintRuleAllowlist(framework) {
|
|
1642
1647
|
if (framework !== "nuxt") return VUE_OXLINT_RULE_IDS;
|
|
1643
|
-
return new Set([...VUE_OXLINT_RULE_IDS, ...NUXT_OXLINT_RULE_IDS]);
|
|
1648
|
+
return /* @__PURE__ */ new Set([...VUE_OXLINT_RULE_IDS, ...NUXT_OXLINT_RULE_IDS]);
|
|
1644
1649
|
}
|
|
1645
1650
|
function toOxlintSeverity(s) {
|
|
1646
1651
|
if (s === "error") return "error";
|
|
@@ -1819,12 +1824,24 @@ async function runOxlint(opts) {
|
|
|
1819
1824
|
let timer;
|
|
1820
1825
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1821
1826
|
const maxOutputBytes = opts.maxOutputBytes ?? 33554432;
|
|
1827
|
+
const onAbort = () => {
|
|
1828
|
+
child.kill("SIGKILL");
|
|
1829
|
+
finish(() => reject(/* @__PURE__ */ new Error("oxlint subprocess aborted")));
|
|
1830
|
+
};
|
|
1822
1831
|
const finish = (fn) => {
|
|
1823
1832
|
if (settled) return;
|
|
1824
1833
|
settled = true;
|
|
1825
1834
|
if (timer) clearTimeout(timer);
|
|
1835
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
1826
1836
|
fn();
|
|
1827
1837
|
};
|
|
1838
|
+
if (opts.signal) {
|
|
1839
|
+
if (opts.signal.aborted) {
|
|
1840
|
+
onAbort();
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
1844
|
+
}
|
|
1828
1845
|
if (timeoutMs > 0) timer = setTimeout(() => {
|
|
1829
1846
|
child.kill("SIGKILL");
|
|
1830
1847
|
finish(() => reject(/* @__PURE__ */ new Error(`oxlint subprocess timed out after ${timeoutMs}ms`)));
|
|
@@ -1906,7 +1923,8 @@ async function runScriptPass(opts) {
|
|
|
1906
1923
|
configPath,
|
|
1907
1924
|
oxlintBin,
|
|
1908
1925
|
timeoutMs: opts.timeoutMs,
|
|
1909
|
-
fix: opts.fix
|
|
1926
|
+
fix: opts.fix,
|
|
1927
|
+
signal: opts.signal
|
|
1910
1928
|
});
|
|
1911
1929
|
return {
|
|
1912
1930
|
diagnostics: toCanonicalDiagnostics(raw.diagnostics, opts.rootDir),
|
|
@@ -2819,7 +2837,7 @@ async function parseSfcDescriptor(absPath) {
|
|
|
2819
2837
|
const RULE_ID$5 = "vue-doctor/sfc/no-mixed-options-and-composition-api";
|
|
2820
2838
|
const MESSAGE$5 = "Mixed Options API in a <script setup> SFC. Move data/methods/computed/watch/lifecycle into <script setup> Composition API; keep <script> only for options like name/inheritAttrs. See https://vuejs.org/api/sfc-script-setup.html#usage-alongside-normal-script";
|
|
2821
2839
|
const RECOMMENDATION$5 = "Move this option into the <script setup> block using the Composition API, or keep <script> only for options-only config such as name or inheritAttrs.";
|
|
2822
|
-
const DISALLOWED = new Set([
|
|
2840
|
+
const DISALLOWED = /* @__PURE__ */ new Set([
|
|
2823
2841
|
"data",
|
|
2824
2842
|
"methods",
|
|
2825
2843
|
"computed",
|
|
@@ -3000,7 +3018,7 @@ function check$20(ctx) {
|
|
|
3000
3018
|
const RULE_ID$3 = "nuxt-doctor/seo/og-image-defined";
|
|
3001
3019
|
const MESSAGE$3 = "Page uses SEO meta but has no og:image property. Open Graph images improve social sharing previews. Add an og:image value or install @nuxtjs/og-image for automatic OG images.";
|
|
3002
3020
|
const RECOMMENDATION$3 = "Add ogImage: \"/path/to/image.png\" to useSeoMeta / useHead, or install @nuxtjs/og-image.";
|
|
3003
|
-
const SEO_PRIMITIVES$1 = new Set(["useSeoMeta", "useHead"]);
|
|
3021
|
+
const SEO_PRIMITIVES$1 = /* @__PURE__ */ new Set(["useSeoMeta", "useHead"]);
|
|
3004
3022
|
function isSeoWrapperName$1(name) {
|
|
3005
3023
|
if (SEO_PRIMITIVES$1.has(name)) return false;
|
|
3006
3024
|
return /^use[a-z]*seo/i.test(name);
|
|
@@ -3066,7 +3084,7 @@ function check$19(ctx) {
|
|
|
3066
3084
|
const RULE_ID$2 = "nuxt-doctor/seo/useSeoMeta-on-public-page";
|
|
3067
3085
|
const MESSAGE$2 = "Public page component is missing SEO metadata. Add useSeoMeta, useHead, or definePageMeta with a title so search engines can index it properly.";
|
|
3068
3086
|
const RECOMMENDATION$2 = "Call useSeoMeta({ title: \"...\" }) in<script setup> to define page title and meta tags for search engines and social previews.";
|
|
3069
|
-
const SEO_PRIMITIVES = new Set([
|
|
3087
|
+
const SEO_PRIMITIVES = /* @__PURE__ */ new Set([
|
|
3070
3088
|
"useSeoMeta",
|
|
3071
3089
|
"useHead",
|
|
3072
3090
|
"definePageMeta"
|
|
@@ -3259,7 +3277,7 @@ function ruleNoSharedKeyAcrossPages(pages) {
|
|
|
3259
3277
|
}
|
|
3260
3278
|
return diags;
|
|
3261
3279
|
}
|
|
3262
|
-
const BROWSER_GLOBALS = new Set([
|
|
3280
|
+
const BROWSER_GLOBALS = /* @__PURE__ */ new Set([
|
|
3263
3281
|
"window",
|
|
3264
3282
|
"document",
|
|
3265
3283
|
"navigator",
|
|
@@ -3303,7 +3321,7 @@ function ruleSsrSafeOnMountedOnlyForClient(pages) {
|
|
|
3303
3321
|
}
|
|
3304
3322
|
return diags;
|
|
3305
3323
|
}
|
|
3306
|
-
const CLIENT_SAFE_WRAPPERS = new Set([
|
|
3324
|
+
const CLIENT_SAFE_WRAPPERS = /* @__PURE__ */ new Set([
|
|
3307
3325
|
"onMounted",
|
|
3308
3326
|
"watchEffect",
|
|
3309
3327
|
"watch"
|
|
@@ -3739,7 +3757,7 @@ function check$8(ctx) {
|
|
|
3739
3757
|
//#endregion
|
|
3740
3758
|
//#region src/template/rules/no-javascript-uri.ts
|
|
3741
3759
|
const NODE_ATTRIBUTE$1 = 6;
|
|
3742
|
-
const URI_SINKS = new Set([
|
|
3760
|
+
const URI_SINKS = /* @__PURE__ */ new Set([
|
|
3743
3761
|
"href",
|
|
3744
3762
|
"src",
|
|
3745
3763
|
"srcset",
|
|
@@ -3972,7 +3990,7 @@ function check$2(ctx) {
|
|
|
3972
3990
|
}
|
|
3973
3991
|
//#endregion
|
|
3974
3992
|
//#region src/template/rules/no-missing-alt.ts
|
|
3975
|
-
const IMAGE_TAGS = new Set([
|
|
3993
|
+
const IMAGE_TAGS = /* @__PURE__ */ new Set([
|
|
3976
3994
|
"img",
|
|
3977
3995
|
"NuxtImg",
|
|
3978
3996
|
"nuxt-img"
|
|
@@ -4200,24 +4218,36 @@ async function audit(config = {}) {
|
|
|
4200
4218
|
exclude
|
|
4201
4219
|
});
|
|
4202
4220
|
const overallStart = performance.now();
|
|
4221
|
+
const maxDurationMs = config.maxDurationMs;
|
|
4222
|
+
const deadlineMs = maxDurationMs ?? 0;
|
|
4223
|
+
const budgetExhausted = () => maxDurationMs !== void 0 && performance.now() - overallStart >= maxDurationMs;
|
|
4224
|
+
const skippedPasses = [];
|
|
4225
|
+
const deadlineController = new AbortController();
|
|
4226
|
+
let deadlineTimer;
|
|
4227
|
+
if (maxDurationMs !== void 0) deadlineTimer = setTimeout(() => deadlineController.abort(), maxDurationMs);
|
|
4203
4228
|
const project = await detectProject(rootDir);
|
|
4204
4229
|
const templateStart = performance.now();
|
|
4205
|
-
|
|
4230
|
+
let templateDiagnostics = [];
|
|
4231
|
+
if (lintEnabled) if (budgetExhausted()) skippedPasses.push("template");
|
|
4232
|
+
else templateDiagnostics = await runTemplatePass({
|
|
4206
4233
|
files,
|
|
4207
4234
|
ruleOverrides: config.rules
|
|
4208
|
-
})
|
|
4235
|
+
});
|
|
4209
4236
|
const templateElapsed = performance.now() - templateStart;
|
|
4210
4237
|
const sfcStart = performance.now();
|
|
4211
|
-
|
|
4238
|
+
let sfcDiagnostics = [];
|
|
4239
|
+
if (lintEnabled) if (budgetExhausted()) skippedPasses.push("sfc");
|
|
4240
|
+
else sfcDiagnostics = await runSfcPass({
|
|
4212
4241
|
files,
|
|
4213
4242
|
ruleOverrides: config.rules,
|
|
4214
4243
|
projectInfo: project
|
|
4215
|
-
})
|
|
4244
|
+
});
|
|
4216
4245
|
const sfcElapsed = performance.now() - sfcStart;
|
|
4217
4246
|
let scriptDiagnostics = [];
|
|
4218
4247
|
let oxlintStderr = "";
|
|
4219
4248
|
const scriptStart = performance.now();
|
|
4220
|
-
if (lintEnabled)
|
|
4249
|
+
if (lintEnabled) if (budgetExhausted()) skippedPasses.push("lint");
|
|
4250
|
+
else try {
|
|
4221
4251
|
const result = await runScriptPass({
|
|
4222
4252
|
rootDir,
|
|
4223
4253
|
targetPath: rootDir,
|
|
@@ -4225,18 +4255,26 @@ async function audit(config = {}) {
|
|
|
4225
4255
|
framework: project.framework === "nuxt" ? "nuxt" : "vue",
|
|
4226
4256
|
fix: config.fix === true && config.scopeFiles === void 0,
|
|
4227
4257
|
fixExcludes: config.fixExcludes,
|
|
4228
|
-
exclude
|
|
4258
|
+
exclude,
|
|
4259
|
+
signal: deadlineController.signal
|
|
4229
4260
|
});
|
|
4230
4261
|
scriptDiagnostics = result.diagnostics;
|
|
4231
4262
|
oxlintStderr = result.stderr;
|
|
4232
4263
|
} catch (err) {
|
|
4233
|
-
|
|
4234
|
-
|
|
4264
|
+
if (deadlineController.signal.aborted) {
|
|
4265
|
+
skippedPasses.push("lint");
|
|
4266
|
+
oxlintStderr = "aborted: time-budget exhausted";
|
|
4267
|
+
} else {
|
|
4268
|
+
oxlintStderr = err instanceof Error ? err.message : String(err);
|
|
4269
|
+
if (process.env.DOCTOR_DEBUG) process.stderr.write(`[doctor-core] script pass failed: ${oxlintStderr}\n`);
|
|
4270
|
+
}
|
|
4235
4271
|
}
|
|
4236
4272
|
const scriptElapsed = performance.now() - scriptStart;
|
|
4237
4273
|
let deadCodeDiagnostics = [];
|
|
4238
4274
|
let deadCodeElapsed = 0;
|
|
4239
|
-
|
|
4275
|
+
const deadCodeEnabled = config.deadCode !== false && !budgetExhausted();
|
|
4276
|
+
if (config.deadCode !== false && !deadCodeEnabled) skippedPasses.push("dead-code");
|
|
4277
|
+
if (deadCodeEnabled) {
|
|
4240
4278
|
const deadCodeStart = performance.now();
|
|
4241
4279
|
try {
|
|
4242
4280
|
const { loadDoctorConfig } = await Promise.resolve().then(() => load_exports);
|
|
@@ -4255,48 +4293,54 @@ async function audit(config = {}) {
|
|
|
4255
4293
|
deadCodeElapsed = performance.now() - deadCodeStart;
|
|
4256
4294
|
}
|
|
4257
4295
|
let buildQualityDiagnostics = [];
|
|
4258
|
-
try {
|
|
4259
|
-
buildQualityDiagnostics = (await checkBuildQuality(project)).filter((d) => config.rules?.[d.ruleId] !== "off").map((d) => {
|
|
4260
|
-
const override = config.rules?.[d.ruleId];
|
|
4261
|
-
return override ? {
|
|
4262
|
-
...d,
|
|
4263
|
-
severity: override
|
|
4264
|
-
} : d;
|
|
4265
|
-
});
|
|
4266
|
-
} catch {}
|
|
4267
4296
|
let depsDiagnostics = [];
|
|
4268
|
-
try {
|
|
4269
|
-
depsDiagnostics = (await checkDeps(project)).filter((d) => config.rules?.[d.ruleId] !== "off").map((d) => {
|
|
4270
|
-
const override = config.rules?.[d.ruleId];
|
|
4271
|
-
return override ? {
|
|
4272
|
-
...d,
|
|
4273
|
-
severity: override
|
|
4274
|
-
} : d;
|
|
4275
|
-
});
|
|
4276
|
-
} catch {}
|
|
4277
4297
|
let nuxtProjectDiagnostics = [];
|
|
4278
|
-
if (project.framework === "nuxt") try {
|
|
4279
|
-
nuxtProjectDiagnostics = (await checkNuxtProject(project)).filter((d) => config.rules?.[d.ruleId] !== "off").map((d) => {
|
|
4280
|
-
const override = config.rules?.[d.ruleId];
|
|
4281
|
-
return override ? {
|
|
4282
|
-
...d,
|
|
4283
|
-
severity: override
|
|
4284
|
-
} : d;
|
|
4285
|
-
});
|
|
4286
|
-
} catch {}
|
|
4287
4298
|
let crossFileDiagnostics = [];
|
|
4288
|
-
if (
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
}
|
|
4298
|
-
}
|
|
4299
|
-
|
|
4299
|
+
if (budgetExhausted()) skippedPasses.push("project");
|
|
4300
|
+
else {
|
|
4301
|
+
try {
|
|
4302
|
+
buildQualityDiagnostics = (await checkBuildQuality(project)).filter((d) => config.rules?.[d.ruleId] !== "off").map((d) => {
|
|
4303
|
+
const override = config.rules?.[d.ruleId];
|
|
4304
|
+
return override ? {
|
|
4305
|
+
...d,
|
|
4306
|
+
severity: override
|
|
4307
|
+
} : d;
|
|
4308
|
+
});
|
|
4309
|
+
} catch {}
|
|
4310
|
+
try {
|
|
4311
|
+
depsDiagnostics = (await checkDeps(project)).filter((d) => config.rules?.[d.ruleId] !== "off").map((d) => {
|
|
4312
|
+
const override = config.rules?.[d.ruleId];
|
|
4313
|
+
return override ? {
|
|
4314
|
+
...d,
|
|
4315
|
+
severity: override
|
|
4316
|
+
} : d;
|
|
4317
|
+
});
|
|
4318
|
+
} catch {}
|
|
4319
|
+
if (project.framework === "nuxt") {
|
|
4320
|
+
try {
|
|
4321
|
+
nuxtProjectDiagnostics = (await checkNuxtProject(project)).filter((d) => config.rules?.[d.ruleId] !== "off").map((d) => {
|
|
4322
|
+
const override = config.rules?.[d.ruleId];
|
|
4323
|
+
return override ? {
|
|
4324
|
+
...d,
|
|
4325
|
+
severity: override
|
|
4326
|
+
} : d;
|
|
4327
|
+
});
|
|
4328
|
+
} catch {}
|
|
4329
|
+
try {
|
|
4330
|
+
crossFileDiagnostics = (await runCrossFilePass({
|
|
4331
|
+
files,
|
|
4332
|
+
projectInfo: project
|
|
4333
|
+
})).filter((d) => config.rules?.[d.ruleId] !== "off").map((d) => {
|
|
4334
|
+
const override = config.rules?.[d.ruleId];
|
|
4335
|
+
return override ? {
|
|
4336
|
+
...d,
|
|
4337
|
+
severity: override
|
|
4338
|
+
} : d;
|
|
4339
|
+
});
|
|
4340
|
+
} catch {}
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
if (deadlineTimer) clearTimeout(deadlineTimer);
|
|
4300
4344
|
const elapsedMs = performance.now() - overallStart;
|
|
4301
4345
|
const timings = {
|
|
4302
4346
|
template: templateElapsed,
|
|
@@ -4315,6 +4359,7 @@ async function audit(config = {}) {
|
|
|
4315
4359
|
const ruleCounts = countRuleCounts(diagnostics);
|
|
4316
4360
|
const projectInfo = {
|
|
4317
4361
|
framework: project.framework,
|
|
4362
|
+
frameworkDetected: project.frameworkDetected,
|
|
4318
4363
|
vueVersion: project.vueVersion,
|
|
4319
4364
|
nuxtVersion: project.nuxtVersion,
|
|
4320
4365
|
capabilities: [...project.capabilities].sort(),
|
|
@@ -4325,6 +4370,13 @@ async function audit(config = {}) {
|
|
|
4325
4370
|
else if (failOn !== "none") {
|
|
4326
4371
|
if ((failOn === "warn" ? scored.errorCount + scored.warnCount : scored.errorCount) > 0) exitCode = 1;
|
|
4327
4372
|
}
|
|
4373
|
+
const incomplete = skippedPasses.length > 0;
|
|
4374
|
+
const skippedCheckReasons = incomplete ? [{
|
|
4375
|
+
kind: "time-budget-exhausted",
|
|
4376
|
+
deadlineMs,
|
|
4377
|
+
elapsedMs,
|
|
4378
|
+
skippedPasses
|
|
4379
|
+
}] : void 0;
|
|
4328
4380
|
return {
|
|
4329
4381
|
rootDir,
|
|
4330
4382
|
filesScanned: files.length,
|
|
@@ -4338,7 +4390,9 @@ async function audit(config = {}) {
|
|
|
4338
4390
|
projectInfo,
|
|
4339
4391
|
elapsedMs,
|
|
4340
4392
|
timings,
|
|
4341
|
-
ruleCounts
|
|
4393
|
+
ruleCounts,
|
|
4394
|
+
incomplete,
|
|
4395
|
+
...skippedCheckReasons ? { skippedCheckReasons } : {}
|
|
4342
4396
|
};
|
|
4343
4397
|
}
|
|
4344
4398
|
//#endregion
|
|
@@ -4564,7 +4618,8 @@ async function loadDoctorConfig(rootDir, explicitPathOrOptions) {
|
|
|
4564
4618
|
source = "flag";
|
|
4565
4619
|
configFile = resolve(rootDir, explicitPath);
|
|
4566
4620
|
} else if (result._configFile) {
|
|
4567
|
-
|
|
4621
|
+
const ext = extname(result._configFile).slice(1);
|
|
4622
|
+
source = SOURCE_MAP[ext];
|
|
4568
4623
|
configFile = result._configFile;
|
|
4569
4624
|
} else if (Object.keys(raw).length > 0) {
|
|
4570
4625
|
source = "package.json";
|
|
@@ -4719,6 +4774,146 @@ async function planInit(options) {
|
|
|
4719
4774
|
};
|
|
4720
4775
|
}
|
|
4721
4776
|
//#endregion
|
|
4777
|
+
//#region src/ci/provider-detect.ts
|
|
4778
|
+
/**
|
|
4779
|
+
* Detects the CI provider from an explicit env snapshot. Takes the env as an
|
|
4780
|
+
* argument (instead of reading process.env) so provider detection stays a
|
|
4781
|
+
* deterministic, fully-testable pure function.
|
|
4782
|
+
*/
|
|
4783
|
+
function detectCiProvider(env) {
|
|
4784
|
+
if (env.GITHUB_ACTIONS === "true") return "github";
|
|
4785
|
+
if (env.GITLAB_CI === "true") return "gitlab";
|
|
4786
|
+
return "unknown";
|
|
4787
|
+
}
|
|
4788
|
+
//#endregion
|
|
4789
|
+
//#region src/ci/scaffold-workflow.ts
|
|
4790
|
+
function renderGithubWorkflow(opts) {
|
|
4791
|
+
return `name: doctor
|
|
4792
|
+
|
|
4793
|
+
on:
|
|
4794
|
+
push:
|
|
4795
|
+
branches: [main]
|
|
4796
|
+
pull_request:
|
|
4797
|
+
|
|
4798
|
+
permissions:
|
|
4799
|
+
contents: read
|
|
4800
|
+
pull-requests: write
|
|
4801
|
+
|
|
4802
|
+
jobs:
|
|
4803
|
+
audit:
|
|
4804
|
+
runs-on: ubuntu-latest
|
|
4805
|
+
steps:
|
|
4806
|
+
- uses: actions/checkout@v6
|
|
4807
|
+
|
|
4808
|
+
- name: Run doctor
|
|
4809
|
+
uses: geoql/doctor-action@v2
|
|
4810
|
+
with:
|
|
4811
|
+
framework: ${opts.framework}
|
|
4812
|
+
preset: recommended
|
|
4813
|
+
threshold: '0'
|
|
4814
|
+
fail-on: error
|
|
4815
|
+
pr-comment: '${opts.prComment}'
|
|
4816
|
+
api-key: \${{ secrets.DOCTOR_API_KEY }}
|
|
4817
|
+
project: \${{ github.repository }}
|
|
4818
|
+
`;
|
|
4819
|
+
}
|
|
4820
|
+
function renderGithubPrWorkflow(opts) {
|
|
4821
|
+
return `name: doctor-pr
|
|
4822
|
+
|
|
4823
|
+
on:
|
|
4824
|
+
pull_request:
|
|
4825
|
+
|
|
4826
|
+
permissions:
|
|
4827
|
+
contents: read
|
|
4828
|
+
pull-requests: write
|
|
4829
|
+
|
|
4830
|
+
jobs:
|
|
4831
|
+
review:
|
|
4832
|
+
runs-on: ubuntu-latest
|
|
4833
|
+
steps:
|
|
4834
|
+
- uses: actions/checkout@v6
|
|
4835
|
+
with:
|
|
4836
|
+
fetch-depth: 0
|
|
4837
|
+
|
|
4838
|
+
- name: Doctor PR review
|
|
4839
|
+
uses: geoql/doctor-action@v2
|
|
4840
|
+
with:
|
|
4841
|
+
framework: ${opts.framework}
|
|
4842
|
+
preset: recommended
|
|
4843
|
+
fail-on: error
|
|
4844
|
+
diff: 'true'
|
|
4845
|
+
pr-comment: 'true'
|
|
4846
|
+
comment-mode: both
|
|
4847
|
+
api-key: \${{ secrets.DOCTOR_API_KEY }}
|
|
4848
|
+
project: \${{ github.repository }}
|
|
4849
|
+
`;
|
|
4850
|
+
}
|
|
4851
|
+
function renderGitlabPipeline(opts) {
|
|
4852
|
+
return `# Doctor quality gate — https://docs.the-doctor.report
|
|
4853
|
+
doctor:
|
|
4854
|
+
image: node:24
|
|
4855
|
+
stage: test
|
|
4856
|
+
script:
|
|
4857
|
+
- npx -y @geoql/${opts.bin}@latest --fail-on error
|
|
4858
|
+
rules:
|
|
4859
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
4860
|
+
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
4861
|
+
`;
|
|
4862
|
+
}
|
|
4863
|
+
async function scaffoldCiWorkflow(opts) {
|
|
4864
|
+
const requested = opts.provider ?? "auto";
|
|
4865
|
+
let provider;
|
|
4866
|
+
if (requested === "auto") provider = detectCiProvider(opts.env ?? process$1.env) === "gitlab" ? "gitlab" : "github";
|
|
4867
|
+
else provider = requested;
|
|
4868
|
+
const writes = [];
|
|
4869
|
+
let conflict = false;
|
|
4870
|
+
let conflictPath = null;
|
|
4871
|
+
if (provider === "gitlab") {
|
|
4872
|
+
const pipelinePath = join(opts.dir, ".gitlab-ci.yml");
|
|
4873
|
+
if (!opts.force && await pathExists(pipelinePath)) {
|
|
4874
|
+
conflict = true;
|
|
4875
|
+
conflictPath = pipelinePath;
|
|
4876
|
+
} else writes.push({
|
|
4877
|
+
path: pipelinePath,
|
|
4878
|
+
content: renderGitlabPipeline({ bin: opts.bin })
|
|
4879
|
+
});
|
|
4880
|
+
return {
|
|
4881
|
+
writes,
|
|
4882
|
+
conflict,
|
|
4883
|
+
conflictPath,
|
|
4884
|
+
provider
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
const workflowPath = join(opts.dir, ".github", "workflows", "doctor.yml");
|
|
4888
|
+
if (!opts.force && await pathExists(workflowPath)) {
|
|
4889
|
+
conflict = true;
|
|
4890
|
+
conflictPath = workflowPath;
|
|
4891
|
+
return {
|
|
4892
|
+
writes,
|
|
4893
|
+
conflict,
|
|
4894
|
+
conflictPath,
|
|
4895
|
+
provider
|
|
4896
|
+
};
|
|
4897
|
+
}
|
|
4898
|
+
writes.push({
|
|
4899
|
+
path: workflowPath,
|
|
4900
|
+
content: renderGithubWorkflow({
|
|
4901
|
+
framework: opts.framework,
|
|
4902
|
+
prComment: opts.noComments !== true
|
|
4903
|
+
})
|
|
4904
|
+
});
|
|
4905
|
+
if (opts.pr === true) writes.push({
|
|
4906
|
+
path: join(opts.dir, ".github", "workflows", "doctor-pr.yml"),
|
|
4907
|
+
content: renderGithubPrWorkflow({ framework: opts.framework })
|
|
4908
|
+
});
|
|
4909
|
+
return {
|
|
4910
|
+
writes,
|
|
4911
|
+
conflict,
|
|
4912
|
+
conflictPath,
|
|
4913
|
+
provider
|
|
4914
|
+
};
|
|
4915
|
+
}
|
|
4916
|
+
//#endregion
|
|
4722
4917
|
//#region src/git-scope.ts
|
|
4723
4918
|
const execFileAsync = promisify(execFile);
|
|
4724
4919
|
const SOURCE_EXTENSIONS = [
|
|
@@ -5119,6 +5314,7 @@ function buildDoctorReport(input) {
|
|
|
5119
5314
|
},
|
|
5120
5315
|
projectInfo: {
|
|
5121
5316
|
framework: input.projectInfo.framework,
|
|
5317
|
+
frameworkDetected: input.projectInfo.frameworkDetected,
|
|
5122
5318
|
vueVersion: input.projectInfo.vueVersion,
|
|
5123
5319
|
nuxtVersion: input.projectInfo.nuxtVersion,
|
|
5124
5320
|
capabilities: [...input.projectInfo.capabilities].sort(),
|
|
@@ -5162,7 +5358,16 @@ function buildDoctorReport(input) {
|
|
|
5162
5358
|
timing: {
|
|
5163
5359
|
elapsedMs: input.elapsedMs,
|
|
5164
5360
|
analyzedFileCount: input.analyzedFileCount
|
|
5165
|
-
}
|
|
5361
|
+
},
|
|
5362
|
+
...input.incomplete !== void 0 && input.incomplete ? {
|
|
5363
|
+
incomplete: input.incomplete,
|
|
5364
|
+
skippedCheckReasons: input.skippedCheckReasons?.map((r) => ({
|
|
5365
|
+
kind: r.kind,
|
|
5366
|
+
deadlineMs: r.deadlineMs,
|
|
5367
|
+
elapsedMs: r.elapsedMs,
|
|
5368
|
+
skippedPasses: [...r.skippedPasses]
|
|
5369
|
+
}))
|
|
5370
|
+
} : {}
|
|
5166
5371
|
};
|
|
5167
5372
|
}
|
|
5168
5373
|
function jsonReport(input) {
|
|
@@ -5643,6 +5848,6 @@ async function pushFindings(opts) {
|
|
|
5643
5848
|
}
|
|
5644
5849
|
}
|
|
5645
5850
|
//#endregion
|
|
5646
|
-
export { BUILT_IN_RECOMMENDED, ConfigCycleError, ConfigFileNotFoundError, DOCTOR_REPORT_SCHEMA_VERSION, DeadCodeImportFailed, DeadCodeTimeoutError, DoctorUserConfigSchema, InvalidConfigError, LevelSchema, OxlintOutputTooLarge, OxlintSpawnFailed, RULE_REGISTRY, RuleEntrySchema, SCORE_DIMENSIONS, agentReport, applyInlineDisables, audit, buildDoctorReport, buildJsonSchema, buildPushPayload, categoriesForDimension, checkBuildQuality, checkDeadCode, checkDeps, checkNuxtProject, dedupeDeadCodeAgainstLint, defineConfig, detectProject, detectSummary, dimensionForCategory, docsUrl, encodeAnnotation, encodeAnnotations, filterReportByRules, filterRuleIdsByCategory, findMonorepoRoot, format, isNuxtLayoutFile, isNuxtPageFile, isNuxtServerFile, isScoreDimension, jsonCompactReport, jsonReport, listChangedFiles, listRules, listWorkspacePackages, loadAllRuleDocs, loadDoctorConfig, loadRuleDoc, mergeCliOverrides, normalizeInitAnswers, parseDirectives, parseExcludeList, planInit, prCommentReport, prettyReport, pushFindings, renderAgentPlaybook, renderDetectSummary, renderRulePrompt, renderVerboseTrace, resolveCategoryScope, runCrossFilePass, sarifReport, scoreDiagnostics, stripFindings, validateConfig };
|
|
5851
|
+
export { BUILT_IN_RECOMMENDED, ConfigCycleError, ConfigFileNotFoundError, DOCTOR_REPORT_SCHEMA_VERSION, DeadCodeImportFailed, DeadCodeTimeoutError, DoctorUserConfigSchema, InvalidConfigError, LevelSchema, OxlintOutputTooLarge, OxlintSpawnFailed, RULE_REGISTRY, RuleEntrySchema, SCORE_DIMENSIONS, agentReport, applyInlineDisables, audit, buildDoctorReport, buildJsonSchema, buildPushPayload, categoriesForDimension, checkBuildQuality, checkDeadCode, checkDeps, checkNuxtProject, dedupeDeadCodeAgainstLint, defineConfig, detectCiProvider, detectProject, detectSummary, dimensionForCategory, docsUrl, encodeAnnotation, encodeAnnotations, filterReportByRules, filterRuleIdsByCategory, findMonorepoRoot, format, isNuxtLayoutFile, isNuxtPageFile, isNuxtServerFile, isScoreDimension, jsonCompactReport, jsonReport, listChangedFiles, listRules, listWorkspacePackages, loadAllRuleDocs, loadDoctorConfig, loadRuleDoc, mergeCliOverrides, normalizeInitAnswers, parseDirectives, parseExcludeList, planInit, prCommentReport, prettyReport, pushFindings, renderAgentPlaybook, renderDetectSummary, renderRulePrompt, renderVerboseTrace, resolveCategoryScope, runCrossFilePass, sarifReport, scaffoldCiWorkflow, scoreDiagnostics, stripFindings, validateConfig };
|
|
5647
5852
|
|
|
5648
5853
|
//# sourceMappingURL=index.js.map
|