svelte-vitals 0.43.0 → 0.44.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/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-WD5TSADH.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,73 @@ 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 (v !== void 0 && (typeof v !== "string" || v.trim() === "" || v.startsWith("-"))) {
136
+ errors.push(`svelte-vitals: --${flag} requires a value.`);
137
+ }
138
+ }
139
+ let minHealth;
140
+ const minHealthRaw = argv["min-health"];
141
+ if (minHealthRaw !== void 0) {
142
+ const n = Number(minHealthRaw);
143
+ if (!Number.isFinite(n) || n < 0 || n > 100) {
144
+ errors.push(`svelte-vitals: invalid --min-health '${minHealthRaw}'; expected a number 0-100.`);
145
+ } else {
146
+ minHealth = n;
147
+ }
148
+ }
88
149
  const positional = argv._[0];
89
- const metaComponents = typeof argv["meta-components"] === "string" ? argv["meta-components"].split(",").map((s) => s.trim()).filter(Boolean) : void 0;
150
+ const metaComponents = typeof argv["meta-components"] === "string" ? toList(argv["meta-components"]) : void 0;
90
151
  const treatRaw = argv["treat-dynamic-as"];
91
152
  const treatDynamicAs = treatRaw === "warn" || treatRaw === "fail" || treatRaw === "pass" ? treatRaw : void 0;
