@svelte-vitals/vite 0.9.2 → 0.11.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.
@@ -108,6 +108,26 @@ function parseHtmlHead(html) {
108
108
  return { tags, htmlLang, headings, images };
109
109
  }
110
110
 
111
+ // src/loopback.ts
112
+ function isLoopbackOrigin(origin) {
113
+ try {
114
+ const host = new URL(origin).hostname;
115
+ return host === "localhost" || host === "127.0.0.1" || host === "[::1]";
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+ function isLoopbackHost(host) {
121
+ if (host === void 0) return false;
122
+ try {
123
+ return isLoopbackOrigin(`http://${host}`);
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
111
129
  export {
112
- parseHtmlHead
130
+ parseHtmlHead,
131
+ isLoopbackOrigin,
132
+ isLoopbackHost
113
133
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
+ isLoopbackOrigin,
2
3
  parseHtmlHead
3
- } from "../chunk-LIC7DE52.js";
4
+ } from "../chunk-4LXPHVAW.js";
4
5
 
5
6
  // src/hooks/handle.ts
6
7
  import { DEV } from "esm-env";
@@ -35,14 +36,6 @@ function findingSignature(results, config) {
35
36
  }
36
37
 
37
38
  // src/hooks/handle.ts
38
- function isLoopbackOrigin(origin) {
39
- try {
40
- const host = new URL(origin).hostname;
41
- return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
42
- } catch {
43
- return false;
44
- }
45
- }
46
39
  async function postIngest(origin, route, results) {
47
40
  if (!isLoopbackOrigin(origin)) {
48
41
  if (globalThis.process?.env?.SVELTE_VITALS_DEBUG) {
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import {
2
+ isLoopbackHost,
3
+ isLoopbackOrigin,
2
4
  parseHtmlHead
3
- } from "./chunk-LIC7DE52.js";
5
+ } from "./chunk-4LXPHVAW.js";
4
6
 
5
7
  // src/plugin.ts
6
8
  import { existsSync } from "fs";
7
9
  import { writeFile, mkdir } from "fs/promises";
8
- import { join as join3, isAbsolute, dirname } from "path";
10
+ import { join as join4, isAbsolute, dirname } from "path";
9
11
  import { defineConfig as defineConfig2 } from "@svelte-vitals/core";
10
12
 
11
13
  // src/analyze.ts
@@ -97,6 +99,28 @@ async function collectRenderedProject(cwd, htmlLang) {
97
99
  };
98
100
  }
99
101
 
102
+ // src/providers/source/components.ts
103
+ import { readFile as readFile3, access as access2 } from "fs/promises";
104
+ import { join as join3 } from "path";
105
+ import { glob as glob2 } from "tinyglobby";
106
+ import { collectComponentFacts as collect } from "@svelte-vitals/core";
107
+ var nodeRuntime = {
108
+ readFile: (path) => readFile3(path, "utf8"),
109
+ async exists(path) {
110
+ try {
111
+ await access2(path);
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ },
117
+ glob: (pattern, cwd) => glob2(pattern, { cwd, dot: false }),
118
+ join: (...parts) => join3(...parts)
119
+ };
120
+ function collectComponentFacts(root) {
121
+ return collect(nodeRuntime, root);
122
+ }
123
+
100
124
  // src/version.ts
101
125
  import { readFileSync } from "fs";
102
126
  function readPackageVersion() {
@@ -118,14 +142,16 @@ async function analyze(prerenderPagesDir, cwd, options) {
118
142
  });
119
143
  const { heads, headings, images, htmlLang } = await collectRenderedHeads(prerenderPagesDir);
120
144
  const project = await collectRenderedProject(cwd, htmlLang);
145
+ const components = await collectComponentFacts(cwd);
121
146
  const results = applyRuleSeverities(
122
- await runRules(selectRules(allRules, config), { heads, headings, images, project, config }),
147
+ await runRules(selectRules(allRules, config), { heads, headings, images, project, components, config }),
123
148
  config
124
149
  );
125
150
  const { score } = computeScore(results, config);
126
151
  const summary = summarize(results, config);
127
152
  const failed = hasFailureAtOrAbove(summary, config.failOn);
128
- const coverageNote = `Analyzed ${heads.length} prerendered route(s). SSR/dynamic routes are not covered \u2014 run \`npx svelte-vitals\` for those.`;
153
+ const coverageNote = `Analyzed ${heads.length} prerendered route(s). SSR/dynamic routes are not covered \u2014 run \`npx svelte-vitals\` for those.
154
+ Scanned ${components.length} component(s) under src/ for Correctness/Security/Architecture/Bundle findings.`;
129
155
  const consoleReport = formatConsoleReport(results, config, { mode: "rendered / plugin" }) + "\n" + coverageNote + "\n";
130
156
  const jsonReport = formatJsonReport(results, config, { version: readPackageVersion() });
131
157
  return {
@@ -186,11 +212,16 @@ function renderDashboard(results, config, meta) {
186
212
 
187
213
  // src/ui/middleware.ts
188
214
  var SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning", "info"]);
215
+ var CATEGORIES = /* @__PURE__ */ new Set(["seo", "performance", "correctness", "security", "architecture"]);
216
+ function isOptionalString(v) {
217
+ return v === void 0 || typeof v === "string";
218
+ }
189
219
  function isResultLike(x) {
190
220
  if (typeof x !== "object" || x === null) return false;
191
221
  const r = x;
192
222
  const d = r.detection;
193
- return typeof r.id === "string" && typeof r.message === "string" && typeof r.severity === "string" && SEVERITIES.has(r.severity) && typeof d === "object" && d !== null && typeof d.presence === "string" && typeof d.value === "string";
223
+ const f = r.fix;
224
+ return typeof r.id === "string" && typeof r.message === "string" && typeof r.severity === "string" && SEVERITIES.has(r.severity) && typeof d === "object" && d !== null && typeof d.presence === "string" && typeof d.value === "string" && (r.category === void 0 || typeof r.category === "string" && CATEGORIES.has(r.category)) && isOptionalString(r.location) && isOptionalString(r.recommendation) && isOptionalString(r.docsUrl) && (f === void 0 || typeof f === "object" && f !== null && typeof f.description === "string" && isOptionalString(f.snippet) && isOptionalString(f.lang)) && (r.line === void 0 || typeof r.line === "number") && isOptionalString(r.route);
194
225
  }
195
226
  function installUiMiddleware(server, config, version) {
196
227
  const store = createStore();
@@ -210,6 +241,14 @@ function installUiMiddleware(server, config, version) {
210
241
  });
211
242
  server.middlewares.use("/__svelte-vitals", (req, res) => {
212
243
  const url = req.url ?? "/";
244
+ const origin = req.headers.origin;
245
+ const host = req.headers.host;
246
+ if (typeof origin === "string" && !isLoopbackOrigin(origin) || !isLoopbackHost(host)) {
247
+ req.resume();
248
+ res.statusCode = 403;
249
+ res.end("svelte-vitals dev UI is only available from localhost");
250
+ return;
251
+ }
213
252
  if (req.method === "POST" && url.startsWith("/ingest")) {
214
253
  const chunks = [];
215
254
  req.on("data", (c) => chunks.push(c));
@@ -237,8 +276,14 @@ function installUiMiddleware(server, config, version) {
237
276
  req.on("close", () => clients.delete(res));
238
277
  return;
239
278
  }
240
- res.setHeader("Content-Type", "text/html");
241
- res.end(renderDashboard(store.snapshot(), config, { version }));
279
+ try {
280
+ const html = renderDashboard(store.snapshot(), config, { version });
281
+ res.setHeader("Content-Type", "text/html");
282
+ res.end(html);
283
+ } catch {
284
+ res.statusCode = 500;
285
+ res.end("svelte-vitals dashboard failed to render");
286
+ }
242
287
  });
243
288
  }
244
289
 
@@ -254,8 +299,8 @@ function svelteVitals(options = {}) {
254
299
  if (!options.cwd) root = config.root;
255
300
  },
256
301
  async closeBundle() {
257
- const pagesDir = options.prerenderDir ? options.prerenderDir : join3(root, DEFAULT_PRERENDER_DIR);
258
- const resolved = isAbsolute(pagesDir) ? pagesDir : join3(root, pagesDir);
302
+ const pagesDir = options.prerenderDir ? options.prerenderDir : join4(root, DEFAULT_PRERENDER_DIR);
303
+ const resolved = isAbsolute(pagesDir) ? pagesDir : join4(root, pagesDir);
259
304
  if (!existsSync(resolved)) return;
260
305
  let result;
261
306
  try {
@@ -270,7 +315,7 @@ function svelteVitals(options = {}) {
270
315
  console.log(out);
271
316
  }
272
317
  if (options.outFile) {
273
- const outPath = isAbsolute(options.outFile) ? options.outFile : join3(root, options.outFile);
318
+ const outPath = isAbsolute(options.outFile) ? options.outFile : join4(root, options.outFile);
274
319
  await mkdir(dirname(outPath), { recursive: true });
275
320
  await writeFile(outPath, result.jsonReport);
276
321
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/vite",
3
- "version": "0.9.2",
3
+ "version": "0.11.0",
4
4
  "description": "Vite/SvelteKit plugin for svelte-vitals — analyzes prerendered HTML during vite build.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "homepage": "https://github.com/oekazuma/svelte-vitals#readme",
24
24
  "engines": {
25
- "node": ">=18.20.8"
25
+ "node": ">=22.13.0"
26
26
  },
27
27
  "sideEffects": false,
28
28
  "exports": {
@@ -40,13 +40,13 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "esm-env": "^1.2.2",
43
- "node-html-parser": "^6.1.13",
43
+ "node-html-parser": "^8.0.4",
44
44
  "tinyglobby": "^0.2.17",
45
- "@svelte-vitals/core": "0.18.0"
45
+ "@svelte-vitals/core": "0.20.0"
46
46
  },
47
47
  "peerDependencies": {
48
- "@sveltejs/kit": "^2.0.0",
49
- "vite": "^8.1.0"
48
+ "@sveltejs/kit": "^2.69.1",
49
+ "vite": "^8.1.3"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "@sveltejs/kit": {
@@ -54,10 +54,10 @@
54
54
  }
55
55
  },
56
56
  "devDependencies": {
57
- "@sveltejs/kit": "^2.0.0",
58
- "@types/node": "^24.7.0",
59
- "svelte": "^5.56.3",
60
- "vite": "^8.1.0"
57
+ "@sveltejs/kit": "^2.69.1",
58
+ "@types/node": "^24.13.2",
59
+ "svelte": "^5.56.4",
60
+ "vite": "^8.1.3"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsup",