@nwire/cli 0.9.1 → 0.10.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.
Files changed (189) hide show
  1. package/dist/cache-runner.d.ts +0 -1
  2. package/dist/cache-runner.js +107 -9
  3. package/dist/cli.d.ts +3 -6
  4. package/dist/cli.js +5 -7
  5. package/dist/commands/bench.d.ts +0 -1
  6. package/dist/commands/bench.js +0 -1
  7. package/dist/commands/build.d.ts +0 -1
  8. package/dist/commands/build.js +13 -17
  9. package/dist/commands/cache.d.ts +3 -3
  10. package/dist/commands/cache.js +7 -17
  11. package/dist/commands/check.d.ts +10 -3
  12. package/dist/commands/check.js +34 -10
  13. package/dist/commands/dev.d.ts +0 -1
  14. package/dist/commands/dev.js +0 -1
  15. package/dist/commands/doctor.d.ts +1 -2
  16. package/dist/commands/doctor.js +14 -15
  17. package/dist/commands/fmt.d.ts +8 -4
  18. package/dist/commands/fmt.js +35 -12
  19. package/dist/commands/greeting.d.ts +0 -1
  20. package/dist/commands/greeting.js +0 -1
  21. package/dist/commands/infra.d.ts +0 -1
  22. package/dist/commands/infra.js +0 -1
  23. package/dist/commands/lint.d.ts +7 -3
  24. package/dist/commands/lint.js +34 -13
  25. package/dist/commands/logs.d.ts +0 -1
  26. package/dist/commands/logs.js +0 -1
  27. package/dist/commands/ls.d.ts +1 -1
  28. package/dist/commands/ls.js +3 -1
  29. package/dist/commands/mcp.d.ts +0 -1
  30. package/dist/commands/mcp.js +2 -2
  31. package/dist/commands/please.d.ts +0 -1
  32. package/dist/commands/please.js +0 -1
  33. package/dist/commands/ps.d.ts +0 -1
  34. package/dist/commands/ps.js +0 -1
  35. package/dist/commands/replay.d.ts +0 -1
  36. package/dist/commands/replay.js +0 -1
  37. package/dist/commands/run.d.ts +0 -1
  38. package/dist/commands/run.js +0 -1
  39. package/dist/commands/studio.d.ts +0 -1
  40. package/dist/commands/studio.js +16 -6
  41. package/dist/commands/test.d.ts +0 -1
  42. package/dist/commands/test.js +0 -1
  43. package/dist/commands/trace.d.ts +0 -1
  44. package/dist/commands/trace.js +0 -1
  45. package/dist/commands/watch.d.ts +0 -1
  46. package/dist/commands/watch.js +0 -1
  47. package/dist/index.d.ts +0 -1
  48. package/dist/index.js +0 -1
  49. package/dist/lib/colors.d.ts +0 -1
  50. package/dist/lib/colors.js +0 -1
  51. package/dist/lib/dev-entry.d.ts +0 -1
  52. package/dist/lib/dev-entry.js +0 -1
  53. package/dist/lib/ensure-scan.d.ts +28 -0
  54. package/dist/lib/ensure-scan.js +41 -0
  55. package/dist/lib/exec.d.ts +0 -1
  56. package/dist/lib/exec.js +0 -1
  57. package/dist/lib/layout.d.ts +40 -0
  58. package/dist/lib/layout.js +128 -0
  59. package/dist/lib/package-manager.d.ts +17 -0
  60. package/dist/lib/package-manager.js +67 -0
  61. package/dist/lib/process-state.d.ts +0 -1
  62. package/dist/lib/process-state.js +0 -1
  63. package/dist/lib/project.d.ts +3 -4
  64. package/dist/lib/project.js +16 -34
  65. package/dist/lib/run-task.d.ts +0 -1
  66. package/dist/lib/run-task.js +0 -1
  67. package/dist/lib/scan-cache.d.ts +27 -0
  68. package/dist/lib/scan-cache.js +102 -0
  69. package/dist/lib/script-runner.d.ts +42 -0
  70. package/dist/lib/script-runner.js +90 -0
  71. package/dist/lib/sse.d.ts +0 -1
  72. package/dist/lib/sse.js +0 -1
  73. package/dist/lib/version.d.ts +8 -0
  74. package/dist/lib/version.js +29 -0
  75. package/dist/lib/vite-node.d.ts +0 -1
  76. package/dist/lib/vite-node.js +0 -1
  77. package/dist/lib/wire-discovery.d.ts +0 -1
  78. package/dist/lib/wire-discovery.js +0 -1
  79. package/dist/load-config.d.ts +0 -1
  80. package/dist/load-config.js +0 -1
  81. package/dist/ls-runner.d.ts +0 -1
  82. package/dist/ls-runner.js +34 -40
  83. package/dist/ui/dev-dashboard.d.ts +0 -1
  84. package/dist/ui/dev-dashboard.js +0 -1
  85. package/dist/ui/greeting.d.ts +0 -1
  86. package/dist/ui/greeting.js +0 -1
  87. package/dist/ui/process-table.d.ts +0 -1
  88. package/dist/ui/process-table.js +0 -1
  89. package/package.json +5 -6
  90. package/dist/__tests__/bench.test.d.ts +0 -2
  91. package/dist/__tests__/bench.test.d.ts.map +0 -1
  92. package/dist/__tests__/bench.test.js +0 -94
  93. package/dist/__tests__/bench.test.js.map +0 -1
  94. package/dist/__tests__/doctor.test.d.ts +0 -10
  95. package/dist/__tests__/doctor.test.d.ts.map +0 -1
  96. package/dist/__tests__/doctor.test.js +0 -105
  97. package/dist/__tests__/doctor.test.js.map +0 -1
  98. package/dist/__tests__/replay.test.d.ts +0 -2
  99. package/dist/__tests__/replay.test.d.ts.map +0 -1
  100. package/dist/__tests__/replay.test.js +0 -203
  101. package/dist/__tests__/replay.test.js.map +0 -1
  102. package/dist/__tests__/trace.test.d.ts +0 -2
  103. package/dist/__tests__/trace.test.d.ts.map +0 -1
  104. package/dist/__tests__/trace.test.js +0 -136
  105. package/dist/__tests__/trace.test.js.map +0 -1
  106. package/dist/__tests__/watch.test.d.ts +0 -2
  107. package/dist/__tests__/watch.test.d.ts.map +0 -1
  108. package/dist/__tests__/watch.test.js +0 -110
  109. package/dist/__tests__/watch.test.js.map +0 -1
  110. package/dist/cache-runner.d.ts.map +0 -1
  111. package/dist/cache-runner.js.map +0 -1
  112. package/dist/cli.d.ts.map +0 -1
  113. package/dist/cli.js.map +0 -1
  114. package/dist/commands/bench.d.ts.map +0 -1
  115. package/dist/commands/bench.js.map +0 -1
  116. package/dist/commands/build.d.ts.map +0 -1
  117. package/dist/commands/build.js.map +0 -1
  118. package/dist/commands/cache.d.ts.map +0 -1
  119. package/dist/commands/cache.js.map +0 -1
  120. package/dist/commands/check.d.ts.map +0 -1
  121. package/dist/commands/check.js.map +0 -1
  122. package/dist/commands/dev.d.ts.map +0 -1
  123. package/dist/commands/dev.js.map +0 -1
  124. package/dist/commands/doctor.d.ts.map +0 -1
  125. package/dist/commands/doctor.js.map +0 -1
  126. package/dist/commands/fmt.d.ts.map +0 -1
  127. package/dist/commands/fmt.js.map +0 -1
  128. package/dist/commands/greeting.d.ts.map +0 -1
  129. package/dist/commands/greeting.js.map +0 -1
  130. package/dist/commands/infra.d.ts.map +0 -1
  131. package/dist/commands/infra.js.map +0 -1
  132. package/dist/commands/lint.d.ts.map +0 -1
  133. package/dist/commands/lint.js.map +0 -1
  134. package/dist/commands/logs.d.ts.map +0 -1
  135. package/dist/commands/logs.js.map +0 -1
  136. package/dist/commands/ls.d.ts.map +0 -1
  137. package/dist/commands/ls.js.map +0 -1
  138. package/dist/commands/mcp.d.ts.map +0 -1
  139. package/dist/commands/mcp.js.map +0 -1
  140. package/dist/commands/please.d.ts.map +0 -1
  141. package/dist/commands/please.js.map +0 -1
  142. package/dist/commands/ps.d.ts.map +0 -1
  143. package/dist/commands/ps.js.map +0 -1
  144. package/dist/commands/replay.d.ts.map +0 -1
  145. package/dist/commands/replay.js.map +0 -1
  146. package/dist/commands/run.d.ts.map +0 -1
  147. package/dist/commands/run.js.map +0 -1
  148. package/dist/commands/studio.d.ts.map +0 -1
  149. package/dist/commands/studio.js.map +0 -1
  150. package/dist/commands/test.d.ts.map +0 -1
  151. package/dist/commands/test.js.map +0 -1
  152. package/dist/commands/trace.d.ts.map +0 -1
  153. package/dist/commands/trace.js.map +0 -1
  154. package/dist/commands/watch.d.ts.map +0 -1
  155. package/dist/commands/watch.js.map +0 -1
  156. package/dist/index.d.ts.map +0 -1
  157. package/dist/index.js.map +0 -1
  158. package/dist/kernel-instance.d.ts +0 -8
  159. package/dist/kernel-instance.d.ts.map +0 -1
  160. package/dist/kernel-instance.js +0 -13
  161. package/dist/kernel-instance.js.map +0 -1
  162. package/dist/lib/colors.d.ts.map +0 -1
  163. package/dist/lib/colors.js.map +0 -1
  164. package/dist/lib/dev-entry.d.ts.map +0 -1
  165. package/dist/lib/dev-entry.js.map +0 -1
  166. package/dist/lib/exec.d.ts.map +0 -1
  167. package/dist/lib/exec.js.map +0 -1
  168. package/dist/lib/process-state.d.ts.map +0 -1
  169. package/dist/lib/process-state.js.map +0 -1
  170. package/dist/lib/project.d.ts.map +0 -1
  171. package/dist/lib/project.js.map +0 -1
  172. package/dist/lib/run-task.d.ts.map +0 -1
  173. package/dist/lib/run-task.js.map +0 -1
  174. package/dist/lib/sse.d.ts.map +0 -1
  175. package/dist/lib/sse.js.map +0 -1
  176. package/dist/lib/vite-node.d.ts.map +0 -1
  177. package/dist/lib/vite-node.js.map +0 -1
  178. package/dist/lib/wire-discovery.d.ts.map +0 -1
  179. package/dist/lib/wire-discovery.js.map +0 -1
  180. package/dist/load-config.d.ts.map +0 -1
  181. package/dist/load-config.js.map +0 -1
  182. package/dist/ls-runner.d.ts.map +0 -1
  183. package/dist/ls-runner.js.map +0 -1
  184. package/dist/ui/dev-dashboard.d.ts.map +0 -1
  185. package/dist/ui/dev-dashboard.js.map +0 -1
  186. package/dist/ui/greeting.d.ts.map +0 -1
  187. package/dist/ui/greeting.js.map +0 -1
  188. package/dist/ui/process-table.d.ts.map +0 -1
  189. package/dist/ui/process-table.js.map +0 -1
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Detect the consumer's package manager from lockfile / packageManager
3
+ * field. `pnpm` is preferred when ambiguous (matches Nwire's own dev
4
+ * convention) but the CLI follows whatever the project already uses.
5
+ */
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { resolve } from "node:path";
8
+ export function detectPackageManager(cwd = process.cwd()) {
9
+ const pkgPath = resolve(cwd, "package.json");
10
+ if (existsSync(pkgPath)) {
11
+ try {
12
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
13
+ if (pkg.packageManager) {
14
+ if (pkg.packageManager.startsWith("pnpm"))
15
+ return "pnpm";
16
+ if (pkg.packageManager.startsWith("yarn"))
17
+ return "yarn";
18
+ if (pkg.packageManager.startsWith("bun"))
19
+ return "bun";
20
+ if (pkg.packageManager.startsWith("npm"))
21
+ return "npm";
22
+ }
23
+ }
24
+ catch {
25
+ // fall through to lockfile detection
26
+ }
27
+ }
28
+ if (existsSync(resolve(cwd, "pnpm-lock.yaml")))
29
+ return "pnpm";
30
+ if (existsSync(resolve(cwd, "yarn.lock")))
31
+ return "yarn";
32
+ if (existsSync(resolve(cwd, "bun.lockb")))
33
+ return "bun";
34
+ if (existsSync(resolve(cwd, "package-lock.json")))
35
+ return "npm";
36
+ return "npm";
37
+ }
38
+ /**
39
+ * Build the command + args for `<pm> run <script>`.
40
+ */
41
+ export function runScriptCommand(pm, script) {
42
+ switch (pm) {
43
+ case "pnpm":
44
+ case "yarn":
45
+ case "bun":
46
+ return [pm, ["run", script]];
47
+ case "npm":
48
+ return ["npm", ["run", script]];
49
+ }
50
+ }
51
+ /**
52
+ * Build the command + args for `<pm> exec <bin> [...args]`. yarn 1.x
53
+ * does not have `exec`; we fall back to invoking `node_modules/.bin/<bin>`
54
+ * directly for that case.
55
+ */
56
+ export function execBinaryCommand(pm, bin, args = []) {
57
+ switch (pm) {
58
+ case "pnpm":
59
+ return ["pnpm", ["exec", bin, ...args]];
60
+ case "yarn":
61
+ return ["yarn", [bin, ...args]];
62
+ case "bun":
63
+ return ["bun", ["x", bin, ...args]];
64
+ case "npm":
65
+ return ["npx", ["--no-install", bin, ...args]];
66
+ }
67
+ }
@@ -27,4 +27,3 @@ export declare function writeRecord(cwd: string, record: ProcessRecord): void;
27
27
  export declare function readRecord(cwd: string, id: string): ProcessRecord | undefined;
