svelte-vitals 0.35.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/bin.js +131 -187
- package/dist/index.d.ts +2 -2
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -78,12 +78,20 @@ Useful as a CI gate.
|
|
|
78
78
|
|
|
79
79
|
### `svelte-vitals install`
|
|
80
80
|
|
|
81
|
-
An interactive wizard that wires up the [
|
|
81
|
+
An interactive wizard that wires up the [Vite plugin](https://www.npmjs.com/package/@svelte-vitals/vite)'s live dashboard, Agent Skills (`/svelte-vitals`, `/improve-svelte`) for Claude Code, Codex, and Cursor, a `svelte-vitals.config` file, and a GitHub Actions CI workflow — grouped by category in the picker so it's clear what each target is for:
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
npx svelte-vitals@latest install
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### `svelte-vitals explain <rule-id>`
|
|
88
|
+
|
|
89
|
+
Prints one rule's rationale, docs link, fix template, and — for a configurable rule — every option's default, bounds, and how a configured value merges with the built-in default. `--json` emits the same data as an object. This is the per-rule detail an agent needs when it has to decide whether a finding is a defect or a threshold disagreement.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx svelte-vitals@latest explain performance/heavy-import
|
|
93
|
+
```
|
|
94
|
+
|
|
87
95
|
### CI integration
|
|
88
96
|
|
|
89
97
|
`svelte-vitals ci install` scaffolds a GitHub Actions workflow around `@svelte-vitals/action` — inline PR annotations, a job summary, and a sticky PR comment, no YAML to hand-write. The same workflow is also a selectable `ci-workflow` target inside `svelte-vitals install`, so it can be set up in the same pass as everything else. See [CI integration](https://oekazuma.github.io/svelte-vitals/guides/ci/).
|
package/dist/bin.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-D6AUX2GC.js";
|
|
13
13
|
|
|
14
14
|
// src/bin.ts
|
|
15
|
-
import
|
|
15
|
+
import mri4 from "mri";
|
|
16
16
|
import * as p2 from "@clack/prompts";
|
|
17
17
|
|
|
18
18
|
// src/resolve-args.ts
|
|
@@ -179,96 +179,12 @@ function resolveArgs(argv) {
|
|
|
179
179
|
// src/install/cli.ts
|
|
180
180
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
181
181
|
import { dirname } from "path";
|
|
182
|
-
import { homedir } from "os";
|
|
183
182
|
import { spawnSync } from "child_process";
|
|
184
183
|
import mri from "mri";
|
|
185
184
|
import * as p from "@clack/prompts";
|
|
186
185
|
|
|
187
186
|
// src/install/index.ts
|
|
188
|
-
import { join as
|
|
189
|
-
|
|
190
|
-
// src/install/clients.ts
|
|
191
|
-
import { join } from "path";
|
|
192
|
-
var MCP_ENTRY = { command: "npx", args: ["-y", "@svelte-vitals/mcp"] };
|
|
193
|
-
var CLIENTS = [
|
|
194
|
-
{
|
|
195
|
-
id: "claude-code",
|
|
196
|
-
label: "Claude Code",
|
|
197
|
-
scopes: ["project", "global"],
|
|
198
|
-
format: "json",
|
|
199
|
-
resolvePath: (scope, cwd, home) => scope === "project" ? join(cwd, ".mcp.json") : join(home, ".claude.json")
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
id: "cursor",
|
|
203
|
-
label: "Cursor",
|
|
204
|
-
scopes: ["project", "global"],
|
|
205
|
-
format: "json",
|
|
206
|
-
resolvePath: (scope, cwd, home) => scope === "project" ? join(cwd, ".cursor", "mcp.json") : join(home, ".cursor", "mcp.json")
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
id: "codex",
|
|
210
|
-
label: "Codex",
|
|
211
|
-
scopes: ["global"],
|
|
212
|
-
format: "toml",
|
|
213
|
-
resolvePath: (_scope, _cwd, home) => join(home, ".codex", "config.toml")
|
|
214
|
-
}
|
|
215
|
-
];
|
|
216
|
-
function clientById(id) {
|
|
217
|
-
return CLIENTS.find((c) => c.id === id);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// src/install/merge.ts
|
|
221
|
-
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
222
|
-
var SERVER_KEY = "svelte-vitals";
|
|
223
|
-
function isPlainObject(v) {
|
|
224
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
225
|
-
}
|
|
226
|
-
function sameEntry(prior, entry) {
|
|
227
|
-
if (typeof prior !== "object" || prior === null) return false;
|
|
228
|
-
const o = prior;
|
|
229
|
-
return o.command === entry.command && Array.isArray(o.args) && o.args.length === entry.args.length && o.args.every((v, i) => v === entry.args[i]);
|
|
230
|
-
}
|
|
231
|
-
function statusFor(prior, entry, force, created) {
|
|
232
|
-
if (prior !== void 0) {
|
|
233
|
-
if (sameEntry(prior, entry)) return "exists";
|
|
234
|
-
return force ? "updated" : "skip";
|
|
235
|
-
}
|
|
236
|
-
return created ? "created" : "added";
|
|
237
|
-
}
|
|
238
|
-
function mergeJson(existing, entry, force) {
|
|
239
|
-
const created = existing === void 0;
|
|
240
|
-
const parsed = created ? {} : JSON.parse(existing);
|
|
241
|
-
if (!isPlainObject(parsed)) {
|
|
242
|
-
throw new Error("existing config is not a JSON object");
|
|
243
|
-
}
|
|
244
|
-
const root = parsed;
|
|
245
|
-
if (root.mcpServers !== void 0 && !isPlainObject(root.mcpServers)) {
|
|
246
|
-
throw new Error('existing config has a non-object "mcpServers" table');
|
|
247
|
-
}
|
|
248
|
-
const servers = isPlainObject(root.mcpServers) ? root.mcpServers : {};
|
|
249
|
-
const status = statusFor(servers[SERVER_KEY], entry, force, created);
|
|
250
|
-
if (status === "exists" || status === "skip") return { content: existing, status: "exists" };
|
|
251
|
-
servers[SERVER_KEY] = { command: entry.command, args: entry.args };
|
|
252
|
-
root.mcpServers = servers;
|
|
253
|
-
return { content: JSON.stringify(root, null, 2) + "\n", status };
|
|
254
|
-
}
|
|
255
|
-
function mergeToml(existing, entry, force) {
|
|
256
|
-
const created = existing === void 0;
|
|
257
|
-
const parsed = created ? {} : parseToml(existing);
|
|
258
|
-
if (!isPlainObject(parsed)) {
|
|
259
|
-
throw new Error("existing config is not a TOML table");
|
|
260
|
-
}
|
|
261
|
-
const root = parsed;
|
|
262
|
-
if (root.mcp_servers !== void 0 && !isPlainObject(root.mcp_servers)) {
|
|
263
|
-
throw new Error('existing config has a non-table "mcp_servers" section');
|
|
264
|
-
}
|
|
265
|
-
const servers = isPlainObject(root.mcp_servers) ? root.mcp_servers : {};
|
|
266
|
-
const status = statusFor(servers[SERVER_KEY], entry, force, created);
|
|
267
|
-
if (status === "exists" || status === "skip") return { content: existing, status: "exists" };
|
|
268
|
-
servers[SERVER_KEY] = { command: entry.command, args: entry.args };
|
|
269
|
-
root.mcp_servers = servers;
|
|
270
|
-
return { content: stringifyToml(root), status };
|
|
271
|
-
}
|
|
187
|
+
import { join as join3 } from "path";
|
|
272
188
|
|
|
273
189
|
// src/install/vite-targets.ts
|
|
274
190
|
var VITE_TARGETS = [
|
|
@@ -444,7 +360,7 @@ Use this whenever you are writing or reviewing SvelteKit route files (\`+page.sv
|
|
|
444
360
|
|
|
445
361
|
1. After writing or editing code, run \`npx svelte-vitals . --diff --reporter agent\` and fix any findings it reports.
|
|
446
362
|
2. Before committing, run \`npx svelte-vitals . --staged\` as a pre-commit gate.
|
|
447
|
-
3. For a rule's full rationale and fix examples,
|
|
363
|
+
3. For a rule's full rationale, configurable options and fix examples, run \`npx svelte-vitals explain <rule-id>\` (add \`--json\` for a structured object) or open its docs link below.
|
|
448
364
|
|
|
449
365
|
## Rule digest
|
|
450
366
|
|
|
@@ -535,9 +451,9 @@ Every svelte-vitals rule already carries a reviewer-written fix:
|
|
|
535
451
|
\`recommendation\` (one line), and where applicable \`fix.description\` +
|
|
536
452
|
\`fix.snippet\` (literal code to drop in). These are embedded verbatim in the
|
|
537
453
|
rule catalog below \u2014 copy them into the plan's Target section, never
|
|
538
|
-
approximate from memory. For the full rationale behind a rule,
|
|
539
|
-
\`
|
|
540
|
-
open its docs link, also in the catalog below.
|
|
454
|
+
approximate from memory. For the full rationale behind a rule, run
|
|
455
|
+
\`npx svelte-vitals explain <rule-id>\` (it also names the rule's configurable
|
|
456
|
+
options) or open its docs link, also in the catalog below.
|
|
541
457
|
|
|
542
458
|
## Workflow
|
|
543
459
|
|
|
@@ -559,8 +475,7 @@ Get the machine map before applying judgment:
|
|
|
559
475
|
findings even appear (see Hard Rule 5).
|
|
560
476
|
- **Stack**: SvelteKit version, static/prerendered vs. SSR vs. adapter-node,
|
|
561
477
|
whether the Vite dev dashboard (\`@svelte-vitals/vite\`, \`ui: true\`) is
|
|
562
|
-
already wired up, whether
|
|
563
|
-
already installed.
|
|
478
|
+
already wired up, whether the \`svelte-vitals\` skill is already installed.
|
|
564
479
|
- **Verification commands**: read \`package.json\`'s \`scripts\` \u2014 do not assume
|
|
565
480
|
a specific package manager; this project's build/typecheck/test/lint
|
|
566
481
|
commands may differ from svelte-vitals' own repo.
|
|
@@ -786,7 +701,7 @@ adapted to this file \u2014 never approximated from memory.
|
|
|
786
701
|
## Tone
|
|
787
702
|
|
|
788
703
|
State findings plainly with evidence, and cite the rule id so the reader can
|
|
789
|
-
look it up in the catalog above or via \`
|
|
704
|
+
look it up in the catalog above or via \`svelte-vitals explain\`. A short list of
|
|
790
705
|
high-confidence, high-leverage plans beats a long padded one \u2014 "this route
|
|
791
706
|
is already solid" is a valid audit result. Flag uncertainty honestly: when
|
|
792
707
|
correctness can't be judged from static code alone (a race that depends on
|
|
@@ -824,7 +739,7 @@ ${options}
|
|
|
824
739
|
}
|
|
825
740
|
|
|
826
741
|
// src/install/config-file-format.ts
|
|
827
|
-
import { join
|
|
742
|
+
import { join } from "path";
|
|
828
743
|
function nodeSupportsNativeTypeScript(version) {
|
|
829
744
|
const match = /^v?(\d+)\.(\d+)/.exec(version);
|
|
830
745
|
if (!match) return false;
|
|
@@ -833,10 +748,10 @@ function nodeSupportsNativeTypeScript(version) {
|
|
|
833
748
|
return major > 23 || major === 23 && minor >= 6 || major === 22 && minor >= 18;
|
|
834
749
|
}
|
|
835
750
|
function findExistingConfigFile(readFile, cwd) {
|
|
836
|
-
return CONFIG_FILENAMES.find((rel) => readFile(
|
|
751
|
+
return CONFIG_FILENAMES.find((rel) => readFile(join(cwd, rel)) !== void 0);
|
|
837
752
|
}
|
|
838
753
|
function hasSvelteVitalsDependency(readFile, cwd) {
|
|
839
|
-
const raw = readFile(
|
|
754
|
+
const raw = readFile(join(cwd, "package.json"));
|
|
840
755
|
if (raw === void 0) return false;
|
|
841
756
|
try {
|
|
842
757
|
const pkg = JSON.parse(raw);
|
|
@@ -846,7 +761,7 @@ function hasSvelteVitalsDependency(readFile, cwd) {
|
|
|
846
761
|
}
|
|
847
762
|
}
|
|
848
763
|
function isEsmProject(readFile, cwd) {
|
|
849
|
-
const raw = readFile(
|
|
764
|
+
const raw = readFile(join(cwd, "package.json"));
|
|
850
765
|
if (raw === void 0) return false;
|
|
851
766
|
try {
|
|
852
767
|
return JSON.parse(raw).type === "module";
|
|
@@ -856,7 +771,7 @@ function isEsmProject(readFile, cwd) {
|
|
|
856
771
|
}
|
|
857
772
|
function detectBestConfigExtension(opts) {
|
|
858
773
|
if (!nodeSupportsNativeTypeScript(opts.nodeVersion)) return "mjs";
|
|
859
|
-
const looksTypeScript = opts.readFile(
|
|
774
|
+
const looksTypeScript = opts.readFile(join(opts.cwd, "tsconfig.json")) !== void 0 || opts.readFile(join(opts.cwd, "vite.config.ts")) !== void 0;
|
|
860
775
|
if (!looksTypeScript) return "mjs";
|
|
861
776
|
return hasSvelteVitalsDependency(opts.readFile, opts.cwd) ? "ts" : "mjs";
|
|
862
777
|
}
|
|
@@ -967,7 +882,7 @@ function codemodHooksServer(existing) {
|
|
|
967
882
|
}
|
|
968
883
|
|
|
969
884
|
// src/install/package-manager.ts
|
|
970
|
-
import { join as
|
|
885
|
+
import { join as join2 } from "path";
|
|
971
886
|
var LOCKFILE_TO_PM = {
|
|
972
887
|
"pnpm-lock.yaml": "pnpm",
|
|
973
888
|
"yarn.lock": "yarn",
|
|
@@ -977,7 +892,7 @@ var LOCKFILE_TO_PM = {
|
|
|
977
892
|
};
|
|
978
893
|
function detectPackageManagerFromLockfile(io) {
|
|
979
894
|
for (const [file, pm] of Object.entries(LOCKFILE_TO_PM)) {
|
|
980
|
-
if (io.readFile(
|
|
895
|
+
if (io.readFile(join2(io.cwd, file)) !== void 0) return pm;
|
|
981
896
|
}
|
|
982
897
|
return void 0;
|
|
983
898
|
}
|
|
@@ -985,7 +900,7 @@ function detectPackageManager(io) {
|
|
|
985
900
|
return detectPackageManagerFromLockfile(io) ?? "npm";
|
|
986
901
|
}
|
|
987
902
|
function hasVitePackage(io) {
|
|
988
|
-
const raw = io.readFile(
|
|
903
|
+
const raw = io.readFile(join2(io.cwd, "package.json"));
|
|
989
904
|
if (raw === void 0) return false;
|
|
990
905
|
try {
|
|
991
906
|
const pkg = JSON.parse(raw);
|
|
@@ -999,7 +914,7 @@ function installCommand(pm) {
|
|
|
999
914
|
return { command: pm, args: [action, "-D", "@svelte-vitals/vite"] };
|
|
1000
915
|
}
|
|
1001
916
|
function readInstalledViteVersion(io) {
|
|
1002
|
-
const raw = io.readFile(
|
|
917
|
+
const raw = io.readFile(join2(io.cwd, "node_modules/@svelte-vitals/vite/package.json"));
|
|
1003
918
|
if (raw === void 0) return void 0;
|
|
1004
919
|
try {
|
|
1005
920
|
return JSON.parse(raw).version;
|
|
@@ -1016,19 +931,13 @@ var ACTION_VERSION = "0.4.0";
|
|
|
1016
931
|
function detectPackageManagerNear(io, appDir) {
|
|
1017
932
|
return detectPackageManagerFromLockfile({ ...io, cwd: appDir }) ?? detectPackageManager(io);
|
|
1018
933
|
}
|
|
1019
|
-
function planForClient(client, scope, io, force) {
|
|
1020
|
-
const path = client.resolvePath(scope, io.cwd, io.home);
|
|
1021
|
-
const existing = io.readFile(path);
|
|
1022
|
-
const merged = client.format === "toml" ? mergeToml(existing, MCP_ENTRY, force) : mergeJson(existing, MCP_ENTRY, force);
|
|
1023
|
-
return { id: client.id, label: client.label, scope, path, status: merged.status, content: merged.content };
|
|
1024
|
-
}
|
|
1025
934
|
function resolveCandidate(io, baseDir, candidates) {
|
|
1026
935
|
for (const rel of candidates) {
|
|
1027
|
-
const path =
|
|
936
|
+
const path = join3(baseDir, rel);
|
|
1028
937
|
const content = io.readFile(path);
|
|
1029
938
|
if (content !== void 0) return { path, content };
|
|
1030
939
|
}
|
|
1031
|
-
return { path:
|
|
940
|
+
return { path: join3(baseDir, candidates[0]), content: void 0 };
|
|
1032
941
|
}
|
|
1033
942
|
function planForVitePlugin(io, appDir) {
|
|
1034
943
|
const { path, content } = resolveCandidate(io, appDir, ["vite.config.ts", "vite.config.js", "vite.config.mjs"]);
|
|
@@ -1057,7 +966,7 @@ function agentTargetContent(id, version) {
|
|
|
1057
966
|
function planForAgentTarget(target, io, force, version) {
|
|
1058
967
|
const content = agentTargetContent(target.id, version);
|
|
1059
968
|
return target.relPaths.map((relPath) => {
|
|
1060
|
-
const path =
|
|
969
|
+
const path = join3(io.cwd, relPath);
|
|
1061
970
|
const existing = io.readFile(path);
|
|
1062
971
|
const status = existing === void 0 ? "created" : force ? "updated" : "exists";
|
|
1063
972
|
return { id: target.id, label: target.label, path, status, content };
|
|
@@ -1066,7 +975,7 @@ function planForAgentTarget(target, io, force, version) {
|
|
|
1066
975
|
function planForConfigTarget(target, io, force, appDir) {
|
|
1067
976
|
const existingRel = findExistingConfigFile(io.readFile, appDir);
|
|
1068
977
|
if (existingRel !== void 0) {
|
|
1069
|
-
const path2 =
|
|
978
|
+
const path2 = join3(appDir, existingRel);
|
|
1070
979
|
const status = force ? "updated" : "exists";
|
|
1071
980
|
const content2 = force ? buildConfigFileTemplate({
|
|
1072
981
|
useDefineConfig: existingRel.endsWith(".ts") && hasSvelteVitalsDependency(io.readFile, appDir),
|
|
@@ -1079,12 +988,12 @@ function planForConfigTarget(target, io, force, appDir) {
|
|
|
1079
988
|
cwd: appDir,
|
|
1080
989
|
nodeVersion: io.nodeVersion ?? process.version
|
|
1081
990
|
});
|
|
1082
|
-
const path =
|
|
991
|
+
const path = join3(appDir, `svelte-vitals.config.${ext}`);
|
|
1083
992
|
const content = buildConfigFileTemplate({ useDefineConfig: ext === "ts" });
|
|
1084
993
|
return { id: target.id, label: target.label, path, status: "created", content };
|
|
1085
994
|
}
|
|
1086
995
|
function planForCiTarget(target, io, force) {
|
|
1087
|
-
const path =
|
|
996
|
+
const path = join3(io.cwd, target.relPath);
|
|
1088
997
|
const existing = io.readFile(path);
|
|
1089
998
|
const plan = planWorkflowWrite(existing, force);
|
|
1090
999
|
const content = plan.status === "exists" ? void 0 : buildWorkflowYaml({ actionSha: ACTION_SHA, actionVersion: ACTION_VERSION });
|
|
@@ -1094,7 +1003,7 @@ function indent(text) {
|
|
|
1094
1003
|
return text.split("\n").map((l) => ` ${l}`).join("\n");
|
|
1095
1004
|
}
|
|
1096
1005
|
function rowLine(r) {
|
|
1097
|
-
const head = ` ${r.label}
|
|
1006
|
+
const head = ` ${r.label} \u2192 ${r.path} [${r.status}]`;
|
|
1098
1007
|
return r.status === "manual" && r.snippet ? `${head}
|
|
1099
1008
|
${indent(r.snippet)}` : head;
|
|
1100
1009
|
}
|
|
@@ -1104,7 +1013,7 @@ async function runRefresh(io, flags, version) {
|
|
|
1104
1013
|
for (const target of AGENT_TARGETS) {
|
|
1105
1014
|
const content = agentTargetContent(target.id, version);
|
|
1106
1015
|
for (const relPath of target.relPaths) {
|
|
1107
|
-
const path =
|
|
1016
|
+
const path = join3(io.cwd, relPath);
|
|
1108
1017
|
try {
|
|
1109
1018
|
if (io.readFile(path) === void 0) continue;
|
|
1110
1019
|
rows.push({ id: target.id, label: target.label, path, status: "updated", content });
|
|
@@ -1157,29 +1066,30 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
|
|
|
1157
1066
|
return false;
|
|
1158
1067
|
}
|
|
1159
1068
|
};
|
|
1160
|
-
const detectedClients = CLIENTS.filter(
|
|
1161
|
-
(c) => c.scopes.some((s) => configExists(c.resolvePath(s, io.cwd, io.home)))
|
|
1162
|
-
).map((c) => c.id);
|
|
1163
1069
|
const viteConfigExists = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].some(
|
|
1164
|
-
(f) => configExists(
|
|
1070
|
+
(f) => configExists(join3(io.cwd, f))
|
|
1165
1071
|
);
|
|
1166
|
-
const claudeSkillDetected = configExists(
|
|
1167
|
-
const cursorRulesDetected =
|
|
1072
|
+
const claudeSkillDetected = configExists(join3(io.cwd, ".claude", "settings.json"));
|
|
1073
|
+
const cursorRulesDetected = [
|
|
1074
|
+
".cursor/mcp.json",
|
|
1075
|
+
".cursor/environment.json",
|
|
1076
|
+
".cursorrules",
|
|
1077
|
+
".cursorignore",
|
|
1078
|
+
agentTargetById("cursor-rules").relPaths[0]
|
|
1079
|
+
].some((rel) => configExists(join3(io.cwd, rel)));
|
|
1168
1080
|
const detectedAgents = [
|
|
1169
1081
|
...claudeSkillDetected ? ["claude-skill"] : [],
|
|
1170
1082
|
...cursorRulesDetected ? ["cursor-rules"] : []
|
|
1171
1083
|
];
|
|
1172
|
-
const ciWorkflowDetected = configExists(
|
|
1084
|
+
const ciWorkflowDetected = configExists(join3(io.cwd, CI_TARGETS[0].relPath));
|
|
1173
1085
|
const configFileDetected = findExistingConfigFile((p3) => configExists(p3) ? "" : void 0, io.cwd) !== void 0;
|
|
1174
1086
|
const detected = [
|
|
1175
|
-
...detectedClients,
|
|
1176
1087
|
...viteConfigExists ? VITE_TARGETS.map((t) => t.id) : [],
|
|
1177
1088
|
...detectedAgents,
|
|
1178
1089
|
...ciWorkflowDetected ? CI_TARGETS.map((t) => t.id) : [],
|
|
1179
1090
|
...configFileDetected ? CONFIG_TARGETS.map((t) => t.id) : []
|
|
1180
1091
|
];
|
|
1181
1092
|
const groups = {
|
|
1182
|
-
"MCP server": CLIENTS.map((c) => ({ id: c.id, label: c.label })),
|
|
1183
1093
|
"Vite integration": VITE_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
|
|
1184
1094
|
"Agent Skills & rules": AGENT_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
|
|
1185
1095
|
"CI (GitHub Actions)": CI_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
|
|
@@ -1193,25 +1103,24 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
|
|
|
1193
1103
|
ids = picked;
|
|
1194
1104
|
} else {
|
|
1195
1105
|
io.errorLog(
|
|
1196
|
-
"svelte-vitals: no TTY; pass --client <
|
|
1106
|
+
"svelte-vitals: no TTY; pass --client <vite-plugin,vite-hooks,claude-skill,cursor-rules,claude-skill-improve,config-file,ci-workflow> to install non-interactively."
|
|
1197
1107
|
);
|
|
1198
1108
|
return 2;
|
|
1199
1109
|
}
|
|
1200
|
-
const clients = ids.map(clientById).filter((c) => c !== void 0);
|
|
1201
1110
|
const viteIds = ids.filter(isViteTargetId);
|
|
1202
1111
|
const agentIds = ids.filter(isAgentTargetId);
|
|
1203
1112
|
const configIds = ids.filter(isConfigTargetId);
|
|
1204
1113
|
const ciIds = ids.filter(isCiTargetId);
|
|
1205
|
-
if (
|
|
1206
|
-
io.errorLog("svelte-vitals: no valid
|
|
1114
|
+
if (viteIds.length === 0 && agentIds.length === 0 && configIds.length === 0 && ciIds.length === 0) {
|
|
1115
|
+
io.errorLog("svelte-vitals: no valid targets selected.");
|
|
1207
1116
|
return 2;
|
|
1208
1117
|
}
|
|
1209
1118
|
const isSvelteKitApp = (dir) => {
|
|
1210
1119
|
try {
|
|
1211
|
-
if (io.readFile(
|
|
1120
|
+
if (io.readFile(join3(dir, "svelte.config.js")) !== void 0 || io.readFile(join3(dir, "svelte.config.ts")) !== void 0) {
|
|
1212
1121
|
return true;
|
|
1213
1122
|
}
|
|
1214
|
-
const pkgRaw = io.readFile(
|
|
1123
|
+
const pkgRaw = io.readFile(join3(dir, "package.json"));
|
|
1215
1124
|
if (pkgRaw === void 0) return false;
|
|
1216
1125
|
const pkg = JSON.parse(pkgRaw);
|
|
1217
1126
|
return Boolean(pkg.dependencies?.["@sveltejs/kit"] ?? pkg.devDependencies?.["@sveltejs/kit"]);
|
|
@@ -1223,7 +1132,7 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
|
|
|
1223
1132
|
let appDir = io.cwd;
|
|
1224
1133
|
if (needsApp) {
|
|
1225
1134
|
if (flags.app) {
|
|
1226
|
-
const candidate =
|
|
1135
|
+
const candidate = join3(io.cwd, flags.app);
|
|
1227
1136
|
if (!isSvelteKitApp(candidate)) {
|
|
1228
1137
|
io.errorLog(
|
|
1229
1138
|
`svelte-vitals: --app '${flags.app}' is not a SvelteKit app (no svelte.config.{js,ts} or @sveltejs/kit dependency there).`
|
|
@@ -1235,7 +1144,7 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
|
|
|
1235
1144
|
const apps = await (io.discoverApps ?? discoverApps)(io.cwd);
|
|
1236
1145
|
if (apps.length === 1) {
|
|
1237
1146
|
io.errorLog(`svelte-vitals: detected SvelteKit app at ${apps[0]}; targeting it for the Vite/config targets.`);
|
|
1238
|
-
appDir =
|
|
1147
|
+
appDir = join3(io.cwd, apps[0]);
|
|
1239
1148
|
} else if (apps.length > 1) {
|
|
1240
1149
|
if (io.isTTY) {
|
|
1241
1150
|
const picked = await prompts.selectApp(apps);
|
|
@@ -1243,7 +1152,7 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
|
|
|
1243
1152
|
io.log("Cancelled.");
|
|
1244
1153
|
return 0;
|
|
1245
1154
|
}
|
|
1246
|
-
appDir =
|
|
1155
|
+
appDir = join3(io.cwd, picked);
|
|
1247
1156
|
} else {
|
|
1248
1157
|
io.errorLog(`svelte-vitals: multiple SvelteKit apps found: ${apps.join(", ")}.`);
|
|
1249
1158
|
io.errorLog(`svelte-vitals: pass one with --app, e.g. \`svelte-vitals install --app ${apps[0]}\`.`);
|
|
@@ -1253,35 +1162,16 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
|
|
|
1253
1162
|
}
|
|
1254
1163
|
}
|
|
1255
1164
|
const rows = [];
|
|
1256
|
-
for (const
|
|
1257
|
-
let scope;
|
|
1258
|
-
if (client.scopes.length === 1) {
|
|
1259
|
-
scope = client.scopes[0];
|
|
1260
|
-
} else if (flags.scope) {
|
|
1261
|
-
scope = flags.scope;
|
|
1262
|
-
} else if (io.isTTY) {
|
|
1263
|
-
const picked = await prompts.selectScope(client);
|
|
1264
|
-
if (picked === null) {
|
|
1265
|
-
io.log("Cancelled.");
|
|
1266
|
-
return 0;
|
|
1267
|
-
}
|
|
1268
|
-
scope = picked;
|
|
1269
|
-
} else {
|
|
1270
|
-
scope = "project";
|
|
1271
|
-
}
|
|
1165
|
+
for (const viteId of viteIds) {
|
|
1272
1166
|
try {
|
|
1273
|
-
rows.push(
|
|
1167
|
+
rows.push(viteId === "vite-plugin" ? planForVitePlugin(io, appDir) : planForViteHooks(io, appDir));
|
|
1274
1168
|
} catch (err) {
|
|
1275
|
-
const path = client.resolvePath(scope, io.cwd, io.home);
|
|
1276
1169
|
io.errorLog(
|
|
1277
|
-
`svelte-vitals: could not
|
|
1170
|
+
`svelte-vitals: could not check existing Vite target ${viteId}: ${err instanceof Error ? err.message : String(err)}`
|
|
1278
1171
|
);
|
|
1279
1172
|
return 2;
|
|
1280
1173
|
}
|
|
1281
1174
|
}
|
|
1282
|
-
for (const viteId of viteIds) {
|
|
1283
|
-
rows.push(viteId === "vite-plugin" ? planForVitePlugin(io, appDir) : planForViteHooks(io, appDir));
|
|
1284
|
-
}
|
|
1285
1175
|
for (const agentId of agentIds) {
|
|
1286
1176
|
const target = agentTargetById(agentId);
|
|
1287
1177
|
try {
|
|
@@ -1310,7 +1200,7 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
|
|
|
1310
1200
|
rows.push(planForCiTarget(target, io, flags.force ?? false));
|
|
1311
1201
|
} catch (err) {
|
|
1312
1202
|
io.errorLog(
|
|
1313
|
-
`svelte-vitals: could not check existing workflow at ${
|
|
1203
|
+
`svelte-vitals: could not check existing workflow at ${join3(io.cwd, target.relPath)}: ${err instanceof Error ? err.message : String(err)}`
|
|
1314
1204
|
);
|
|
1315
1205
|
return 2;
|
|
1316
1206
|
}
|
|
@@ -1375,7 +1265,7 @@ ${indent(r.snippet ?? "")}`);
|
|
|
1375
1265
|
}
|
|
1376
1266
|
if (hadFailure) return 2;
|
|
1377
1267
|
io.log("");
|
|
1378
|
-
if (
|
|
1268
|
+
if (agentIds.length > 0) io.log("Restart your agent (or start a new session) to pick up the generated skill.");
|
|
1379
1269
|
if (viteWasWritten) io.log("Restart `vite dev` (or your build) to pick up the change.");
|
|
1380
1270
|
io.log("Done.");
|
|
1381
1271
|
return 0;
|
|
@@ -1383,7 +1273,6 @@ ${indent(r.snippet ?? "")}`);
|
|
|
1383
1273
|
|
|
1384
1274
|
// src/install/args.ts
|
|
1385
1275
|
var VALID_TARGETS = [
|
|
1386
|
-
...CLIENTS.map((c) => c.id),
|
|
1387
1276
|
...VITE_TARGETS.map((t) => t.id),
|
|
1388
1277
|
...AGENT_TARGETS.map((t) => t.id),
|
|
1389
1278
|
...CONFIG_TARGETS.map((t) => t.id),
|
|
@@ -1405,11 +1294,8 @@ function resolveInstallArgs(argv) {
|
|
|
1405
1294
|
if (rawClients.length > 0 && client.length === 0) {
|
|
1406
1295
|
errors.push(`svelte-vitals: no valid --client values; expected ${EXPECTED_TARGETS}.`);
|
|
1407
1296
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
if (typeof rawScope === "string") {
|
|
1411
|
-
if (rawScope === "project" || rawScope === "global") scope = rawScope;
|
|
1412
|
-
else errors.push(`svelte-vitals: unknown --scope '${rawScope}'; expected project|global.`);
|
|
1297
|
+
if (argv.scope !== void 0) {
|
|
1298
|
+
warnings.push("svelte-vitals: --scope is no longer used (all install targets are project-scoped). Ignoring.");
|
|
1413
1299
|
}
|
|
1414
1300
|
const app = typeof argv.app === "string" && argv.app.trim() !== "" ? argv.app.trim() : void 0;
|
|
1415
1301
|
const refresh = Boolean(argv.refresh);
|
|
@@ -1417,13 +1303,12 @@ function resolveInstallArgs(argv) {
|
|
|
1417
1303
|
errors.push("svelte-vitals: --refresh regenerates existing files and cannot be combined with --client.");
|
|
1418
1304
|
}
|
|
1419
1305
|
if (errors.length > 0) return { flags: null, warnings, errors };
|
|
1420
|
-
if (refresh && (
|
|
1421
|
-
warnings.push("svelte-vitals: --
|
|
1306
|
+
if (refresh && (Boolean(argv.yes) || Boolean(argv.force) || app !== void 0)) {
|
|
1307
|
+
warnings.push("svelte-vitals: --yes, --force, and --app are ignored with --refresh.");
|
|
1422
1308
|
}
|
|
1423
1309
|
return {
|
|
1424
1310
|
flags: {
|
|
1425
1311
|
...client.length > 0 ? { client } : {},
|
|
1426
|
-
...scope ? { scope } : {},
|
|
1427
1312
|
...app !== void 0 && !refresh ? { app } : {},
|
|
1428
1313
|
yes: Boolean(argv.yes),
|
|
1429
1314
|
dryRun: Boolean(argv["dry-run"]),
|
|
@@ -1436,15 +1321,15 @@ function resolveInstallArgs(argv) {
|
|
|
1436
1321
|
}
|
|
1437
1322
|
|
|
1438
1323
|
// src/install/cli.ts
|
|
1439
|
-
var INSTALL_HELP = `svelte-vitals install \u2014 set up the svelte-vitals
|
|
1324
|
+
var INSTALL_HELP = `svelte-vitals install \u2014 set up the svelte-vitals Vite integration, agent skills/rules, config file, and CI
|
|
1440
1325
|
|
|
1441
1326
|
Usage:
|
|
1442
1327
|
svelte-vitals install [options]
|
|
1443
1328
|
|
|
1444
1329
|
Options:
|
|
1445
|
-
--client <ids> Comma-separated:
|
|
1330
|
+
--client <ids> Comma-separated: vite-plugin,vite-hooks,claude-skill,cursor-rules,claude-skill-improve,config-file,ci-workflow
|
|
1446
1331
|
(skips the interactive picker; the picker groups these by category \u2014
|
|
1447
|
-
|
|
1332
|
+
Vite integration, Agent Skills & rules, CI, Config file)
|
|
1448
1333
|
vite-plugin registers the build-mode plugin in vite.config.{ts,js,mjs}; vite-hooks
|
|
1449
1334
|
wires up the svelteVitalsHandle hook in src/hooks.server.{ts,js}, which improves the
|
|
1450
1335
|
live dashboard's per-route accuracy as you browse. --force does not apply
|
|
@@ -1467,13 +1352,12 @@ Options:
|
|
|
1467
1352
|
\`svelte-vitals ci install\` writes standalone \u2014 pick it here to set it up in
|
|
1468
1353
|
the same pass as everything else; supports --force to regenerate. \`svelte-vitals
|
|
1469
1354
|
ci upgrade\` remains the way to bump an existing workflow's pinned action version.
|
|
1470
|
-
--scope <scope> project | global (applies to all selected clients; codex is always global)
|
|
1471
1355
|
--app <dir> Monorepo: the SvelteKit app directory the vite-plugin/vite-hooks/config-file
|
|
1472
1356
|
targets write into (e.g. --app apps/web). Without it, when the current
|
|
1473
1357
|
directory isn't itself a SvelteKit app, one detected app is used
|
|
1474
1358
|
automatically (with a notice), several prompt a picker on a TTY, and
|
|
1475
|
-
non-interactive runs exit 2 asking for --app. All other targets
|
|
1476
|
-
|
|
1359
|
+
non-interactive runs exit 2 asking for --app. All other targets
|
|
1360
|
+
(skills, ci-workflow) always write at the current directory \u2014
|
|
1477
1361
|
the repo root is their correct home.
|
|
1478
1362
|
--yes, -y Skip the confirmation prompt
|
|
1479
1363
|
--dry-run Print the planned changes and exit without writing
|
|
@@ -1497,7 +1381,6 @@ function realIO() {
|
|
|
1497
1381
|
writeFileSync(path, content);
|
|
1498
1382
|
},
|
|
1499
1383
|
cwd: process.cwd(),
|
|
1500
|
-
home: homedir(),
|
|
1501
1384
|
isTTY: Boolean(process.stdout.isTTY),
|
|
1502
1385
|
nodeVersion: process.version,
|
|
1503
1386
|
log: (line) => console.log(line),
|
|
@@ -1537,14 +1420,6 @@ function clackPrompts() {
|
|
|
1537
1420
|
});
|
|
1538
1421
|
return p.isCancel(res) ? null : res;
|
|
1539
1422
|
},
|
|
1540
|
-
selectScope: async (client) => {
|
|
1541
|
-
const res = await p.select({
|
|
1542
|
-
message: `Scope for ${client.label}?`,
|
|
1543
|
-
options: client.scopes.map((s) => ({ value: s, label: s })),
|
|
1544
|
-
initialValue: client.scopes[0]
|
|
1545
|
-
});
|
|
1546
|
-
return p.isCancel(res) ? null : res;
|
|
1547
|
-
},
|
|
1548
1423
|
selectApp: async (apps) => {
|
|
1549
1424
|
const res = await p.select({
|
|
1550
1425
|
message: "Multiple SvelteKit apps found \u2014 which one should the Vite/config targets go into?",
|
|
@@ -1563,6 +1438,8 @@ ${planText}` });
|
|
|
1563
1438
|
async function runInstallCli(args) {
|
|
1564
1439
|
const argv = mri(args, {
|
|
1565
1440
|
boolean: ["yes", "dry-run", "force", "refresh", "help"],
|
|
1441
|
+
// `scope` is still declared although the flag is gone: it keeps `--scope global` from
|
|
1442
|
+
// parsing its value as a positional, so resolveInstallArgs can warn and carry on.
|
|
1566
1443
|
string: ["client", "scope", "app"],
|
|
1567
1444
|
alias: { y: "yes", h: "help" }
|
|
1568
1445
|
});
|
|
@@ -1578,7 +1455,7 @@ async function runInstallCli(args) {
|
|
|
1578
1455
|
}
|
|
1579
1456
|
|
|
1580
1457
|
// src/ci/cli.ts
|
|
1581
|
-
import { join as
|
|
1458
|
+
import { join as join4 } from "path";
|
|
1582
1459
|
import mri2 from "mri";
|
|
1583
1460
|
|
|
1584
1461
|
// src/ci/upgrade.ts
|
|
@@ -1656,7 +1533,7 @@ async function runCiCli(args, io = realIO()) {
|
|
|
1656
1533
|
io.log(CI_HELP);
|
|
1657
1534
|
return 0;
|
|
1658
1535
|
}
|
|
1659
|
-
const path =
|
|
1536
|
+
const path = join4(io.cwd, WORKFLOW_PATH);
|
|
1660
1537
|
const existing = io.readFile(path);
|
|
1661
1538
|
const plan = planWorkflowWrite(existing, Boolean(argv.force));
|
|
1662
1539
|
io.log("Plan:");
|
|
@@ -1690,7 +1567,7 @@ async function runCiUpgrade(args, io) {
|
|
|
1690
1567
|
io.log(CI_HELP);
|
|
1691
1568
|
return 0;
|
|
1692
1569
|
}
|
|
1693
|
-
const path =
|
|
1570
|
+
const path = join4(io.cwd, WORKFLOW_PATH);
|
|
1694
1571
|
const existing = io.readFile(path);
|
|
1695
1572
|
if (existing === void 0) {
|
|
1696
1573
|
io.errorLog(`svelte-vitals: no ${WORKFLOW_PATH} found \u2014 run \`svelte-vitals ci install\` first.`);
|
|
@@ -1720,12 +1597,76 @@ async function runCiUpgrade(args, io) {
|
|
|
1720
1597
|
return 0;
|
|
1721
1598
|
}
|
|
1722
1599
|
|
|
1600
|
+
// src/explain.ts
|
|
1601
|
+
import mri3 from "mri";
|
|
1602
|
+
import { explainRule } from "@svelte-vitals/core";
|
|
1603
|
+
var EXPLAIN_HELP = `svelte-vitals explain \u2014 print a rule's rationale, fix, and configurable options
|
|
1604
|
+
|
|
1605
|
+
Usage:
|
|
1606
|
+
svelte-vitals explain <rule-id>
|
|
1607
|
+
|
|
1608
|
+
Options:
|
|
1609
|
+
--json Print the rule metadata as JSON instead of text
|
|
1610
|
+
-h, --help Show this help
|
|
1611
|
+
|
|
1612
|
+
Rule ids are category/kebab-case and matched exactly, e.g. \`svelte-vitals explain seo/ssr-disabled\`.`;
|
|
1613
|
+
var realIO2 = {
|
|
1614
|
+
log: (line) => console.log(line),
|
|
1615
|
+
errorLog: (line) => console.error(line)
|
|
1616
|
+
};
|
|
1617
|
+
function describeOptions(id, options) {
|
|
1618
|
+
const MERGE = {
|
|
1619
|
+
integer: "replaces the default",
|
|
1620
|
+
"string-list": "added to the default entries, never replaces them",
|
|
1621
|
+
"string-map": "merged over the default entries \u2014 a new key is added, a built-in key has its value overridden"
|
|
1622
|
+
};
|
|
1623
|
+
const lines = options.map((o) => {
|
|
1624
|
+
const bounds = [o.min !== void 0 ? `>= ${o.min}` : "", o.max !== void 0 ? `<= ${o.max}` : ""].filter(Boolean).join(", ");
|
|
1625
|
+
return `- ${o.name} (${o.kind}, default ${JSON.stringify(o.default)}${bounds ? `, ${bounds}` : ""}) \u2014 ${MERGE[o.kind]}`;
|
|
1626
|
+
});
|
|
1627
|
+
return `set in svelte-vitals.config.* as \`rules: { '${id}': { options: { \u2026 } } }\`, or per path in \`overrides\`:
|
|
1628
|
+
${lines.join("\n")}`;
|
|
1629
|
+
}
|
|
1630
|
+
function formatRuleExplanation(info) {
|
|
1631
|
+
return `${info.id} \u2014 ${info.title} (${info.severity}, ${info.category})
|
|
1632
|
+
|
|
1633
|
+
${info.rationale}
|
|
1634
|
+
|
|
1635
|
+
Docs: ${info.docsUrl}` + (info.fix ? `
|
|
1636
|
+
|
|
1637
|
+
Fix: ${info.fix.description}` : "") + (info.options ? `
|
|
1638
|
+
|
|
1639
|
+
Configurable: ${describeOptions(info.id, info.options)}` : "");
|
|
1640
|
+
}
|
|
1641
|
+
function runExplainCli(args, io = realIO2) {
|
|
1642
|
+
const argv = mri3(args, { boolean: ["json", "help"], alias: { h: "help" } });
|
|
1643
|
+
if (argv.help) {
|
|
1644
|
+
io.log(EXPLAIN_HELP);
|
|
1645
|
+
return 0;
|
|
1646
|
+
}
|
|
1647
|
+
const id = argv._[0];
|
|
1648
|
+
if (id === void 0) {
|
|
1649
|
+
io.errorLog("svelte-vitals: explain needs a rule id, e.g. `svelte-vitals explain seo/ssr-disabled`.");
|
|
1650
|
+
io.errorLog(`svelte-vitals: known rule ids: ${knownRuleIds().join(", ")}.`);
|
|
1651
|
+
return 2;
|
|
1652
|
+
}
|
|
1653
|
+
const info = explainRule(String(id));
|
|
1654
|
+
if (!info) {
|
|
1655
|
+
io.errorLog(`svelte-vitals: unknown rule id '${id}'.`);
|
|
1656
|
+
io.errorLog(`svelte-vitals: known rule ids: ${knownRuleIds().join(", ")}.`);
|
|
1657
|
+
return 2;
|
|
1658
|
+
}
|
|
1659
|
+
io.log(argv.json ? JSON.stringify(info, null, 2) : formatRuleExplanation(info));
|
|
1660
|
+
return 0;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1723
1663
|
// src/bin.ts
|
|
1724
1664
|
var HELP = `svelte-vitals \u2014 a deterministic SvelteKit code-health scanner (SEO \xB7 performance \xB7 correctness \xB7 security \xB7 architecture)
|
|
1725
1665
|
|
|
1726
1666
|
Usage:
|
|
1727
1667
|
svelte-vitals [path] [options]
|
|
1728
|
-
svelte-vitals
|
|
1668
|
+
svelte-vitals explain <id> Print a rule's rationale, fix, and configurable options
|
|
1669
|
+
svelte-vitals install Set up the Vite integration, agent skills/rules, config file, or CI
|
|
1729
1670
|
svelte-vitals ci install Add a GitHub Actions PR gate (annotations + summary comment)
|
|
1730
1671
|
svelte-vitals ci upgrade Refresh the pinned @svelte-vitals/action in an existing workflow
|
|
1731
1672
|
|
|
@@ -1771,6 +1712,9 @@ async function selectApp(apps) {
|
|
|
1771
1712
|
}
|
|
1772
1713
|
async function main() {
|
|
1773
1714
|
const rawArgs = process.argv.slice(2);
|
|
1715
|
+
if (rawArgs[0] === "explain") {
|
|
1716
|
+
process.exit(runExplainCli(rawArgs.slice(1)));
|
|
1717
|
+
}
|
|
1774
1718
|
if (rawArgs[0] === "install") {
|
|
1775
1719
|
const code2 = await runInstallCli(rawArgs.slice(1));
|
|
1776
1720
|
process.exit(code2);
|
|
@@ -1779,7 +1723,7 @@ async function main() {
|
|
|
1779
1723
|
const code2 = await runCiCli(rawArgs.slice(1));
|
|
1780
1724
|
process.exit(code2);
|
|
1781
1725
|
}
|
|
1782
|
-
const argv =
|
|
1726
|
+
const argv = mri4(process.argv.slice(2), {
|
|
1783
1727
|
alias: { h: "help", v: "version" },
|
|
1784
1728
|
boolean: ["by-route", "staged", "score", "verbose", "update-suppressions"],
|
|
1785
1729
|
string: [
|
package/dist/index.d.ts
CHANGED
|
@@ -190,7 +190,7 @@ interface AnalyzeOptions {
|
|
|
190
190
|
* the route/layout (head-resolution) parse path via `collectRoutes` —
|
|
191
191
|
* `collectComponentFacts` (Correctness facts) is unaffected and still scans
|
|
192
192
|
* every component on each call. Callers that don't need cross-call reuse
|
|
193
|
-
* (the CLI's `run()`,
|
|
193
|
+
* (the CLI's `run()`, the Action — each analyzes once per process) can
|
|
194
194
|
* omit this; a fresh cache is created automatically.
|
|
195
195
|
*/
|
|
196
196
|
parseCache?: ParseCache;
|
|
@@ -207,7 +207,7 @@ interface AnalyzeResult {
|
|
|
207
207
|
* Throws ProjectError when `cwd` is not a SvelteKit project. Also throws when a
|
|
208
208
|
* `svelte-vitals.config.{mjs,js,ts}` file in `cwd` fails to load or fails
|
|
209
209
|
* validation (unknown rule ids in `rules`, invalid `weights` entries) — see
|
|
210
|
-
* `loadConfigFile`. Shared by the CLI's run() and by
|
|
210
|
+
* `loadConfigFile`. Shared by the CLI's run() and by embedding callers (issue #24).
|
|
211
211
|
*
|
|
212
212
|
* Config precedence is per field: an explicit option here wins, otherwise the
|
|
213
213
|
* config file's value is used, otherwise the built-in default (design doc
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-vitals",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "A SvelteKit SEO checker — not a runtime Web Vitals reporter. Static analysis of your routes' head metadata.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,10 +43,9 @@
|
|
|
43
43
|
"log-update": "^8.0.0",
|
|
44
44
|
"magicast": "^0.5.3",
|
|
45
45
|
"mri": "^1.2.0",
|
|
46
|
-
"smol-toml": "^1.7.1",
|
|
47
46
|
"svelte": "^5.56.8",
|
|
48
47
|
"tinyglobby": "^0.2.17",
|
|
49
|
-
"@svelte-vitals/core": "0.31.
|
|
48
|
+
"@svelte-vitals/core": "0.31.1"
|
|
50
49
|
},
|
|
51
50
|
"devDependencies": {
|
|
52
51
|
"@types/estree": "^1.0.9",
|