@nwire/scan 0.12.1 → 0.13.1

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.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Disk readers for past telemetry runs — the `.nwire/telemetry/*.jsonl` files
3
+ * the telemetry reporter writes per run.
4
+ *
5
+ * These live in `@nwire/scan` because both Studio (the standalone Vite
6
+ * middleware) and the `nwire` CLI's one-Vite dev host need to serve telemetry
7
+ * history off disk, and both already depend on `@nwire/scan`. Keeping the reader
8
+ * here avoids a UI-package → CLI import edge (the CLI must never depend on
9
+ * `@nwire/studio`) and avoids duplicating the parse/guard logic in two places.
10
+ *
11
+ * All functions are synchronous, side-effect-free beyond filesystem reads, and
12
+ * take an explicit project `cwd` so callers can target any root without touching
13
+ * process state.
14
+ */
15
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
16
+ import { resolve } from "node:path";
17
+ /**
18
+ * List `.nwire/telemetry/*.jsonl` files for a project, newest first.
19
+ *
20
+ * Entry ids are the bare filename minus the `.jsonl` extension. Run ids are
21
+ * ISO-timestamp-prefixed by the telemetry reporter, so lexical descending order
22
+ * is chronological descending order. Missing dir or unreadable entries degrade
23
+ * to an empty list rather than throwing.
24
+ */
25
+ export function listTelemetryRuns(cwd) {
26
+ const dir = resolve(cwd, ".nwire", "telemetry");
27
+ if (!existsSync(dir))
28
+ return [];
29
+ let names;
30
+ try {
31
+ names = readdirSync(dir);
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ const runs = [];
37
+ for (const name of names) {
38
+ if (!name.endsWith(".jsonl"))
39
+ continue;
40
+ const file = resolve(dir, name);
41
+ try {
42
+ const st = statSync(file);
43
+ runs.push({ id: name.slice(0, -6), size: st.size, mtime: st.mtime.toISOString() });
44
+ }
45
+ catch {
46
+ // race: file vanished between readdir and stat — skip
47
+ }
48
+ }
49
+ // Lexical descending = newest first (id starts with ISO timestamp).
50
+ runs.sort((a, b) => b.id.localeCompare(a.id));
51
+ return runs;
52
+ }
53
+ /**
54
+ * Read `.nwire/telemetry/<runId>.jsonl` and return each line parsed as JSON.
55
+ *
56
+ * Security: `runId` must not contain path separators or `..` — the function
57
+ * returns an empty array for any id that fails this check. Blank lines and lines
58
+ * that are not valid JSON are silently skipped.
59
+ */
60
+ export function readTelemetryRun(cwd, runId) {
61
+ // Guard: never let runId escape the telemetry dir.
62
+ if (runId.includes("/") || runId.includes("\\") || runId.includes(".."))
63
+ return [];
64
+ const file = resolve(cwd, ".nwire", "telemetry", `${runId}.jsonl`);
65
+ if (!existsSync(file))
66
+ return [];
67
+ let text;
68
+ try {
69
+ text = readFileSync(file, "utf8");
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ const out = [];
75
+ for (const line of text.split(/\r?\n/)) {
76
+ const trimmed = line.trim();
77
+ if (!trimmed)
78
+ continue;
79
+ try {
80
+ out.push(JSON.parse(trimmed));
81
+ }
82
+ catch {
83
+ // unparseable line — skip
84
+ }
85
+ }
86
+ return out;
87
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Topology — re-exported from `@nwire/telemetry`.
3
+ *
4
+ * The live-runtime capture (`captureTopology`) and the `Topology` shape moved to
5
+ * `@nwire/telemetry` (a runtime lib) so a running app can self-emit its topology
6
+ * to `.nwire/topology.json` without the scanner. The scanner keeps this barrel
7
+ * so its instance-based manifest path and existing imports resolve unchanged;
8
+ * `buildManifest` folds the emitted file when no live instance is given.
9
+ */
10
+ export { captureTopology, type Topology, type CapabilityInfo, type StageInfo, type PluginInfo, type BindingInfo, type HandlerInfo, type HookInfo, type ContributionInfo, type TriggerInfo, type AppInfo, type RouteInfo, } from "@nwire/telemetry";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Topology — re-exported from `@nwire/telemetry`.
3
+ *
4
+ * The live-runtime capture (`captureTopology`) and the `Topology` shape moved to
5
+ * `@nwire/telemetry` (a runtime lib) so a running app can self-emit its topology
6
+ * to `.nwire/topology.json` without the scanner. The scanner keeps this barrel
7
+ * so its instance-based manifest path and existing imports resolve unchanged;
8
+ * `buildManifest` folds the emitted file when no live instance is given.
9
+ */
10
+ export { captureTopology, } from "@nwire/telemetry";
@@ -1,12 +1,20 @@
1
1
  /**
2
- * Vite plugin form — invoke buildCache + writeCache at dev/build startup.
2
+ * Vite/unplugin form — emit the build-time manifest.
3
3
  *
4
- * 0.10 stub: see scan.ts header. buildCache returns an empty cache until
5
- * the post-modules scanner lands.
4
+ * On build start and on dev-server start (re-emitted as `.ts` source changes),
5
+ * walks the project's source tree with the AST extractor and writes
6
+ * `.nwire/manifest.json`. No app boot — "scan the graph, not the runtime."
7
+ * Serves the live manifest at `/__nwire/manifest` in dev.
6
8
  */
7
9
  import type { Plugin } from "vite";
8
- export interface NwireCachePluginOptions {
9
- readonly apps: readonly any[];
10
+ export interface NwireManifestPluginOptions {
11
+ /** Source root to scan. Defaults to the resolved Vite `config.root`. */
12
+ readonly root?: string;
13
+ /** App-name stamp on every manifest entry. */
14
+ readonly app?: string;
15
+ /** Output directory for `manifest.json`. Default `.nwire`. */
10
16
  readonly outDir?: string;
11
17
  }
12
- export declare function nwireCachePlugin(options: NwireCachePluginOptions): Plugin;
18
+ export declare function nwireManifestPlugin(options?: NwireManifestPluginOptions): Plugin;
19
+ /** @deprecated Use {@link nwireManifestPlugin}. Kept so existing imports resolve. */
20
+ export declare const nwireCachePlugin: typeof nwireManifestPlugin;
@@ -1,25 +1,40 @@
1
1
  /**
2
- * Vite plugin form — invoke buildCache + writeCache at dev/build startup.
2
+ * Vite/unplugin form — emit the build-time manifest.
3
3
  *
4
- * 0.10 stub: see scan.ts header. buildCache returns an empty cache until
5
- * the post-modules scanner lands.
4
+ * On build start and on dev-server start (re-emitted as `.ts` source changes),
5
+ * walks the project's source tree with the AST extractor and writes
6
+ * `.nwire/manifest.json`. No app boot — "scan the graph, not the runtime."
7
+ * Serves the live manifest at `/__nwire/manifest` in dev.
6
8
  */
7
- import { buildCache, writeCache } from "./scan.js";
8
- export function nwireCachePlugin(options) {
9
+ import { buildManifest, writeManifest } from "./manifest.js";
10
+ export function nwireManifestPlugin(options = {}) {
9
11
  const outDir = options.outDir ?? ".nwire";
12
+ let root = options.root ?? process.cwd();
10
13
  return {
11
- name: "nwire:cache",
14
+ name: "nwire:manifest",
15
+ configResolved(config) {
16
+ if (!options.root && config.root)
17
+ root = config.root;
18
+ },
12
19
  async buildStart() {
13
- const cache = buildCache(options.apps);
14
- await writeCache(cache, outDir);
20
+ await writeManifest(buildManifest(root, options.app), outDir);
15
21
  },
16
22
  async configureServer(server) {
17
- const cache = buildCache(options.apps);
18
- await writeCache(cache, outDir);
19
- server.middlewares.use("/__nwire/cache", (_req, res) => {
23
+ const emit = () => writeManifest(buildManifest(root, options.app), outDir);
24
+ await emit();
25
+ const onChange = (file) => {
26
+ if (file.endsWith(".ts") && !file.endsWith(".d.ts"))
27
+ void emit();
28
+ };
29
+ server.watcher.on("add", onChange);
30
+ server.watcher.on("change", onChange);
31
+ server.watcher.on("unlink", onChange);
32
+ server.middlewares.use("/__nwire/manifest", (_req, res) => {
20
33
  res.setHeader("Content-Type", "application/json");
21
- res.end(JSON.stringify(cache, null, 2));
34
+ res.end(JSON.stringify(buildManifest(root, options.app), null, 2));
22
35
  });
23
36
  },
24
37
  };
25
38
  }
39
+ /** @deprecated Use {@link nwireManifestPlugin}. Kept so existing imports resolve. */
40
+ export const nwireCachePlugin = nwireManifestPlugin;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nwire/scan",
3
- "version": "0.12.1",
4
- "description": "Nwire — system registry scanner. Walks AppDefinition[] manifests and writes the .nwire/ cache (actions, events, actors, projections, queries, modules, routes, event graph). Vite plugin + standalone function.",
3
+ "version": "0.13.1",
4
+ "description": "Nwire — system registry scanner. Walks AppDefinition[] manifests and writes the .nwire/ cache (actions, events, actors, projections, queries, routes, event graph). Vite plugin + standalone function.",
5
5
  "keywords": [
6
6
  "cache",
7
7
  "nwire",
@@ -32,17 +32,19 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
+ "typescript": "^5.9.3",
35
36
  "zod": "^4.4.3",
36
- "@nwire/forge": "0.12.1",
37
- "@nwire/messages": "0.12.1",
38
- "@nwire/hooks": "0.12.1"
37
+ "@nwire/forge": "0.13.1",
38
+ "@nwire/hooks": "0.13.1",
39
+ "@nwire/messages": "0.13.1",
40
+ "@nwire/telemetry": "0.13.1"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@types/node": "^22.19.9",
42
44
  "typescript": "^5.9.3",
43
45
  "vite": "npm:rolldown-vite@latest",
44
46
  "vitest": "^4.0.18",
45
- "@nwire/container": "0.12.1"
47
+ "@nwire/container": "0.13.1"
46
48
  },
47
49
  "scripts": {
48
50
  "build": "tsc && node ../../scripts/fix-dist-extensions.mjs dist",