svelte-vitals 0.43.0 → 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -3,19 +3,19 @@ import {
3
3
  CONFIG_FILENAMES,
4
4
  discoverApps,
5
5
  findUnknownRuleIds,
6
+ hasDep,
6
7
  isReporterName,
7
8
  knownRuleIds,
8
9
  readCoreVersion,
9
10
  readPackageVersion,
11
+ readPkg,
10
12
  run
11
- } from "./chunk-6Y2E7QCQ.js";
13
+ } from "./chunk-4MC7STFI.js";
12
14
  import {
13
- consoleIO
14
- } from "./chunk-SLUMRYUD.js";
15
-
16
- // src/bin.ts
17
- import mri4 from "mri";
18
- import * as p2 from "@clack/prompts";
15
+ consoleIO,
16
+ parseCliArgs,
17
+ toList
18
+ } from "./chunk-5Q2PT47V.js";
19
19
 
20
20
  // src/resolve-args.ts
21
21
  var CATEGORIES = ["seo", "performance", "correctness", "security", "architecture"];
@@ -24,7 +24,7 @@ function parseWeights(raw, errors) {
24
24
  const weights = {};
25
25
  const unknownCategories = [];
26
26
  const invalidValues = [];
27
- for (const pair of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
27
+ for (const pair of toList(raw)) {
28
28
  const eq = pair.indexOf("=");
29
29
  if (eq === -1) {
30
30
  invalidValues.push(pair);
@@ -65,7 +65,7 @@ function parseCategories(raw, errors) {
65
65
  if (typeof raw !== "string" || raw.trim() === "") return void 0;
66
66
  const categories = [];
67
67
  const unknownCategories = [];
68
- for (const entry of raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)) {
68
+ for (const entry of toList(raw).map((s) => s.toLowerCase())) {
69
69
  if (!CATEGORIES.includes(entry)) {
70
70
  unknownCategories.push(entry);
71
71
  continue;
@@ -81,12 +81,74 @@ function parseCategories(raw, errors) {
81
81
  }
82
82
  return categories;
83
83
  }
84
- var toList = (v) => typeof v === "string" ? v.split(",").map((s) => s.trim()).filter(Boolean) : [];
84
+ var VALUE_FLAGS = [
85
+ "meta-components",
86
+ "treat-dynamic-as",
87
+ "route",
88
+ "fail-on",
89
+ "reporter",
90
+ "rules",
91
+ "ignore",
92
+ "min-health",
93
+ "out-file",
94
+ "weights",
95
+ "category"
96
+ ];
97
+ function parseRunArgs(args) {
98
+ const patched = args.map((a, i) => a === "--diff" && (args[i + 1] ?? "--").startsWith("-") ? "--diff=HEAD" : a);
99
+ return parseCliArgs(patched, {
100
+ boolean: [
101
+ "by-route",
102
+ "staged",
103
+ "score",
104
+ "verbose",
105
+ "update-suppressions",
106
+ "no-suppressions",
107
+ "no-color",
108
+ "no-animation",
109
+ "help",
110
+ "version"
111
+ ],
112
+ string: [
113
+ "meta-components",
114
+ "treat-dynamic-as",
115
+ "route",
116
+ "fail-on",
117
+ "reporter",
118
+ "rules",
119
+ "ignore",
120
+ "min-health",
121
+ "out-file",
122
+ "diff",
123
+ "baseline",
124
+ "weights",
125
+ "category"
126
+ ],
127
+ short: { h: "help", v: "version" }
128
+ });
129
+ }
85
130
  function resolveArgs(argv) {
86
131
  const warnings = [];
87
132
  const errors = [];
133
+ for (const flag of VALUE_FLAGS) {
134
+ const v = argv[flag];
135
+ if (flag === "out-file" && v === "-") continue;
136
+ if (v !== void 0 && (typeof v !== "string" || v.trim() === "" || v.startsWith("-"))) {
137
+ errors.push(`svelte-vitals: --${flag} requires a value.`);
138
+ }
139
+ }
140
+ let minHealth;
141
+ const minHealthRaw = argv["min-health"];
142
+ if (minHealthRaw !== void 0) {
143
+ const n = Number(minHealthRaw);
144
+ if (!Number.isFinite(n) || n < 0 || n > 100) {
145
+ errors.push(`svelte-vitals: invalid --min-health '${minHealthRaw}'; expected a number 0-100.`);
146
+ } else {
147
+ minHealth = n;
148
+ }
149
+ }
88
150
  const positional = argv._[0];
89
- const metaComponents = typeof argv["meta-components"] === "string" ? argv["meta-components"].split(",").map((s) => s.trim()).filter(Boolean) : void 0;
151
+ const metaComponents = typeof argv["meta-components"] === "string" ? toList(argv["meta-components"]) : void 0;
90
152
  const treatRaw = argv["treat-dynamic-as"];
91
153
  const treatDynamicAs = treatRaw === "warn" || treatRaw === "fail" || treatRaw === "pass" ? treatRaw : void 0;
92
154
  if (typeof treatRaw === "string" && treatDynamicAs === void 0) {
@@ -98,8 +160,8 @@ function resolveArgs(argv) {
98
160
  const diffBase = typeof argv.diff === "string" ? argv.diff || "HEAD" : void 0;
99
161
  const staged = Boolean(argv.staged);
100
162
  let baselineRef;
101
- if (typeof argv.baseline === "string") {
102
- if (argv.baseline.trim() === "") {
163
+ if (argv.baseline !== void 0) {
164
+ if (typeof argv.baseline !== "string" || argv.baseline.trim() === "" || argv.baseline.startsWith("-")) {
103
165
  errors.push("svelte-vitals: --baseline requires a git ref (e.g. --baseline origin/main).");
104
166
  } else {
105
167
  baselineRef = argv.baseline;
@@ -132,14 +194,23 @@ function resolveArgs(argv) {
132
194
  const failOn = failOnValid ? failOnRaw : void 0;
133
195
  const weights = parseWeights(argv.weights, errors);
134
196
  const categories = parseCategories(argv.category, errors);
197
+ if (categories !== void 0 && categories.length > 0 && allow.length > 0) {
198
+ const excluded = allow.filter((id) => !unknown.includes(id)).filter((id) => !categories.includes(id.split("/")[0]));
199
+ if (excluded.length > 0) {
200
+ errors.push(
201
+ `svelte-vitals: --rules id(s) excluded by --category ${categories.join(", ")}: ${excluded.join(", ")}`
202
+ );
203
+ errors.push("Add the rule's category to --category, or drop the rule from --rules.");
204
+ }
205
+ }
135
206
  const score = Boolean(argv.score);
136
207
  if (score && typeof argv.reporter === "string") {
137
208
  warnings.push("svelte-vitals: --score overrides --reporter; reporter output suppressed.");
138
209
  }
139
210
  const verbose = Boolean(argv["verbose"]);
140
- const noColor = argv.color === false;
141
- const noAnimation = argv.animation === false;
142
- const noSuppressions = argv.suppressions === false;
211
+ const noColor = Boolean(argv["no-color"]);
212
+ const noAnimation = Boolean(argv["no-animation"]);
213
+ const noSuppressions = Boolean(argv["no-suppressions"]);
143
214
  const updateSuppressions = Boolean(argv["update-suppressions"]);
144
215
  if (updateSuppressions && noSuppressions) {
145
216
  errors.push("svelte-vitals: --update-suppressions and --no-suppressions cannot be used together.");
@@ -175,7 +246,8 @@ function resolveArgs(argv) {
175
246
  ...updateSuppressions ? { updateSuppressions } : {}
176
247
  },
177
248
  warnings,
178
- errors
249
+ errors,
250
+ minHealth
179
251
  };
180
252
  }
181
253
 
@@ -183,83 +255,11 @@ function resolveArgs(argv) {
183
255
  import { mkdirSync, readFileSync, writeFileSync } from "fs";
184
256
  import { dirname } from "path";
185
257
  import { spawnSync } from "child_process";
186
- import mri from "mri";
187
258
  import * as p from "@clack/prompts";
188
259
 
189
260
  // src/install/index.ts
190
261
  import { join as join3 } from "path";
191
262
 
192
- // src/install/vite-targets.ts
193
- var VITE_TARGETS = [
194
- {
195
- id: "vite-plugin",
196
- label: "Vite plugin (build gate)",
197
- hint: "Fails `vite build` when prerendered pages cross the SEO/Performance threshold"
198
- },
199
- {
200
- id: "vite-hooks",
201
- label: "Live dashboard accuracy",
202
- hint: "Feeds real rendered results into the live dashboard as you browse \u2014 improves per-route accuracy, never fails a build"
203
- }
204
- ];
205
- function viteTargetById(id) {
206
- return VITE_TARGETS.find((t) => t.id === id);
207
- }
208
- function isViteTargetId(id) {
209
- return VITE_TARGETS.some((t) => t.id === id);
210
- }
211
-
212
- // src/install/agent-targets.ts
213
- var AGENT_TARGETS = [
214
- {
215
- id: "claude-skill",
216
- label: "Agent skill: svelte-vitals",
217
- hint: "Teaches the agent svelte-vitals rules + when to run the scanner (Claude Code, Codex, Cursor)",
218
- relPaths: [
219
- ".claude/skills/svelte-vitals/SKILL.md",
220
- ".agents/skills/svelte-vitals/SKILL.md",
221
- ".cursor/skills/svelte-vitals/SKILL.md"
222
- ]
223
- },
224
- {
225
- id: "cursor-rules",
226
- label: "Cursor rules",
227
- hint: "Project rules file so Cursor avoids flagged patterns up front",
228
- relPaths: [".cursor/rules/svelte-vitals.mdc"]
229
- },
230
- {
231
- id: "claude-skill-improve",
232
- label: "Agent skill: improve-svelte",
233
- hint: "Senior-advisor audit \u2192 implementation plans (read-only), for a project-wide improvement roadmap (Claude Code, Codex, Cursor)",
234
- relPaths: [
235
- ".claude/skills/improve-svelte/SKILL.md",
236
- ".agents/skills/improve-svelte/SKILL.md",
237
- ".cursor/skills/improve-svelte/SKILL.md"
238
- ]
239
- }
240
- ];
241
- function agentTargetById(id) {
242
- return AGENT_TARGETS.find((t) => t.id === id);
243
- }
244
- function isAgentTargetId(id) {
245
- return AGENT_TARGETS.some((t) => t.id === id);
246
- }
247
-
248
- // src/install/config-targets.ts
249
- var CONFIG_TARGETS = [
250
- {
251
- id: "config-file",
252
- label: "Config file",
253
- hint: "Scaffolds svelte-vitals.config.{mjs,ts} (auto-picks the best one) with every option commented out"
254
- }
255
- ];
256
- function configTargetById(id) {
257
- return CONFIG_TARGETS.find((t) => t.id === id);
258
- }
259
- function isConfigTargetId(id) {
260
- return CONFIG_TARGETS.some((t) => t.id === id);
261
- }
262
-
263
263
  // src/ci/workflow.ts
264
264
  var WORKFLOW_PATH = ".github/workflows/svelte-vitals.yml";
265
265
  var CHECKOUT_SHA = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0";
@@ -298,20 +298,74 @@ function buildWorkflowYaml(opts) {
298
298
  ].join("\n");
299
299
  }
300
300
 
301
- // src/install/ci-targets.ts
302
- var CI_TARGETS = [
301
+ // src/install/targets.ts
302
+ var INSTALL_TARGETS = [
303
+ {
304
+ id: "vite-plugin",
305
+ kind: "vite",
306
+ label: "Vite plugin (build gate)",
307
+ hint: "Fails `vite build` when prerendered pages cross the SEO/Performance threshold",
308
+ relPaths: []
309
+ },
310
+ {
311
+ id: "vite-hooks",
312
+ kind: "vite",
313
+ label: "Live dashboard accuracy",
314
+ hint: "Feeds real rendered results into the live dashboard as you browse \u2014 improves per-route accuracy, never fails a build",
315
+ relPaths: []
316
+ },
317
+ {
318
+ id: "claude-skill",
319
+ kind: "agent",
320
+ label: "Agent skill: svelte-vitals",
321
+ hint: "Teaches the agent svelte-vitals rules + when to run the scanner (Claude Code, Codex, Cursor)",
322
+ relPaths: [
323
+ ".claude/skills/svelte-vitals/SKILL.md",
324
+ ".agents/skills/svelte-vitals/SKILL.md",
325
+ ".cursor/skills/svelte-vitals/SKILL.md"
326
+ ]
327
+ },
328
+ {
329
+ id: "cursor-rules",
330
+ kind: "agent",
331
+ label: "Cursor rules",
332
+ hint: "Project rules file so Cursor avoids flagged patterns up front",
333
+ relPaths: [".cursor/rules/svelte-vitals.mdc"]
334
+ },
335
+ {
336
+ id: "claude-skill-improve",
337
+ kind: "agent",
338
+ label: "Agent skill: improve-svelte",
339
+ hint: "Senior-advisor audit \u2192 implementation plans (read-only), for a project-wide improvement roadmap (Claude Code, Codex, Cursor)",
340
+ relPaths: [
341
+ ".claude/skills/improve-svelte/SKILL.md",
342
+ ".agents/skills/improve-svelte/SKILL.md",
343
+ ".cursor/skills/improve-svelte/SKILL.md"
344
+ ]
345
+ },
346
+ {
347
+ id: "config-file",
348
+ kind: "config",
349
+ label: "Config file",
350
+ hint: "Scaffolds svelte-vitals.config.{mjs,ts} (auto-picks the best one) with every option commented out",
351
+ relPaths: []
352
+ },
303
353
  {
304
354
  id: "ci-workflow",
355
+ kind: "ci",
305
356
  label: "GitHub Actions CI",
306
357
  hint: "Scaffolds a workflow that runs @svelte-vitals/action on pull requests \u2014 inline annotations, job summary, sticky PR comment",
307
- relPath: WORKFLOW_PATH
358
+ relPaths: [WORKFLOW_PATH]
308
359
  }
309
360
  ];
310
- function ciTargetById(id) {
311
- return CI_TARGETS.find((t) => t.id === id);
361
+ function targetById(id) {
362
+ return INSTALL_TARGETS.find((t) => t.id === id);
312
363
  }
313
- function isCiTargetId(id) {
314
- return CI_TARGETS.some((t) => t.id === id);
364
+ function targetsOfKind(kind) {
365
+ return INSTALL_TARGETS.filter((t) => t.kind === kind);
366
+ }
367
+ function isKind(id, kind) {
368
+ return targetById(id)?.kind === kind;
315
369
  }
316
370
 
317
371
  // src/install/skill-content.ts
@@ -351,7 +405,7 @@ ${lines}`;
351
405
  }).join("\n\n");
352
406
  }
353
407
  function sharedBody(version) {
354
- return `<!-- Generated by \`svelte-vitals install\` (svelte-vitals ${version}). Re-run with --force to refresh. -->
408
+ return `<!-- Generated by \`svelte-vitals install\` (svelte-vitals ${version}). Re-run \`svelte-vitals install --refresh\` to regenerate. -->
355
409
 
356
410
  # svelte-vitals
357
411
 
@@ -398,7 +452,7 @@ name: improve-svelte
398
452
  description: Survey a whole SvelteKit codebase as a senior Svelte/SvelteKit engineer, using svelte-vitals' scan as evidence, then produce a prioritized audit and self-contained implementation plans for other agents (or cheaper models) to execute. Read-only on source code \u2014 it plans improvements, it does not apply them. Use when the user asks to "improve this SvelteKit app", "audit this codebase", "make this app more SEO/performance/security solid", or wants a roadmap of fixes rather than a review of a single diff. For routine regression checks while writing code, use the \`svelte-vitals\` skill instead.
399
453
  ---
400
454
 
401
- <!-- Generated by \`svelte-vitals install\` (svelte-vitals ${version}). Re-run with --force to refresh. -->
455
+ <!-- Generated by \`svelte-vitals install\` (svelte-vitals ${version}). Re-run \`svelte-vitals install --refresh\` to regenerate. -->
402
456
 
403
457
  # improve-svelte
404
458
 
@@ -755,23 +809,10 @@ function findExistingConfigFile(readFile, cwd) {
755
809
  return CONFIG_FILENAMES.find((rel) => readFile(join(cwd, rel)) !== void 0);
756
810
  }
757
811
  function hasSvelteVitalsDependency(readFile, cwd) {
758
- const raw = readFile(join(cwd, "package.json"));
759
- if (raw === void 0) return false;
760
- try {
761
- const pkg = JSON.parse(raw);
762
- return Boolean(pkg.dependencies?.["svelte-vitals"] ?? pkg.devDependencies?.["svelte-vitals"]);
763
- } catch {
764
- return false;
765
- }
812
+ return hasDep(readPkg(readFile, cwd), "svelte-vitals");
766
813
  }
767
814
  function isEsmProject(readFile, cwd) {
768
- const raw = readFile(join(cwd, "package.json"));
769
- if (raw === void 0) return false;
770
- try {
771
- return JSON.parse(raw).type === "module";
772
- } catch {
773
- return false;
774
- }
815
+ return readPkg(readFile, cwd)?.type === "module";
775
816
  }
776
817
  function detectBestConfigExtension(opts) {
777
818
  if (!nodeSupportsNativeTypeScript(opts.nodeVersion)) return "mjs";
@@ -796,7 +837,7 @@ function codemodViteConfig(existing) {
796
837
  return { status: "manual", snippet: MANUAL_SNIPPET };
797
838
  }
798
839
  const already = configObj.plugins.find(
799
- (p3) => p3?.$type === "function-call" && p3?.$callee === "svelteVitals"
840
+ (p2) => p2?.$type === "function-call" && p2?.$callee === "svelteVitals"
800
841
  );
801
842
  if (already !== void 0) {
802
843
  return { status: "exists" };
@@ -904,14 +945,10 @@ function detectPackageManager(io) {
904
945
  return detectPackageManagerFromLockfile(io) ?? "npm";
905
946
  }
906
947
  function hasVitePackage(io) {
907
- const raw = io.readFile(join2(io.cwd, "package.json"));
908
- if (raw === void 0) return false;
909
- try {
910
- const pkg = JSON.parse(raw);
911
- return Boolean(pkg.dependencies?.["@svelte-vitals/vite"] || pkg.devDependencies?.["@svelte-vitals/vite"]);
912
- } catch {
913
- return false;
914
- }
948
+ return hasDep(
949
+ readPkg((p2) => io.readFile(p2), io.cwd),
950
+ "@svelte-vitals/vite"
951
+ );
915
952
  }
916
953
  function installCommand(pm) {
917
954
  const action = pm === "npm" ? "install" : "add";
@@ -946,12 +983,12 @@ function resolveCandidate(io, baseDir, candidates) {
946
983
  function planForVitePlugin(io, appDir) {
947
984
  const { path, content } = resolveCandidate(io, appDir, ["vite.config.ts", "vite.config.js", "vite.config.mjs"]);
948
985
  const result = codemodViteConfig(content);
949
- return { id: "vite-plugin", label: viteTargetById("vite-plugin").label, path, ...result };
986
+ return { id: "vite-plugin", label: targetById("vite-plugin").label, path, ...result };
950
987
  }
951
988
  function planForViteHooks(io, appDir) {
952
989
  const { path, content } = resolveCandidate(io, appDir, ["src/hooks.server.ts", "src/hooks.server.js"]);
953
990
  const result = codemodHooksServer(content);
954
- return { id: "vite-hooks", label: viteTargetById("vite-hooks").label, path, ...result };
991
+ return { id: "vite-hooks", label: targetById("vite-hooks").label, path, ...result };
955
992
  }
956
993
  function agentTargetContent(id, version) {
957
994
  switch (id) {
@@ -967,8 +1004,9 @@ function agentTargetContent(id, version) {
967
1004
  }
968
1005
  }
969
1006
  }
970
- function planForAgentTarget(target, io, force, version) {
971
- const content = agentTargetContent(target.id, version);
1007
+ function planForAgentTarget(id, io, force, version) {
1008
+ const target = targetById(id);
1009
+ const content = agentTargetContent(id, version);
972
1010
  return target.relPaths.map((relPath) => {
973
1011
  const path = join3(io.cwd, relPath);
974
1012
  const existing = io.readFile(path);
@@ -976,7 +1014,8 @@ function planForAgentTarget(target, io, force, version) {
976
1014
  return { id: target.id, label: target.label, path, status, content };
977
1015
  });
978
1016
  }
979
- function planForConfigTarget(target, io, force, appDir) {
1017
+ function planForConfigTarget(io, force, appDir) {
1018
+ const target = targetById("config-file");
980
1019
  const existingRel = findExistingConfigFile(io.readFile, appDir);
981
1020
  if (existingRel !== void 0) {
982
1021
  const path2 = join3(appDir, existingRel);
@@ -996,8 +1035,9 @@ function planForConfigTarget(target, io, force, appDir) {
996
1035
  const content = buildConfigFileTemplate({ useDefineConfig: ext === "ts" });
997
1036
  return { id: target.id, label: target.label, path, status: "created", content };
998
1037
  }
999
- function planForCiTarget(target, io, force) {
1000
- const path = join3(io.cwd, target.relPath);
1038
+ function planForCiTarget(io, force) {
1039
+ const target = targetById("ci-workflow");
1040
+ const path = join3(io.cwd, target.relPaths[0]);
1001
1041
  const existing = io.readFile(path);
1002
1042
  const plan = planWorkflowWrite(existing, force);
1003
1043
  const content = plan.status === "exists" ? void 0 : buildWorkflowYaml({ actionSha: ACTION_SHA, actionVersion: ACTION_VERSION });
@@ -1014,7 +1054,7 @@ ${indent(r.snippet)}` : head;
1014
1054
  async function runRefresh(io, flags, version) {
1015
1055
  let hadFailure = false;
1016
1056
  const rows = [];
1017
- for (const target of AGENT_TARGETS) {
1057
+ for (const target of targetsOfKind("agent")) {
1018
1058
  const content = agentTargetContent(target.id, version);
1019
1059
  for (const relPath of target.relPaths) {
1020
1060
  const path = join3(io.cwd, relPath);
@@ -1079,25 +1119,26 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1079
1119
  ".cursor/environment.json",
1080
1120
  ".cursorrules",
1081
1121
  ".cursorignore",
1082
- agentTargetById("cursor-rules").relPaths[0]
1122
+ targetById("cursor-rules").relPaths[0]
1083
1123
  ].some((rel) => configExists(join3(io.cwd, rel)));
1084
1124
  const detectedAgents = [
1085
1125
  ...claudeSkillDetected ? ["claude-skill"] : [],
1086
1126
  ...cursorRulesDetected ? ["cursor-rules"] : []
1087
1127
  ];
1088
- const ciWorkflowDetected = configExists(join3(io.cwd, CI_TARGETS[0].relPath));
1089
- const configFileDetected = findExistingConfigFile((p3) => configExists(p3) ? "" : void 0, io.cwd) !== void 0;
1128
+ const ciWorkflowDetected = configExists(join3(io.cwd, targetById("ci-workflow").relPaths[0]));
1129
+ const configFileDetected = findExistingConfigFile((p2) => configExists(p2) ? "" : void 0, io.cwd) !== void 0;
1090
1130
  const detected = [
1091
- ...viteConfigExists ? VITE_TARGETS.map((t) => t.id) : [],
1131
+ ...viteConfigExists ? targetsOfKind("vite").map((t) => t.id) : [],
1092
1132
  ...detectedAgents,
1093
- ...ciWorkflowDetected ? CI_TARGETS.map((t) => t.id) : [],
1094
- ...configFileDetected ? CONFIG_TARGETS.map((t) => t.id) : []
1133
+ ...ciWorkflowDetected ? ["ci-workflow"] : [],
1134
+ ...configFileDetected ? ["config-file"] : []
1095
1135
  ];
1136
+ const asOption = (t) => ({ id: t.id, label: t.label, hint: t.hint });
1096
1137
  const groups = {
1097
- "Vite integration": VITE_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1098
- "Agent Skills & rules": AGENT_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1099
- "CI (GitHub Actions)": CI_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1100
- "Config file": CONFIG_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint }))
1138
+ "Vite integration": targetsOfKind("vite").map(asOption),
1139
+ "Agent Skills & rules": targetsOfKind("agent").map(asOption),
1140
+ "CI (GitHub Actions)": targetsOfKind("ci").map(asOption),
1141
+ "Config file": targetsOfKind("config").map(asOption)
1101
1142
  };
1102
1143
  const picked = await prompts.selectClients(groups, detected);
1103
1144
  if (picked === null) {
@@ -1111,10 +1152,10 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1111
1152
  );
1112
1153
  return 2;
1113
1154
  }
1114
- const viteIds = ids.filter(isViteTargetId);
1115
- const agentIds = ids.filter(isAgentTargetId);
1116
- const configIds = ids.filter(isConfigTargetId);
1117
- const ciIds = ids.filter(isCiTargetId);
1155
+ const viteIds = ids.filter((id) => isKind(id, "vite"));
1156
+ const agentIds = ids.filter((id) => isKind(id, "agent"));
1157
+ const configIds = ids.filter((id) => isKind(id, "config"));
1158
+ const ciIds = ids.filter((id) => isKind(id, "ci"));
1118
1159
  if (viteIds.length === 0 && agentIds.length === 0 && configIds.length === 0 && ciIds.length === 0) {
1119
1160
  io.errorLog("svelte-vitals: no valid targets selected.");
1120
1161
  return 2;
@@ -1124,10 +1165,10 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1124
1165
  if (io.readFile(join3(dir, "svelte.config.js")) !== void 0 || io.readFile(join3(dir, "svelte.config.ts")) !== void 0) {
1125
1166
  return true;
1126
1167
  }
1127
- const pkgRaw = io.readFile(join3(dir, "package.json"));
1128
- if (pkgRaw === void 0) return false;
1129
- const pkg = JSON.parse(pkgRaw);
1130
- return Boolean(pkg.dependencies?.["@sveltejs/kit"] ?? pkg.devDependencies?.["@sveltejs/kit"]);
1168
+ return hasDep(
1169
+ readPkg((p2) => io.readFile(p2), dir),
1170
+ "@sveltejs/kit"
1171
+ );
1131
1172
  } catch {
1132
1173
  return false;
1133
1174
  }
@@ -1177,20 +1218,18 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1177
1218
  }
1178
1219
  }
1179
1220
  for (const agentId of agentIds) {
1180
- const target = agentTargetById(agentId);
1181
1221
  try {
1182
- rows.push(...planForAgentTarget(target, io, flags.force ?? false, version));
1222
+ rows.push(...planForAgentTarget(agentId, io, flags.force ?? false, version));
1183
1223
  } catch (err) {
1184
1224
  io.errorLog(
1185
- `svelte-vitals: could not check existing agent target ${target.id}: ${err instanceof Error ? err.message : String(err)}`
1225
+ `svelte-vitals: could not check existing agent target ${agentId}: ${err instanceof Error ? err.message : String(err)}`
1186
1226
  );
1187
1227
  return 2;
1188
1228
  }
1189
1229
  }
1190
- for (const configId of configIds) {
1191
- const target = configTargetById(configId);
1230
+ if (configIds.length > 0) {
1192
1231
  try {
1193
- rows.push(planForConfigTarget(target, io, flags.force ?? false, appDir));
1232
+ rows.push(planForConfigTarget(io, flags.force ?? false, appDir));
1194
1233
  } catch (err) {
1195
1234
  io.errorLog(
1196
1235
  `svelte-vitals: could not check existing config file: ${err instanceof Error ? err.message : String(err)}`
@@ -1198,13 +1237,12 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1198
1237
  return 2;
1199
1238
  }
1200
1239
  }
1201
- for (const ciId of ciIds) {
1202
- const target = ciTargetById(ciId);
1240
+ if (ciIds.length > 0) {
1203
1241
  try {
1204
- rows.push(planForCiTarget(target, io, flags.force ?? false));
1242
+ rows.push(planForCiTarget(io, flags.force ?? false));
1205
1243
  } catch (err) {
1206
1244
  io.errorLog(
1207
- `svelte-vitals: could not check existing workflow at ${join3(io.cwd, target.relPath)}: ${err instanceof Error ? err.message : String(err)}`
1245
+ `svelte-vitals: could not check existing workflow at ${join3(io.cwd, targetById("ci-workflow").relPaths[0])}: ${err instanceof Error ? err.message : String(err)}`
1208
1246
  );
1209
1247
  return 2;
1210
1248
  }
@@ -1227,7 +1265,7 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1227
1265
  let viteWasWritten = false;
1228
1266
  for (const r of rows) {
1229
1267
  if (r.status === "exists") {
1230
- const hint = isViteTargetId(r.id) ? "" : " \u2014 use --force to overwrite";
1268
+ const hint = isKind(r.id, "vite") ? "" : " \u2014 use --force to overwrite";
1231
1269
  io.log(`= ${r.label}: already configured (${r.path})${hint}.`);
1232
1270
  continue;
1233
1271
  }
@@ -1239,8 +1277,8 @@ ${indent(r.snippet ?? "")}`);
1239
1277
  try {
1240
1278
  io.writeFile(r.path, r.content ?? "");
1241
1279
  io.log(`\u2713 ${r.label}: ${r.status} ${r.path}`);
1242
- if (isViteTargetId(r.id)) viteWasWritten = true;
1243
- if (isConfigTargetId(r.id) && r.path.endsWith(".ts")) {
1280
+ if (isKind(r.id, "vite")) viteWasWritten = true;
1281
+ if (isKind(r.id, "config") && r.path.endsWith(".ts")) {
1244
1282
  io.log(
1245
1283
  "svelte-vitals: note \u2014 a .ts config needs Node 22.18+ (or 23.6+) everywhere svelte-vitals runs, CI included; rename to .mjs if that is not guaranteed."
1246
1284
  );
@@ -1276,17 +1314,21 @@ ${indent(r.snippet ?? "")}`);
1276
1314
  }
1277
1315
 
1278
1316
  // src/install/args.ts
1279
- var VALID_TARGETS = [
1280
- ...VITE_TARGETS.map((t) => t.id),
1281
- ...AGENT_TARGETS.map((t) => t.id),
1282
- ...CONFIG_TARGETS.map((t) => t.id),
1283
- ...CI_TARGETS.map((t) => t.id)
1284
- ];
1317
+ var VALID_TARGETS = INSTALL_TARGETS.map((t) => t.id);
1285
1318
  var EXPECTED_TARGETS = VALID_TARGETS.join("|");
1319
+ function parseInstallArgs(args) {
1320
+ return parseCliArgs(args, {
1321
+ boolean: ["yes", "dry-run", "force", "refresh", "help"],
1322
+ // `scope` is still declared although the flag is gone: it keeps `--scope global` from
1323
+ // parsing its value as a positional, so resolveInstallArgs can warn and carry on.
1324
+ string: ["client", "scope", "app"],
1325
+ short: { y: "yes", h: "help" }
1326
+ });
1327
+ }
1286
1328
  function resolveInstallArgs(argv) {
1287
1329
  const warnings = [];
1288
1330
  const errors = [];
1289
- const rawClients = typeof argv.client === "string" ? argv.client.split(",").map((s) => s.trim()).filter(Boolean) : [];
1331
+ const rawClients = toList(argv.client);
1290
1332
  const client = [];
1291
1333
  for (const c of rawClients) {
1292
1334
  if (VALID_TARGETS.includes(c)) {
@@ -1408,6 +1450,14 @@ function realIO() {
1408
1450
  }
1409
1451
  };
1410
1452
  }
1453
+ async function selectAppPrompt(apps, message) {
1454
+ const res = await p.select({
1455
+ message,
1456
+ options: apps.map((a) => ({ value: a, label: a })),
1457
+ initialValue: apps[0]
1458
+ });
1459
+ return p.isCancel(res) ? null : res;
1460
+ }
1411
1461
  function clackPrompts() {
1412
1462
  return {
1413
1463
  selectClients: async (groups, defaults) => {
@@ -1424,14 +1474,7 @@ function clackPrompts() {
1424
1474
  });
1425
1475
  return p.isCancel(res) ? null : res;
1426
1476
  },
1427
- selectApp: async (apps) => {
1428
- const res = await p.select({
1429
- message: "Multiple SvelteKit apps found \u2014 which one should the Vite/config targets go into?",
1430
- options: apps.map((a) => ({ value: a, label: a })),
1431
- initialValue: apps[0]
1432
- });
1433
- return p.isCancel(res) ? null : res;
1434
- },
1477
+ selectApp: (apps) => selectAppPrompt(apps, "Multiple SvelteKit apps found \u2014 which one should the Vite/config targets go into?"),
1435
1478
  confirm: async (planText) => {
1436
1479
  const res = await p.confirm({ message: `Apply this plan?
1437
1480
  ${planText}` });
@@ -1440,13 +1483,7 @@ ${planText}` });
1440
1483
  };
1441
1484
  }
1442
1485
  async function runInstallCli(args) {
1443
- const argv = mri(args, {
1444
- boolean: ["yes", "dry-run", "force", "refresh", "help"],
1445
- // `scope` is still declared although the flag is gone: it keeps `--scope global` from
1446
- // parsing its value as a positional, so resolveInstallArgs can warn and carry on.
1447
- string: ["client", "scope", "app"],
1448
- alias: { y: "yes", h: "help" }
1449
- });
1486
+ const argv = parseInstallArgs(args);
1450
1487
  if (argv.help) {
1451
1488
  console.log(INSTALL_HELP);
1452
1489
  return 0;
@@ -1460,7 +1497,6 @@ async function runInstallCli(args) {
1460
1497
 
1461
1498
  // src/ci/cli.ts
1462
1499
  import { join as join4 } from "path";
1463
- import mri2 from "mri";
1464
1500
 
1465
1501
  // src/ci/upgrade.ts
1466
1502
  var CANONICAL_PATH = "oekazuma/svelte-vitals-action";
@@ -1529,10 +1565,7 @@ async function runCiCli(args, io = realIO()) {
1529
1565
  io.log(CI_HELP);
1530
1566
  return 2;
1531
1567
  }
1532
- const argv = mri2(args.slice(1), {
1533
- boolean: ["force", "dry-run", "help"],
1534
- alias: { h: "help" }
1535
- });
1568
+ const argv = parseCliArgs(args.slice(1), { boolean: ["force", "dry-run", "help"], short: { h: "help" } });
1536
1569
  if (argv.help) {
1537
1570
  io.log(CI_HELP);
1538
1571
  return 0;
@@ -1563,10 +1596,7 @@ async function runCiCli(args, io = realIO()) {
1563
1596
  return 0;
1564
1597
  }
1565
1598
  async function runCiUpgrade(args, io) {
1566
- const argv = mri2(args, {
1567
- boolean: ["dry-run", "help"],
1568
- alias: { h: "help" }
1569
- });
1599
+ const argv = parseCliArgs(args, { boolean: ["dry-run", "help"], short: { h: "help" } });
1570
1600
  if (argv.help) {
1571
1601
  io.log(CI_HELP);
1572
1602
  return 0;
@@ -1602,7 +1632,6 @@ async function runCiUpgrade(args, io) {
1602
1632
  }
1603
1633
 
1604
1634
  // src/explain.ts
1605
- import mri3 from "mri";
1606
1635
  import { allRules as allRules2, CATEGORIES as CATEGORIES2, explainRule } from "@svelte-vitals/core";
1607
1636
  var EXPLAIN_HELP = `svelte-vitals explain \u2014 print a rule's rationale, fix, and configurable options
1608
1637
 
@@ -1652,7 +1681,7 @@ function renderRuleList() {
1652
1681
  );
1653
1682
  }
1654
1683
  function runExplainCli(args, io = consoleIO) {
1655
- const argv = mri3(args, { boolean: ["json", "list", "help"], alias: { h: "help" } });
1684
+ const argv = parseCliArgs(args, { boolean: ["json", "list", "help"], short: { h: "help" } });
1656
1685
  if (argv.help) {
1657
1686
  io.log(EXPLAIN_HELP);
1658
1687
  return 0;
@@ -1750,17 +1779,13 @@ If you are an AI agent:
1750
1779
  naming the flag to pass. \`install\` is the exception \u2014 non-interactively it skips its
1751
1780
  confirmation and writes, so pass \`--dry-run\` first if you need to see the plan.`;
1752
1781
  var VERSION = readPackageVersion();
1753
- async function selectApp(apps) {
1754
- const res = await p2.select({
1755
- message: "Multiple SvelteKit apps found \u2014 which one should svelte-vitals analyze?",
1756
- options: apps.map((a) => ({ value: a, label: a }))
1757
- });
1758
- return p2.isCancel(res) ? null : res;
1782
+ function selectApp(apps) {
1783
+ return selectAppPrompt(apps, "Multiple SvelteKit apps found \u2014 which one should svelte-vitals analyze?");
1759
1784
  }
1760
1785
  async function main() {
1761
1786
  const rawArgs = process.argv.slice(2);
1762
1787
  if (rawArgs[0] === "docs") {
1763
- const { runDocsCli } = await import("./cli-J5V65YIN.js");
1788
+ const { runDocsCli } = await import("./cli-LGD6NDAO.js");
1764
1789
  process.exitCode = runDocsCli(rawArgs.slice(1));
1765
1790
  return;
1766
1791
  }
@@ -1776,25 +1801,7 @@ async function main() {
1776
1801
  const code2 = await runCiCli(rawArgs.slice(1));
1777
1802
  process.exit(code2);
1778
1803
  }
1779
- const argv = mri4(process.argv.slice(2), {
1780
- alias: { h: "help", v: "version" },
1781
- boolean: ["by-route", "staged", "score", "verbose", "update-suppressions"],
1782
- string: [
1783
- "meta-components",
1784
- "treat-dynamic-as",
1785
- "route",
1786
- "fail-on",
1787
- "reporter",
1788
- "rules",
1789
- "ignore",
1790
- "min-health",
1791
- "out-file",
1792
- "diff",
1793
- "baseline",
1794
- "weights",
1795
- "category"
1796
- ]
1797
- });
1804
+ const argv = parseRunArgs(rawArgs);
1798
1805
  if (argv.help) {
1799
1806
  console.log(HELP);
1800
1807
  return;
@@ -1804,20 +1811,10 @@ async function main() {
1804
1811
  console.error("svelte-vitals: run `svelte-vitals docs list` for the bundled guides.");
1805
1812
  return;
1806
1813
  }
1807
- const { options, warnings, errors } = resolveArgs(argv);
1814
+ const { options, warnings, errors, minHealth } = resolveArgs(argv);
1808
1815
  for (const w of warnings) console.error(w);
1809
1816
  for (const e of errors) console.error(e);
1810
1817
  if (!options) process.exit(2);
1811
- const minHealthRaw = argv["min-health"];
1812
- let minHealth;
1813
- if (minHealthRaw !== void 0) {
1814
- const n = Number(minHealthRaw);
1815
- if (!Number.isFinite(n) || n < 0 || n > 100) {
1816
- console.error(`svelte-vitals: invalid --min-health '${minHealthRaw}'; expected a number 0-100.`);
1817
- process.exit(2);
1818
- }
1819
- minHealth = n;
1820
- }
1821
1818
  const code = await run({
1822
1819
  ...options,
1823
1820
  minHealth,