28
28
  export declare function listRecords(cwd: string): ProcessRecord[];
29
29
  export declare function removeRecord(cwd: string, id: string): void;
30
- //# sourceMappingURL=process-state.d.ts.map
@@ -65,4 +65,3 @@ export function removeRecord(cwd, id) {
65
65
  // already gone
66
66
  }
67
67
  }
68
- //# sourceMappingURL=process-state.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Project introspection figures out what shape of Nwire project we're
3
- * sitting inside. Commands use this to render the greeting and to validate
4
- * arguments before spawning anything.
2
+ * Project introspection. Thin adapter over `detectLayout` preserves
3
+ * the older `ProjectInfo` shape so existing CLI callers keep working
4
+ * while pointing at the unified layout discoverer.
5
5
  */
6
6
  export interface ProjectInfo {
7
7
  readonly cwd: string;
@@ -12,4 +12,3 @@ export interface ProjectInfo {
12
12
  readonly hasPlease: boolean;
13
13
  }
14
14
  export declare function detectProject(cwd?: string): ProjectInfo;
15
- //# sourceMappingURL=project.d.ts.map
@@ -1,52 +1,34 @@
1
1
  /**
2
- * Project introspection figures out what shape of Nwire project we're
3
- * sitting inside. Commands use this to render the greeting and to validate
4
- * arguments before spawning anything.
2
+ * Project introspection. Thin adapter over `detectLayout` preserves
3
+ * the older `ProjectInfo` shape so existing CLI callers keep working
4
+ * while pointing at the unified layout discoverer.
5
5
  */
6
- import { existsSync, readdirSync, readFileSync } from "node:fs";
6
+ import { existsSync, readFileSync } from "node:fs";
7
7
  import { resolve } from "node:path";
8
+ import { detectLayout } from "./layout.js";
8
9
  export function detectProject(cwd = process.cwd()) {
9
- const pkgPath = resolve(cwd, "package.json");
10
+ const layout = detectLayout(cwd);
10
11
  let name = "(unnamed)";
11
- try {
12
- if (existsSync(pkgPath)) {
12
+ const pkgPath = resolve(cwd, "package.json");
13
+ if (existsSync(pkgPath)) {
14
+ try {
13
15
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
14
16
  if (pkg.name)
15
17
  name = pkg.name;
16
18
  }
17
- }
18
- catch {
19
- // keep default name
20
- }
21
- const appsDir = resolve(cwd, "apps");
22
- const wires = [];
23
- let hasDevAll = false;
24
- let hasPlease = false;
25
- if (existsSync(appsDir)) {
26
- for (const entry of readdirSync(appsDir, { withFileTypes: true })) {
27
- if (!entry.isDirectory())
28
- continue;
29
- const appDir = resolve(appsDir, entry.name);
30
- // A wire is any app folder with a recognized entry file. We accept
31
- // both the older `run.ts` convention and the current `main.ts`
32
- // convention so the greeting works across projects mid-migration.
33
- const hasEntry = existsSync(resolve(appDir, "run.ts")) || existsSync(resolve(appDir, "main.ts"));
34
- if (hasEntry)
35
- wires.push(entry.name);
36
- if (entry.name === "dev-all" && existsSync(resolve(appDir, "run.ts"))) {
37
- hasDevAll = true;
38
- }
39
- if (existsSync(resolve(appDir, "run.please.ts")))
40
- hasPlease = true;
19
+ catch {
20
+ // keep default
41
21
  }
42
22
  }
23
+ const wires = layout.wires.map((w) => w.name).sort();
24
+ const hasDevAll = wires.includes("dev-all");
25
+ const hasPlease = layout.wires.some((w) => w.entry.endsWith("run.please.ts") || w.entry.endsWith("please.ts"));
43
26
  return {
44
27
  cwd,
45
28
  name,
46
- hasNwireConfig: existsSync(resolve(cwd, "nwire.config.ts")),
47
- wires: wires.sort(),
29
+ hasNwireConfig: !!layout.configPath,
30
+ wires,
48
31
  hasDevAll,
49
32
  hasPlease,
50
33
  };
51
34
  }
52
- //# sourceMappingURL=project.js.map
@@ -29,4 +29,3 @@ export declare function runCommand(spec: TaskRunSpec): Promise<{
29
29
  * is set). Returns the aggregate exit code.
30
30
  */
31
31
  export declare function runTaskList(tasks: readonly TaskRunSpec[]): Promise<number>;
32
- //# sourceMappingURL=run-task.d.ts.map
@@ -72,4 +72,3 @@ export async function runTaskList(tasks) {
72
72
  }
73
73
  return aggregate;
74
74
  }
75
- //# sourceMappingURL=run-task.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Incremental scan cache. Walks the project's source tree, computes a
3
+ * fingerprint over file mtimes and sizes, and persists it under
4
+ * `.nwire/scan-fingerprint.json`. `isFresh()` returns true when the
5
+ * current tree matches the saved fingerprint — in that case CLI
6
+ * commands can read the existing `.nwire/manifest.json` without
7
+ * re-running the consumer's app to rebuild it.
8
+ *
9
+ * The fingerprint is intentionally cheap: a single sha1 over a sorted
10
+ * list of `<relative path> <mtimeMs> <size>` lines for every `.ts`,
11
+ * `.mts`, `.js`, `.mjs` file under the project root, excluding
12
+ * `node_modules/`, `dist/`, `.nwire/`, `.turbo/`, and dotfiles.
13
+ *
14
+ * The cost of a fresh fingerprint scan on a typical service-sized tree
15
+ * (hundreds of files) is a few milliseconds — fast enough to run on
16
+ * every CLI invocation that needs it.
17
+ */
18
+ export declare function fingerprint(cwd: string): string;
19
+ export declare function readFingerprint(cwd: string): string | undefined;
20
+ export declare function writeFingerprint(cwd: string, value: string): void;
21
+ /**
22
+ * True when `.nwire/manifest.json` exists AND the saved fingerprint
23
+ * matches the current tree. The caller passes the fingerprint it just
24
+ * computed so the same scan can serve both "is fresh?" and "what's the
25
+ * new fingerprint to save?"
26
+ */
27
+ export declare function isFresh(cwd: string, currentFingerprint: string): boolean;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Incremental scan cache. Walks the project's source tree, computes a
3
+ * fingerprint over file mtimes and sizes, and persists it under
4
+ * `.nwire/scan-fingerprint.json`. `isFresh()` returns true when the
5
+ * current tree matches the saved fingerprint — in that case CLI
6
+ * commands can read the existing `.nwire/manifest.json` without
7
+ * re-running the consumer's app to rebuild it.
8
+ *
9
+ * The fingerprint is intentionally cheap: a single sha1 over a sorted
10
+ * list of `<relative path> <mtimeMs> <size>` lines for every `.ts`,
11
+ * `.mts`, `.js`, `.mjs` file under the project root, excluding
12
+ * `node_modules/`, `dist/`, `.nwire/`, `.turbo/`, and dotfiles.
13
+ *
14
+ * The cost of a fresh fingerprint scan on a typical service-sized tree
15
+ * (hundreds of files) is a few milliseconds — fast enough to run on
16
+ * every CLI invocation that needs it.
17
+ */
18
+ import { createHash } from "node:crypto";
19
+ import { existsSync, readdirSync, statSync, writeFileSync, readFileSync, mkdirSync } from "node:fs";
20
+ import { dirname, relative, resolve } from "node:path";
21
+ const SCAN_EXTENSIONS = [".ts", ".mts", ".js", ".mjs"];
22
+ const SKIP_DIRS = new Set(["node_modules", "dist", ".nwire", ".turbo", ".git", ".vitepress"]);
23
+ function walk(root, out) {
24
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
25
+ if (entry.name.startsWith("."))
26
+ continue;
27
+ if (SKIP_DIRS.has(entry.name))
28
+ continue;
29
+ const full = resolve(root, entry.name);
30
+ if (entry.isDirectory()) {
31
+ walk(full, out);
32
+ continue;
33
+ }
34
+ if (!entry.isFile())
35
+ continue;
36
+ if (entry.name.endsWith(".d.ts"))
37
+ continue;
38
+ if (!SCAN_EXTENSIONS.some((ext) => entry.name.endsWith(ext)))
39
+ continue;
40
+ out.push(full);
41
+ }
42
+ }
43
+ export function fingerprint(cwd) {
44
+ const files = [];
45
+ walk(cwd, files);
46
+ files.sort();
47
+ const h = createHash("sha1");
48
+ for (const f of files) {
49
+ try {
50
+ const st = statSync(f);
51
+ h.update(`${relative(cwd, f)} ${st.mtimeMs} ${st.size}\n`);
52
+ }
53
+ catch {
54
+ // Race with concurrent rm; skip — the next fingerprint will catch it.
55
+ }
56
+ }
57
+ // Include package.json so dependency edits invalidate too.
58
+ const pkg = resolve(cwd, "package.json");
59
+ if (existsSync(pkg)) {
60
+ const st = statSync(pkg);
61
+ h.update(`package.json ${st.mtimeMs} ${st.size}\n`);
62
+ }
63
+ return h.digest("hex");
64
+ }
65
+ const FILE = "scan-fingerprint.json";
66
+ export function readFingerprint(cwd) {
67
+ const p = resolve(cwd, ".nwire", FILE);
68
+ if (!existsSync(p))
69
+ return undefined;
70
+ try {
71
+ const parsed = JSON.parse(readFileSync(p, "utf8"));
72
+ return parsed.version === 1 ? parsed.fingerprint : undefined;
73
+ }
74
+ catch {
75
+ return undefined;
76
+ }
77
+ }
78
+ export function writeFingerprint(cwd, value) {
79
+ const dir = resolve(cwd, ".nwire");
80
+ if (!existsSync(dir))
81
+ mkdirSync(dir, { recursive: true });
82
+ const payload = {
83
+ version: 1,
84
+ fingerprint: value,
85
+ generatedAt: new Date().toISOString(),
86
+ };
87
+ writeFileSync(resolve(dir, FILE), JSON.stringify(payload, null, 2));
88
+ }
89
+ /**
90
+ * True when `.nwire/manifest.json` exists AND the saved fingerprint
91
+ * matches the current tree. The caller passes the fingerprint it just
92
+ * computed so the same scan can serve both "is fresh?" and "what's the
93
+ * new fingerprint to save?"
94
+ */
95
+ export function isFresh(cwd, currentFingerprint) {
96
+ const manifest = resolve(cwd, ".nwire", "manifest.json");
97
+ if (!existsSync(manifest))
98
+ return false;
99
+ const saved = readFingerprint(cwd);
100
+ return saved === currentFingerprint;
101
+ }
102
+ void dirname; // silence "imported but unused" in some builds
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Run-an-operation-three-ways helper. Every CLI command that exists to
3
+ * orchestrate a check or build follows the same pattern:
4
+ *
5
+ * 1. If the consumer defined a `scripts.<name>` in their package.json,
6
+ * run that. Honors whatever the project already encodes.
7
+ * 2. Otherwise, if a list of sub-scripts is defined (e.g. `check`
8
+ * decomposes into `format:check`, `lint`, `typecheck`), run each
9
+ * that the consumer has defined.
10
+ * 3. Otherwise, fall back to spawning installed binaries — but only
11
+ * if the binary actually exists in `node_modules/.bin`. We never
12
+ * spawn a tool we haven't verified is present, so consumers that
13
+ * use a different formatter / linter / type checker don't see a
14
+ * surprise `ENOENT` from our hardcoded defaults.
15
+ *
16
+ * The result is that `nwire check` reads as "run the project's check,
17
+ * whatever that means in this project" rather than "run our chosen
18
+ * formatter on the project, hope they have it installed."
19
+ */
20
+ interface FallbackBinary {
21
+ /** Display title for the listr task. */
22
+ readonly title: string;
23
+ /** Binary name as installed under `node_modules/.bin/`. */
24
+ readonly bin: string;
25
+ /** Extra args appended after the binary name. */
26
+ readonly args?: readonly string[];
27
+ }
28
+ export interface ScriptStrategy {
29
+ /** Single script name to try first (e.g. "check"). */
30
+ readonly consumerScript?: string;
31
+ /** Sub-scripts to try in sequence if `consumerScript` is not defined. */
32
+ readonly consumerSubScripts?: readonly string[];
33
+ /** Binaries to try if no consumer scripts apply. */
34
+ readonly fallback?: readonly FallbackBinary[];
35
+ /** Title for the top-level operation; passed to error messages. */
36
+ readonly operationName: string;
37
+ }
38
+ export declare function runScriptStrategy(cwd: string, strategy: ScriptStrategy): Promise<{
39
+ code: number;
40
+ ran: boolean;
41
+ }>;
42
+ export {};
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Run-an-operation-three-ways helper. Every CLI command that exists to
3
+ * orchestrate a check or build follows the same pattern:
4
+ *
5
+ * 1. If the consumer defined a `scripts.<name>` in their package.json,
6
+ * run that. Honors whatever the project already encodes.
7
+ * 2. Otherwise, if a list of sub-scripts is defined (e.g. `check`
8
+ * decomposes into `format:check`, `lint`, `typecheck`), run each
9
+ * that the consumer has defined.
10
+ * 3. Otherwise, fall back to spawning installed binaries — but only
11
+ * if the binary actually exists in `node_modules/.bin`. We never
12
+ * spawn a tool we haven't verified is present, so consumers that
13
+ * use a different formatter / linter / type checker don't see a
14
+ * surprise `ENOENT` from our hardcoded defaults.
15
+ *
16
+ * The result is that `nwire check` reads as "run the project's check,
17
+ * whatever that means in this project" rather than "run our chosen
18
+ * formatter on the project, hope they have it installed."
19
+ */
20
+ import { existsSync, readFileSync } from "node:fs";
21
+ import { resolve } from "node:path";
22
+ import { detectPackageManager, execBinaryCommand, runScriptCommand, } from "./package-manager.js";
23
+ import { runTaskList } from "./run-task.js";
24
+ function readScripts(cwd) {
25
+ const p = resolve(cwd, "package.json");
26
+ if (!existsSync(p))
27
+ return {};
28
+ try {
29
+ const pkg = JSON.parse(readFileSync(p, "utf8"));
30
+ return pkg.scripts ?? {};
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ function binaryInstalled(cwd, bin) {
37
+ return (existsSync(resolve(cwd, "node_modules", ".bin", bin)) ||
38
+ existsSync(resolve(cwd, "node_modules", ".bin", `${bin}.cmd`)));
39
+ }
40
+ function buildTasksForStrategy(cwd, pm, strategy) {
41
+ const scripts = readScripts(cwd);
42
+ if (strategy.consumerScript && scripts[strategy.consumerScript]) {
43
+ const [command, args] = runScriptCommand(pm, strategy.consumerScript);
44
+ return {
45
+ tasks: [
46
+ {
47
+ title: `${pm} run ${strategy.consumerScript}`,
48
+ command,
49
+ args,
50
+ },
51
+ ],
52
+ source: "consumer-script",
53
+ };
54
+ }
55
+ if (strategy.consumerSubScripts && strategy.consumerSubScripts.length > 0) {
56
+ const available = strategy.consumerSubScripts.filter((s) => scripts[s] !== undefined);
57
+ if (available.length > 0) {
58
+ return {
59
+ tasks: available.map((script) => {
60
+ const [command, args] = runScriptCommand(pm, script);
61
+ return { title: `${pm} run ${script}`, command, args };
62
+ }),
63
+ source: "consumer-subscripts",
64
+ };
65
+ }
66
+ }
67
+ if (strategy.fallback && strategy.fallback.length > 0) {
68
+ const available = strategy.fallback.filter((f) => binaryInstalled(cwd, f.bin));
69
+ if (available.length > 0) {
70
+ return {
71
+ tasks: available.map((f) => {
72
+ const [command, args] = execBinaryCommand(pm, f.bin, f.args);
73
+ return { title: f.title, command, args };
74
+ }),
75
+ source: "fallback",
76
+ };
77
+ }
78
+ }
79
+ return { tasks: [], source: "none" };
80
+ }
81
+ export async function runScriptStrategy(cwd, strategy) {
82
+ const pm = detectPackageManager(cwd);
83
+ const { tasks, source } = buildTasksForStrategy(cwd, pm, strategy);
84
+ if (source === "none") {
85
+ return { code: 0, ran: false };
86
+ }
87
+ void source;
88
+ const code = await runTaskList(tasks);
89
+ return { code, ran: true };
90
+ }
package/dist/lib/sse.d.ts CHANGED
@@ -21,4 +21,3 @@ export declare class SseParser {
21
21
  */
22
22
  feed(chunk: string): unknown[];
23
23
  }
24
- //# sourceMappingURL=sse.d.ts.map
package/dist/lib/sse.js CHANGED
@@ -60,4 +60,3 @@ function parseFrame(frame) {
60
60
  return undefined;
61
61
  }
62
62
  }
63
- //# sourceMappingURL=sse.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Single-source the CLI version from its own package.json. Read once at
3
+ * module import; the result is stable across the process lifetime.
4
+ *
5
+ * Looks for `package.json` next to the compiled `dist/lib/version.js` or
6
+ * next to the source `src/lib/version.ts` (the dev runner path).
7
+ */
8
+ export declare function cliVersion(): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Single-source the CLI version from its own package.json. Read once at
3
+ * module import; the result is stable across the process lifetime.
4
+ *
5
+ * Looks for `package.json` next to the compiled `dist/lib/version.js` or
6
+ * next to the source `src/lib/version.ts` (the dev runner path).
7
+ */
8
+ import { readFileSync, existsSync } from "node:fs";
9
+ import { dirname, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ function locate() {
13
+ for (const candidate of [
14
+ resolve(here, "..", "..", "package.json"),
15
+ resolve(here, "..", "..", "..", "package.json"),
16
+ ]) {
17
+ if (existsSync(candidate))
18
+ return candidate;
19
+ }
20
+ throw new Error("nwire-cli: package.json not found relative to version.ts");
21
+ }
22
+ let cached;
23
+ export function cliVersion() {
24
+ if (cached)
25
+ return cached;
26
+ const pkg = JSON.parse(readFileSync(locate(), "utf8"));
27
+ cached = pkg.version ?? "0.0.0";
28
+ return cached;
29
+ }
@@ -7,4 +7,3 @@ export declare function viteNodeWatch(scriptPath: string): {
7
7
  child: import("child_process").ChildProcess;
8
8
  done: Promise<number>;
9
9
  };
10
- //# sourceMappingURL=vite-node.d.ts.map
@@ -11,4 +11,3 @@ export function viteNodeWatch(scriptPath) {
11
11
  stoppedMessage: "Dev server stopped.",
12
12
  });
13
13
  }
14
- //# sourceMappingURL=vite-node.js.map
@@ -6,4 +6,3 @@
6
6
  * whose `/_nwire/events/recent?limit=1` responds with a JSON array.
7
7
  */
8
8
  export declare function findWireUrl(): Promise<string | null>;
9
- //# sourceMappingURL=wire-discovery.d.ts.map
@@ -78,4 +78,3 @@ function isNwireWire(port, timeoutMs) {
78
78
  });
79
79
  });
