svelte-vitals 0.54.2 → 0.54.4
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/dist/{analyze-Dl-VIKsc.js → analyze-BH6tM7-d.js} +3 -3
- package/dist/bin.js +6 -6
- package/dist/{ci-DmdNtgzC.js → ci-DdxIti4m.js} +1 -1
- package/dist/{complete-Dr0cRw1j.js → complete-DopIU_Sb.js} +6 -6
- package/dist/{docs-DZD2UDg-.js → docs-hor--7iY.js} +2 -2
- package/dist/{explain-C4eXHI7I.js → explain-BQWRixs6.js} +2 -2
- package/dist/gunshi-registry.js +2 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +2 -2
- package/dist/install/improve-skill-content.js +10 -8
- package/dist/install/skill-content.js +1 -1
- package/dist/{install-D-OIDsrS.js → install-BEsNEdKk.js} +2 -2
- package/dist/{src-BV_3ylhu.js → src-BhVse1Wr.js} +250 -240
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as findUnknownRuleIds, h as readPackageVersion, i as run, l as knownRuleIds, m as readCoreVersion, p as isReporterName } from "./src-
|
|
1
|
+
import { c as findUnknownRuleIds, h as readPackageVersion, i as run, l as knownRuleIds, m as readCoreVersion, p as isReporterName } from "./src-BhVse1Wr.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { terminalSafe } from "@svelte-vitals/core/internal";
|
|
@@ -53,7 +53,7 @@ async function selectAppPrompt(apps, message) {
|
|
|
53
53
|
message,
|
|
54
54
|
options: apps.map((a) => ({
|
|
55
55
|
value: a,
|
|
56
|
-
label: a
|
|
56
|
+
label: terminalSafe(a)
|
|
57
57
|
})),
|
|
58
58
|
initialValue: apps[0]
|
|
59
59
|
});
|
|
@@ -77,7 +77,7 @@ function clackPrompts() {
|
|
|
77
77
|
},
|
|
78
78
|
selectApp: (apps) => selectAppPrompt(apps, "Multiple SvelteKit apps found — which one should the Vite/config targets go into?"),
|
|
79
79
|
confirm: async (planText) => {
|
|
80
|
-
const res = await p.confirm({ message: `Apply this plan?\n${planText}` });
|
|
80
|
+
const res = await p.confirm({ message: `Apply this plan?\n${terminalSafe(planText)}` });
|
|
81
81
|
return p.isCancel(res) ? false : Boolean(res);
|
|
82
82
|
}
|
|
83
83
|
};
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { g as realIO, i as resolveLocale, m as consoleIO, n as runAnalyzeCliGunshi } from "./analyze-
|
|
2
|
+
import { g as realIO, i as resolveLocale, m as consoleIO, n as runAnalyzeCliGunshi } from "./analyze-BH6tM7-d.js";
|
|
3
3
|
import { terminalSafe } from "@svelte-vitals/core/internal";
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
/**
|
|
@@ -17,35 +17,35 @@ async function runCli(argv, io = consoleIO, env = process.env) {
|
|
|
17
17
|
try {
|
|
18
18
|
const locale = resolveLocale(env);
|
|
19
19
|
if (argv[0] === "complete") {
|
|
20
|
-
const { runCompleteCliGunshi } = await import("./complete-
|
|
20
|
+
const { runCompleteCliGunshi } = await import("./complete-DopIU_Sb.js");
|
|
21
21
|
return {
|
|
22
22
|
code: await runCompleteCliGunshi(argv, io),
|
|
23
23
|
exit: "natural"
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
if (argv[0] === "docs") {
|
|
27
|
-
const { runDocsCliGunshi } = await import("./docs-
|
|
27
|
+
const { runDocsCliGunshi } = await import("./docs-hor--7iY.js");
|
|
28
28
|
return {
|
|
29
29
|
code: await runDocsCliGunshi(argv.slice(1), io, locale),
|
|
30
30
|
exit: "natural"
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
if (argv[0] === "explain") {
|
|
34
|
-
const { runExplainCliGunshi } = await import("./explain-
|
|
34
|
+
const { runExplainCliGunshi } = await import("./explain-BQWRixs6.js");
|
|
35
35
|
return {
|
|
36
36
|
code: await runExplainCliGunshi(argv.slice(1), io, locale),
|
|
37
37
|
exit: "natural"
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
if (argv[0] === "install") {
|
|
41
|
-
const { runInstallCliGunshi } = await import("./install-
|
|
41
|
+
const { runInstallCliGunshi } = await import("./install-BEsNEdKk.js").then((n) => n.n);
|
|
42
42
|
return {
|
|
43
43
|
code: await runInstallCliGunshi(argv.slice(1), io, locale),
|
|
44
44
|
exit: "immediate"
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
if (argv[0] === "ci") {
|
|
48
|
-
const { runCiCliGunshi } = await import("./ci-
|
|
48
|
+
const { runCiCliGunshi } = await import("./ci-DdxIti4m.js");
|
|
49
49
|
return {
|
|
50
50
|
code: await runCiCliGunshi(argv.slice(1), {
|
|
51
51
|
...realIO(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as guardArgs, c as stripUnknownFlags, g as realIO, l as suggestClosest, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
1
|
+
import { a as guardArgs, c as stripUnknownFlags, g as realIO, l as suggestClosest, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-BH6tM7-d.js";
|
|
2
2
|
import { a as planWorkflowWrite, i as buildWorkflowYaml, n as ACTION_VERSION, r as WORKFLOW_PATH, t as ACTION_SHA } from "./action-pin.generated-C2XJJ3NW.js";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { cli } from "gunshi/bone";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { f as REPORTER_NAMES } from "./src-
|
|
2
|
-
import { d as FAIL_ON_VALUES, f as TREAT_DYNAMIC_AS_VALUES, m as consoleIO, t as ROOT_ARGS, u as CATEGORIES } from "./analyze-
|
|
3
|
-
import { t as INSTALL_ARGS } from "./install-
|
|
4
|
-
import { CI_ARGS, CI_UPGRADE_ARGS } from "./ci-
|
|
5
|
-
import { DOCS_LIST_ARGS, DOCS_ROOT_ARGS, DOCS_SHOW_ARGS } from "./docs-
|
|
6
|
-
import { EXPLAIN_ARGS } from "./explain-
|
|
1
|
+
import { f as REPORTER_NAMES } from "./src-BhVse1Wr.js";
|
|
2
|
+
import { d as FAIL_ON_VALUES, f as TREAT_DYNAMIC_AS_VALUES, m as consoleIO, t as ROOT_ARGS, u as CATEGORIES } from "./analyze-BH6tM7-d.js";
|
|
3
|
+
import { t as INSTALL_ARGS } from "./install-BEsNEdKk.js";
|
|
4
|
+
import { CI_ARGS, CI_UPGRADE_ARGS } from "./ci-DdxIti4m.js";
|
|
5
|
+
import { DOCS_LIST_ARGS, DOCS_ROOT_ARGS, DOCS_SHOW_ARGS } from "./docs-hor--7iY.js";
|
|
6
|
+
import { EXPLAIN_ARGS } from "./explain-BQWRixs6.js";
|
|
7
7
|
import { cli } from "gunshi/bone";
|
|
8
8
|
import { define } from "gunshi/definition";
|
|
9
9
|
import { kebabnize } from "gunshi/utils";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { l as knownRuleIds } from "./src-
|
|
2
|
-
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
1
|
+
import { l as knownRuleIds } from "./src-BhVse1Wr.js";
|
|
2
|
+
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-BH6tM7-d.js";
|
|
3
3
|
import { docsUrlFor } from "@svelte-vitals/core/internal";
|
|
4
4
|
import { cli } from "gunshi/bone";
|
|
5
5
|
import { define } from "gunshi/definition";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { l as knownRuleIds } from "./src-
|
|
2
|
-
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
1
|
+
import { l as knownRuleIds } from "./src-BhVse1Wr.js";
|
|
2
|
+
import { a as guardArgs, c as stripUnknownFlags, l as suggestClosest, m as consoleIO, o as splitAtTerminator, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-BH6tM7-d.js";
|
|
3
3
|
import { CATEGORIES } from "@svelte-vitals/core";
|
|
4
4
|
import { allRules, explainRule } from "@svelte-vitals/core/internal";
|
|
5
5
|
import { cli } from "gunshi/bone";
|
package/dist/gunshi-registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ROOT_ARGS } from "./analyze-
|
|
2
|
-
import { t as INSTALL_ARGS } from "./install-
|
|
1
|
+
import { t as ROOT_ARGS } from "./analyze-BH6tM7-d.js";
|
|
2
|
+
import { t as INSTALL_ARGS } from "./install-BEsNEdKk.js";
|
|
3
3
|
import { t as JA_ARG_DESCRIPTIONS } from "./ja-BDLP3KLG.js";
|
|
4
4
|
export { INSTALL_ARGS, JA_ARG_DESCRIPTIONS, ROOT_ARGS };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Category, Config, JsonReport, Result, RuleSetting, Severity, defineConfig } from "@svelte-vitals/core";
|
|
2
|
-
import { BranchStep, HeadTag, RuleOptionsSpec, SuppressionDirective } from "@svelte-vitals/core/internal";
|
|
2
|
+
import { BranchStep, HeadTag, RuleOptionsSpec, Runtime, SuppressionDirective } from "@svelte-vitals/core/internal";
|
|
3
3
|
import { AST } from "svelte/compiler";
|
|
4
4
|
//#region src/providers/source/imports.d.ts
|
|
5
5
|
/** A resolved import binding: which module, and which export ('default' for default imports). */
|
|
@@ -135,6 +135,14 @@ declare class ProjectError extends Error {
|
|
|
135
135
|
//#region src/route-matcher.d.ts
|
|
136
136
|
declare function routeMatcher(glob: string | undefined): (route: string) => boolean;
|
|
137
137
|
//#endregion
|
|
138
|
+
//#region src/runtime/node.d.ts
|
|
139
|
+
/**
|
|
140
|
+
* Node implementation of the core Runtime abstraction (design §8). This is the
|
|
141
|
+
* only place in the static-mode pipeline allowed to touch `node:` APIs; the
|
|
142
|
+
* provider and rules stay runtime-agnostic by going through this interface.
|
|
143
|
+
*/
|
|
144
|
+
declare function createNodeRuntime(): Runtime;
|
|
145
|
+
//#endregion
|
|
138
146
|
//#region src/rules-config.d.ts
|
|
139
147
|
/** Rule ids passed to --rules/--ignore that aren't part of the built-in registry. */
|
|
140
148
|
declare function findUnknownRuleIds(ids: string[]): string[];
|
|
@@ -370,4 +378,4 @@ declare function applyScope(results: Result[], opts: ApplyScopeOptions): Promise
|
|
|
370
378
|
*/
|
|
371
379
|
declare function run(opts?: RunOptions): Promise<number>;
|
|
372
380
|
//#endregion
|
|
373
|
-
export { AnalyzeOptions, AnalyzeResult, ApplyScopeOptions, CONFIG_FILENAMES, type LoadedConfigFile, type ParseCache, ProjectError, RunOptions, analyzeProject, applyScope, defineConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, routeMatcher, ruleOptionsSpec, run, spinnerEnabled };
|
|
381
|
+
export { AnalyzeOptions, AnalyzeResult, ApplyScopeOptions, CONFIG_FILENAMES, type LoadedConfigFile, type ParseCache, ProjectError, RunOptions, analyzeProject, applyScope, createNodeRuntime, defineConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, routeMatcher, ruleOptionsSpec, run, spinnerEnabled };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as spinnerEnabled, b as ProjectError, c as findUnknownRuleIds, d as ruleOptionsSpec, i as run, l as knownRuleIds, n as applyScope, o as CONFIG_FILENAMES, r as defineConfig, s as loadConfigFile, t as analyzeProject, u as registryTag, y as routeMatcher } from "./src-
|
|
2
|
-
export { CONFIG_FILENAMES, ProjectError, analyzeProject, applyScope, defineConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, routeMatcher, ruleOptionsSpec, run, spinnerEnabled };
|
|
1
|
+
import { a as spinnerEnabled, b as ProjectError, c as findUnknownRuleIds, d as ruleOptionsSpec, i as run, l as knownRuleIds, n as applyScope, o as CONFIG_FILENAMES, r as defineConfig, s as loadConfigFile, t as analyzeProject, u as registryTag, x as createNodeRuntime, y as routeMatcher } from "./src-BhVse1Wr.js";
|
|
2
|
+
export { CONFIG_FILENAMES, ProjectError, analyzeProject, applyScope, createNodeRuntime, defineConfig, findUnknownRuleIds, knownRuleIds, loadConfigFile, registryTag, routeMatcher, ruleOptionsSpec, run, spinnerEnabled };
|
|
@@ -42,8 +42,8 @@ without a judgment call of its own.
|
|
|
42
42
|
which you delete before finishing. If asked to "just fix it", decline and
|
|
43
43
|
point to \`improve-svelte execute <plan>\`, to running the plan with any
|
|
44
44
|
agent, or to the \`svelte-vitals\` skill's own diff/staged gate.
|
|
45
|
-
2. **No mutating operations.** No \`--fix\`-style flags (svelte-vitals
|
|
46
|
-
none
|
|
45
|
+
2. **No mutating operations.** No \`--fix\`-style flags (svelte-vitals ships
|
|
46
|
+
none, by design), no code edits, no commits, no formatters, no
|
|
47
47
|
dependency installs. Run svelte-vitals read-only, for evidence only.
|
|
48
48
|
3. **Plans must be fully self-contained.** The executor has zero context
|
|
49
49
|
from this conversation. Never write "fix it like seo/title-presence above" — inline
|
|
@@ -123,7 +123,7 @@ Get the machine map before applying judgment:
|
|
|
123
123
|
Audit against svelte-vitals' six categories: SEO, Performance, Correctness,
|
|
124
124
|
Security, Architecture, Accessibility (see the rule catalog below for the
|
|
125
125
|
full "hunt for" list per category, generated from svelte-vitals' own rule
|
|
126
|
-
metadata
|
|
126
|
+
metadata).
|
|
127
127
|
|
|
128
128
|
For anything beyond a small project, fan out read-only subagents — one per
|
|
129
129
|
category. Each subagent prompt must include: the recon facts (stack,
|
|
@@ -167,7 +167,8 @@ Severity here is leverage-driven, **not** svelte-vitals' raw rule severity:
|
|
|
167
167
|
- **LOW** — polish and hygiene: an \`info\`-severity finding on a low-traffic
|
|
168
168
|
route, a namespace import that could be more tree-shakeable.
|
|
169
169
|
|
|
170
|
-
After the table, list
|
|
170
|
+
After the table, list the **missed opportunities** worth naming — additive
|
|
171
|
+
improvements
|
|
171
172
|
svelte-vitals doesn't (and by design won't) flag, since it's a static
|
|
172
173
|
analyzer, not a runtime auditor: actual Core Web Vitals measurement, a
|
|
173
174
|
missing \`sitemap.xml\` entry for a new route, structured-data types beyond
|
|
@@ -198,10 +199,11 @@ order, dependencies between plans, and a status column.
|
|
|
198
199
|
|
|
199
200
|
## Rule catalog
|
|
200
201
|
|
|
201
|
-
(This section is generated
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
(This section is generated from svelte-vitals' own rule metadata — every
|
|
203
|
+
rule's id, title, severity, rationale, docs link and, where the rule ships
|
|
204
|
+
one, its canonical fix — grouped by category. It reflects the svelte-vitals
|
|
205
|
+
source this skill was generated from; \`npx svelte-vitals explain --list\` is
|
|
206
|
+
the authority for what the version installed in this project checks.)
|
|
205
207
|
|
|
206
208
|
${ruleDigest()}
|
|
207
209
|
|
|
@@ -103,4 +103,4 @@ alwaysApply: false
|
|
|
103
103
|
---\n\n${sharedBody(header)}`;
|
|
104
104
|
}
|
|
105
105
|
//#endregion
|
|
106
|
-
export { REPO_SKILLS_HEADER, buildCursorRules, buildSkillMarkdown, installHeader,
|
|
106
|
+
export { REPO_SKILLS_HEADER, buildCursorRules, buildSkillMarkdown, installHeader, isInertUntilConfigured, oneLine, ruleDigest };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { _ as hasDep, g as discoverApps, h as readPackageVersion, o as CONFIG_FILENAMES, v as readPkg } from "./src-
|
|
3
|
-
import { a as guardArgs, c as stripUnknownFlags, g as realIO, h as clackPrompts, m as consoleIO, o as splitAtTerminator, p as toList, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-
|
|
2
|
+
import { _ as hasDep, g as discoverApps, h as readPackageVersion, o as CONFIG_FILENAMES, v as readPkg } from "./src-BhVse1Wr.js";
|
|
3
|
+
import { a as guardArgs, c as stripUnknownFlags, g as realIO, h as clackPrompts, m as consoleIO, o as splitAtTerminator, p as toList, r as localizedOptionsSection, s as stripAutoVersionLine } from "./analyze-BH6tM7-d.js";
|
|
4
4
|
import { a as planWorkflowWrite, i as buildWorkflowYaml, n as ACTION_VERSION, r as WORKFLOW_PATH, t as ACTION_SHA } from "./action-pin.generated-C2XJJ3NW.js";
|
|
5
5
|
import { buildCursorRules, installHeader } from "./install/skill-content.js";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { CATEGORIES, defineConfig, defineConfig as defineConfig$1 } from "@svelte-vitals/core";
|
|
4
|
-
import { ANCESTRY_DEPENDENT_TAGS, ASIDE_DEMOTING_TAGS, CHILD_NODE_KEYS, IDREF_ATTRS, NAMING_ATTRS, ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, addFactsDirectives, allRules, attrText, attrTextOf, attrValue, attrValueOf, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, decodeFragmentId, defaultConfig, docsUrlFor, findAttr, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, isClassicScriptType, isPenalized, isTopFragment, lineOf, noColorPalette, parseSvelte, resolveKitAliases, resolveKitPathsBase, resolveLandmark, resolveRepoLocalPath, resolveRuleOptions, runAnalysis, scoreColor, selectRules, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleSetting, valueFromNodes, withReadLimit } from "@svelte-vitals/core/internal";
|
|
4
|
+
import { ANCESTRY_DEPENDENT_TAGS, ASIDE_DEMOTING_TAGS, CHILD_NODE_KEYS, IDREF_ATTRS, NAMING_ATTRS, ROBOTS_SOURCE_PATHS, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, VITE_CONFIG_FILES, addFactsDirectives, allRules, attrText, attrTextOf, attrValue, attrValueOf, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, collectSuppressions, compileOverrides, computeHealth, decodeFragmentId, defaultConfig, docsUrlFor, findAttr, findMinifyDisabled, foldOccurrences, formatAgentReport, formatConsoleReport, formatFailedRuleWarning, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, isClassicScriptType, isPenalized, isPlainObject, isTopFragment, lineOf, noColorPalette, parseSvelte, resolveKitAliases, resolveKitPathsBase, resolveLandmark, resolveRepoLocalPath, resolveRuleOptions, runAnalysis, scoreColor, selectRules, settingSeverity, shouldSkipRangeCheck, skippedFileWarnings, splitTokens, stripTextDirective, summarize, terminalSafe, textFromNodes, unknownDirectiveIds, validateRuleSetting, valueFromNodes, withReadLimit } from "@svelte-vitals/core/internal";
|
|
5
5
|
import { access, glob, readFile, stat } from "node:fs/promises";
|
|
6
6
|
import { getAgentProfile } from "gunshi/agent";
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
|
-
import { pathToFileURL } from "node:url";
|
|
10
9
|
import { styleText } from "node:util";
|
|
11
10
|
import { setTimeout } from "node:timers/promises";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { pathToFileURL } from "node:url";
|
|
12
13
|
//#region src/a11y-skips.ts
|
|
13
14
|
const ID_REF_RULE = "a11y/no-missing-id-ref";
|
|
14
15
|
/** One entry per analyzed route whose closed world failed; sorted for stable report output. */
|
|
@@ -1693,35 +1694,42 @@ function isGithubActionsEnv(env = process.env) {
|
|
|
1693
1694
|
function isCiEnv(env = process.env) {
|
|
1694
1695
|
return env.CI === "true" || env.CI === "1";
|
|
1695
1696
|
}
|
|
1696
|
-
/**
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1697
|
+
/** Precedence: explicit flag → SVELTE_VITALS_REPORTER → agent-env auto-detect → GitHub-Actions auto-detect → console. `auto` marks the two detected layers. */
|
|
1698
|
+
function reporterChoice(explicit, env) {
|
|
1699
|
+
if (explicit) return {
|
|
1700
|
+
name: explicit,
|
|
1701
|
+
auto: false
|
|
1702
|
+
};
|
|
1702
1703
|
const fromEnv = env.SVELTE_VITALS_REPORTER;
|
|
1703
|
-
if (isReporterName(fromEnv)) return
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1704
|
+
if (isReporterName(fromEnv)) return {
|
|
1705
|
+
name: fromEnv,
|
|
1706
|
+
auto: false
|
|
1707
|
+
};
|
|
1708
|
+
if (isAgentEnv(env)) return {
|
|
1709
|
+
name: "agent",
|
|
1710
|
+
auto: true
|
|
1711
|
+
};
|
|
1712
|
+
if (isGithubActionsEnv(env)) return {
|
|
1713
|
+
name: "github",
|
|
1714
|
+
auto: true
|
|
1715
|
+
};
|
|
1716
|
+
return {
|
|
1717
|
+
name: "console",
|
|
1718
|
+
auto: false
|
|
1719
|
+
};
|
|
1707
1720
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
* CLI inside an agent terminal would otherwise get Markdown unexpectedly.
|
|
1713
|
-
*/
|
|
1721
|
+
function resolveReporter(explicit, env = process.env) {
|
|
1722
|
+
return reporterChoice(explicit, env).name;
|
|
1723
|
+
}
|
|
1724
|
+
/** Auto-detected (not asked for) — the run prints a one-line "how to override" hint so a human in an agent terminal isn't surprised by Markdown. */
|
|
1714
1725
|
function isAutoDetectedAgent(explicit, env = process.env) {
|
|
1715
|
-
|
|
1726
|
+
const c = reporterChoice(explicit, env);
|
|
1727
|
+
return c.auto && c.name === "agent";
|
|
1716
1728
|
}
|
|
1717
|
-
/**
|
|
1718
|
-
* True when the github reporter is chosen purely by GITHUB_ACTIONS auto-detection
|
|
1719
|
-
* (no explicit flag, no SVELTE_VITALS_REPORTER, not an agent env). Used to surface
|
|
1720
|
-
* a one-line "how to override" hint so an existing CI user isn't surprised by the
|
|
1721
|
-
* switch from console output to workflow commands.
|
|
1722
|
-
*/
|
|
1729
|
+
/** Auto-detected (not asked for) — same hint, so an existing CI user isn't surprised by workflow commands replacing console output. */
|
|
1723
1730
|
function isAutoDetectedGithub(explicit, env = process.env) {
|
|
1724
|
-
|
|
1731
|
+
const c = reporterChoice(explicit, env);
|
|
1732
|
+
return c.auto && c.name === "github";
|
|
1725
1733
|
}
|
|
1726
1734
|
//#endregion
|
|
1727
1735
|
//#region src/changed-files.ts
|
|
@@ -1917,219 +1925,6 @@ function filterToNewFindings(results, baselineResults, config = defaultConfig) {
|
|
|
1917
1925
|
return penalized(results).filter((r) => !baselineKeys.has(findingKey(r)));
|
|
1918
1926
|
}
|
|
1919
1927
|
//#endregion
|
|
1920
|
-
//#region src/rules-config.ts
|
|
1921
|
-
const KNOWN_IDS = new Set(allRules.map((r) => r.id));
|
|
1922
|
-
const RULE_BY_ID = new Map(allRules.map((r) => [r.id, r]));
|
|
1923
|
-
/** Rule ids passed to --rules/--ignore that aren't part of the built-in registry. */
|
|
1924
|
-
function findUnknownRuleIds(ids) {
|
|
1925
|
-
return [...new Set(ids.filter((id) => !KNOWN_IDS.has(id)))];
|
|
1926
|
-
}
|
|
1927
|
-
/** All built-in rule ids, sorted — for help and error messages. */
|
|
1928
|
-
function knownRuleIds() {
|
|
1929
|
-
return [...KNOWN_IDS].sort();
|
|
1930
|
-
}
|
|
1931
|
-
/** The options a rule declares, or undefined when it takes none. */
|
|
1932
|
-
function ruleOptionsSpec(id) {
|
|
1933
|
-
return RULE_BY_ID.get(id)?.options;
|
|
1934
|
-
}
|
|
1935
|
-
/**
|
|
1936
|
-
* Stamps the rule registry's provenance onto error messages that list known ids.
|
|
1937
|
-
* Two copies of svelte-vitals can coexist in one tree (e.g. the Vite plugin's
|
|
1938
|
-
* resolved copy and a directly installed CLI, issue #583); without the versions,
|
|
1939
|
-
* a rule that exists in one copy but not the other reads as a mystery rather
|
|
1940
|
-
* than a version skew.
|
|
1941
|
-
*/
|
|
1942
|
-
function registryTag() {
|
|
1943
|
-
return `svelte-vitals ${readPackageVersion()}, core ${readCoreVersion()}`;
|
|
1944
|
-
}
|
|
1945
|
-
//#endregion
|
|
1946
|
-
//#region src/config-file.ts
|
|
1947
|
-
/**
|
|
1948
|
-
* Loads `svelte-vitals.config.{js,ts}` from the analyzed directory (design
|
|
1949
|
-
* doc: docs/superpowers/specs/2026-07-05-config-file-design.md). Wired into
|
|
1950
|
-
* `analyzeProject` (packages/cli/src/index.ts), so every caller of it inherits
|
|
1951
|
-
* the config file.
|
|
1952
|
-
*
|
|
1953
|
-
* Candidate filenames, in priority order. Only `cwd` itself is searched (no
|
|
1954
|
-
* upward directory walk — see design doc §1). Exported so the install wizard's
|
|
1955
|
-
* config-file scaffolder (install/config-file-format.ts) detects existing
|
|
1956
|
-
* configs against the exact same list — a second hand-maintained copy could
|
|
1957
|
-
* drift and make the scaffolder create a duplicate config the loader then
|
|
1958
|
-
* shadows or ignores.
|
|
1959
|
-
*/
|
|
1960
|
-
const CONFIG_FILENAMES = ["svelte-vitals.config.js", "svelte-vitals.config.ts"];
|
|
1961
|
-
/** The extension set `--config` accepts — derived so it cannot drift from discovery's list. */
|
|
1962
|
-
const CONFIG_EXTENSIONS = CONFIG_FILENAMES.map((name) => name.slice(name.lastIndexOf(".")));
|
|
1963
|
-
const TREAT_DYNAMIC_AS_VALUES = [
|
|
1964
|
-
"pass",
|
|
1965
|
-
"warn",
|
|
1966
|
-
"fail"
|
|
1967
|
-
];
|
|
1968
|
-
const FAIL_ON_VALUES = [
|
|
1969
|
-
"critical",
|
|
1970
|
-
"warning",
|
|
1971
|
-
"info"
|
|
1972
|
-
];
|
|
1973
|
-
const KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
1974
|
-
"treatDynamicAs",
|
|
1975
|
-
"metaComponents",
|
|
1976
|
-
"rules",
|
|
1977
|
-
"failOn",
|
|
1978
|
-
"weights",
|
|
1979
|
-
"overrides"
|
|
1980
|
-
]);
|
|
1981
|
-
/** Whether `value` is a plain object (not null, not an array) — usable with Object.keys/entries. */
|
|
1982
|
-
function isPlainObject(value) {
|
|
1983
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1984
|
-
}
|
|
1985
|
-
/**
|
|
1986
|
-
* Validate one rule setting — the bare severity string or the object form — and
|
|
1987
|
-
* throw on the first problem, prefixed with the config file's path. The rules
|
|
1988
|
-
* themselves live in core's `validateRuleSetting`, shared with the Vite plugin's
|
|
1989
|
-
* `rules`/`overrides` options so a config file and the equivalent plugin option
|
|
1990
|
-
* can't be judged differently. Everything is fatal, on the same reasoning as
|
|
1991
|
-
* unknown rule ids — a typo that silently leaves the config inert is the failure
|
|
1992
|
-
* being prevented.
|
|
1993
|
-
*
|
|
1994
|
-
* `baseline`, when given, is the already-resolved options this setting's
|
|
1995
|
-
* `options` would be merged onto at run time — the global `rules[id].options`
|
|
1996
|
-
* layer, for a setting that lives in `overrides[].rules` (design 2026-07-26
|
|
1997
|
-
* review, Finding A). Omitted for the global `rules` layer itself, so its
|
|
1998
|
-
* min/max cross-check falls back to the rule's own built-in default.
|
|
1999
|
-
*/
|
|
2000
|
-
function validateSetting(path, where, key, setting, allowOptions, baseline, skipRangeCheck) {
|
|
2001
|
-
const errors = validateRuleSetting(`${where}.${key}`, key, setting, ruleOptionsSpec(key), {
|
|
2002
|
-
allowOptions,
|
|
2003
|
-
...baseline !== void 0 ? { baseline } : {},
|
|
2004
|
-
...skipRangeCheck !== void 0 ? { skipRangeCheck } : {}
|
|
2005
|
-
});
|
|
2006
|
-
if (errors.length > 0) throw new Error(`${path}: ${errors.join(" ")}`);
|
|
2007
|
-
}
|
|
2008
|
-
/**
|
|
2009
|
-
* Validate a config file's default export (design doc §4):
|
|
2010
|
-
*
|
|
2011
|
-
* - **Fatal (thrown)**: unknown rule ids in `rules` (reuses `findUnknownRuleIds`
|
|
2012
|
-
* / `knownRuleIds`, same message shape as the `--rules`/`--ignore` error);
|
|
2013
|
-
* unknown category keys or negative/non-finite values in `weights`; an
|
|
2014
|
-
* invalid rule setting (string or object form) or invalid rule options,
|
|
2015
|
-
* in both `rules` and `overrides[].rules` (see `validateSetting`).
|
|
2016
|
-
* - **Warning (returned, field dropped)**: invalid enum values for
|
|
2017
|
-
* `treatDynamicAs` / `failOn`; unknown top-level keys (forward-compatibility).
|
|
2018
|
-
*/
|
|
2019
|
-
function validateConfigFile(raw, path) {
|
|
2020
|
-
const warnings = [];
|
|
2021
|
-
const config = {};
|
|
2022
|
-
for (const key of Object.keys(raw)) if (!KNOWN_TOP_LEVEL_KEYS.has(key)) warnings.push(`${path}: unknown config key '${key}' ignored.`);
|
|
2023
|
-
if (raw.treatDynamicAs !== void 0) if (TREAT_DYNAMIC_AS_VALUES.includes(raw.treatDynamicAs)) config.treatDynamicAs = raw.treatDynamicAs;
|
|
2024
|
-
else warnings.push(`${path}: unknown treatDynamicAs '${String(raw.treatDynamicAs)}'; expected pass|warn|fail. Ignoring.`);
|
|
2025
|
-
if (raw.failOn !== void 0) if (FAIL_ON_VALUES.includes(raw.failOn)) config.failOn = raw.failOn;
|
|
2026
|
-
else warnings.push(`${path}: unknown failOn '${String(raw.failOn)}'; expected critical|warning|info. Ignoring.`);
|
|
2027
|
-
if (raw.metaComponents !== void 0) if (Array.isArray(raw.metaComponents) && raw.metaComponents.every((c) => typeof c === "string")) config.metaComponents = raw.metaComponents;
|
|
2028
|
-
else warnings.push(`${path}: metaComponents must be an array of strings. Ignoring.`);
|
|
2029
|
-
if (raw.rules !== void 0) {
|
|
2030
|
-
if (!isPlainObject(raw.rules)) throw new Error(`${path}: rules must be an object of rule-id → setting.`);
|
|
2031
|
-
const rules = raw.rules;
|
|
2032
|
-
const unknown = findUnknownRuleIds(Object.keys(rules));
|
|
2033
|
-
if (unknown.length > 0) throw new Error(`${path}: unknown rule id(s) in rules: ${unknown.join(", ")}. Known rule ids (${registryTag()}): ${knownRuleIds().join(", ")}`);
|
|
2034
|
-
for (const [key, setting] of Object.entries(rules)) validateSetting(path, "rules", key, setting, true);
|
|
2035
|
-
config.rules = rules;
|
|
2036
|
-
}
|
|
2037
|
-
if (raw.overrides !== void 0) {
|
|
2038
|
-
if (!Array.isArray(raw.overrides)) throw new Error(`${path}: overrides must be an array of { route/files, rules } entries.`);
|
|
2039
|
-
const isGlob = (v) => typeof v === "string" && v.length > 0;
|
|
2040
|
-
const isGlobs = (v) => isGlob(v) || Array.isArray(v) && v.length > 0 && v.every(isGlob);
|
|
2041
|
-
const overrides = [];
|
|
2042
|
-
const rawOverrides = raw.overrides;
|
|
2043
|
-
rawOverrides.forEach((entry, i) => {
|
|
2044
|
-
if (!isPlainObject(entry)) throw new Error(`${path}: overrides[${i}] must be an object with 'route' and/or 'files', and 'rules'.`);
|
|
2045
|
-
if (entry.route !== void 0 && !isGlobs(entry.route)) throw new Error(`${path}: overrides[${i}].route must be a non-empty string or a non-empty array of non-empty strings.`);
|
|
2046
|
-
if (entry.files !== void 0 && !isGlobs(entry.files)) throw new Error(`${path}: overrides[${i}].files must be a non-empty string or a non-empty array of non-empty strings.`);
|
|
2047
|
-
if (entry.route === void 0 && entry.files === void 0) throw new Error(`${path}: overrides[${i}] must set 'route' and/or 'files' to scope the override.`);
|
|
2048
|
-
if (!isPlainObject(entry.rules)) throw new Error(`${path}: overrides[${i}].rules must be an object of rule-id/category → setting.`);
|
|
2049
|
-
if (Object.keys(entry.rules).length === 0) throw new Error(`${path}: overrides[${i}].rules must contain at least one rule id or category.`);
|
|
2050
|
-
const unknown = findUnknownRuleIds(Object.keys(entry.rules).filter((k) => !CATEGORIES.includes(k)));
|
|
2051
|
-
if (unknown.length > 0) throw new Error(`${path}: unknown rule id(s) or categories in overrides[${i}].rules: ${unknown.join(", ")}. Known categories: ${CATEGORIES.join(", ")}. Known rule ids (${registryTag()}): ${knownRuleIds().join(", ")}`);
|
|
2052
|
-
for (const [key, setting] of Object.entries(entry.rules)) {
|
|
2053
|
-
const isCategory = CATEGORIES.includes(key);
|
|
2054
|
-
const baseline = isCategory ? void 0 : resolveRuleOptions(key, ruleOptionsSpec(key), {
|
|
2055
|
-
...defaultConfig,
|
|
2056
|
-
rules: config.rules ?? {}
|
|
2057
|
-
});
|
|
2058
|
-
const skipRangeCheck = shouldSkipRangeCheck(rawOverrides, i, key, setting);
|
|
2059
|
-
validateSetting(path, `overrides[${i}].rules`, key, setting, !isCategory, baseline, skipRangeCheck);
|
|
2060
|
-
}
|
|
2061
|
-
overrides.push({
|
|
2062
|
-
...entry.route !== void 0 ? { route: entry.route } : {},
|
|
2063
|
-
...entry.files !== void 0 ? { files: entry.files } : {},
|
|
2064
|
-
rules: entry.rules
|
|
2065
|
-
});
|
|
2066
|
-
});
|
|
2067
|
-
config.overrides = overrides;
|
|
2068
|
-
}
|
|
2069
|
-
if (raw.weights !== void 0) {
|
|
2070
|
-
if (!isPlainObject(raw.weights)) throw new Error(`${path}: weights must be an object of category → number.`);
|
|
2071
|
-
const weights = {};
|
|
2072
|
-
for (const [rawCat, w] of Object.entries(raw.weights)) {
|
|
2073
|
-
const cat = rawCat.toLowerCase();
|
|
2074
|
-
if (!CATEGORIES.includes(cat)) throw new Error(`${path}: unknown category '${rawCat}' in weights. Known categories: ${CATEGORIES.join(", ")}`);
|
|
2075
|
-
if (typeof w !== "number" || !Number.isFinite(w) || w < 0) throw new Error(`${path}: invalid weight for '${cat}': ${String(w)}; expected a finite number >= 0.`);
|
|
2076
|
-
weights[cat] = w;
|
|
2077
|
-
}
|
|
2078
|
-
config.weights = weights;
|
|
2079
|
-
}
|
|
2080
|
-
return {
|
|
2081
|
-
config,
|
|
2082
|
-
warnings
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
/**
|
|
2086
|
-
* Find and load `svelte-vitals.config.{js,ts}` from `cwd` (only `cwd`, no
|
|
2087
|
-
* upward search). Returns `undefined` when no candidate file exists.
|
|
2088
|
-
*
|
|
2089
|
-
* Loader mechanism (design doc §2): plain native `import()`. `.js` is parsed as
|
|
2090
|
-
* ESM (the project must be `"type": "module"` — SvelteKit's default); `.ts`
|
|
2091
|
-
* loads via native type-stripping, unflagged on every supported Node
|
|
2092
|
-
* (engines.node >=24.16.0).
|
|
2093
|
-
*
|
|
2094
|
-
* Throws when: the file exists but has no usable default export, or the loaded
|
|
2095
|
-
* config fails validation (unknown rule ids in `rules`, invalid `weights`
|
|
2096
|
-
* entries).
|
|
2097
|
-
*/
|
|
2098
|
-
async function loadConfigFile(cwd) {
|
|
2099
|
-
const found = CONFIG_FILENAMES.map((name) => join(cwd, name)).find((path) => existsSync(path));
|
|
2100
|
-
if (!found) {
|
|
2101
|
-
const retired = join(cwd, "svelte-vitals.config.mjs");
|
|
2102
|
-
if (existsSync(retired)) throw new Error(`${retired} is no longer read — svelte-vitals loads svelte-vitals.config.{js,ts} only. Rename the file to .js (the project must be "type": "module") or .ts.`);
|
|
2103
|
-
return;
|
|
2104
|
-
}
|
|
2105
|
-
return loadFrom(found);
|
|
2106
|
-
}
|
|
2107
|
-
/**
|
|
2108
|
-
* Load and validate a config file the caller named (`--config`) instead of one discovered in
|
|
2109
|
-
* `cwd`. Same loader, same validation; the difference is what absence means — the caller chose
|
|
2110
|
-
* this file, so a missing one is fatal where a missing discovered file is simply "no config".
|
|
2111
|
-
* The extension is checked first, and before the disk is touched: discovery narrowed to
|
|
2112
|
-
* `{js,ts}` and kept a loud tripwire for `.mjs`, and a by-path loader that went straight to
|
|
2113
|
-
* `import()` would quietly accept the file that tripwire exists to reject.
|
|
2114
|
-
*/
|
|
2115
|
-
async function loadConfigFromPath(path) {
|
|
2116
|
-
if (!CONFIG_EXTENSIONS.some((ext) => path.endsWith(ext))) throw new Error(`${path} is not a supported config file — svelte-vitals loads ${CONFIG_EXTENSIONS.join(" and ")} only.`);
|
|
2117
|
-
if (!existsSync(path)) throw new Error(`${path} does not exist.`);
|
|
2118
|
-
return loadFrom(path);
|
|
2119
|
-
}
|
|
2120
|
-
/** Import one known-present config file and validate it. Shared by discovery and `--config`. */
|
|
2121
|
-
async function loadFrom(path) {
|
|
2122
|
-
let mod;
|
|
2123
|
-
try {
|
|
2124
|
-
mod = await import(pathToFileURL(path).href);
|
|
2125
|
-
} catch (err) {
|
|
2126
|
-
if (path.endsWith(".js") && err instanceof SyntaxError) throw new Error(`could not load ${path}: ${err.message} — config files are ESM, so the nearest package.json above the config file needs "type": "module" (SvelteKit's default), or use a .ts config.`, { cause: err });
|
|
2127
|
-
throw err;
|
|
2128
|
-
}
|
|
2129
|
-
if (!isPlainObject(mod.default)) throw new Error(`${path} must have a default export that is a plain object (e.g. \`export default defineConfig({...})\` or a plain object literal).`);
|
|
2130
|
-
return validateConfigFile(mod.default, path);
|
|
2131
|
-
}
|
|
2132
|
-
//#endregion
|
|
2133
1928
|
//#region src/suppressions.ts
|
|
2134
1929
|
/**
|
|
2135
1930
|
* Persistent adoption ramp (design doc 2026-07-13-suppressions-file-design.md):
|
|
@@ -2659,6 +2454,221 @@ async function playMascotGreeting(opts) {
|
|
|
2659
2454
|
render.clear();
|
|
2660
2455
|
}
|
|
2661
2456
|
//#endregion
|
|
2457
|
+
//#region src/rules-config.ts
|
|
2458
|
+
const KNOWN_IDS = new Set(allRules.map((r) => r.id));
|
|
2459
|
+
const RULE_BY_ID = new Map(allRules.map((r) => [r.id, r]));
|
|
2460
|
+
/** Rule ids passed to --rules/--ignore that aren't part of the built-in registry. */
|
|
2461
|
+
function findUnknownRuleIds(ids) {
|
|
2462
|
+
return [...new Set(ids.filter((id) => !KNOWN_IDS.has(id)))];
|
|
2463
|
+
}
|
|
2464
|
+
/** All built-in rule ids, sorted — for help and error messages. */
|
|
2465
|
+
function knownRuleIds() {
|
|
2466
|
+
return [...KNOWN_IDS].sort();
|
|
2467
|
+
}
|
|
2468
|
+
/** The options a rule declares, or undefined when it takes none. */
|
|
2469
|
+
function ruleOptionsSpec(id) {
|
|
2470
|
+
return RULE_BY_ID.get(id)?.options;
|
|
2471
|
+
}
|
|
2472
|
+
/**
|
|
2473
|
+
* Stamps the rule registry's provenance onto error messages that list known ids.
|
|
2474
|
+
* Two copies of svelte-vitals can coexist in one tree (e.g. the Vite plugin's
|
|
2475
|
+
* resolved copy and a directly installed CLI, issue #583); without the versions,
|
|
2476
|
+
* a rule that exists in one copy but not the other reads as a mystery rather
|
|
2477
|
+
* than a version skew.
|
|
2478
|
+
*/
|
|
2479
|
+
function registryTag() {
|
|
2480
|
+
return `svelte-vitals ${readPackageVersion()}, core ${readCoreVersion()}`;
|
|
2481
|
+
}
|
|
2482
|
+
//#endregion
|
|
2483
|
+
//#region src/config-file.ts
|
|
2484
|
+
/**
|
|
2485
|
+
* Loads `svelte-vitals.config.{js,ts}` from the analyzed directory (design
|
|
2486
|
+
* doc: docs/superpowers/specs/2026-07-05-config-file-design.md). Wired into
|
|
2487
|
+
* `analyzeProject` (packages/cli/src/index.ts), so every caller of it inherits
|
|
2488
|
+
* the config file.
|
|
2489
|
+
*
|
|
2490
|
+
* Candidate filenames, in priority order. Only `cwd` itself is searched (no
|
|
2491
|
+
* upward directory walk — see design doc §1). Exported so the install wizard's
|
|
2492
|
+
* config-file scaffolder (install/config-file-format.ts) detects existing
|
|
2493
|
+
* configs against the exact same list — a second hand-maintained copy could
|
|
2494
|
+
* drift and make the scaffolder create a duplicate config the loader then
|
|
2495
|
+
* shadows or ignores.
|
|
2496
|
+
*/
|
|
2497
|
+
const CONFIG_FILENAMES = ["svelte-vitals.config.js", "svelte-vitals.config.ts"];
|
|
2498
|
+
/** The extension set `--config` accepts — derived so it cannot drift from discovery's list. */
|
|
2499
|
+
const CONFIG_EXTENSIONS = CONFIG_FILENAMES.map((name) => name.slice(name.lastIndexOf(".")));
|
|
2500
|
+
const TREAT_DYNAMIC_AS_VALUES = [
|
|
2501
|
+
"pass",
|
|
2502
|
+
"warn",
|
|
2503
|
+
"fail"
|
|
2504
|
+
];
|
|
2505
|
+
const FAIL_ON_VALUES = [
|
|
2506
|
+
"critical",
|
|
2507
|
+
"warning",
|
|
2508
|
+
"info"
|
|
2509
|
+
];
|
|
2510
|
+
const KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
2511
|
+
"treatDynamicAs",
|
|
2512
|
+
"metaComponents",
|
|
2513
|
+
"rules",
|
|
2514
|
+
"failOn",
|
|
2515
|
+
"weights",
|
|
2516
|
+
"overrides"
|
|
2517
|
+
]);
|
|
2518
|
+
/**
|
|
2519
|
+
* Validate one rule setting — the bare severity string or the object form — and
|
|
2520
|
+
* throw on the first problem, prefixed with the config file's path. The rules
|
|
2521
|
+
* themselves live in core's `validateRuleSetting`, shared with the Vite plugin's
|
|
2522
|
+
* `rules`/`overrides` options so a config file and the equivalent plugin option
|
|
2523
|
+
* can't be judged differently. Everything is fatal, on the same reasoning as
|
|
2524
|
+
* unknown rule ids — a typo that silently leaves the config inert is the failure
|
|
2525
|
+
* being prevented.
|
|
2526
|
+
*
|
|
2527
|
+
* `baseline`, when given, is the already-resolved options this setting's
|
|
2528
|
+
* `options` would be merged onto at run time — the global `rules[id].options`
|
|
2529
|
+
* layer, for a setting that lives in `overrides[].rules` (design 2026-07-26
|
|
2530
|
+
* review, Finding A). Omitted for the global `rules` layer itself, so its
|
|
2531
|
+
* min/max cross-check falls back to the rule's own built-in default.
|
|
2532
|
+
*/
|
|
2533
|
+
function validateSetting(path, where, key, setting, allowOptions, baseline, skipRangeCheck) {
|
|
2534
|
+
const errors = validateRuleSetting(`${where}.${key}`, key, setting, ruleOptionsSpec(key), {
|
|
2535
|
+
allowOptions,
|
|
2536
|
+
...baseline !== void 0 ? { baseline } : {},
|
|
2537
|
+
...skipRangeCheck !== void 0 ? { skipRangeCheck } : {}
|
|
2538
|
+
});
|
|
2539
|
+
if (errors.length > 0) throw new Error(`${path}: ${errors.join(" ")}`);
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Validate a config file's default export (design doc §4):
|
|
2543
|
+
*
|
|
2544
|
+
* - **Fatal (thrown)**: unknown rule ids in `rules` (reuses `findUnknownRuleIds`
|
|
2545
|
+
* / `knownRuleIds`, same message shape as the `--rules`/`--ignore` error);
|
|
2546
|
+
* unknown category keys or negative/non-finite values in `weights`; an
|
|
2547
|
+
* invalid rule setting (string or object form) or invalid rule options,
|
|
2548
|
+
* in both `rules` and `overrides[].rules` (see `validateSetting`).
|
|
2549
|
+
* - **Warning (returned, field dropped)**: invalid enum values for
|
|
2550
|
+
* `treatDynamicAs` / `failOn`; unknown top-level keys (forward-compatibility).
|
|
2551
|
+
*/
|
|
2552
|
+
function validateConfigFile(raw, path) {
|
|
2553
|
+
const warnings = [];
|
|
2554
|
+
const config = {};
|
|
2555
|
+
for (const key of Object.keys(raw)) if (!KNOWN_TOP_LEVEL_KEYS.has(key)) warnings.push(`${path}: unknown config key '${key}' ignored.`);
|
|
2556
|
+
if (raw.treatDynamicAs !== void 0) if (TREAT_DYNAMIC_AS_VALUES.includes(raw.treatDynamicAs)) config.treatDynamicAs = raw.treatDynamicAs;
|
|
2557
|
+
else warnings.push(`${path}: unknown treatDynamicAs '${String(raw.treatDynamicAs)}'; expected pass|warn|fail. Ignoring.`);
|
|
2558
|
+
if (raw.failOn !== void 0) if (FAIL_ON_VALUES.includes(raw.failOn)) config.failOn = raw.failOn;
|
|
2559
|
+
else warnings.push(`${path}: unknown failOn '${String(raw.failOn)}'; expected critical|warning|info. Ignoring.`);
|
|
2560
|
+
if (raw.metaComponents !== void 0) if (Array.isArray(raw.metaComponents) && raw.metaComponents.every((c) => typeof c === "string")) config.metaComponents = raw.metaComponents;
|
|
2561
|
+
else warnings.push(`${path}: metaComponents must be an array of strings. Ignoring.`);
|
|
2562
|
+
if (raw.rules !== void 0) {
|
|
2563
|
+
if (!isPlainObject(raw.rules)) throw new Error(`${path}: rules must be an object of rule-id → setting.`);
|
|
2564
|
+
const rules = raw.rules;
|
|
2565
|
+
const unknown = findUnknownRuleIds(Object.keys(rules));
|
|
2566
|
+
if (unknown.length > 0) throw new Error(`${path}: unknown rule id(s) in rules: ${unknown.join(", ")}. Known rule ids (${registryTag()}): ${knownRuleIds().join(", ")}`);
|
|
2567
|
+
for (const [key, setting] of Object.entries(rules)) validateSetting(path, "rules", key, setting, true);
|
|
2568
|
+
config.rules = rules;
|
|
2569
|
+
}
|
|
2570
|
+
if (raw.overrides !== void 0) {
|
|
2571
|
+
if (!Array.isArray(raw.overrides)) throw new Error(`${path}: overrides must be an array of { route/files, rules } entries.`);
|
|
2572
|
+
const isGlob = (v) => typeof v === "string" && v.length > 0;
|
|
2573
|
+
const isGlobs = (v) => isGlob(v) || Array.isArray(v) && v.length > 0 && v.every(isGlob);
|
|
2574
|
+
const overrides = [];
|
|
2575
|
+
const rawOverrides = raw.overrides;
|
|
2576
|
+
rawOverrides.forEach((entry, i) => {
|
|
2577
|
+
if (!isPlainObject(entry)) throw new Error(`${path}: overrides[${i}] must be an object with 'route' and/or 'files', and 'rules'.`);
|
|
2578
|
+
if (entry.route !== void 0 && !isGlobs(entry.route)) throw new Error(`${path}: overrides[${i}].route must be a non-empty string or a non-empty array of non-empty strings.`);
|
|
2579
|
+
if (entry.files !== void 0 && !isGlobs(entry.files)) throw new Error(`${path}: overrides[${i}].files must be a non-empty string or a non-empty array of non-empty strings.`);
|
|
2580
|
+
if (entry.route === void 0 && entry.files === void 0) throw new Error(`${path}: overrides[${i}] must set 'route' and/or 'files' to scope the override.`);
|
|
2581
|
+
if (!isPlainObject(entry.rules)) throw new Error(`${path}: overrides[${i}].rules must be an object of rule-id/category → setting.`);
|
|
2582
|
+
if (Object.keys(entry.rules).length === 0) throw new Error(`${path}: overrides[${i}].rules must contain at least one rule id or category.`);
|
|
2583
|
+
const unknown = findUnknownRuleIds(Object.keys(entry.rules).filter((k) => !CATEGORIES.includes(k)));
|
|
2584
|
+
if (unknown.length > 0) throw new Error(`${path}: unknown rule id(s) or categories in overrides[${i}].rules: ${unknown.join(", ")}. Known categories: ${CATEGORIES.join(", ")}. Known rule ids (${registryTag()}): ${knownRuleIds().join(", ")}`);
|
|
2585
|
+
for (const [key, setting] of Object.entries(entry.rules)) {
|
|
2586
|
+
const isCategory = CATEGORIES.includes(key);
|
|
2587
|
+
const baseline = isCategory ? void 0 : resolveRuleOptions(key, ruleOptionsSpec(key), {
|
|
2588
|
+
...defaultConfig,
|
|
2589
|
+
rules: config.rules ?? {}
|
|
2590
|
+
});
|
|
2591
|
+
const skipRangeCheck = shouldSkipRangeCheck(rawOverrides, i, key, setting);
|
|
2592
|
+
validateSetting(path, `overrides[${i}].rules`, key, setting, !isCategory, baseline, skipRangeCheck);
|
|
2593
|
+
}
|
|
2594
|
+
overrides.push({
|
|
2595
|
+
...entry.route !== void 0 ? { route: entry.route } : {},
|
|
2596
|
+
...entry.files !== void 0 ? { files: entry.files } : {},
|
|
2597
|
+
rules: entry.rules
|
|
2598
|
+
});
|
|
2599
|
+
});
|
|
2600
|
+
config.overrides = overrides;
|
|
2601
|
+
}
|
|
2602
|
+
if (raw.weights !== void 0) {
|
|
2603
|
+
if (!isPlainObject(raw.weights)) throw new Error(`${path}: weights must be an object of category → number.`);
|
|
2604
|
+
const weights = {};
|
|
2605
|
+
for (const [rawCat, w] of Object.entries(raw.weights)) {
|
|
2606
|
+
const cat = rawCat.toLowerCase();
|
|
2607
|
+
if (!CATEGORIES.includes(cat)) throw new Error(`${path}: unknown category '${rawCat}' in weights. Known categories: ${CATEGORIES.join(", ")}`);
|
|
2608
|
+
if (typeof w !== "number" || !Number.isFinite(w) || w < 0) throw new Error(`${path}: invalid weight for '${cat}': ${String(w)}; expected a finite number >= 0.`);
|
|
2609
|
+
weights[cat] = w;
|
|
2610
|
+
}
|
|
2611
|
+
config.weights = weights;
|
|
2612
|
+
}
|
|
2613
|
+
return {
|
|
2614
|
+
config,
|
|
2615
|
+
warnings
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Find and load `svelte-vitals.config.{js,ts}` from `cwd` (only `cwd`, no
|
|
2620
|
+
* upward search). Returns `undefined` when no candidate file exists.
|
|
2621
|
+
*
|
|
2622
|
+
* Loader mechanism (design doc §2): plain native `import()`. `.js` is parsed as
|
|
2623
|
+
* ESM (the project must be `"type": "module"` — SvelteKit's default); `.ts`
|
|
2624
|
+
* loads via native type-stripping, unflagged on every supported Node
|
|
2625
|
+
* (engines.node >=24.16.0).
|
|
2626
|
+
*
|
|
2627
|
+
* Throws when: the file exists but has no usable default export, or the loaded
|
|
2628
|
+
* config fails validation (unknown rule ids in `rules`, invalid `weights`
|
|
2629
|
+
* entries).
|
|
2630
|
+
*/
|
|
2631
|
+
async function loadConfigFile(cwd) {
|
|
2632
|
+
const found = CONFIG_FILENAMES.map((name) => join(cwd, name)).find((path) => existsSync(path));
|
|
2633
|
+
if (!found) {
|
|
2634
|
+
const retired = join(cwd, "svelte-vitals.config.mjs");
|
|
2635
|
+
if (existsSync(retired)) throw new Error(`${retired} is no longer read — svelte-vitals loads svelte-vitals.config.{js,ts} only. Rename the file to .js (the project must be "type": "module") or .ts.`);
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
return loadFrom(found);
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Load and validate a config file the caller named (`--config`) instead of one discovered in
|
|
2642
|
+
* `cwd`. Same loader, same validation; the difference is what absence means — the caller chose
|
|
2643
|
+
* this file, so a missing one is fatal where a missing discovered file is simply "no config".
|
|
2644
|
+
* The extension is checked first, and before the disk is touched: discovery narrowed to
|
|
2645
|
+
* `{js,ts}` and kept a loud tripwire for `.mjs`, and a by-path loader that went straight to
|
|
2646
|
+
* `import()` would quietly accept the file that tripwire exists to reject.
|
|
2647
|
+
*/
|
|
2648
|
+
async function loadConfigFromPath(path) {
|
|
2649
|
+
if (!CONFIG_EXTENSIONS.some((ext) => path.endsWith(ext))) throw new Error(`${path} is not a supported config file — svelte-vitals loads ${CONFIG_EXTENSIONS.join(" and ")} only.`);
|
|
2650
|
+
if (!existsSync(path)) throw new Error(`${path} does not exist.`);
|
|
2651
|
+
return loadFrom(path);
|
|
2652
|
+
}
|
|
2653
|
+
/** Import one known-present config file and validate it. Shared by discovery and `--config`. */
|
|
2654
|
+
async function loadFrom(path) {
|
|
2655
|
+
let mod;
|
|
2656
|
+
try {
|
|
2657
|
+
let digest = createHash("sha1").update(readFileSync(path)).digest("hex").slice(0, 16);
|
|
2658
|
+
for (let attempt = 0;; attempt++) {
|
|
2659
|
+
mod = await import(`${pathToFileURL(path).href}?v=${digest}`);
|
|
2660
|
+
const digestAfter = createHash("sha1").update(readFileSync(path)).digest("hex").slice(0, 16);
|
|
2661
|
+
if (digestAfter === digest || attempt >= 2) break;
|
|
2662
|
+
digest = digestAfter;
|
|
2663
|
+
}
|
|
2664
|
+
} catch (err) {
|
|
2665
|
+
if (path.endsWith(".js") && err instanceof SyntaxError) throw new Error(`could not load ${path}: ${err.message} — config files are ESM, so the nearest package.json above the config file needs "type": "module" (SvelteKit's default), or use a .ts config.`, { cause: err });
|
|
2666
|
+
throw err;
|
|
2667
|
+
}
|
|
2668
|
+
if (!isPlainObject(mod.default)) throw new Error(`${path} must have a default export that is a plain object (e.g. \`export default defineConfig({...})\` or a plain object literal).`);
|
|
2669
|
+
return validateConfigFile(mod.default, path);
|
|
2670
|
+
}
|
|
2671
|
+
//#endregion
|
|
2662
2672
|
//#region src/pulse-animation.ts
|
|
2663
2673
|
const FRAME_COUNT = 6;
|
|
2664
2674
|
const FRAME_DELAY_MS = 200;
|
|
@@ -3136,4 +3146,4 @@ async function run(opts = {}) {
|
|
|
3136
3146
|
}
|
|
3137
3147
|
}
|
|
3138
3148
|
//#endregion
|
|
3139
|
-
export { hasDep as _, spinnerEnabled as a, ProjectError as b, findUnknownRuleIds as c, ruleOptionsSpec as d, REPORTER_NAMES as f, discoverApps as g, readPackageVersion as h, run as i, knownRuleIds as l, readCoreVersion as m, applyScope as n, CONFIG_FILENAMES as o, isReporterName as p, defineConfig$1 as r, loadConfigFile as s, analyzeProject as t, registryTag as u, readPkg as v, routeMatcher as y };
|
|
3149
|
+
export { hasDep as _, spinnerEnabled as a, ProjectError as b, findUnknownRuleIds as c, ruleOptionsSpec as d, REPORTER_NAMES as f, discoverApps as g, readPackageVersion as h, run as i, knownRuleIds as l, readCoreVersion as m, applyScope as n, CONFIG_FILENAMES as o, isReporterName as p, defineConfig$1 as r, loadConfigFile as s, analyzeProject as t, registryTag as u, readPkg as v, createNodeRuntime as x, routeMatcher as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-vitals",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.4",
|
|
4
4
|
"description": "A deterministic SvelteKit code-health scanner (SEO, performance, correctness, security, architecture, accessibility).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dist"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@svelte-vitals/core": "0.51.
|
|
46
|
+
"@svelte-vitals/core": "0.51.2",
|
|
47
47
|
"@clack/prompts": "^1.7.0",
|
|
48
48
|
"@gunshi/plugin-completion": "0.37.1",
|
|
49
49
|
"@gunshi/plugin-i18n": "0.37.1",
|