92
153
  if (typeof treatRaw === "string" && treatDynamicAs === void 0) {
@@ -98,8 +159,8 @@ function resolveArgs(argv) {
98
159
  const diffBase = typeof argv.diff === "string" ? argv.diff || "HEAD" : void 0;
99
160
  const staged = Boolean(argv.staged);
100
161
  let baselineRef;
101
- if (typeof argv.baseline === "string") {
102
- if (argv.baseline.trim() === "") {
162
+ if (argv.baseline !== void 0) {
163
+ if (typeof argv.baseline !== "string" || argv.baseline.trim() === "" || argv.baseline.startsWith("-")) {
103
164
  errors.push("svelte-vitals: --baseline requires a git ref (e.g. --baseline origin/main).");
104
165
  } else {
105
166
  baselineRef = argv.baseline;
@@ -137,9 +198,9 @@ function resolveArgs(argv) {
137
198
  warnings.push("svelte-vitals: --score overrides --reporter; reporter output suppressed.");
138
199
  }
139
200
  const verbose = Boolean(argv["verbose"]);
140
- const noColor = argv.color === false;
141
- const noAnimation = argv.animation === false;
142
- const noSuppressions = argv.suppressions === false;
201
+ const noColor = Boolean(argv["no-color"]);
202
+ const noAnimation = Boolean(argv["no-animation"]);
203
+ const noSuppressions = Boolean(argv["no-suppressions"]);
143
204
  const updateSuppressions = Boolean(argv["update-suppressions"]);
144
205
  if (updateSuppressions && noSuppressions) {
145
206
  errors.push("svelte-vitals: --update-suppressions and --no-suppressions cannot be used together.");
@@ -175,7 +236,8 @@ function resolveArgs(argv) {
175
236
  ...updateSuppressions ? { updateSuppressions } : {}
176
237
  },
177
238
  warnings,
178
- errors
239
+ errors,
240
+ minHealth
179
241
  };
180
242
  }
181
243
 
@@ -183,83 +245,11 @@ function resolveArgs(argv) {
183
245
  import { mkdirSync, readFileSync, writeFileSync } from "fs";
184
246
  import { dirname } from "path";
185
247
  import { spawnSync } from "child_process";
186
- import mri from "mri";
187
248
  import * as p from "@clack/prompts";
188
249
 
189
250
  // src/install/index.ts
190
251
  import { join as join3 } from "path";
191
252
 
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
253
  // src/ci/workflow.ts
264
254
  var WORKFLOW_PATH = ".github/workflows/svelte-vitals.yml";
265
255
  var CHECKOUT_SHA = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0";
@@ -298,20 +288,74 @@ function buildWorkflowYaml(opts) {
298
288
  ].join("\n");
299
289
  }
300
290
 
301
- // src/install/ci-targets.ts
302
- var CI_TARGETS = [
291
+ // src/install/targets.ts
292
+ var INSTALL_TARGETS = [
293
+ {
294
+ id: "vite-plugin",
295
+ kind: "vite",
296
+ label: "Vite plugin (build gate)",
297
+ hint: "Fails `vite build` when prerendered pages cross the SEO/Performance threshold",
298
+ relPaths: []
299
+ },
300
+ {
301
+ id: "vite-hooks",
302
+ kind: "vite",
303
+ label: "Live dashboard accuracy",
304
+ hint: "Feeds real rendered results into the live dashboard as you browse \u2014 improves per-route accuracy, never fails a build",
305
+ relPaths: []
306
+ },
307
+ {
308
+ id: "claude-skill",
309
+ kind: "agent",
310
+ label: "Agent skill: svelte-vitals",
311
+ hint: "Teaches the agent svelte-vitals rules + when to run the scanner (Claude Code, Codex, Cursor)",
312
+ relPaths: [
313
+ ".claude/skills/svelte-vitals/SKILL.md",
314
+ ".agents/skills/svelte-vitals/SKILL.md",
315
+ ".cursor/skills/svelte-vitals/SKILL.md"
316
+ ]
317
+ },
318
+ {
319
+ id: "cursor-rules",
320
+ kind: "agent",
321
+ label: "Cursor rules",
322
+ hint: "Project rules file so Cursor avoids flagged patterns up front",
323
+ relPaths: [".cursor/rules/svelte-vitals.mdc"]
324
+ },
325
+ {
326
+ id: "claude-skill-improve",
327
+ kind: "agent",
328
+ label: "Agent skill: improve-svelte",
329
+ hint: "Senior-advisor audit \u2192 implementation plans (read-only), for a project-wide improvement roadmap (Claude Code, Codex, Cursor)",
330
+ relPaths: [
331
+ ".claude/skills/improve-svelte/SKILL.md",
332
+ ".agents/skills/improve-svelte/SKILL.md",
333
+ ".cursor/skills/improve-svelte/SKILL.md"
334
+ ]
335
+ },
336
+ {
337
+ id: "config-file",
338
+ kind: "config",
339
+ label: "Config file",
340
+ hint: "Scaffolds svelte-vitals.config.{mjs,ts} (auto-picks the best one) with every option commented out",
341
+ relPaths: []
342
+ },
303
343
  {
304
344
  id: "ci-workflow",
345
+ kind: "ci",
305
346
  label: "GitHub Actions CI",
306
347
  hint: "Scaffolds a workflow that runs @svelte-vitals/action on pull requests \u2014 inline annotations, job summary, sticky PR comment",
307
- relPath: WORKFLOW_PATH
348
+ relPaths: [WORKFLOW_PATH]
308
349
  }
309
350
  ];
310
- function ciTargetById(id) {
311
- return CI_TARGETS.find((t) => t.id === id);
351
+ function targetById(id) {
352
+ return INSTALL_TARGETS.find((t) => t.id === id);
312
353
  }
313
- function isCiTargetId(id) {
314
- return CI_TARGETS.some((t) => t.id === id);
354
+ function targetsOfKind(kind) {
355
+ return INSTALL_TARGETS.filter((t) => t.kind === kind);
356
+ }
357
+ function isKind(id, kind) {
358
+ return targetById(id)?.kind === kind;
315
359
  }
316
360
 
317
361
  // src/install/skill-content.ts
@@ -755,23 +799,10 @@ function findExistingConfigFile(readFile, cwd) {
755
799
  return CONFIG_FILENAMES.find((rel) => readFile(join(cwd, rel)) !== void 0);
756
800
  }
757
801
  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
- }
802
+ return hasDep(readPkg(readFile, cwd), "svelte-vitals");
766
803
  }
767
804
  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
- }
805
+ return readPkg(readFile, cwd)?.type === "module";
775
806
  }
776
807
  function detectBestConfigExtension(opts) {
777
808
  if (!nodeSupportsNativeTypeScript(opts.nodeVersion)) return "mjs";
@@ -796,7 +827,7 @@ function codemodViteConfig(existing) {
796
827
  return { status: "manual", snippet: MANUAL_SNIPPET };
797
828
  }
798
829
  const already = configObj.plugins.find(
799
- (p3) => p3?.$type === "function-call" && p3?.$callee === "svelteVitals"
830
+ (p2) => p2?.$type === "function-call" && p2?.$callee === "svelteVitals"
800
831
  );
801
832
  if (already !== void 0) {
802
833
  return { status: "exists" };
@@ -904,14 +935,10 @@ function detectPackageManager(io) {
904
935
  return detectPackageManagerFromLockfile(io) ?? "npm";
905
936
  }
906
937
  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
- }
938
+ return hasDep(
939
+ readPkg((p2) => io.readFile(p2), io.cwd),
940
+ "@svelte-vitals/vite"
941
+ );
915
942
  }
916
943
  function installCommand(pm) {
917
944
  const action = pm === "npm" ? "install" : "add";
@@ -946,12 +973,12 @@ function resolveCandidate(io, baseDir, candidates) {
946
973
  function planForVitePlugin(io, appDir) {
947
974
  const { path, content } = resolveCandidate(io, appDir, ["vite.config.ts", "vite.config.js", "vite.config.mjs"]);
948
975
  const result = codemodViteConfig(content);
949
- return { id: "vite-plugin", label: viteTargetById("vite-plugin").label, path, ...result };
976
+ return { id: "vite-plugin", label: targetById("vite-plugin").label, path, ...result };
950
977
  }
951
978
  function planForViteHooks(io, appDir) {
952
979
  const { path, content } = resolveCandidate(io, appDir, ["src/hooks.server.ts", "src/hooks.server.js"]);
953
980
  const result = codemodHooksServer(content);
954
- return { id: "vite-hooks", label: viteTargetById("vite-hooks").label, path, ...result };
981
+ return { id: "vite-hooks", label: targetById("vite-hooks").label, path, ...result };
955
982
  }
956
983
  function agentTargetContent(id, version) {
957
984
  switch (id) {
@@ -967,8 +994,9 @@ function agentTargetContent(id, version) {
967
994
  }
968
995
  }
969
996
  }
970
- function planForAgentTarget(target, io, force, version) {
971
- const content = agentTargetContent(target.id, version);
997
+ function planForAgentTarget(id, io, force, version) {
998
+ const target = targetById(id);
999
+ const content = agentTargetContent(id, version);
972
1000
  return target.relPaths.map((relPath) => {
973
1001
  const path = join3(io.cwd, relPath);
974
1002
  const existing = io.readFile(path);
@@ -976,7 +1004,8 @@ function planForAgentTarget(target, io, force, version) {
976
1004
  return { id: target.id, label: target.label, path, status, content };
977
1005
  });
978
1006
  }
979
- function planForConfigTarget(target, io, force, appDir) {
1007
+ function planForConfigTarget(io, force, appDir) {
1008
+ const target = targetById("config-file");
980
1009
  const existingRel = findExistingConfigFile(io.readFile, appDir);
981
1010
  if (existingRel !== void 0) {
982
1011
  const path2 = join3(appDir, existingRel);
@@ -996,8 +1025,9 @@ function planForConfigTarget(target, io, force, appDir) {
996
1025
  const content = buildConfigFileTemplate({ useDefineConfig: ext === "ts" });
997
1026
  return { id: target.id, label: target.label, path, status: "created", content };
998
1027
  }
999
- function planForCiTarget(target, io, force) {
1000
- const path = join3(io.cwd, target.relPath);
1028
+ function planForCiTarget(io, force) {
1029
+ const target = targetById("ci-workflow");
1030
+ const path = join3(io.cwd, target.relPaths[0]);
1001
1031
  const existing = io.readFile(path);
1002
1032
  const plan = planWorkflowWrite(existing, force);
1003
1033
  const content = plan.status === "exists" ? void 0 : buildWorkflowYaml({ actionSha: ACTION_SHA, actionVersion: ACTION_VERSION });
@@ -1014,7 +1044,7 @@ ${indent(r.snippet)}` : head;
1014
1044
  async function runRefresh(io, flags, version) {
1015
1045
  let hadFailure = false;
1016
1046
  const rows = [];
1017
- for (const target of AGENT_TARGETS) {
1047
+ for (const target of targetsOfKind("agent")) {
1018
1048
  const content = agentTargetContent(target.id, version);
1019
1049
  for (const relPath of target.relPaths) {
1020
1050
  const path = join3(io.cwd, relPath);
@@ -1079,25 +1109,26 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1079
1109
  ".cursor/environment.json",
1080
1110
  ".cursorrules",
1081
1111
  ".cursorignore",
1082
- agentTargetById("cursor-rules").relPaths[0]
1112
+ targetById("cursor-rules").relPaths[0]
1083
1113
  ].some((rel) => configExists(join3(io.cwd, rel)));
1084
1114
  const detectedAgents = [
1085
1115
  ...claudeSkillDetected ? ["claude-skill"] : [],
1086
1116
  ...cursorRulesDetected ? ["cursor-rules"] : []
1087
1117
  ];
1088
- const ciWorkflowDetected = configExists(join3(io.cwd, CI_TARGETS[0].relPath));
1089
- const configFileDetected = findExistingConfigFile((p3) => configExists(p3) ? "" : void 0, io.cwd) !== void 0;
1118
+ const ciWorkflowDetected = configExists(join3(io.cwd, targetById("ci-workflow").relPaths[0]));
1119
+ const configFileDetected = findExistingConfigFile((p2) => configExists(p2) ? "" : void 0, io.cwd) !== void 0;
1090
1120
  const detected = [
1091
- ...viteConfigExists ? VITE_TARGETS.map((t) => t.id) : [],
1121
+ ...viteConfigExists ? targetsOfKind("vite").map((t) => t.id) : [],
1092
1122
  ...detectedAgents,
1093
- ...ciWorkflowDetected ? CI_TARGETS.map((t) => t.id) : [],
1094
- ...configFileDetected ? CONFIG_TARGETS.map((t) => t.id) : []
1123
+ ...ciWorkflowDetected ? ["ci-workflow"] : [],
1124
+ ...configFileDetected ? ["config-file"] : []
1095
1125
  ];
1126
+ const asOption = (t) => ({ id: t.id, label: t.label, hint: t.hint });
1096
1127
  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 }))
1128
+ "Vite integration": targetsOfKind("vite").map(asOption),
1129
+ "Agent Skills & rules": targetsOfKind("agent").map(asOption),
1130
+ "CI (GitHub Actions)": targetsOfKind("ci").map(asOption),
1131
+ "Config file": targetsOfKind("config").map(asOption)
1101
1132
  };
1102
1133
  const picked = await prompts.selectClients(groups, detected);
1103
1134
  if (picked === null) {
@@ -1111,10 +1142,10 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1111
1142
  );
1112
1143
  return 2;
1113
1144
  }
1114
- const viteIds = ids.filter(isViteTargetId);
1115
- const agentIds = ids.filter(isAgentTargetId);
1116
- const configIds = ids.filter(isConfigTargetId);
1117
- const ciIds = ids.filter(isCiTargetId);
1145
+ const viteIds = ids.filter((id) => isKind(id, "vite"));
1146
+ const agentIds = ids.filter((id) => isKind(id, "agent"));
1147
+ const configIds = ids.filter((id) => isKind(id, "config"));
1148
+ const ciIds = ids.filter((id) => isKind(id, "ci"));
1118
1149
  if (viteIds.length === 0 && agentIds.length === 0 && configIds.length === 0 && ciIds.length === 0) {
1119
1150
  io.errorLog("svelte-vitals: no valid targets selected.");
1120
1151
  return 2;
@@ -1124,10 +1155,10 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1124
1155
  if (io.readFile(join3(dir, "svelte.config.js")) !== void 0 || io.readFile(join3(dir, "svelte.config.ts")) !== void 0) {
1125
1156
  return true;
1126
1157
  }
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"]);
1158
+ return hasDep(
1159
+ readPkg((p2) => io.readFile(p2), dir),
1160
+ "@sveltejs/kit"
1161
+ );
1131
1162
  } catch {
1132
1163
  return false;
1133
1164
  }
@@ -1177,20 +1208,18 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1177
1208
  }
1178
1209
  }
1179
1210
  for (const agentId of agentIds) {
1180
- const target = agentTargetById(agentId);
1181
1211
  try {
1182
- rows.push(...planForAgentTarget(target, io, flags.force ?? false, version));
1212
+ rows.push(...planForAgentTarget(agentId, io, flags.force ?? false, version));
1183
1213
  } catch (err) {
1184
1214
  io.errorLog(
1185
- `svelte-vitals: could not check existing agent target ${target.id}: ${err instanceof Error ? err.message : String(err)}`
1215
+ `svelte-vitals: could not check existing agent target ${agentId}: ${err instanceof Error ? err.message : String(err)}`
1186
1216
  );
1187
1217
  return 2;
1188
1218
  }
1189
1219
  }
1190
- for (const configId of configIds) {
1191
- const target = configTargetById(configId);
1220
+ if (configIds.length > 0) {
1192
1221
  try {
1193
- rows.push(planForConfigTarget(target, io, flags.force ?? false, appDir));
1222
+ rows.push(planForConfigTarget(io, flags.force ?? false, appDir));
1194
1223
  } catch (err) {
1195
1224
  io.errorLog(
1196
1225
  `svelte-vitals: could not check existing config file: ${err instanceof Error ? err.message : String(err)}`
@@ -1198,13 +1227,12 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1198
1227
  return 2;
1199
1228
  }
1200
1229
  }
1201
- for (const ciId of ciIds) {
1202
- const target = ciTargetById(ciId);
1230
+ if (ciIds.length > 0) {
1203
1231
  try {
1204
- rows.push(planForCiTarget(target, io, flags.force ?? false));
1232
+ rows.push(planForCiTarget(io, flags.force ?? false));
1205
1233
  } catch (err) {
1206
1234
  io.errorLog(
1207
- `svelte-vitals: could not check existing workflow at ${join3(io.cwd, target.relPath)}: ${err instanceof Error ? err.message : String(err)}`
1235
+ `svelte-vitals: could not check existing workflow at ${join3(io.cwd, targetById("ci-workflow").relPaths[0])}: ${err instanceof Error ? err.message : String(err)}`
1208
1236
  );
1209
1237
  return 2;
1210
1238
  }
@@ -1227,7 +1255,7 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1227
1255
  let viteWasWritten = false;
1228
1256
  for (const r of rows) {
1229
1257
  if (r.status === "exists") {
1230
- const hint = isViteTargetId(r.id) ? "" : " \u2014 use --force to overwrite";
1258
+ const hint = isKind(r.id, "vite") ? "" : " \u2014 use --force to overwrite";
1231
1259
  io.log(`= ${r.label}: already configured (${r.path})${hint}.`);
1232
1260
  continue;
1233
1261
  }
@@ -1239,8 +1267,8 @@ ${indent(r.snippet ?? "")}`);
1239
1267
  try {
1240
1268
  io.writeFile(r.path, r.content ?? "");
1241
1269
  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")) {
1270
+ if (isKind(r.id, "vite")) viteWasWritten = true;
1271
+ if (isKind(r.id, "config") && r.path.endsWith(".ts")) {
1244
1272
  io.log(
1245
1273
  "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
1274
  );
@@ -1276,17 +1304,21 @@ ${indent(r.snippet ?? "")}`);
1276
1304
  }
1277
1305
 
1278
1306
  // 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
- ];
1307
+ var VALID_TARGETS = INSTALL_TARGETS.map((t) => t.id);
1285
1308
  var EXPECTED_TARGETS = VALID_TARGETS.join("|");
1309
+ function parseInstallArgs(args) {
1310
+ return parseCliArgs(args, {
1311
+ boolean: ["yes", "dry-run", "force", "refresh", "help"],
1312
+ // `scope` is still declared although the flag is gone: it keeps `--scope global` from
1313
+ // parsing its value as a positional, so resolveInstallArgs can warn and carry on.
1314
+ string: ["client", "scope", "app"],
1315
+ short: { y: "yes", h: "help" }
1316
+ });
1317
+ }
1286
1318
  function resolveInstallArgs(argv) {
1287
1319
  const warnings = [];
1288
1320
  const errors = [];
1289
- const rawClients = typeof argv.client === "string" ? argv.client.split(",").map((s) => s.trim()).filter(Boolean) : [];
1321
+ const rawClients = toList(argv.client);
1290
1322
  const client = [];
1291
1323
  for (const c of rawClients) {
1292
1324
  if (VALID_TARGETS.includes(c)) {
@@ -1408,6 +1440,14 @@ function realIO() {
1408
1440
  }
1409
1441
  };
1410
1442
  }
1443
+ async function selectAppPrompt(apps, message) {
1444
+ const res = await p.select({
1445
+ message,
1446
+ options: apps.map((a) => ({ value: a, label: a })),
1447
+ initialValue: apps[0]
1448
+ });
1449
+ return p.isCancel(res) ? null : res;
1450
+ }
1411
1451
  function clackPrompts() {
1412
1452
  return {
1413
1453
  selectClients: async (groups, defaults) => {
@@ -1424,14 +1464,7 @@ function clackPrompts() {
1424
1464
  });
1425
1465
  return p.isCancel(res) ? null : res;
1426
1466
  },
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
- },
1467
+ selectApp: (apps) => selectAppPrompt(apps, "Multiple SvelteKit apps found \u2014 which one should the Vite/config targets go into?"),
1435
1468
  confirm: async (planText) => {
1436
1469
  const res = await p.confirm({ message: `Apply this plan?
1437
1470
  ${planText}` });
@@ -1440,13 +1473,7 @@ ${planText}` });
1440
1473
  };
1441
1474
  }
1442
1475
  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
- });
1476
+ const argv = parseInstallArgs(args);
1450
1477
  if (argv.help) {
1451
1478
  console.log(INSTALL_HELP);
1452
1479
  return 0;
@@ -1460,7 +1487,6 @@ async function runInstallCli(args) {
1460
1487
 
1461
1488
  // src/ci/cli.ts
1462
1489
  import { join as join4 } from "path";
1463
- import mri2 from "mri";
1464
1490
 
1465
1491
  // src/ci/upgrade.ts
1466
1492
  var CANONICAL_PATH = "oekazuma/svelte-vitals-action";
@@ -1529,10 +1555,7 @@ async function runCiCli(args, io = realIO()) {
1529
1555
  io.log(CI_HELP);
1530
1556
  return 2;
1531
1557
  }
1532
- const argv = mri2(args.slice(1), {
1533
- boolean: ["force", "dry-run", "help"],
1534
- alias: { h: "help" }
1535
- });
1558
+ const argv = parseCliArgs(args.slice(1), { boolean: ["force", "dry-run", "help"], short: { h: "help" } });
1536
1559
  if (argv.help) {
1537
1560
  io.log(CI_HELP);
1538
1561
  return 0;
@@ -1563,10 +1586,7 @@ async function runCiCli(args, io = realIO()) {
1563
1586
  return 0;
1564
1587
  }
1565
1588
  async function runCiUpgrade(args, io) {
1566
- const argv = mri2(args, {
1567
- boolean: ["dry-run", "help"],
1568
- alias: { h: "help" }
1569
- });
1589
+ const argv = parseCliArgs(args, { boolean: ["dry-run", "help"], short: { h: "help" } });
1570
1590
  if (argv.help) {
1571
1591
  io.log(CI_HELP);
1572
1592
  return 0;
@@ -1602,7 +1622,6 @@ async function runCiUpgrade(args, io) {
1602
1622
  }
1603
1623
 
1604
1624
  // src/explain.ts
1605
- import mri3 from "mri";
1606
1625
  import { allRules as allRules2, CATEGORIES as CATEGORIES2, explainRule } from "@svelte-vitals/core";
1607
1626
  var EXPLAIN_HELP = `svelte-vitals explain \u2014 print a rule's rationale, fix, and configurable options
1608
1627
 
@@ -1652,7 +1671,7 @@ function renderRuleList() {
1652
1671
  );
1653
1672
  }
1654
1673
  function runExplainCli(args, io = consoleIO) {
1655
- const argv = mri3(args, { boolean: ["json", "list", "help"], alias: { h: "help" } });
1674
+ const argv = parseCliArgs(args, { boolean: ["json", "list", "help"], short: { h: "help" } });
1656
1675
  if (argv.help) {
1657
1676
  io.log(EXPLAIN_HELP);
1658
1677
  return 0;
@@ -1750,17 +1769,13 @@ If you are an AI agent:
1750
1769
  naming the flag to pass. \`install\` is the exception \u2014 non-interactively it skips its
1751
1770
  confirmation and writes, so pass \`--dry-run\` first if you need to see the plan.`;
1752
1771
  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;
1772
+ function selectApp(apps) {
1773
+ return selectAppPrompt(apps, "Multiple SvelteKit apps found \u2014 which one should svelte-vitals analyze?");
1759
1774
  }
1760
1775
  async function main() {
1761
1776
  const rawArgs = process.argv.slice(2);
1762
1777
  if (rawArgs[0] === "docs") {
1763
- const { runDocsCli } = await import("./cli-J5V65YIN.js");
1778
+ const { runDocsCli } = await import("./cli-LGD6NDAO.js");
1764
1779
  process.exitCode = runDocsCli(rawArgs.slice(1));
1765
1780
  return;
1766
1781
  }
@@ -1776,25 +1791,7 @@ async function main() {
1776
1791
  const code2 = await runCiCli(rawArgs.slice(1));
1777
1792
  process.exit(code2);
1778
1793
  }
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
- });
1794
+ const argv = parseRunArgs(rawArgs);
1798
1795
  if (argv.help) {
1799
1796
  console.log(HELP);
1800
1797
  return;
@@ -1804,20 +1801,10 @@ async function main() {
1804
1801
  console.error("svelte-vitals: run `svelte-vitals docs list` for the bundled guides.");
1805
1802
  return;
1806
1803
  }
1807
- const { options, warnings, errors } = resolveArgs(argv);
1804
+ const { options, warnings, errors, minHealth } = resolveArgs(argv);
1808
1805
  for (const w of warnings) console.error(w);
1809
1806
  for (const e of errors) console.error(e);
1810
1807
  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
1808
  const code = await run({
1822
1809
  ...options,
1823
1810
  minHealth,