svelte-vitals 0.26.0 → 0.28.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 CHANGED
@@ -3,17 +3,17 @@
3
3
  [![npm](https://img.shields.io/npm/v/svelte-vitals)](https://www.npmjs.com/package/svelte-vitals)
4
4
  [![MIT](https://img.shields.io/npm/l/svelte-vitals)](https://opensource.org/licenses/MIT)
5
5
 
6
- > **A SvelteKit SEO checker — not a runtime Web Vitals reporter.**
7
- > Diagnose your project's SEO health by statically analyzing your source code, before it ships. No browser, no build server, no headless Chrome.
6
+ > **A static SvelteKit code-health scanner — not a runtime Web Vitals reporter.**
7
+ > Diagnose your project's SEO, Performance, Correctness, Security, and Architecture health by statically analyzing your source code, before it ships. No browser, no build server, no headless Chrome.
8
8
  >
9
- > **ESM-only** (Node 18+). Ships ES modules only; `require()` is unsupported by design.
9
+ > **ESM-only** (Node 22.13+). Ships ES modules only; `require()` is unsupported by design.
10
10
 
11
11
  ```bash
12
12
  npx svelte-vitals@latest
13
13
  ```
14
14
 
15
- > [!NOTE]
16
- > **Early development.** Currently ships static-mode analysis and the first SEO rule (`<title>` presence). More rules, scoring, and a build-time plugin are on the roadmap. Output may change before `1.0`.
15
+ > [!WARNING]
16
+ > **Pre-1.0.** APIs, rule IDs, scoring, and output formats can change at any time, including breaking changes between minor releases.
17
17
 
18
18
  ## Usage
19
19
 
@@ -25,15 +25,31 @@ npx svelte-vitals@latest ./apps/web # or a specific path
25
25
  ```
26
26
 
27
27
  ```
28
- Svelte Vitals · SEO (static mode)
28
+ Svelte Vitals · static mode
29
29
 
30
- Critical (1)
30
+ Health: 76/100
31
+ SEO Score: 51/100 (route avg 61 · site −10)
32
+ Architecture Score: 100/100 (route avg 100)
33
+
34
+ Critical (2)
31
35
  ────────────────────────
32
36
  ✗ SEO001 Missing <title>
33
- /none
34
- src/routes/none/+page.svelte
37
+ /
38
+ src/routes/+page.svelte
39
+ ✗ SEO002 Missing <meta name="description">
40
+ /
41
+ src/routes/+page.svelte
42
+
43
+ Warnings (10)
44
+ ────────────────────────
45
+ ✗ SEO003 Missing <link rel="canonical">
46
+ /
47
+ src/routes/+page.svelte
48
+ …and 1 more
49
+ …and 1 more rule affected — run with --verbose to see all
35
50
 
36
- Passed (3)
51
+ Passed (11)
52
+ ────────────────────────
37
53
 
38
54
  ↯ = set dynamically (verified at runtime).
39
55
  ```
@@ -44,14 +60,34 @@ On an interactive terminal wide enough for the mascot (20+ columns), a small lin
44
60
 
45
61
  ### Exit codes
46
62
 
47
- | Code | Meaning |
48
- | ---- | --------------------------------------------------------------- |
49
- | `0` | No failing findings |
50
- | `1` | A critical finding is present |
51
- | `2` | Execution error (not a SvelteKit project, internal error, etc.) |
63
+ | Code | Meaning |
64
+ | ---- | --------------------------------------------------------------------------- |
65
+ | `0` | No failing findings |
66
+ | `1` | Critical finding present, or `--fail-on` / `--min-health` threshold reached |
67
+ | `2` | Execution error (not a SvelteKit project, internal error, etc.) |
52
68
 
53
69
  Useful as a CI gate.
54
70
 
71
+ ### Reporters
72
+
73
+ `--reporter <fmt>` selects the output format: `console` (default), `json`, `agent`, `sarif`, `github`, `html` (self-contained, `--out-file`), and `md` (compact, for PR comments/job summaries). `agent` auto-selects inside known AI-agent harnesses (e.g. Claude Code); `github` auto-selects under GitHub Actions.
74
+
75
+ ### Ramping up on an existing project
76
+
77
+ `--diff [ref]` / `--staged` scope findings to changed/staged files; `--baseline <ref>` scopes to findings that are genuinely new versus a ref; `--update-suppressions` records today's findings once so only newly-introduced issues gate the build afterward. `--rules`/`--ignore`/`--category` select which rules run, `--weights` reweights the combined Health score, and `--min-health`/`--score` gate or print just the number. A `svelte-vitals.config.{mjs,js,ts}` file (scaffolded via `svelte-vitals install --client config-file`) can set any of these once instead of repeating flags.
78
+
79
+ ### `svelte-vitals install`
80
+
81
+ An interactive wizard that wires up the [MCP server](https://www.npmjs.com/package/@svelte-vitals/mcp), the [Vite plugin](https://www.npmjs.com/package/@svelte-vitals/vite)'s live dashboard, Agent Skills (`/svelte-vitals`, `/improve-svelte`) for Claude Code, Cursor, and Codex, and a GitHub Actions CI workflow — grouped by category in the picker so it's clear what each target is for:
82
+
83
+ ```bash
84
+ npx svelte-vitals@latest install
85
+ ```
86
+
87
+ ### CI integration
88
+
89
+ `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/).
90
+
55
91
  ### Agent-native output
56
92
 
57
93
  `svelte-vitals --reporter agent` emits a Markdown remediation document an AI coding agent can act on directly: each failing finding lists its location, a concrete fix (with a code snippet), and an acceptance check.
@@ -64,7 +100,7 @@ svelte-vitals resolves the effective `<head>` of every route by walking the layo
64
100
 
65
101
  A dynamic title such as `<title>{data.title}</title>` — the most common, correct SvelteKit pattern — is **never** flagged as missing; it passes with a `↯` marker. Only genuinely missing or empty metadata is penalized.
66
102
 
67
- See the [project README](https://github.com/oekazuma/svelte-vitals#readme) for the full picture and roadmap.
103
+ See the [full documentation](https://oekazuma.github.io/svelte-vitals/) for every flag, rule, and reporter, or the [project README](https://github.com/oekazuma/svelte-vitals#readme) for the full picture and roadmap.
68
104
 
69
105
  ## License
70
106
 
package/dist/bin.js CHANGED
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CONFIG_FILENAMES,
3
4
  buildRulesConfig,
5
+ discoverApps,
4
6
  findUnknownRuleIds,
5
7
  isReporterName,
6
8
  knownRuleIds,
7
9
  readCoreVersion,
8
10
  readPackageVersion,
9
11
  run
10
- } from "./chunk-OCLDCX4Y.js";
12
+ } from "./chunk-O5VMXV2Q.js";
11
13
 
12
14
  // src/bin.ts
13
15
  import mri3 from "mri";
@@ -183,7 +185,7 @@ import mri from "mri";
183
185
  import * as p from "@clack/prompts";
184
186
 
185
187
  // src/install/index.ts
186
- import { join as join3 } from "path";
188
+ import { join as join4 } from "path";
187
189
 
188
190
  // src/install/clients.ts
189
191
  import { join } from "path";
@@ -292,21 +294,29 @@ function isViteTargetId(id) {
292
294
  var AGENT_TARGETS = [
293
295
  {
294
296
  id: "claude-skill",
295
- label: "Claude Code skill",
296
- hint: "Teaches the agent svelte-vitals rules + when to run the scanner",
297
- relPath: ".claude/skills/svelte-vitals/SKILL.md"
297
+ label: "Agent skill: svelte-vitals",
298
+ hint: "Teaches the agent svelte-vitals rules + when to run the scanner (Claude Code, Codex, Cursor)",
299
+ relPaths: [
300
+ ".claude/skills/svelte-vitals/SKILL.md",
301
+ ".agents/skills/svelte-vitals/SKILL.md",
302
+ ".cursor/skills/svelte-vitals/SKILL.md"
303
+ ]
298
304
  },
299
305
  {
300
306
  id: "cursor-rules",
301
307
  label: "Cursor rules",
302
308
  hint: "Project rules file so Cursor avoids flagged patterns up front",
303
- relPath: ".cursor/rules/svelte-vitals.mdc"
309
+ relPaths: [".cursor/rules/svelte-vitals.mdc"]
304
310
  },
305
311
  {
306
312
  id: "claude-skill-improve",
307
- label: "Claude Code improve-svelte skill",
308
- hint: "Senior-advisor audit \u2192 implementation plans (read-only), for a project-wide improvement roadmap",
309
- relPath: ".claude/skills/improve-svelte/SKILL.md"
313
+ label: "Agent skill: improve-svelte",
314
+ hint: "Senior-advisor audit \u2192 implementation plans (read-only), for a project-wide improvement roadmap (Claude Code, Codex, Cursor)",
315
+ relPaths: [
316
+ ".claude/skills/improve-svelte/SKILL.md",
317
+ ".agents/skills/improve-svelte/SKILL.md",
318
+ ".cursor/skills/improve-svelte/SKILL.md"
319
+ ]
310
320
  }
311
321
  ];
312
322
  function agentTargetById(id) {
@@ -321,8 +331,7 @@ var CONFIG_TARGETS = [
321
331
  {
322
332
  id: "config-file",
323
333
  label: "Config file",
324
- hint: "Scaffolds svelte-vitals.config.mjs with every option commented out",
325
- relPath: "svelte-vitals.config.mjs"
334
+ hint: "Scaffolds svelte-vitals.config.{mjs,ts} (auto-picks the best one) with every option commented out"
326
335
  }
327
336
  ];
328
337
  function configTargetById(id) {
@@ -332,6 +341,60 @@ function isConfigTargetId(id) {
332
341
  return CONFIG_TARGETS.some((t) => t.id === id);
333
342
  }
334
343
 
344
+ // src/ci/workflow.ts
345
+ var WORKFLOW_PATH = ".github/workflows/svelte-vitals.yml";
346
+ var CHECKOUT_SHA = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0";
347
+ var CHECKOUT_VERSION = "v7.0.0";
348
+ function planWorkflowWrite(existing, force) {
349
+ if (existing === void 0) return { status: "created" };
350
+ if (!force) return { status: "exists" };
351
+ return { status: "updated" };
352
+ }
353
+ function buildWorkflowYaml(opts) {
354
+ const { actionSha, actionVersion } = opts;
355
+ return [
356
+ "# Generated by svelte-vitals (`ci install` or `install --client ci-workflow`).",
357
+ "# Re-run with --force to regenerate.",
358
+ "name: svelte-vitals",
359
+ "",
360
+ "on:",
361
+ " pull_request:",
362
+ "",
363
+ "permissions:",
364
+ " contents: read",
365
+ " pull-requests: write",
366
+ "",
367
+ "jobs:",
368
+ " svelte-vitals:",
369
+ " runs-on: ubuntu-latest",
370
+ " steps:",
371
+ ` - uses: actions/checkout@${CHECKOUT_SHA} # ${CHECKOUT_VERSION}`,
372
+ " with:",
373
+ " fetch-depth: 0",
374
+ ` - uses: oekazuma/svelte-vitals/packages/action@${actionSha} # @svelte-vitals/action@${actionVersion}`,
375
+ " with:",
376
+ " diff: origin/${{ github.base_ref }}",
377
+ " baseline: origin/${{ github.base_ref }}",
378
+ ""
379
+ ].join("\n");
380
+ }
381
+
382
+ // src/install/ci-targets.ts
383
+ var CI_TARGETS = [
384
+ {
385
+ id: "ci-workflow",
386
+ label: "GitHub Actions CI",
387
+ hint: "Scaffolds a workflow that runs @svelte-vitals/action on pull requests \u2014 inline annotations, job summary, sticky PR comment",
388
+ relPath: WORKFLOW_PATH
389
+ }
390
+ ];
391
+ function ciTargetById(id) {
392
+ return CI_TARGETS.find((t) => t.id === id);
393
+ }
394
+ function isCiTargetId(id) {
395
+ return CI_TARGETS.some((t) => t.id === id);
396
+ }
397
+
335
398
  // src/install/skill-content.ts
336
399
  import { allRules, docsUrlFor } from "@svelte-vitals/core";
337
400
  var CATEGORY_ORDER = ["seo", "performance", "correctness", "security", "architecture"];
@@ -722,18 +785,71 @@ measure), say so and suggest the runtime check instead of guessing.
722
785
  }
723
786
 
724
787
  // src/install/config-content.ts
725
- function buildConfigFileTemplate() {
726
- return `// svelte-vitals config file \u2014 https://oekazuma.github.io/svelte-vitals/guides/configuration/
727
- export default {
728
- // treatDynamicAs: 'pass', // 'pass' | 'warn' | 'fail' \u2014 how {data.title}-style dynamic values are scored
788
+ function buildConfigFileTemplate(opts = {}) {
789
+ const header = "// svelte-vitals config file \u2014 https://oekazuma.github.io/svelte-vitals/guides/configuration/\n";
790
+ const options = ` // treatDynamicAs: 'pass', // 'pass' | 'warn' | 'fail' \u2014 how {data.title}-style dynamic values are scored
729
791
  // metaComponents: ['Seo'], // component names that resolve SEO tags into <head>
730
792
  // rules: {}, // e.g. { SEO001: 'off' } to disable a rule
731
793
  // failOn: 'critical', // 'critical' | 'warning' | 'info'
732
- // weights: {} // e.g. { seo: 2 } \u2014 per-category weight for the combined Health score
794
+ // weights: {} // e.g. { seo: 2 } \u2014 per-category weight for the combined Health score`;
795
+ if (opts.useDefineConfig) {
796
+ return `${header}import { defineConfig } from 'svelte-vitals';
797
+
798
+ export default defineConfig({
799
+ ${options}
800
+ });
801
+ `;
802
+ }
803
+ if (opts.useCommonJs) {
804
+ return `${header}module.exports = {
805
+ ${options}
806
+ };
807
+ `;
808
+ }
809
+ return `${header}export default {
810
+ ${options}
733
811
  };
734
812
  `;
735
813
  }
736
814
 
815
+ // src/install/config-file-format.ts
816
+ import { join as join2 } from "path";
817
+ function nodeSupportsNativeTypeScript(version) {
818
+ const match = /^v?(\d+)\.(\d+)/.exec(version);
819
+ if (!match) return false;
820
+ const major = Number(match[1]);
821
+ const minor = Number(match[2]);
822
+ return major > 23 || major === 23 && minor >= 6 || major === 22 && minor >= 18;
823
+ }
824
+ function findExistingConfigFile(readFile, cwd) {
825
+ return CONFIG_FILENAMES.find((rel) => readFile(join2(cwd, rel)) !== void 0);
826
+ }
827
+ function hasSvelteVitalsDependency(readFile, cwd) {
828
+ const raw = readFile(join2(cwd, "package.json"));
829
+ if (raw === void 0) return false;
830
+ try {
831
+ const pkg = JSON.parse(raw);
832
+ return Boolean(pkg.dependencies?.["svelte-vitals"] ?? pkg.devDependencies?.["svelte-vitals"]);
833
+ } catch {
834
+ return false;
835
+ }
836
+ }
837
+ function isEsmProject(readFile, cwd) {
838
+ const raw = readFile(join2(cwd, "package.json"));
839
+ if (raw === void 0) return false;
840
+ try {
841
+ return JSON.parse(raw).type === "module";
842
+ } catch {
843
+ return false;
844
+ }
845
+ }
846
+ function detectBestConfigExtension(opts) {
847
+ if (!nodeSupportsNativeTypeScript(opts.nodeVersion)) return "mjs";
848
+ const looksTypeScript = opts.readFile(join2(opts.cwd, "tsconfig.json")) !== void 0 || opts.readFile(join2(opts.cwd, "vite.config.ts")) !== void 0;
849
+ if (!looksTypeScript) return "mjs";
850
+ return hasSvelteVitalsDependency(opts.readFile, opts.cwd) ? "ts" : "mjs";
851
+ }
852
+
737
853
  // src/install/codemod-vite-config.ts
738
854
  import { parseModule, generateCode, builders, MagicastError } from "magicast";
739
855
  var MANUAL_SNIPPET = `import { svelteVitals } from '@svelte-vitals/vite';
@@ -840,21 +956,25 @@ function codemodHooksServer(existing) {
840
956
  }
841
957
 
842
958
  // src/install/package-manager.ts
843
- import { join as join2 } from "path";
959
+ import { join as join3 } from "path";
844
960
  var LOCKFILE_TO_PM = {
845
961
  "pnpm-lock.yaml": "pnpm",
846
962
  "yarn.lock": "yarn",
847
963
  "bun.lock": "bun",
848
- "bun.lockb": "bun"
964
+ "bun.lockb": "bun",
965
+ "package-lock.json": "npm"
849
966
  };
850
- function detectPackageManager(io) {
967
+ function detectPackageManagerFromLockfile(io) {
851
968
  for (const [file, pm] of Object.entries(LOCKFILE_TO_PM)) {
852
- if (io.readFile(join2(io.cwd, file)) !== void 0) return pm;
969
+ if (io.readFile(join3(io.cwd, file)) !== void 0) return pm;
853
970
  }
854
- return "npm";
971
+ return void 0;
972
+ }
973
+ function detectPackageManager(io) {
974
+ return detectPackageManagerFromLockfile(io) ?? "npm";
855
975
  }
856
976
  function hasVitePackage(io) {
857
- const raw = io.readFile(join2(io.cwd, "package.json"));
977
+ const raw = io.readFile(join3(io.cwd, "package.json"));
858
978
  if (raw === void 0) return false;
859
979
  try {
860
980
  const pkg = JSON.parse(raw);
@@ -868,7 +988,7 @@ function installCommand(pm) {
868
988
  return { command: pm, args: [action, "-D", "@svelte-vitals/vite"] };
869
989
  }
870
990
  function readInstalledViteVersion(io) {
871
- const raw = io.readFile(join2(io.cwd, "node_modules/@svelte-vitals/vite/package.json"));
991
+ const raw = io.readFile(join3(io.cwd, "node_modules/@svelte-vitals/vite/package.json"));
872
992
  if (raw === void 0) return void 0;
873
993
  try {
874
994
  return JSON.parse(raw).version;
@@ -877,28 +997,35 @@ function readInstalledViteVersion(io) {
877
997
  }
878
998
  }
879
999
 
1000
+ // src/ci/action-pin.generated.ts
1001
+ var ACTION_SHA = "90b7538b5bed65ac123f46d77511dd4b48fa69bb";
1002
+ var ACTION_VERSION = "0.3.3";
1003
+
880
1004
  // src/install/index.ts
1005
+ function detectPackageManagerNear(io, appDir) {
1006
+ return detectPackageManagerFromLockfile({ ...io, cwd: appDir }) ?? detectPackageManager(io);
1007
+ }
881
1008
  function planForClient(client, scope, io, force) {
882
1009
  const path = client.resolvePath(scope, io.cwd, io.home);
883
1010
  const existing = io.readFile(path);
884
1011
  const merged = client.format === "toml" ? mergeToml(existing, MCP_ENTRY, force) : mergeJson(existing, MCP_ENTRY, force);
885
1012
  return { id: client.id, label: client.label, scope, path, status: merged.status, content: merged.content };
886
1013
  }
887
- function resolveCandidate(io, candidates) {
1014
+ function resolveCandidate(io, baseDir, candidates) {
888
1015
  for (const rel of candidates) {
889
- const path = join3(io.cwd, rel);
1016
+ const path = join4(baseDir, rel);
890
1017
  const content = io.readFile(path);
891
1018
  if (content !== void 0) return { path, content };
892
1019
  }
893
- return { path: join3(io.cwd, candidates[0]), content: void 0 };
1020
+ return { path: join4(baseDir, candidates[0]), content: void 0 };
894
1021
  }
895
- function planForVitePlugin(io) {
896
- const { path, content } = resolveCandidate(io, ["vite.config.ts", "vite.config.js", "vite.config.mjs"]);
1022
+ function planForVitePlugin(io, appDir) {
1023
+ const { path, content } = resolveCandidate(io, appDir, ["vite.config.ts", "vite.config.js", "vite.config.mjs"]);
897
1024
  const result = codemodViteConfig(content);
898
1025
  return { id: "vite-plugin", label: viteTargetById("vite-plugin").label, path, ...result };
899
1026
  }
900
- function planForViteHooks(io) {
901
- const { path, content } = resolveCandidate(io, ["src/hooks.server.ts", "src/hooks.server.js"]);
1027
+ function planForViteHooks(io, appDir) {
1028
+ const { path, content } = resolveCandidate(io, appDir, ["src/hooks.server.ts", "src/hooks.server.js"]);
902
1029
  const result = codemodHooksServer(content);
903
1030
  return { id: "vite-hooks", label: viteTargetById("vite-hooks").label, path, ...result };
904
1031
  }
@@ -917,18 +1044,40 @@ function agentTargetContent(id, version) {
917
1044
  }
918
1045
  }
919
1046
  function planForAgentTarget(target, io, force, version) {
920
- const path = join3(io.cwd, target.relPath);
921
- const existing = io.readFile(path);
922
1047
  const content = agentTargetContent(target.id, version);
923
- const status = existing === void 0 ? "created" : force ? "updated" : "exists";
924
- return { id: target.id, label: target.label, path, status, content };
1048
+ return target.relPaths.map((relPath) => {
1049
+ const path = join4(io.cwd, relPath);
1050
+ const existing = io.readFile(path);
1051
+ const status = existing === void 0 ? "created" : force ? "updated" : "exists";
1052
+ return { id: target.id, label: target.label, path, status, content };
1053
+ });
925
1054
  }
926
- function planForConfigTarget(target, io, force) {
927
- const path = join3(io.cwd, target.relPath);
1055
+ function planForConfigTarget(target, io, force, appDir) {
1056
+ const existingRel = findExistingConfigFile(io.readFile, appDir);
1057
+ if (existingRel !== void 0) {
1058
+ const path2 = join4(appDir, existingRel);
1059
+ const status = force ? "updated" : "exists";
1060
+ const content2 = force ? buildConfigFileTemplate({
1061
+ useDefineConfig: existingRel.endsWith(".ts") && hasSvelteVitalsDependency(io.readFile, appDir),
1062
+ useCommonJs: existingRel.endsWith(".js") && !isEsmProject(io.readFile, appDir)
1063
+ }) : void 0;
1064
+ return { id: target.id, label: target.label, path: path2, status, content: content2 };
1065
+ }
1066
+ const ext = detectBestConfigExtension({
1067
+ readFile: io.readFile,
1068
+ cwd: appDir,
1069
+ nodeVersion: io.nodeVersion ?? process.version
1070
+ });
1071
+ const path = join4(appDir, `svelte-vitals.config.${ext}`);
1072
+ const content = buildConfigFileTemplate({ useDefineConfig: ext === "ts" });
1073
+ return { id: target.id, label: target.label, path, status: "created", content };
1074
+ }
1075
+ function planForCiTarget(target, io, force) {
1076
+ const path = join4(io.cwd, target.relPath);
928
1077
  const existing = io.readFile(path);
929
- const content = buildConfigFileTemplate();
930
- const status = existing === void 0 ? "created" : force ? "updated" : "exists";
931
- return { id: target.id, label: target.label, path, status, content };
1078
+ const plan = planWorkflowWrite(existing, force);
1079
+ const content = plan.status === "exists" ? void 0 : buildWorkflowYaml({ actionSha: ACTION_SHA, actionVersion: ACTION_VERSION });
1080
+ return { id: target.id, label: target.label, path, status: plan.status, content };
932
1081
  }
933
1082
  function indent(text) {
934
1083
  return text.split("\n").map((l) => ` ${l}`).join("\n");
@@ -942,19 +1091,16 @@ async function runRefresh(io, flags, version) {
942
1091
  let hadFailure = false;
943
1092
  const rows = [];
944
1093
  for (const target of AGENT_TARGETS) {
945
- const path = join3(io.cwd, target.relPath);
946
- try {
947
- if (io.readFile(path) === void 0) continue;
948
- rows.push(planForAgentTarget(
949
- target,
950
- io,
951
- /* force */
952
- true,
953
- version
954
- ));
955
- } catch (err) {
956
- hadFailure = true;
957
- io.errorLog(`svelte-vitals: failed to read ${path}: ${err instanceof Error ? err.message : String(err)}`);
1094
+ const content = agentTargetContent(target.id, version);
1095
+ for (const relPath of target.relPaths) {
1096
+ const path = join4(io.cwd, relPath);
1097
+ try {
1098
+ if (io.readFile(path) === void 0) continue;
1099
+ rows.push({ id: target.id, label: target.label, path, status: "updated", content });
1100
+ } catch (err) {
1101
+ hadFailure = true;
1102
+ io.errorLog(`svelte-vitals: failed to read ${path}: ${err instanceof Error ? err.message : String(err)}`);
1103
+ }
958
1104
  }
959
1105
  }
960
1106
  if (rows.length === 0) {
@@ -1004,26 +1150,31 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1004
1150
  (c) => c.scopes.some((s) => configExists(c.resolvePath(s, io.cwd, io.home)))
1005
1151
  ).map((c) => c.id);
1006
1152
  const viteConfigExists = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].some(
1007
- (f) => configExists(join3(io.cwd, f))
1153
+ (f) => configExists(join4(io.cwd, f))
1008
1154
  );
1009
- const claudeSkillDetected = configExists(join3(io.cwd, ".claude", "settings.json"));
1010
- const cursorRulesDetected = configExists(join3(io.cwd, ".cursor", "mcp.json"));
1155
+ const claudeSkillDetected = configExists(join4(io.cwd, ".claude", "settings.json"));
1156
+ const cursorRulesDetected = configExists(join4(io.cwd, ".cursor", "mcp.json"));
1011
1157
  const detectedAgents = [
1012
1158
  ...claudeSkillDetected ? ["claude-skill"] : [],
1013
1159
  ...cursorRulesDetected ? ["cursor-rules"] : []
1014
1160
  ];
1161
+ const ciWorkflowDetected = configExists(join4(io.cwd, CI_TARGETS[0].relPath));
1162
+ const configFileDetected = findExistingConfigFile((p3) => configExists(p3) ? "" : void 0, io.cwd) !== void 0;
1015
1163
  const detected = [
1016
1164
  ...detectedClients,
1017
1165
  ...viteConfigExists ? VITE_TARGETS.map((t) => t.id) : [],
1018
- ...detectedAgents
1019
- ];
1020
- const options = [
1021
- ...CLIENTS.map((c) => ({ id: c.id, label: c.label })),
1022
- ...VITE_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1023
- ...AGENT_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1024
- ...CONFIG_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint }))
1166
+ ...detectedAgents,
1167
+ ...ciWorkflowDetected ? CI_TARGETS.map((t) => t.id) : [],
1168
+ ...configFileDetected ? CONFIG_TARGETS.map((t) => t.id) : []
1025
1169
  ];
1026
- const picked = await prompts.selectClients(options, detected);
1170
+ const groups = {
1171
+ "MCP server": CLIENTS.map((c) => ({ id: c.id, label: c.label })),
1172
+ "Vite integration": VITE_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1173
+ "Agent Skills & rules": AGENT_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1174
+ "CI (GitHub Actions)": CI_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint })),
1175
+ "Config file": CONFIG_TARGETS.map((t) => ({ id: t.id, label: t.label, hint: t.hint }))
1176
+ };
1177
+ const picked = await prompts.selectClients(groups, detected);
1027
1178
  if (picked === null) {
1028
1179
  io.log("Cancelled.");
1029
1180
  return 0;
@@ -1031,7 +1182,7 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1031
1182
  ids = picked;
1032
1183
  } else {
1033
1184
  io.errorLog(
1034
- "svelte-vitals: no TTY; pass --client <claude-code,cursor,codex,vite-plugin,vite-hooks,claude-skill,cursor-rules,claude-skill-improve,config-file> to install non-interactively."
1185
+ "svelte-vitals: no TTY; pass --client <claude-code,cursor,codex,vite-plugin,vite-hooks,claude-skill,cursor-rules,claude-skill-improve,config-file,ci-workflow> to install non-interactively."
1035
1186
  );
1036
1187
  return 2;
1037
1188
  }
@@ -1039,10 +1190,49 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1039
1190
  const viteIds = ids.filter(isViteTargetId);
1040
1191
  const agentIds = ids.filter(isAgentTargetId);
1041
1192
  const configIds = ids.filter(isConfigTargetId);
1042
- if (clients.length === 0 && viteIds.length === 0 && agentIds.length === 0 && configIds.length === 0) {
1193
+ const ciIds = ids.filter(isCiTargetId);
1194
+ if (clients.length === 0 && viteIds.length === 0 && agentIds.length === 0 && configIds.length === 0 && ciIds.length === 0) {
1043
1195
  io.errorLog("svelte-vitals: no valid clients or targets selected.");
1044
1196
  return 2;
1045
1197
  }
1198
+ const hasSvelteConfig = (dir) => {
1199
+ try {
1200
+ return io.readFile(join4(dir, "svelte.config.js")) !== void 0 || io.readFile(join4(dir, "svelte.config.ts")) !== void 0;
1201
+ } catch {
1202
+ return false;
1203
+ }
1204
+ };
1205
+ const needsApp = viteIds.length > 0 || configIds.length > 0;
1206
+ let appDir = io.cwd;
1207
+ if (needsApp) {
1208
+ if (flags.app) {
1209
+ const candidate = join4(io.cwd, flags.app);
1210
+ if (!hasSvelteConfig(candidate)) {
1211
+ io.errorLog(`svelte-vitals: --app '${flags.app}' is not a SvelteKit app (no svelte.config.{js,ts} there).`);
1212
+ return 2;
1213
+ }
1214
+ appDir = candidate;
1215
+ } else if (!hasSvelteConfig(io.cwd)) {
1216
+ const apps = await (io.discoverApps ?? discoverApps)(io.cwd);
1217
+ if (apps.length === 1) {
1218
+ io.errorLog(`svelte-vitals: detected SvelteKit app at ${apps[0]}; targeting it for the Vite/config targets.`);
1219
+ appDir = join4(io.cwd, apps[0]);
1220
+ } else if (apps.length > 1) {
1221
+ if (io.isTTY) {
1222
+ const picked = await prompts.selectApp(apps);
1223
+ if (picked === null) {
1224
+ io.log("Cancelled.");
1225
+ return 0;
1226
+ }
1227
+ appDir = join4(io.cwd, picked);
1228
+ } else {
1229
+ io.errorLog(`svelte-vitals: multiple SvelteKit apps found: ${apps.join(", ")}.`);
1230
+ io.errorLog(`svelte-vitals: pass one with --app, e.g. \`svelte-vitals install --app ${apps[0]}\`.`);
1231
+ return 2;
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1046
1236
  const rows = [];
1047
1237
  for (const client of clients) {
1048
1238
  let scope;
@@ -1071,15 +1261,40 @@ async function runInstall(flags, io, prompts, version = "0.0.0") {
1071
1261
  }
1072
1262
  }
1073
1263
  for (const viteId of viteIds) {
1074
- rows.push(viteId === "vite-plugin" ? planForVitePlugin(io) : planForViteHooks(io));
1264
+ rows.push(viteId === "vite-plugin" ? planForVitePlugin(io, appDir) : planForViteHooks(io, appDir));
1075
1265
  }
1076
1266
  for (const agentId of agentIds) {
1077
1267
  const target = agentTargetById(agentId);
1078
- rows.push(planForAgentTarget(target, io, flags.force ?? false, version));
1268
+ try {
1269
+ rows.push(...planForAgentTarget(target, io, flags.force ?? false, version));
1270
+ } catch (err) {
1271
+ io.errorLog(
1272
+ `svelte-vitals: could not check existing agent target ${target.id}: ${err instanceof Error ? err.message : String(err)}`
1273
+ );
1274
+ return 2;
1275
+ }
1079
1276
  }
1080
1277
  for (const configId of configIds) {
1081
1278
  const target = configTargetById(configId);
1082
- rows.push(planForConfigTarget(target, io, flags.force ?? false));
1279
+ try {
1280
+ rows.push(planForConfigTarget(target, io, flags.force ?? false, appDir));
1281
+ } catch (err) {
1282
+ io.errorLog(
1283
+ `svelte-vitals: could not check existing config file: ${err instanceof Error ? err.message : String(err)}`
1284
+ );
1285
+ return 2;
1286
+ }
1287
+ }
1288
+ for (const ciId of ciIds) {
1289
+ const target = ciTargetById(ciId);
1290
+ try {
1291
+ rows.push(planForCiTarget(target, io, flags.force ?? false));
1292
+ } catch (err) {
1293
+ io.errorLog(
1294
+ `svelte-vitals: could not check existing workflow at ${join4(io.cwd, target.relPath)}: ${err instanceof Error ? err.message : String(err)}`
1295
+ );
1296
+ return 2;
1297
+ }
1083
1298
  }
1084
1299
  const planText = rows.map(rowLine).join("\n");
1085
1300
  io.log("Plan:");
@@ -1112,22 +1327,28 @@ ${indent(r.snippet ?? "")}`);
1112
1327
  io.writeFile(r.path, r.content ?? "");
1113
1328
  io.log(`\u2713 ${r.label}: ${r.status} ${r.path}`);
1114
1329
  if (isViteTargetId(r.id)) viteWasWritten = true;
1330
+ if (isConfigTargetId(r.id) && r.path.endsWith(".ts")) {
1331
+ io.log(
1332
+ "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."
1333
+ );
1334
+ }
1115
1335
  } catch (err) {
1116
1336
  hadFailure = true;
1117
1337
  io.errorLog(`svelte-vitals: failed to write ${r.path}: ${err instanceof Error ? err.message : String(err)}`);
1118
1338
  }
1119
1339
  }
1120
- if (viteWasWritten && io.runCommand && !hasVitePackage(io)) {
1121
- const pm = detectPackageManager(io);
1340
+ const appIo = { ...io, cwd: appDir };
1341
+ if (viteWasWritten && io.runCommand && !hasVitePackage(appIo)) {
1342
+ const pm = appDir === io.cwd ? detectPackageManager(io) : detectPackageManagerNear(io, appDir);
1122
1343
  const { command, args } = installCommand(pm);
1123
1344
  io.log(`Installing @svelte-vitals/vite via ${pm}...`);
1124
- const code = io.runCommand(command, args, io.cwd);
1345
+ const code = io.runCommand(command, args, appDir);
1125
1346
  if (code !== 0) {
1126
1347
  io.errorLog(
1127
1348
  `svelte-vitals: failed to install @svelte-vitals/vite (${command} ${args.join(" ")} exited ${code}). Install it manually.`
1128
1349
  );
1129
1350
  } else {
1130
- const installedVersion = readInstalledViteVersion(io);
1351
+ const installedVersion = readInstalledViteVersion(appIo) ?? readInstalledViteVersion(io);
1131
1352
  io.log(
1132
1353
  installedVersion ? `svelte-vitals: installed @svelte-vitals/vite@${installedVersion} \u2014 compare against \`svelte-vitals --version\`'s core number if findings ever seem out of sync.` : "svelte-vitals: installed @svelte-vitals/vite (could not read the installed version from node_modules)."
1133
1354
  );
@@ -1145,7 +1366,9 @@ ${indent(r.snippet ?? "")}`);
1145
1366
  var VALID_TARGETS = [
1146
1367
  ...CLIENTS.map((c) => c.id),
1147
1368
  ...VITE_TARGETS.map((t) => t.id),
1148
- ...AGENT_TARGETS.map((t) => t.id)
1369
+ ...AGENT_TARGETS.map((t) => t.id),
1370
+ ...CONFIG_TARGETS.map((t) => t.id),
1371
+ ...CI_TARGETS.map((t) => t.id)
1149
1372
  ];
1150
1373
  var EXPECTED_TARGETS = VALID_TARGETS.join("|");
1151
1374
  function resolveInstallArgs(argv) {
@@ -1169,18 +1392,20 @@ function resolveInstallArgs(argv) {
1169
1392
  if (rawScope === "project" || rawScope === "global") scope = rawScope;
1170
1393
  else errors.push(`svelte-vitals: unknown --scope '${rawScope}'; expected project|global.`);
1171
1394
  }
1395
+ const app = typeof argv.app === "string" && argv.app.trim() !== "" ? argv.app.trim() : void 0;
1172
1396
  const refresh = Boolean(argv.refresh);
1173
1397
  if (refresh && rawClients.length > 0) {
1174
1398
  errors.push("svelte-vitals: --refresh regenerates existing files and cannot be combined with --client.");
1175
1399
  }
1176
1400
  if (errors.length > 0) return { flags: null, warnings, errors };
1177
- if (refresh && (scope !== void 0 || Boolean(argv.yes) || Boolean(argv.force))) {
1178
- warnings.push("svelte-vitals: --scope, --yes, and --force are ignored with --refresh.");
1401
+ if (refresh && (scope !== void 0 || Boolean(argv.yes) || Boolean(argv.force) || app !== void 0)) {
1402
+ warnings.push("svelte-vitals: --scope, --yes, --force, and --app are ignored with --refresh.");
1179
1403
  }
1180
1404
  return {
1181
1405
  flags: {
1182
1406
  ...client.length > 0 ? { client } : {},
1183
1407
  ...scope ? { scope } : {},
1408
+ ...app !== void 0 && !refresh ? { app } : {},
1184
1409
  yes: Boolean(argv.yes),
1185
1410
  dryRun: Boolean(argv["dry-run"]),
1186
1411
  force: Boolean(argv.force),
@@ -1192,27 +1417,45 @@ function resolveInstallArgs(argv) {
1192
1417
  }
1193
1418
 
1194
1419
  // src/install/cli.ts
1195
- var INSTALL_HELP = `svelte-vitals install \u2014 set up the svelte-vitals MCP server, Vite integration, and agent skills/rules
1420
+ var INSTALL_HELP = `svelte-vitals install \u2014 set up the svelte-vitals MCP server, Vite integration, agent skills/rules, and CI
1196
1421
 
1197
1422
  Usage:
1198
1423
  svelte-vitals install [options]
1199
1424
 
1200
1425
  Options:
1201
- --client <ids> Comma-separated: claude-code,cursor,codex,vite-plugin,vite-hooks,claude-skill,cursor-rules,claude-skill-improve,config-file
1202
- (skips the interactive picker)
1426
+ --client <ids> Comma-separated: claude-code,cursor,codex,vite-plugin,vite-hooks,claude-skill,cursor-rules,claude-skill-improve,config-file,ci-workflow
1427
+ (skips the interactive picker; the picker groups these by category \u2014
1428
+ MCP server, Vite integration, Agent Skills & rules, CI, Config file)
1203
1429
  vite-plugin registers the build-mode plugin in vite.config.{ts,js,mjs}; vite-hooks
1204
1430
  wires up the svelteVitalsHandle hook in src/hooks.server.{ts,js}, which improves the
1205
1431
  live dashboard's per-route accuracy as you browse. --force does not apply
1206
1432
  to either of these two \u2014 an existing registration is always left as-is.
1207
- claude-skill writes a Claude Code skill (.claude/skills/svelte-vitals/SKILL.md); cursor-rules
1208
- writes a Cursor rules file (.cursor/rules/svelte-vitals.mdc). Both are generated from the
1209
- current rule set and support --force to regenerate.
1210
- claude-skill-improve writes a second, read-only Claude Code skill
1211
- (.claude/skills/improve-svelte/SKILL.md) that audits the whole project and writes
1212
- implementation plans instead of a run-after-every-edit playbook; also supports --force.
1213
- config-file scaffolds svelte-vitals.config.mjs with every option commented out;
1214
- supports --force to regenerate.
1433
+ claude-skill writes an agent skill (Claude Code, Codex, and Cursor \u2014
1434
+ .claude/skills/, .agents/skills/, and .cursor/skills/ under svelte-vitals/);
1435
+ cursor-rules writes a Cursor rules file (.cursor/rules/svelte-vitals.mdc).
1436
+ Both are generated from the current rule set and support --force to regenerate.
1437
+ claude-skill-improve writes a second, read-only agent skill (same three
1438
+ locations, under improve-svelte/) that audits the whole project and writes
1439
+ implementation plans instead of a run-after-every-edit playbook; also
1440
+ supports --force.
1441
+ config-file scaffolds svelte-vitals.config.{mjs,ts} with every option commented
1442
+ out, auto-picking .ts (with defineConfig) when the current Node supports it, the
1443
+ project looks TypeScript-oriented (tsconfig.json or vite.config.ts present), and
1444
+ svelte-vitals is a declared dependency (defineConfig's import resolves at load
1445
+ time); else the safe .mjs default. Supports --force to regenerate the file
1446
+ that's already there (its extension never changes on --force).
1447
+ ci-workflow scaffolds .github/workflows/svelte-vitals.yml, the same file
1448
+ \`svelte-vitals ci install\` writes standalone \u2014 pick it here to set it up in
1449
+ the same pass as everything else; supports --force to regenerate. \`svelte-vitals
1450
+ ci upgrade\` remains the way to bump an existing workflow's pinned action version.
1215
1451
  --scope <scope> project | global (applies to all selected clients; codex is always global)
1452
+ --app <dir> Monorepo: the SvelteKit app directory the vite-plugin/vite-hooks/config-file
1453
+ targets write into (e.g. --app apps/web). Without it, when the current
1454
+ directory isn't itself a SvelteKit app, one detected app is used
1455
+ automatically (with a notice), several prompt a picker on a TTY, and
1456
+ non-interactive runs exit 2 asking for --app. All other targets (MCP
1457
+ configs, skills, ci-workflow) always write at the current directory \u2014
1458
+ the repo root is their correct home.
1216
1459
  --yes, -y Skip the confirmation prompt
1217
1460
  --dry-run Print the planned changes and exit without writing
1218
1461
  --force Overwrite an existing svelte-vitals entry
@@ -1237,6 +1480,7 @@ function realIO() {
1237
1480
  cwd: process.cwd(),
1238
1481
  home: homedir(),
1239
1482
  isTTY: Boolean(process.stdout.isTTY),
1483
+ nodeVersion: process.version,
1240
1484
  log: (line) => console.log(line),
1241
1485
  errorLog: (line) => console.error(line),
1242
1486
  runCommand: (command, args, cwd) => {
@@ -1260,10 +1504,15 @@ function realIO() {
1260
1504
  }
1261
1505
  function clackPrompts() {
1262
1506
  return {
1263
- selectClients: async (all, defaults) => {
1264
- const res = await p.multiselect({
1507
+ selectClients: async (groups, defaults) => {
1508
+ const res = await p.groupMultiselect({
1265
1509
  message: "Which clients/targets should svelte-vitals be installed for?",
1266
- options: all.map((o) => ({ value: o.id, label: o.label, hint: o.hint })),
1510
+ options: Object.fromEntries(
1511
+ Object.entries(groups).map(([group, opts]) => [
1512
+ group,
1513
+ opts.map((o) => ({ value: o.id, label: o.label, hint: o.hint }))
1514
+ ])
1515
+ ),
1267
1516
  initialValues: defaults,
1268
1517
  required: true
1269
1518
  });
@@ -1277,6 +1526,14 @@ function clackPrompts() {
1277
1526
  });
1278
1527
  return p.isCancel(res) ? null : res;
1279
1528
  },
1529
+ selectApp: async (apps) => {
1530
+ const res = await p.select({
1531
+ message: "Multiple SvelteKit apps found \u2014 which one should the Vite/config targets go into?",
1532
+ options: apps.map((a) => ({ value: a, label: a })),
1533
+ initialValue: apps[0]
1534
+ });
1535
+ return p.isCancel(res) ? null : res;
1536
+ },
1280
1537
  confirm: async (planText) => {
1281
1538
  const res = await p.confirm({ message: `Apply this plan?
1282
1539
  ${planText}` });
@@ -1287,7 +1544,7 @@ ${planText}` });
1287
1544
  async function runInstallCli(args) {
1288
1545
  const argv = mri(args, {
1289
1546
  boolean: ["yes", "dry-run", "force", "refresh", "help"],
1290
- string: ["client", "scope"],
1547
+ string: ["client", "scope", "app"],
1291
1548
  alias: { y: "yes", h: "help" }
1292
1549
  });
1293
1550
  if (argv.help) {
@@ -1302,47 +1559,9 @@ async function runInstallCli(args) {
1302
1559
  }
1303
1560
 
1304
1561
  // src/ci/cli.ts
1305
- import { join as join4 } from "path";
1562
+ import { join as join5 } from "path";
1306
1563
  import mri2 from "mri";
1307
1564
 
1308
- // src/ci/workflow.ts
1309
- var WORKFLOW_PATH = ".github/workflows/svelte-vitals.yml";
1310
- var CHECKOUT_SHA = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0";
1311
- var CHECKOUT_VERSION = "v7.0.0";
1312
- function planWorkflowWrite(existing, force) {
1313
- if (existing === void 0) return { status: "created" };
1314
- if (!force) return { status: "exists" };
1315
- return { status: "updated" };
1316
- }
1317
- function buildWorkflowYaml(opts) {
1318
- const { actionSha, actionVersion } = opts;
1319
- return [
1320
- "# Generated by `svelte-vitals ci install`.",
1321
- "# Re-run with --force to regenerate.",
1322
- "name: svelte-vitals",
1323
- "",
1324
- "on:",
1325
- " pull_request:",
1326
- "",
1327
- "permissions:",
1328
- " contents: read",
1329
- " pull-requests: write",
1330
- "",
1331
- "jobs:",
1332
- " svelte-vitals:",
1333
- " runs-on: ubuntu-latest",
1334
- " steps:",
1335
- ` - uses: actions/checkout@${CHECKOUT_SHA} # ${CHECKOUT_VERSION}`,
1336
- " with:",
1337
- " fetch-depth: 0",
1338
- ` - uses: oekazuma/svelte-vitals/packages/action@${actionSha} # @svelte-vitals/action@${actionVersion}`,
1339
- " with:",
1340
- " diff: origin/${{ github.base_ref }}",
1341
- " baseline: origin/${{ github.base_ref }}",
1342
- ""
1343
- ].join("\n");
1344
- }
1345
-
1346
1565
  // src/ci/upgrade.ts
1347
1566
  var ACTION_USES_LINE = /^(?<indent>\s*-\s*uses:\s*(?:&\S+\s+)?oekazuma\/svelte-vitals\/packages\/action@)(?<ref>[^\s#]+)(?<comment>\s*#.*)?$/;
1348
1567
  function upgradeActionPin(content, sha, version) {
@@ -1371,10 +1590,6 @@ function upgradeActionPin(content, sha, version) {
1371
1590
  return { status: "upgraded", content: next.join("\n"), replaced, from };
1372
1591
  }
1373
1592
 
1374
- // src/ci/action-pin.generated.ts
1375
- var ACTION_SHA = "e30438c8facc8aabac6d4ce00455e29ec02ad9af";
1376
- var ACTION_VERSION = "0.3.1";
1377
-
1378
1593
  // src/ci/cli.ts
1379
1594
  var CI_HELP = `svelte-vitals ci \u2014 scaffold CI integration
1380
1595
 
@@ -1416,7 +1631,7 @@ async function runCiCli(args, io = realIO()) {
1416
1631
  io.log(CI_HELP);
1417
1632
  return 0;
1418
1633
  }
1419
- const path = join4(io.cwd, WORKFLOW_PATH);
1634
+ const path = join5(io.cwd, WORKFLOW_PATH);
1420
1635
  const existing = io.readFile(path);
1421
1636
  const plan = planWorkflowWrite(existing, Boolean(argv.force));
1422
1637
  io.log("Plan:");
@@ -1450,7 +1665,7 @@ async function runCiUpgrade(args, io) {
1450
1665
  io.log(CI_HELP);
1451
1666
  return 0;
1452
1667
  }
1453
- const path = join4(io.cwd, WORKFLOW_PATH);
1668
+ const path = join5(io.cwd, WORKFLOW_PATH);
1454
1669
  const existing = io.readFile(path);
1455
1670
  if (existing === void 0) {
1456
1671
  io.errorLog(`svelte-vitals: no ${WORKFLOW_PATH} found \u2014 run \`svelte-vitals ci install\` first.`);
@@ -1546,12 +1546,14 @@ async function run(opts = {}) {
1546
1546
 
1547
1547
  export {
1548
1548
  ProjectError,
1549
+ discoverApps,
1549
1550
  readPackageVersion,
1550
1551
  readCoreVersion,
1551
1552
  isReporterName,
1552
1553
  findUnknownRuleIds,
1553
1554
  knownRuleIds,
1554
1555
  buildRulesConfig,
1556
+ CONFIG_FILENAMES,
1555
1557
  loadConfigFile,
1556
1558
  spinnerEnabled,
1557
1559
  routeMatcher,
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  routeMatcher,
11
11
  run,
12
12
  spinnerEnabled
13
- } from "./chunk-OCLDCX4Y.js";
13
+ } from "./chunk-O5VMXV2Q.js";
14
14
  export {
15
15
  ProjectError,
16
16
  analyzeProject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-vitals",
3
- "version": "0.26.0",
3
+ "version": "0.28.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",
@@ -46,7 +46,7 @@
46
46
  "smol-toml": "^1.7.0",
47
47
  "svelte": "^5.56.4",
48
48
  "tinyglobby": "^0.2.17",
49
- "@svelte-vitals/core": "0.24.0"
49
+ "@svelte-vitals/core": "0.25.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^24.13.3"