nuxt-layerscope 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as Context, c as Layer, d as Reference, f as ReferenceKind, g as SourceOption, h as Severity, i as BaselineResult, l as LayerRule, m as RuleName, n as AnalyzeResult, o as Edge, p as ResolutionSource, r as BaselineEntry, s as Finding, t as defineConfig, u as LayerscopeConfig } from "./define-DNRGLVG_.mjs";
2
- import { t as ProjectConfig } from "./effective-Dqg4EwH_.mjs";
1
+ import { a as Context, c as Layer, d as Reference, f as ReferenceKind, g as SourceOption, h as Severity, i as BaselineResult, l as LayerRule, m as RuleName, n as AnalyzeResult, o as Edge, p as ResolutionSource, r as BaselineEntry, s as Finding, t as defineConfig, u as LayerscopeConfig } from "./define-DDg4HQRH.mjs";
2
+ import { t as ProjectConfig } from "./effective-BPMNHe9A.mjs";
3
3
  //#region src/module/index.d.ts
4
4
  interface ModuleOptions extends ProjectConfig {
5
5
  /** Write `.nuxt/layerscope/registry.json`. Defaults to `true`. */
package/dist/index.mjs CHANGED
@@ -1,13 +1,15 @@
1
- import { l as REGISTRY_FILE, p as pickProjectConfig, r as layerFromNuxt } from "./layer-factory-DFh7nkFH.mjs";
2
- import { t as summarize } from "./summary-DiUYKMPe.mjs";
1
+ import { o as REGISTRY_FILE, r as layerFromNuxt, x as pickProjectConfig } from "./layer-factory-DVSf2Wj4.mjs";
3
2
  import { i as plural, t as compareStrings } from "./strings-CrOjNZmB.mjs";
4
- import { t as packageVersion } from "./version-DFWOUgi5.mjs";
5
- import { t as globComponents } from "./component-files-DgOXNE6l.mjs";
3
+ import { i as BASELINE_FILE, n as AnalysisCache, t as globComponents } from "./component-files-B_qvkKwv.mjs";
4
+ import { t as summarize } from "./summary-DiUYKMPe.mjs";
5
+ import { t as packageVersion } from "./version-D5JrgoO_.mjs";
6
6
  import { t as defineConfig } from "./define-l62B9fVk.mjs";
7
7
  import { mkdir, writeFile } from "node:fs/promises";
8
8
  import { addDevServerHandler, defineNuxtModule, getLayerDirectories } from "@nuxt/kit";
9
9
  import { dirname, isAbsolute, join, relative, resolve } from "pathe";
10
- import { defineEventHandler, getQuery, setResponseHeader } from "h3";
10
+ import { readdirSync, statSync } from "node:fs";
11
+ import { randomUUID } from "node:crypto";
12
+ import { defineEventHandler, getHeader, getQuery, setResponseHeader, setResponseStatus } from "h3";
11
13
  //#region src/module/nuxt.ts
12
14
  /** `nitro:init` is typed by Nuxt's Nitro integration, not by `@nuxt/schema`. */
13
15
  function onNitroInit(nuxt, handler) {
@@ -18,6 +20,111 @@ function onDevtoolsCustomTabs(nuxt, handler) {
18
20
  nuxt.hook("devtools:customTabs", handler);
19
21
  }
20
22
  //#endregion
23
+ //#region src/devtools/analyzer.ts
24
+ /** The output the revision tracks; the symbol table and edges are large and derived from it. */
25
+ function fingerprint(result) {
26
+ return JSON.stringify([
27
+ result.findings,
28
+ result.baseline?.suppressed,
29
+ result.layers.map((layer) => layer.name),
30
+ result.files.length,
31
+ result.notes
32
+ ]);
33
+ }
34
+ /** Analyzes lazily, shares one run between concurrent callers, and reuses per-file work. */
35
+ var Analyzer = class {
36
+ constructor(options) {
37
+ this.id = randomUUID();
38
+ this.cache = new AnalysisCache();
39
+ this.lastFingerprint = "";
40
+ this.dirty = true;
41
+ this.options = options;
42
+ }
43
+ get current() {
44
+ return this.snapshot;
45
+ }
46
+ /** Marks the snapshot stale; the next `get` re-analyzes. Does no work itself. */
47
+ invalidate() {
48
+ this.dirty = true;
49
+ }
50
+ async get() {
51
+ if (this.snapshot !== void 0 && !this.dirty) return this.snapshot;
52
+ return await (this.inflight ?? this.start());
53
+ }
54
+ /** Always analyzes after the call: a run already in flight may predate the request. */
55
+ async refresh() {
56
+ if (this.inflight === void 0) return await this.start();
57
+ this.queued ??= this.afterInflight();
58
+ return await this.queued;
59
+ }
60
+ async afterInflight() {
61
+ await Promise.allSettled([this.inflight]);
62
+ this.queued = void 0;
63
+ return await this.refresh();
64
+ }
65
+ async start() {
66
+ this.inflight = this.analyze().finally(() => {
67
+ this.inflight = void 0;
68
+ });
69
+ return await this.inflight;
70
+ }
71
+ async analyze() {
72
+ this.dirty = false;
73
+ const started = Date.now();
74
+ try {
75
+ const result = await (this.options.run ?? (await import("./analyze-vZUz30sN.mjs")).analyze)({
76
+ rootDir: this.options.rootDir,
77
+ baseline: this.options.baseline,
78
+ cache: this.cache,
79
+ envKey: await this.options.envKey()
80
+ });
81
+ const next = fingerprint(result);
82
+ const changed = this.snapshot !== void 0 && next !== this.lastFingerprint;
83
+ this.lastFingerprint = next;
84
+ this.snapshot = {
85
+ id: this.id,
86
+ rev: (this.snapshot?.rev ?? 0) + (changed ? 1 : 0),
87
+ analyzedAt: Date.now(),
88
+ durationMs: Date.now() - started,
89
+ result
90
+ };
91
+ return this.snapshot;
92
+ } catch (error) {
93
+ this.dirty = true;
94
+ throw error;
95
+ }
96
+ }
97
+ };
98
+ //#endregion
99
+ //#region src/devtools/env-key.ts
100
+ const WATCHED = [
101
+ "layerscope/registry.json",
102
+ "imports.d.ts",
103
+ "components.d.ts",
104
+ "tsconfig.json",
105
+ "tsconfig.app.json",
106
+ "tsconfig.server.json",
107
+ "tsconfig.shared.json",
108
+ "types/imports.d.ts",
109
+ "types/nitro-imports.d.ts",
110
+ "types/shared-imports.d.ts",
111
+ "types/components.d.ts"
112
+ ];
113
+ const CONFIG_PATTERN = /^(nuxt|layerscope)\.config\./u;
114
+ function stamp(file) {
115
+ try {
116
+ const { mtimeMs, size } = statSync(file);
117
+ return `${file}:${mtimeMs}:${size}`;
118
+ } catch {
119
+ return `${file}:missing`;
120
+ }
121
+ }
122
+ /** Changes when the registry, generated types or config change, which invalidates cached analyses. */
123
+ function computeEnvKey(rootDir, buildDir) {
124
+ const configs = readdirSync(rootDir).filter((name) => CONFIG_PATTERN.test(name));
125
+ return [...WATCHED.map((name) => stamp(join(buildDir, name))), ...configs.map((name) => stamp(join(rootDir, name)))].join("|");
126
+ }
127
+ //#endregion
21
128
  //#region src/devtools/html.ts
22
129
  const ENTITIES = {
23
130
  "&": "&",
@@ -89,7 +196,9 @@ function findingsList(input) {
89
196
  function summaryLine(result) {
90
197
  const { errors, warnings } = summarize(result.findings);
91
198
  const source = result.source === "registry" ? "module registry" : "generated .d.ts files";
92
- return `${plural(result.files.length, "file")} in ${plural(result.layers.length, "layer")} · ${plural(errors, "error")}, ${plural(warnings, "warning")} · symbols from the ${source}`;
199
+ const suppressed = result.baseline?.suppressed.length ?? 0;
200
+ const baseline = suppressed === 0 ? "" : ` · ${suppressed} in baseline`;
201
+ return `${plural(result.files.length, "file")} in ${plural(result.layers.length, "layer")} · ${plural(errors, "error")}, ${plural(warnings, "warning")}${baseline} · symbols from the ${source}`;
93
202
  }
94
203
  function document(body) {
95
204
  return `<!doctype html><html><head><meta charset="utf-8"><title>layerscope</title><style>${PAGE_STYLE}</style></head><body>${body}<script>${PAGE_SCRIPT}<\/script></body></html>`;
@@ -103,30 +212,101 @@ function renderError(message) {
103
212
  return document(`<header><h1>layerscope</h1><button data-reload>Re-run</button></header><p class="error-message">${escapeHtml(message)}</p>`);
104
213
  }
105
214
  //#endregion
106
- //#region src/devtools/index.ts
107
- const DEVTOOLS_ROUTE = "/__layerscope";
108
- async function renderReport(nuxt, format) {
109
- const [{ analyze }, { formatResult }] = await Promise.all([import("./analyze-DVzyPH7N.mjs"), import("./report-CKhXT50W.mjs")]);
110
- const { rootDir, app } = nuxt.options;
111
- const result = await analyze({ rootDir });
112
- if (format === "json") return formatResult(result, "json", rootDir);
215
+ //#region src/devtools/handler.ts
216
+ const JSON_TYPE = "application/json";
217
+ const HTML_TYPE = "text/html; charset=utf-8";
218
+ function meta({ rev, analyzedAt, durationMs }) {
219
+ return {
220
+ rev,
221
+ analyzedAt,
222
+ durationMs
223
+ };
224
+ }
225
+ async function reportOf(snapshot) {
226
+ const { formatResult } = await import("./report-DG-XzEq4.mjs");
227
+ const { rootDir } = snapshot.result;
228
+ return JSON.parse(formatResult(snapshot.result, "json", rootDir));
229
+ }
230
+ function json(event, status, body) {
231
+ setResponseStatus(event, status);
232
+ setResponseHeader(event, "content-type", JSON_TYPE);
233
+ return JSON.stringify(body);
234
+ }
235
+ async function page(event, input) {
236
+ const snapshot = await input.analyzer.refresh();
237
+ if (getQuery(event).format === "json") {
238
+ setResponseHeader(event, "content-type", JSON_TYPE);
239
+ return JSON.stringify(await reportOf(snapshot));
240
+ }
241
+ setResponseHeader(event, "content-type", HTML_TYPE);
113
242
  return renderPage({
114
- result,
115
- openInEditor: join("/", app.baseURL, app.buildAssetsDir, "__open-in-editor")
243
+ result: snapshot.result,
244
+ openInEditor: input.openInEditor
245
+ });
246
+ }
247
+ async function withReport(snapshot) {
248
+ return {
249
+ ...meta(snapshot),
250
+ report: await reportOf(snapshot)
251
+ };
252
+ }
253
+ async function api(event, path, input) {
254
+ if (path === "/api/rerun") return event.method === "POST" ? json(event, 200, await withReport(await input.analyzer.refresh())) : json(event, 405, { error: "Use POST" });
255
+ if (path !== "/api/state" && path !== "/api/report") return json(event, 404, { error: `Not found: ${path}` });
256
+ if (event.method !== "GET") return json(event, 405, { error: "Use GET" });
257
+ const snapshot = await input.analyzer.get();
258
+ const etag = `"${snapshot.id}-${snapshot.rev}"`;
259
+ setResponseHeader(event, "etag", etag);
260
+ setResponseHeader(event, "cache-control", "no-cache");
261
+ if (getHeader(event, "if-none-match") === etag) {
262
+ setResponseStatus(event, 304);
263
+ return "";
264
+ }
265
+ return json(event, 200, path === "/api/state" ? {
266
+ ...meta(snapshot),
267
+ status: "ready"
268
+ } : await withReport(snapshot));
269
+ }
270
+ /** The DevTools tab, mounted at `/__layerscope`: the page, its JSON report and `/api/*`. */
271
+ function createDevtoolsHandler(input) {
272
+ return defineEventHandler(async (event) => {
273
+ const path = event.path.split("?")[0] ?? "/";
274
+ const isPage = path === "/" || path === "";
275
+ try {
276
+ return isPage ? await page(event, input) : await api(event, path, input);
277
+ } catch (error) {
278
+ const message = error.message;
279
+ if (isPage) {
280
+ setResponseHeader(event, "content-type", HTML_TYPE);
281
+ return renderError(message);
282
+ }
283
+ return json(event, 500, { error: message });
284
+ }
116
285
  });
117
286
  }
287
+ //#endregion
288
+ //#region src/devtools/index.ts
289
+ const DEVTOOLS_ROUTE = "/__layerscope";
118
290
  /** A Nuxt DevTools tab with the layer report, served while `nuxi dev` runs. */
119
291
  function setupDevtools(nuxt) {
292
+ const { rootDir, buildDir, app } = nuxt.options;
293
+ let analyzer;
294
+ const invalidate = () => analyzer?.invalidate();
295
+ nuxt.hook("app:templatesGenerated", invalidate);
296
+ nuxt.hook("builder:watch", invalidate);
297
+ const openInEditor = join("/", app.baseURL, app.buildAssetsDir, "__open-in-editor");
120
298
  addDevServerHandler({
121
299
  route: DEVTOOLS_ROUTE,
122
- handler: defineEventHandler(async (event) => {
123
- const { format } = getQuery(event);
124
- setResponseHeader(event, "content-type", format === "json" ? "application/json" : "text/html; charset=utf-8");
125
- try {
126
- return await renderReport(nuxt, format);
127
- } catch (error) {
128
- return renderError(error.message);
129
- }
300
+ handler: createDevtoolsHandler({
301
+ get analyzer() {
302
+ analyzer ??= new Analyzer({
303
+ rootDir,
304
+ baseline: BASELINE_FILE,
305
+ envKey: () => computeEnvKey(rootDir, buildDir)
306
+ });
307
+ return analyzer;
308
+ },
309
+ openInEditor
130
310
  })
131
311
  });
132
312
  onDevtoolsCustomTabs(nuxt, (tabs) => {