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 +52 -16
- package/dist/bin.js +350 -135
- package/dist/{chunk-OCLDCX4Y.js → chunk-O5VMXV2Q.js} +2 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/svelte-vitals)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
> **A SvelteKit
|
|
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
|
|
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
|
-
> [!
|
|
16
|
-
> **
|
|
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 ·
|
|
28
|
+
Svelte Vitals · static mode
|
|
29
29
|
|
|
30
|
-
|
|
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
|
-
/
|
|
34
|
-
src/routes
|
|
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 (
|
|
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` |
|
|
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-
|
|
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
|
|
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: "
|
|
296
|
-
hint: "Teaches the agent svelte-vitals rules + when to run the scanner",
|
|
297
|
-
|
|
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
|
-
|
|
309
|
+
relPaths: [".cursor/rules/svelte-vitals.mdc"]
|
|
304
310
|
},
|
|
305
311
|
{
|
|
306
312
|
id: "claude-skill-improve",
|
|
307
|
-
label: "
|
|
308
|
-
hint: "Senior-advisor audit \u2192 implementation plans (read-only), for a project-wide improvement roadmap",
|
|
309
|
-
|
|
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
|
-
|
|
727
|
-
|
|
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
|
|
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
|
|
967
|
+
function detectPackageManagerFromLockfile(io) {
|
|
851
968
|
for (const [file, pm] of Object.entries(LOCKFILE_TO_PM)) {
|
|
852
|
-
if (io.readFile(
|
|
969
|
+
if (io.readFile(join3(io.cwd, file)) !== void 0) return pm;
|
|
853
970
|
}
|
|
854
|
-
return
|
|
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(
|
|
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(
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
924
|
-
|
|
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
|
|
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
|
|
930
|
-
const
|
|
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
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
true
|
|
953
|
-
|
|
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(
|
|
1153
|
+
(f) => configExists(join4(io.cwd, f))
|
|
1008
1154
|
);
|
|
1009
|
-
const claudeSkillDetected = configExists(
|
|
1010
|
-
const cursorRulesDetected = configExists(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1121
|
-
|
|
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,
|
|
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 --
|
|
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,
|
|
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
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
supports --force
|
|
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 (
|
|
1264
|
-
const res = await p.
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-vitals",
|
|
3
|
-
"version": "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.
|
|
49
|
+
"@svelte-vitals/core": "0.25.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^24.13.3"
|