80
80
  }
81
- //# sourceMappingURL=wire-discovery.js.map
@@ -24,4 +24,3 @@ export interface NwireConfig {
24
24
  export declare function defineConfig(cfg: NwireConfig): NwireConfig;
25
25
  export declare function loadConfig(cwd: string): Promise<NwireConfig>;
26
26
  export declare function resolveAppsEntry(cwd: string, config: NwireConfig): string;
27
- //# sourceMappingURL=load-config.d.ts.map
@@ -47,4 +47,3 @@ export function resolveAppsEntry(cwd, config) {
47
47
  }
48
48
  return resolve(cwd, "apps/apps.ts"); // canonical default; will error if missing
49
49
  }
50
- //# sourceMappingURL=load-config.js.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=ls-runner.d.ts.map
package/dist/ls-runner.js CHANGED
@@ -1,55 +1,49 @@
1
1
  // `nwire ls` — list every wire + every action across all apps.
2
+ import { existsSync, readFileSync } from "node:fs";
2
3
  import { relative, resolve } from "node:path";
3
- import { readdirSync, statSync, existsSync } from "node:fs";
4
- function listWires(appsRoot) {
5
- if (!existsSync(appsRoot))
6
- return [];
7
- const wires = [];
8
- for (const app of readdirSync(appsRoot)) {
9
- const appDir = resolve(appsRoot, app);
10
- if (!statSync(appDir).isDirectory())
11
- continue;
12
- for (const file of readdirSync(appDir)) {
13
- if (!file.startsWith("run") || !file.endsWith(".ts"))
14
- continue;
15
- if (file.endsWith(".d.ts"))
16
- continue;
17
- const stem = file.slice(0, -3);
18
- const variant = stem === "run" ? "" : `.${stem.slice(4)}`;
19
- wires.push({ wire: `${app}${variant}`, path: resolve(appDir, file) });
20
- }
4
+ import { detectLayout } from "./lib/layout.js";
5
+ function loadManifest(cwd) {
6
+ const p = resolve(cwd, ".nwire", "manifest.json");
7
+ if (!existsSync(p))
8
+ return undefined;
9
+ try {
10
+ return JSON.parse(readFileSync(p, "utf8"));
11
+ }
12
+ catch {
13
+ return undefined;
21
14
  }
22
- return wires;
23
15
  }
24
16
  async function main() {
25
17
  const cwd = process.cwd();
26
- const appsRoot = resolve(cwd, "apps");
27
- const wires = listWires(appsRoot);
18
+ const layout = detectLayout(cwd);
28
19
  console.log("Wires:");
29
- for (const { wire, path } of wires) {
30
- const rel = relative(cwd, path);
31
- console.log(` ${wire.padEnd(28)} ${rel}`);
20
+ if (layout.wires.length === 0) {
21
+ console.log(" (none add an entry under app/, apps/<name>/, or src/)");
32
22
  }
33
- console.log();
34
- // Try to list actions from the apps registry.
35
- const appsPath = resolve(cwd, "apps/apps.ts");
36
- if (existsSync(appsPath)) {
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- const mod = await import(/* @vite-ignore */ appsPath);
39
- const apps = mod.allApps ?? mod.apps ?? [];
40
- console.log("Actions:");
41
- for (const app of apps) {
42
- for (const module of app.modules) {
43
- for (const action of module.manifest.actions ?? []) {
44
- const label = `${action.name}`.padEnd(40);
45
- console.log(` ${label} ${app.name}/${module.name}`);
46
- }
47
- }
23
+ else {
24
+ for (const w of layout.wires) {
25
+ console.log(` ${w.name.padEnd(28)} ${relative(cwd, w.entry)}`);
48
26
  }
49
27
  }
28
+ console.log();
29
+ const manifest = loadManifest(cwd);
30
+ if (!manifest) {
31
+ console.log("Actions: (run `nwire cache` to populate)");
32
+ return;
33
+ }
34
+ const actions = manifest.actions ?? [];
35
+ if (actions.length === 0) {
36
+ console.log("Actions: (none registered)");
37
+ return;
38
+ }
39
+ console.log("Actions:");
40
+ for (const a of actions) {
41
+ const label = a.name.padEnd(40);
42
+ const owner = a.app && a.module ? `${a.app}/${a.module}` : "";
43
+ console.log(` ${label} ${owner}`);
44
+ }
50
45
  }
51
46
  main().catch((err) => {
52
47
  console.error(err);
53
48
  process.exit(1);
54
49
  });
55
- //# sourceMappingURL=ls-runner.js.map
@@ -20,4 +20,3 @@ interface DevDashboardProps {
20
20
  }
21
21
  export declare function DevDashboard({ title, subtitle, subscribe, maxLines, }: DevDashboardProps): React.ReactElement;
22
22
  export {};
23
- //# sourceMappingURL=dev-dashboard.d.ts.map
@@ -50,4 +50,3 @@ export function DevDashboard({ title, subtitle, subscribe, maxLines = 18, }) {
50
50
  const statusLabel = ready.ready ? "running" : "booting";
51
51
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: title }), subtitle ? _jsx(Text, { dimColor: true, children: ` · ${subtitle}` }) : null] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Box, { width: 10, children: _jsx(Text, { dimColor: true, children: "status" }) }), _jsx(Text, { color: statusColor, children: statusLabel }), ready.url ? _jsx(Text, { dimColor: true, children: ` · ${ready.url}` }) : null, ready.elapsed ? _jsx(Text, { dimColor: true, children: ` · ${ready.elapsed}` }) : null] }), _jsxs(Box, { children: [_jsx(Box, { width: 10, children: _jsx(Text, { dimColor: true, children: "output" }) }), _jsx(Text, { dimColor: true, children: `${counter} line${counter === 1 ? "" : "s"}` })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Ctrl+C to stop." }) })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, marginLeft: 1, children: _jsx(Static, { items: lines, children: (chunk) => (_jsx(Box, { children: _jsx(Text, { color: chunk.stream === "stderr" ? "red" : undefined, children: chunk.text }) }, chunk.id)) }) })] }));
52
52
  }
53
- //# sourceMappingURL=dev-dashboard.js.map
@@ -13,4 +13,3 @@ interface GreetingProps {
13
13
  }
14
14
  export declare function Greeting({ project, version }: GreetingProps): React.ReactElement;
15
15
  export {};
16
- //# sourceMappingURL=greeting.d.ts.map
@@ -13,4 +13,3 @@ export function Greeting({ project, version }) {
13
13
  ? project.wires.join(", ")
14
14
  : "(none detected — expected apps/<name>/{run,main}.ts)" })] }), _jsxs(Box, { children: [_jsx(Box, { width: 10, children: _jsx(Text, { dimColor: true, children: "please" }) }), _jsx(Text, { color: project.hasPlease ? "green" : undefined, children: project.hasPlease ? "ready" : "—" })] }), _jsxs(Box, { children: [_jsx(Box, { width: 10, children: _jsx(Text, { dimColor: true, children: "config" }) }), _jsx(Text, { color: project.hasNwireConfig ? "green" : undefined, children: project.hasNwireConfig ? "nwire.config.ts" : "(default)" })] })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 1, children: [_jsx(Text, { dimColor: true, children: "Try:" }), suggestions.map((s) => (_jsxs(Box, { children: [_jsx(Text, { color: "magenta", children: " › " }), _jsx(Text, { children: s })] }, s)))] })] }));
15
15
  }
16
- //# sourceMappingURL=greeting.js.map