@nwire/cli 0.12.1 → 0.13.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/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/cache.js +1 -1
- package/dist/commands/dev.d.ts +20 -7
- package/dist/commands/dev.js +142 -45
- package/dist/commands/ls.js +5 -3
- package/dist/commands/please.js +4 -2
- package/dist/commands/run.js +6 -2
- package/dist/commands/studio.js +74 -7
- package/dist/lib/dev-entry.d.ts +8 -9
- package/dist/lib/dev-entry.js +24 -25
- package/dist/lib/dev-host.d.ts +88 -0
- package/dist/lib/dev-host.js +426 -0
- package/dist/lib/ensure-built.d.ts +32 -0
- package/dist/lib/ensure-built.js +62 -0
- package/dist/lib/ensure-scan.d.ts +9 -4
- package/dist/lib/ensure-scan.js +46 -19
- package/dist/lib/studio-host-api.d.ts +97 -0
- package/dist/lib/studio-host-api.js +336 -0
- package/dist/lib/studio-probe.d.ts +63 -0
- package/dist/lib/studio-probe.js +132 -0
- package/dist/lib/vite-node.d.ts +7 -6
- package/dist/lib/vite-node.js +8 -8
- package/dist/lib/vite-run.d.ts +15 -0
- package/dist/lib/vite-run.js +33 -0
- package/dist/load-config.d.ts +10 -10
- package/dist/load-config.js +3 -3
- package/dist/studio/assets/abap-DVwoIrM0.js +1 -0
- package/dist/studio/assets/apex-B9XtvxSu.js +1 -0
- package/dist/studio/assets/azcli-D7JTNGKs.js +1 -0
- package/dist/studio/assets/bat-BNHAuPwR.js +1 -0
- package/dist/studio/assets/bicep-C3w6oSfK.js +2 -0
- package/dist/studio/assets/cameligo-DM9kSiq7.js +1 -0
- package/dist/studio/assets/clojure-FWLBUPxU.js +1 -0
- package/dist/studio/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/studio/assets/coffee-DCoMPIwW.js +1 -0
- package/dist/studio/assets/cpp-BNbIvdcw.js +1 -0
- package/dist/studio/assets/csharp-Dj4ULDZr.js +1 -0
- package/dist/studio/assets/csp-C-n5jZKF.js +1 -0
- package/dist/studio/assets/css-COIa8ZTR.js +3 -0
- package/dist/studio/assets/css.worker-CpJJqcA4.js +89 -0
- package/dist/studio/assets/cssMode-CFR5_xwk.js +1 -0
- package/dist/studio/assets/cypher-CW08XVUh.js +1 -0
- package/dist/studio/assets/dart-Bz550Pyv.js +1 -0
- package/dist/studio/assets/dockerfile-DW5REF8E.js +1 -0
- package/dist/studio/assets/ecl-BqdYhwmw.js +1 -0
- package/dist/studio/assets/editor-Br_kD0ds.css +1 -0
- package/dist/studio/assets/editor.api2-CTGEM8gT.js +872 -0
- package/dist/studio/assets/editor.main-sW1qgHFj.js +6 -0
- package/dist/studio/assets/elixir-Oi_9aIAu.js +1 -0
- package/dist/studio/assets/flow9-CIb9youF.js +1 -0
- package/dist/studio/assets/freemarker2-CPrni8hw.js +3 -0
- package/dist/studio/assets/fsharp-64tUaD-0.js +1 -0
- package/dist/studio/assets/go-DLKGL0rd.js +1 -0
- package/dist/studio/assets/graphql-Bz88xn3Q.js +1 -0
- package/dist/studio/assets/handlebars-DkvSNpQB.js +1 -0
- package/dist/studio/assets/hcl-Cq76tSVN.js +1 -0
- package/dist/studio/assets/html-ceN7ITxG.js +1 -0
- package/dist/studio/assets/html.worker-wsVgX3gp.js +502 -0
- package/dist/studio/assets/htmlMode-wduanCXn.js +1 -0
- package/dist/studio/assets/index-4tH0-1cA.js +41 -0
- package/dist/studio/assets/index-Fy3xDmV2.css +1 -0
- package/dist/studio/assets/ini-BTNe9zdh.js +1 -0
- package/dist/studio/assets/java-DzRJKRF3.js +1 -0
- package/dist/studio/assets/javascript-WF3LGLje.js +1 -0
- package/dist/studio/assets/json.worker-CcNiYOcv.js +58 -0
- package/dist/studio/assets/jsonMode-DSujY8tB.js +7 -0
- package/dist/studio/assets/julia-Bgv08lKa.js +1 -0
- package/dist/studio/assets/kotlin-Dzz8TWAt.js +1 -0
- package/dist/studio/assets/less-ak6GUtsl.js +2 -0
- package/dist/studio/assets/lexon-zuaObGAE.js +1 -0
- package/dist/studio/assets/liquid-C5Z7zFr3.js +1 -0
- package/dist/studio/assets/lspLanguageFeatures-B55yfFgf.js +4 -0
- package/dist/studio/assets/lua-ClKCZMmP.js +1 -0
- package/dist/studio/assets/m3-C7XHeDz_.js +1 -0
- package/dist/studio/assets/markdown-LT3qFBoR.js +1 -0
- package/dist/studio/assets/mdx-Bu5jRl19.js +1 -0
- package/dist/studio/assets/mips-B8clQ9KB.js +1 -0
- package/dist/studio/assets/monaco.contribution-KjQ4yOxj.js +2 -0
- package/dist/studio/assets/msdax-DBxc5qPJ.js +1 -0
- package/dist/studio/assets/mysql-qocW_xba.js +1 -0
- package/dist/studio/assets/objective-c-DhkpBlGX.js +1 -0
- package/dist/studio/assets/pascal-C_PJR40u.js +1 -0
- package/dist/studio/assets/pascaligo-BI_Gz9Bp.js +1 -0
- package/dist/studio/assets/perl-CIqGOHTo.js +1 -0
- package/dist/studio/assets/pgsql-DI_z9qfW.js +1 -0
- package/dist/studio/assets/php-Dkwn_yn0.js +1 -0
- package/dist/studio/assets/pla-DvzjACL6.js +1 -0
- package/dist/studio/assets/postiats-Cc9-hkUx.js +1 -0
- package/dist/studio/assets/powerquery-IGzsITFg.js +1 -0
- package/dist/studio/assets/powershell-BHlZlUN6.js +1 -0
- package/dist/studio/assets/protobuf-pGrmMUz5.js +2 -0
- package/dist/studio/assets/pug-B4eH693Y.js +1 -0
- package/dist/studio/assets/python-DpEFuk0I.js +1 -0
- package/dist/studio/assets/qsharp-CwO3kTIU.js +1 -0
- package/dist/studio/assets/r-CiZUpdIa.js +1 -0
- package/dist/studio/assets/razor-BF1svRn9.js +1 -0
- package/dist/studio/assets/redis-DjdIzLdf.js +1 -0
- package/dist/studio/assets/redshift-vCL5QMyw.js +1 -0
- package/dist/studio/assets/restructuredtext-D5hoMHB1.js +1 -0
- package/dist/studio/assets/ruby-ByPQrqP4.js +1 -0
- package/dist/studio/assets/rust-Nz5wukP7.js +1 -0
- package/dist/studio/assets/sb-DgR1RbMJ.js +1 -0
- package/dist/studio/assets/scala-BCgNuXrV.js +1 -0
- package/dist/studio/assets/scheme-TgKpKGpb.js +1 -0
- package/dist/studio/assets/scss-BKxAkvnT.js +3 -0
- package/dist/studio/assets/shell-COgstXIQ.js +1 -0
- package/dist/studio/assets/solidity-DaqmtBSV.js +1 -0
- package/dist/studio/assets/sophia-Da67i9pL.js +1 -0
- package/dist/studio/assets/sparql-CtN8jEDV.js +1 -0
- package/dist/studio/assets/sql-BnJfQHXt.js +1 -0
- package/dist/studio/assets/st-cGKU4FoP.js +1 -0
- package/dist/studio/assets/swift-DyyME8zA.js +1 -0
- package/dist/studio/assets/systemverilog-BYZY5TEG.js +1 -0
- package/dist/studio/assets/tcl-CHv1_zaR.js +1 -0
- package/dist/studio/assets/ts.worker-DfMAw22J.js +67719 -0
- package/dist/studio/assets/tsMode-BiiF1JOM.js +11 -0
- package/dist/studio/assets/twig-CNwULq4h.js +1 -0
- package/dist/studio/assets/typescript-BefpzegH.js +1 -0
- package/dist/studio/assets/typespec-B3KUNs_P.js +1 -0
- package/dist/studio/assets/vb-phZZ2pCs.js +1 -0
- package/dist/studio/assets/wgsl-Df-y4I4e.js +298 -0
- package/dist/studio/assets/workers-DqAl3RFu.js +1 -0
- package/dist/studio/assets/xml-CWw7bbeo.js +1 -0
- package/dist/studio/assets/yaml-CZ0k9DUm.js +1 -0
- package/dist/studio/index.html +13 -0
- package/dist/wire-runner.d.ts +12 -0
- package/dist/wire-runner.js +19 -0
- package/package.json +8 -6
- package/dist/cache-runner.d.ts +0 -1
- package/dist/cache-runner.js +0 -206
package/dist/cli.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `nwire` — the umbrella CLI. Built on citty: one root command, one
|
|
4
4
|
* subcommand per file under `commands/`. The CLI is intentionally thin —
|
|
5
5
|
* every command delegates to a small set of execution helpers
|
|
6
|
-
* (`
|
|
6
|
+
* (`viteRun`, `spawnInteractive`) and, where needed, an in-tree
|
|
7
7
|
* RunnerSupervisor for long-lived processes.
|
|
8
8
|
*
|
|
9
9
|
* Adding a new command:
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `nwire` — the umbrella CLI. Built on citty: one root command, one
|
|
4
4
|
* subcommand per file under `commands/`. The CLI is intentionally thin —
|
|
5
5
|
* every command delegates to a small set of execution helpers
|
|
6
|
-
* (`
|
|
6
|
+
* (`viteRun`, `spawnInteractive`) and, where needed, an in-tree
|
|
7
7
|
* RunnerSupervisor for long-lived processes.
|
|
8
8
|
*
|
|
9
9
|
* Adding a new command:
|
package/dist/commands/cache.js
CHANGED
|
@@ -30,7 +30,7 @@ export const cacheCommand = defineCommand({
|
|
|
30
30
|
},
|
|
31
31
|
async run({ args }) {
|
|
32
32
|
const force = !args["if-stale"];
|
|
33
|
-
const result = ensureScanFresh(process.cwd(), { force, quiet: args.quiet });
|
|
33
|
+
const result = await ensureScanFresh(process.cwd(), { force, quiet: args.quiet });
|
|
34
34
|
// exit 0 when the cache is current — whether we just rebuilt it or
|
|
35
35
|
// the cheap-path skipped. exit 1 only when we tried to rebuild and
|
|
36
36
|
// the builder failed.
|
package/dist/commands/dev.d.ts
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `nwire dev` — boots the project's wire
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* stdout/stderr flow through unmodified afterwards.
|
|
2
|
+
* `nwire dev` — boots the project's wire + Studio on one port via the
|
|
3
|
+
* in-process Vite dev host. Prints a branded banner, then hands off to
|
|
4
|
+
* `startDevHost` which serves both surfaces without a child process.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* For projects that declare a `scripts.dev` fallback instead of a recognized
|
|
7
|
+
* wire entry, the old child-spawn path is preserved so nothing breaks.
|
|
8
|
+
*
|
|
9
|
+
* Also writes a state file under `.nwire/processes/` so `nwire ps` sees
|
|
10
|
+
* the dev session from any other shell.
|
|
9
11
|
*/
|
|
10
|
-
export declare const devCommand: import("citty").CommandDef<
|
|
12
|
+
export declare const devCommand: import("citty").CommandDef<{
|
|
13
|
+
endpoint: {
|
|
14
|
+
type: "string";
|
|
15
|
+
description: string;
|
|
16
|
+
required: false;
|
|
17
|
+
};
|
|
18
|
+
port: {
|
|
19
|
+
type: "string";
|
|
20
|
+
description: string;
|
|
21
|
+
required: false;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,36 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `nwire dev` — boots the project's wire
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* stdout/stderr flow through unmodified afterwards.
|
|
2
|
+
* `nwire dev` — boots the project's wire + Studio on one port via the
|
|
3
|
+
* in-process Vite dev host. Prints a branded banner, then hands off to
|
|
4
|
+
* `startDevHost` which serves both surfaces without a child process.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* For projects that declare a `scripts.dev` fallback instead of a recognized
|
|
7
|
+
* wire entry, the old child-spawn path is preserved so nothing breaks.
|
|
8
|
+
*
|
|
9
|
+
* Also writes a state file under `.nwire/processes/` so `nwire ps` sees
|
|
10
|
+
* the dev session from any other shell.
|
|
9
11
|
*/
|
|
10
12
|
import { defineCommand } from "citty";
|
|
11
|
-
import { appendFileSync, closeSync, openSync } from "node:fs";
|
|
12
|
-
import { spawn } from "node:child_process";
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
import { palette } from "../lib/colors.js";
|
|
15
|
-
/** Windows toggle — `shell:true` so `pnpm.cmd` resolves on spawn. */
|
|
16
|
-
const IS_WIN = process.platform === "win32";
|
|
17
15
|
import { detectProject } from "../lib/project.js";
|
|
16
|
+
import { ensureWorkspaceBuilt } from "../lib/ensure-built.js";
|
|
18
17
|
import { resolveDevEntry, devEntryCandidates } from "../lib/dev-entry.js";
|
|
18
|
+
import { startDevHost } from "../lib/dev-host.js";
|
|
19
19
|
import { ensureDir as ensureProcDir, logPath as procLogPath, removeRecord, writeRecord, } from "../lib/process-state.js";
|
|
20
|
+
// ── npm-script fallback (child spawn) ───────────────────────────────────────
|
|
21
|
+
// Only used when resolved.kind === "npm-script". Kept self-contained so the
|
|
22
|
+
// host path above has no child-process machinery at all.
|
|
23
|
+
import { appendFileSync, closeSync, openSync } from "node:fs";
|
|
24
|
+
import { spawn } from "node:child_process";
|
|
25
|
+
/** Windows toggle — `shell:true` so `pnpm.cmd` resolves on spawn. */
|
|
26
|
+
const IS_WIN = process.platform === "win32";
|
|
27
|
+
const DEFAULT_PORT = 4000;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the host port: explicit `--port` wins, then `$PORT`, then the
|
|
30
|
+
* default. A non-numeric or out-of-range value is ignored (falls through to
|
|
31
|
+
* the next source) so a typo can't silently bind an ephemeral port.
|
|
32
|
+
*/
|
|
33
|
+
function resolvePort(flag) {
|
|
34
|
+
for (const candidate of [flag, process.env.PORT]) {
|
|
35
|
+
if (candidate === undefined || candidate === "")
|
|
36
|
+
continue;
|
|
37
|
+
const n = Number(candidate);
|
|
38
|
+
if (Number.isInteger(n) && n >= 0 && n <= 65535)
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
return DEFAULT_PORT;
|
|
42
|
+
}
|
|
20
43
|
export const devCommand = defineCommand({
|
|
21
44
|
meta: {
|
|
22
45
|
name: "dev",
|
|
23
|
-
description: "Boot the
|
|
46
|
+
description: "Boot the project wire with watch mode",
|
|
24
47
|
},
|
|
25
|
-
|
|
48
|
+
args: {
|
|
49
|
+
endpoint: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Name of the endpoint to front on the HTTP surface (multi-endpoint projects).",
|
|
52
|
+
required: false,
|
|
53
|
+
},
|
|
54
|
+
port: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: `Port for the wire + Studio host. Falls back to $PORT, then ${DEFAULT_PORT}.`,
|
|
57
|
+
required: false,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
async run({ args }) {
|
|
26
61
|
const cwd = process.cwd();
|
|
62
|
+
// Monorepo only: keep workspace `dist/` fresh so a stale `@nwire/*`
|
|
63
|
+
// doesn't crash the example wire. No-op in consumer apps.
|
|
64
|
+
ensureWorkspaceBuilt(cwd);
|
|
27
65
|
const project = detectProject(cwd);
|
|
28
|
-
// Resolution covers every recognized project shape:
|
|
29
|
-
// 1. apps/dev-all/run.ts (multi-wire)
|
|
30
|
-
// 2. apps/<single>/{run,main}.ts
|
|
31
|
-
// 3. app/{main,run,index}.ts (singular small-example shape)
|
|
32
|
-
// 4. package.json scripts.dev (last resort — honors whatever the
|
|
33
|
-
// consumer scripts already encode, e.g. vite-node app/main.ts)
|
|
34
66
|
const resolved = resolveDevEntry(cwd);
|
|
35
67
|
if (!resolved) {
|
|
36
68
|
// eslint-disable-next-line no-console
|
|
@@ -39,12 +71,95 @@ export const devCommand = defineCommand({
|
|
|
39
71
|
` looked for: ${devEntryCandidates().join(", ")}`);
|
|
40
72
|
process.exit(1);
|
|
41
73
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
74
|
+
if (resolved.kind === "wire") {
|
|
75
|
+
// ── In-process one-Vite host ─────────────────────────────────────
|
|
76
|
+
// Port precedence: --port flag → $PORT env → default. Invalid values
|
|
77
|
+
// fall through to the next source so a typo never silently binds 0.
|
|
78
|
+
const port = resolvePort(args.port);
|
|
79
|
+
const id = randomUUID();
|
|
80
|
+
const startedAt = new Date().toISOString();
|
|
81
|
+
// eslint-disable-next-line no-console
|
|
82
|
+
console.log([
|
|
83
|
+
"",
|
|
84
|
+
` ${palette.brand(palette.bold("Nwire dev"))} ${palette.dim(project.name)}`,
|
|
85
|
+
` ${palette.dim("entry ")} ${resolved.rel}`,
|
|
86
|
+
` ${palette.dim("wire ")} ${resolved.label}`,
|
|
87
|
+
` ${palette.dim("host ")} http://127.0.0.1:${port}`,
|
|
88
|
+
` ${palette.dim("mode ")} ${palette.accent("dev-host")}`,
|
|
89
|
+
` ${palette.dim("Ctrl+C ")} stop`,
|
|
90
|
+
"",
|
|
91
|
+
].join("\n"));
|
|
92
|
+
ensureProcDir(cwd);
|
|
93
|
+
let currentStatus = "starting";
|
|
94
|
+
const persistRecord = (p) => {
|
|
95
|
+
const record = {
|
|
96
|
+
id,
|
|
97
|
+
name: `dev:${resolved.label}`,
|
|
98
|
+
pid: process.pid,
|
|
99
|
+
status: currentStatus,
|
|
100
|
+
port: p ?? port,
|
|
101
|
+
startedAt,
|
|
102
|
+
cwd,
|
|
103
|
+
command: `nwire dev-host ${resolved.rel}`,
|
|
104
|
+
logPath: procLogPath(cwd, id),
|
|
105
|
+
lastUpdated: new Date().toISOString(),
|
|
106
|
+
};
|
|
107
|
+
writeRecord(cwd, record);
|
|
108
|
+
};
|
|
109
|
+
persistRecord();
|
|
110
|
+
let host;
|
|
111
|
+
let stopping = false;
|
|
112
|
+
const stop = async () => {
|
|
113
|
+
if (stopping)
|
|
114
|
+
return;
|
|
115
|
+
stopping = true;
|
|
116
|
+
currentStatus = "stopping";
|
|
117
|
+
persistRecord();
|
|
118
|
+
if (host) {
|
|
119
|
+
try {
|
|
120
|
+
await host.close();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// best-effort
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
removeRecord(cwd, id);
|
|
127
|
+
// eslint-disable-next-line no-console
|
|
128
|
+
console.log(palette.dim("\nDev stopped."));
|
|
129
|
+
process.exit(0);
|
|
130
|
+
};
|
|
131
|
+
process.on("SIGINT", () => void stop());
|
|
132
|
+
process.on("SIGTERM", () => void stop());
|
|
133
|
+
try {
|
|
134
|
+
host = await startDevHost({
|
|
135
|
+
cwd,
|
|
136
|
+
entry: resolved.entry,
|
|
137
|
+
port,
|
|
138
|
+
activeEndpoint: args.endpoint || undefined,
|
|
139
|
+
});
|
|
140
|
+
currentStatus = "running";
|
|
141
|
+
persistRecord(port);
|
|
142
|
+
// eslint-disable-next-line no-console
|
|
143
|
+
console.log(` ${palette.ok("ready")} wire + Studio on http://127.0.0.1:${port}\n`);
|
|
144
|
+
// Park the process — the http.Server keeps the event loop alive.
|
|
145
|
+
await new Promise(() => { });
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.error(palette.err("nwire dev: host failed to start —"), err);
|
|
150
|
+
currentStatus = "crashed";
|
|
151
|
+
persistRecord();
|
|
152
|
+
removeRecord(cwd, id);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// ── npm-script fallback (child spawn) ────────────────────────────────
|
|
158
|
+
// `resolved.kind === "npm-script"`: the project has no recognized wire
|
|
159
|
+
// entry but declares a `scripts.dev`. Honor it via spawn so `nwire dev`
|
|
160
|
+
// still works on those projects without any behavior change.
|
|
161
|
+
const subtitle = `pnpm run ${resolved.script}`;
|
|
162
|
+
const entryRel = `package.json scripts.${resolved.script}`;
|
|
48
163
|
// eslint-disable-next-line no-console
|
|
49
164
|
console.log([
|
|
50
165
|
"",
|
|
@@ -55,22 +170,11 @@ export const devCommand = defineCommand({
|
|
|
55
170
|
` ${palette.dim("Ctrl+C ")} stop`,
|
|
56
171
|
"",
|
|
57
172
|
].join("\n"));
|
|
58
|
-
|
|
59
|
-
// Inherit means logs go straight to the user's terminal — no ink
|
|
60
|
-
// capture, no log buffering. The trade-off: `nwire logs <id>`
|
|
61
|
-
// can't tail the live stream (only what we tee to disk). For dev
|
|
62
|
-
// sessions that's fine — the user is watching the terminal.
|
|
63
|
-
const spawnCmd = resolved.kind === "vite-node"
|
|
64
|
-
? ["pnpm", ["exec", "vite-node", "--watch", resolved.entry]]
|
|
65
|
-
: ["pnpm", ["run", resolved.script]];
|
|
66
|
-
const child = spawn(spawnCmd[0], spawnCmd[1], {
|
|
173
|
+
const child = spawn("pnpm", ["run", resolved.script], {
|
|
67
174
|
stdio: ["inherit", "pipe", "pipe"],
|
|
68
175
|
cwd,
|
|
69
176
|
shell: IS_WIN,
|
|
70
177
|
});
|
|
71
|
-
// Tee child stdio through to BOTH the user's terminal AND the
|
|
72
|
-
// per-process log file so `nwire logs <id>` works from another
|
|
73
|
-
// shell. Direct passthrough to stdout to keep ordering intact.
|
|
74
178
|
const id = randomUUID();
|
|
75
179
|
const startedAt = new Date().toISOString();
|
|
76
180
|
ensureProcDir(cwd);
|
|
@@ -81,15 +185,13 @@ export const devCommand = defineCommand({
|
|
|
81
185
|
const persistRecord = () => {
|
|
82
186
|
const record = {
|
|
83
187
|
id,
|
|
84
|
-
name: `dev:${
|
|
188
|
+
name: `dev:${resolved.script}`,
|
|
85
189
|
pid: child.pid ?? -1,
|
|
86
190
|
status: currentStatus,
|
|
87
191
|
port,
|
|
88
192
|
startedAt,
|
|
89
193
|
cwd,
|
|
90
|
-
command: resolved.
|
|
91
|
-
? `vite-node --watch ${resolved.rel}`
|
|
92
|
-
: `pnpm run ${resolved.script} (${resolved.command})`,
|
|
194
|
+
command: `pnpm run ${resolved.script} (${resolved.command})`,
|
|
93
195
|
logPath: logFile,
|
|
94
196
|
lastUpdated: new Date().toISOString(),
|
|
95
197
|
};
|
|
@@ -107,7 +209,6 @@ export const devCommand = defineCommand({
|
|
|
107
209
|
}
|
|
108
210
|
out.write(text);
|
|
109
211
|
if (currentStatus !== "running" && READY_RE.test(text)) {
|
|
110
|
-
// Try to capture the port from "Local: http://...:<port>"
|
|
111
212
|
const match = /:(\d+)\b/.exec(text);
|
|
112
213
|
if (match)
|
|
113
214
|
port = Number(match[1]);
|
|
@@ -117,7 +218,6 @@ export const devCommand = defineCommand({
|
|
|
117
218
|
};
|
|
118
219
|
child.stdout?.on("data", handleStream(process.stdout));
|
|
119
220
|
child.stderr?.on("data", handleStream(process.stderr));
|
|
120
|
-
// ── Shutdown ────────────────────────────────────────────────────
|
|
121
221
|
let stopping = false;
|
|
122
222
|
const stop = (signal) => {
|
|
123
223
|
if (stopping)
|
|
@@ -134,9 +234,6 @@ export const devCommand = defineCommand({
|
|
|
134
234
|
setTimeout(() => {
|
|
135
235
|
if (child.exitCode === null && child.signalCode === null) {
|
|
136
236
|
try {
|
|
137
|
-
// Windows has no signal mechanism — SIGTERM and SIGKILL
|
|
138
|
-
// collapse to the same TerminateProcess call. Skip the
|
|
139
|
-
// explicit SIGKILL and let Node use the platform default.
|
|
140
237
|
if (IS_WIN)
|
|
141
238
|
child.kill();
|
|
142
239
|
else
|
package/dist/commands/ls.js
CHANGED
|
@@ -8,7 +8,7 @@ import { existsSync } from "node:fs";
|
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { palette } from "../lib/colors.js";
|
|
10
10
|
import { ensureScanFresh } from "../lib/ensure-scan.js";
|
|
11
|
-
import {
|
|
11
|
+
import { execSync } from "../lib/exec.js";
|
|
12
12
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
export const lsCommand = defineCommand({
|
|
14
14
|
meta: {
|
|
@@ -16,13 +16,15 @@ export const lsCommand = defineCommand({
|
|
|
16
16
|
description: "List discovered wires + actions",
|
|
17
17
|
},
|
|
18
18
|
async run({ rawArgs }) {
|
|
19
|
-
ensureScanFresh(process.cwd(), { quiet: true });
|
|
19
|
+
await ensureScanFresh(process.cwd(), { quiet: true });
|
|
20
20
|
const builderPath = resolve(here, "..", "ls-runner.js");
|
|
21
21
|
if (!existsSync(builderPath)) {
|
|
22
22
|
// eslint-disable-next-line no-console
|
|
23
23
|
console.error(palette.err("nwire ls:") + ` ls runner missing at ${builderPath}`);
|
|
24
24
|
process.exit(1);
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
// ls-runner is plain built JS that reads `.nwire/manifest.json` — plain node,
|
|
27
|
+
// no Vite needed.
|
|
28
|
+
process.exit(execSync("node", [builderPath, ...rawArgs]));
|
|
27
29
|
},
|
|
28
30
|
});
|
package/dist/commands/please.js
CHANGED
|
@@ -7,7 +7,7 @@ import { defineCommand } from "citty";
|
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { existsSync, readdirSync } from "node:fs";
|
|
9
9
|
import { palette } from "../lib/colors.js";
|
|
10
|
-
import {
|
|
10
|
+
import { viteRun } from "../lib/vite-run.js";
|
|
11
11
|
function findPleaseEntry(cwd) {
|
|
12
12
|
const appsDir = resolve(cwd, "apps");
|
|
13
13
|
if (!existsSync(appsDir))
|
|
@@ -40,6 +40,8 @@ export const pleaseCommand = defineCommand({
|
|
|
40
40
|
console.error(palette.err("nwire please:") + " no apps/*/run.please.ts found in this project");
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
43
|
-
process
|
|
43
|
+
// The please wire runs in-process; it exits with its own code, or we exit 0.
|
|
44
|
+
await viteRun(pleasePath, rawArgs);
|
|
45
|
+
process.exit(0);
|
|
44
46
|
},
|
|
45
47
|
});
|
package/dist/commands/run.js
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
* difference is `dev` adds `--watch` and reloads on change.
|
|
11
11
|
*/
|
|
12
12
|
import { defineCommand } from "citty";
|
|
13
|
-
import { basename, relative, resolve } from "node:path";
|
|
13
|
+
import { basename, relative, resolve, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
14
15
|
import { existsSync } from "node:fs";
|
|
15
16
|
import { spawn } from "node:child_process";
|
|
16
17
|
import { palette } from "../lib/colors.js";
|
|
18
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
19
|
/** Windows toggle — `shell:true` so `.cmd` shims like `pnpm` resolve. */
|
|
18
20
|
const IS_WIN = process.platform === "win32";
|
|
19
21
|
export const runCommand = defineCommand({
|
|
@@ -68,7 +70,9 @@ export const runCommand = defineCommand({
|
|
|
68
70
|
// The child writes directly to the user's terminal. runApp() emits
|
|
69
71
|
// its own banner (ports, docs, openapi paths) so the user gets
|
|
70
72
|
// immediate feedback once the wire is listening.
|
|
71
|
-
|
|
73
|
+
// Load the wire via the Vite-backed wire-runner (no vite-node).
|
|
74
|
+
const wireRunner = resolve(here, "..", "wire-runner.js");
|
|
75
|
+
const child = spawn("node", [wireRunner, entry, ...passthrough], {
|
|
72
76
|
stdio: "inherit",
|
|
73
77
|
cwd,
|
|
74
78
|
shell: IS_WIN,
|
package/dist/commands/studio.js
CHANGED
|
@@ -7,19 +7,66 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { defineCommand } from "citty";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
|
-
import { resolve, dirname } from "node:path";
|
|
11
|
-
import {
|
|
10
|
+
import { resolve, dirname, basename } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
13
|
import { palette } from "../lib/colors.js";
|
|
13
14
|
import { spawnInteractive } from "../lib/exec.js";
|
|
15
|
+
import { ensureWorkspaceBuilt } from "../lib/ensure-built.js";
|
|
16
|
+
import { decideStudioProbe, hasExplicitPort, openBrowser, probeStudio, registerProject, resolveStudioPort, studioBaseUrl, } from "../lib/studio-probe.js";
|
|
17
|
+
/** A project's display name — its `package.json` `name`, else the dir name. */
|
|
18
|
+
function projectName(cwd) {
|
|
19
|
+
try {
|
|
20
|
+
const pj = resolve(cwd, "package.json");
|
|
21
|
+
if (existsSync(pj)) {
|
|
22
|
+
const parsed = JSON.parse(readFileSync(pj, "utf8"));
|
|
23
|
+
if (parsed.name)
|
|
24
|
+
return parsed.name;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// fall through to the directory name
|
|
29
|
+
}
|
|
30
|
+
return basename(cwd);
|
|
31
|
+
}
|
|
14
32
|
export const studioCommand = defineCommand({
|
|
15
33
|
meta: {
|
|
16
34
|
name: "studio",
|
|
17
35
|
description: "Boot Nwire Studio (Vue + Vite UI)",
|
|
18
36
|
},
|
|
19
37
|
async run({ rawArgs }) {
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
38
|
+
// ── Singleton: attach to an already-running studio ───────────────
|
|
39
|
+
// Probe the studio port. If a studio is live there, register this
|
|
40
|
+
// project with it, open the browser, and exit 0 — never bind a second
|
|
41
|
+
// server. Only on the spawn path do we ensure the workspace is built.
|
|
42
|
+
const port = resolveStudioPort(rawArgs);
|
|
43
|
+
const cwd = process.cwd();
|
|
44
|
+
const decision = decideStudioProbe(await probeStudio(port));
|
|
45
|
+
if (decision.action === "attach") {
|
|
46
|
+
await registerProject(port, cwd);
|
|
47
|
+
// Name the project we just registered (this cwd), not the host studio.
|
|
48
|
+
const label = projectName(cwd);
|
|
49
|
+
// eslint-disable-next-line no-console
|
|
50
|
+
console.log(palette.brand("nwire studio:") +
|
|
51
|
+
` already running on :${port} — registered ${palette.bold(label)}, opening…`);
|
|
52
|
+
openBrowser(`${studioBaseUrl(port)}/projects`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
if (decision.action === "spawn-foreign") {
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.log(palette.warn("nwire studio:") +
|
|
58
|
+
` port ${port} is taken by another service — letting vite pick a free port.`);
|
|
59
|
+
}
|
|
60
|
+
// Monorepo only: keep workspace `dist/` fresh before booting so
|
|
61
|
+
// examples don't crash on stale `@nwire/*`. No-op in consumer apps.
|
|
62
|
+
ensureWorkspaceBuilt(cwd);
|
|
63
|
+
// Resolve the Studio source dir so we can find its vite binary.
|
|
64
|
+
//
|
|
65
|
+
// Resolution order:
|
|
66
|
+
// 1. Bundled inside the CLI itself (dist/studio ships the prebuilt SPA but
|
|
67
|
+
// we need the *source* dir for vite — fall through to node_modules paths).
|
|
68
|
+
// 2. Consumer's node_modules (explicit devDep install, dev-hacking workflow).
|
|
69
|
+
// 3. CLI's own node_modules (framework workspace + `pnpm dlx` flows).
|
|
23
70
|
let studioDir;
|
|
24
71
|
const tryResolveFrom = (basePath) => {
|
|
25
72
|
try {
|
|
@@ -33,6 +80,18 @@ export const studioCommand = defineCommand({
|
|
|
33
80
|
studioDir =
|
|
34
81
|
tryResolveFrom(resolve(process.cwd(), "package.json")) ?? tryResolveFrom(import.meta.url);
|
|
35
82
|
if (!studioDir) {
|
|
83
|
+
// @nwire/studio not reachable as a package — check whether the bundled
|
|
84
|
+
// prebuilt SPA is present (the normal consumer path). When it is, the
|
|
85
|
+
// `nwire dev` host already serves Studio at `/`; `nwire studio` (the
|
|
86
|
+
// standalone Vite dev server) is only needed in the framework workspace.
|
|
87
|
+
const bundledDist = resolve(dirname(fileURLToPath(import.meta.url)), "..", "studio");
|
|
88
|
+
if (existsSync(bundledDist)) {
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log(palette.brand("nwire studio:") +
|
|
91
|
+
" Studio is served by `nwire dev` at http://localhost:<port>/. " +
|
|
92
|
+
"Run `nwire dev` to see the live Studio UI.");
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
36
95
|
// eslint-disable-next-line no-console
|
|
37
96
|
console.error(palette.err("nwire studio:") +
|
|
38
97
|
" @nwire/studio not installed. `pnpm add -D @nwire/studio` first.");
|
|
@@ -51,9 +110,17 @@ export const studioCommand = defineCommand({
|
|
|
51
110
|
` vite binary missing at ${viteBin} — run \`pnpm install\` in ${studioDir}`);
|
|
52
111
|
process.exit(1);
|
|
53
112
|
}
|
|
54
|
-
|
|
113
|
+
// Deterministic port: on the normal (free-port) spawn path, pin the
|
|
114
|
+
// studio to `port` with `--strictPort` so it OWNS that port and the
|
|
115
|
+
// next `nwire studio` invocation finds + attaches to it rather than
|
|
116
|
+
// scattering onto 7778, 7779, … . Skip when the user already pinned a
|
|
117
|
+
// `--port`, or on the foreign-port fallback (let vite pick a free one).
|
|
118
|
+
const spawnArgs = decision.action === "spawn" && !hasExplicitPort(rawArgs)
|
|
119
|
+
? [...rawArgs, "--port", String(port), "--strictPort"]
|
|
120
|
+
: rawArgs;
|
|
121
|
+
const { done } = spawnInteractive(viteBin, spawnArgs, {
|
|
55
122
|
cwd: studioDir,
|
|
56
|
-
env: { NWIRE_CWD:
|
|
123
|
+
env: { NWIRE_CWD: cwd },
|
|
57
124
|
stoppedMessage: "Studio stopped.",
|
|
58
125
|
});
|
|
59
126
|
process.exit(await done);
|
package/dist/lib/dev-entry.d.ts
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `resolveDevEntry` — figure out how to boot the project's dev server.
|
|
3
3
|
*
|
|
4
|
-
* Nwire projects come in
|
|
5
|
-
* of
|
|
4
|
+
* Nwire projects come in two shapes, and `nwire dev` works on both out
|
|
5
|
+
* of the box (no `nwire.config.ts` required):
|
|
6
6
|
*
|
|
7
|
-
* 1.
|
|
8
|
-
* 2. Single
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 4. Fallback — `package.json` exposes a `scripts.dev` entry; we honor it.
|
|
7
|
+
* 1. Single wire under `apps/` — `apps/<name>/{run,main}.ts`.
|
|
8
|
+
* 2. Single-app style — `app/{main,run,index}.ts` (the small-example
|
|
9
|
+
* shape used by hello-world / todo-app / moderation-queue).
|
|
10
|
+
* 3. Fallback — `package.json` exposes a `scripts.dev` entry; we honor it.
|
|
12
11
|
*
|
|
13
12
|
* Returns a discriminated value the caller can spawn directly. The CLI prints
|
|
14
13
|
* a banner before handing off, so the kind + label flow through to the user.
|
|
15
14
|
*/
|
|
16
15
|
export type DevEntry = {
|
|
17
|
-
readonly kind: "
|
|
16
|
+
readonly kind: "wire";
|
|
18
17
|
/** Absolute path to the .ts entry file. */
|
|
19
18
|
readonly entry: string;
|
|
20
19
|
/** Project-relative path of `entry`, for the banner. */
|
|
21
20
|
readonly rel: string;
|
|
22
|
-
/** Subtitle shown in the banner:
|
|
21
|
+
/** Subtitle shown in the banner: <wire-name> | app. */
|
|
23
22
|
readonly label: string;
|
|
24
23
|
} | {
|
|
25
24
|
readonly kind: "npm-script";
|
package/dist/lib/dev-entry.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `resolveDevEntry` — figure out how to boot the project's dev server.
|
|
3
3
|
*
|
|
4
|
-
* Nwire projects come in
|
|
5
|
-
* of
|
|
4
|
+
* Nwire projects come in two shapes, and `nwire dev` works on both out
|
|
5
|
+
* of the box (no `nwire.config.ts` required):
|
|
6
6
|
*
|
|
7
|
-
* 1.
|
|
8
|
-
* 2. Single
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 4. Fallback — `package.json` exposes a `scripts.dev` entry; we honor it.
|
|
7
|
+
* 1. Single wire under `apps/` — `apps/<name>/{run,main}.ts`.
|
|
8
|
+
* 2. Single-app style — `app/{main,run,index}.ts` (the small-example
|
|
9
|
+
* shape used by hello-world / todo-app / moderation-queue).
|
|
10
|
+
* 3. Fallback — `package.json` exposes a `scripts.dev` entry; we honor it.
|
|
12
11
|
*
|
|
13
12
|
* Returns a discriminated value the caller can spawn directly. The CLI prints
|
|
14
13
|
* a banner before handing off, so the kind + label flow through to the user.
|
|
@@ -21,17 +20,9 @@ import { relative, resolve } from "node:path";
|
|
|
21
20
|
* with a clear message listing what was tried.
|
|
22
21
|
*/
|
|
23
22
|
export function resolveDevEntry(cwd) {
|
|
24
|
-
// 1.
|
|
25
|
-
const devAll = resolve(cwd, "apps/dev-all/run.ts");
|
|
26
|
-
if (existsSync(devAll)) {
|
|
27
|
-
return { kind: "vite-node", entry: devAll, rel: relPath(cwd, devAll), label: "dev-all" };
|
|
28
|
-
}
|
|
29
|
-
// 2. Single wire under apps/ — accept run.ts OR main.ts.
|
|
23
|
+
// 1. Single wire under apps/ — accept run.ts OR main.ts.
|
|
30
24
|
const appsDir = resolve(cwd, "apps");
|
|
31
25
|
if (existsSync(appsDir)) {
|
|
32
|
-
// Cheap enumeration — projects rarely have more than a few entries here.
|
|
33
|
-
// We don't need detectProject (avoids the import cycle and keeps this
|
|
34
|
-
// helper focused).
|
|
35
26
|
try {
|
|
36
27
|
const wires = [];
|
|
37
28
|
for (const ent of readdirSync(appsDir, { withFileTypes: true })) {
|
|
@@ -47,7 +38,7 @@ export function resolveDevEntry(cwd) {
|
|
|
47
38
|
for (const f of ["run.ts", "main.ts"]) {
|
|
48
39
|
const p = resolve(appsDir, only, f);
|
|
49
40
|
if (existsSync(p)) {
|
|
50
|
-
return { kind: "
|
|
41
|
+
return { kind: "wire", entry: p, rel: relPath(cwd, p), label: only };
|
|
51
42
|
}
|
|
52
43
|
}
|
|
53
44
|
}
|
|
@@ -56,14 +47,25 @@ export function resolveDevEntry(cwd) {
|
|
|
56
47
|
// ignore — fall through to app/ and scripts
|
|
57
48
|
}
|
|
58
49
|
}
|
|
59
|
-
//
|
|
50
|
+
// 2. Root entry — main.ts / run.ts at the project root (the template shape:
|
|
51
|
+
// the file that calls `endpoint().run()` lives at the root, with an
|
|
52
|
+
// `app/index.ts` barrel beside it). Checked BEFORE the app/ folder so the
|
|
53
|
+
// real entry wins over a barrel that mounts nothing.
|
|
54
|
+
for (const f of ["main.ts", "run.ts"]) {
|
|
55
|
+
const p = resolve(cwd, f);
|
|
56
|
+
if (existsSync(p)) {
|
|
57
|
+
return { kind: "wire", entry: p, rel: relPath(cwd, p), label: "app" };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 3. Single-app folder — app/main.ts, app/run.ts, then the app/index.ts
|
|
61
|
+
// barrel as a last resort.
|
|
60
62
|
for (const f of ["app/main.ts", "app/run.ts", "app/index.ts"]) {
|
|
61
63
|
const p = resolve(cwd, f);
|
|
62
64
|
if (existsSync(p)) {
|
|
63
|
-
return { kind: "
|
|
65
|
+
return { kind: "wire", entry: p, rel: relPath(cwd, p), label: "app" };
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
|
-
//
|
|
68
|
+
// 3. package.json scripts.dev fallback.
|
|
67
69
|
const pj = resolve(cwd, "package.json");
|
|
68
70
|
if (existsSync(pj)) {
|
|
69
71
|
try {
|
|
@@ -82,9 +84,10 @@ export function resolveDevEntry(cwd) {
|
|
|
82
84
|
/** Same fallback list rendered for the "no dev entry found" error message. */
|
|
83
85
|
export function devEntryCandidates() {
|
|
84
86
|
return [
|
|
85
|
-
"apps/dev-all/run.ts",
|
|
86
87
|
"apps/<wire>/run.ts",
|
|
87
88
|
"apps/<wire>/main.ts",
|
|
89
|
+
"main.ts",
|
|
90
|
+
"run.ts",
|
|
88
91
|
"app/main.ts",
|
|
89
92
|
"app/run.ts",
|
|
90
93
|
"app/index.ts",
|
|
@@ -92,10 +95,6 @@ export function devEntryCandidates() {
|
|
|
92
95
|
];
|
|
93
96
|
}
|
|
94
97
|
function relPath(cwd, abs) {
|
|
95
|
-
// `path.relative` already handles the cross-platform `path.sep` story
|
|
96
|
-
// (Windows backslashes vs POSIX forward slashes) and the
|
|
97
|
-
// "abs is outside cwd" case (returns a `..` chain) — no manual
|
|
98
|
-
// `startsWith(cwd + "/")` math needed.
|
|
99
98
|
const rel = relative(cwd, abs);
|
|
100
99
|
return rel === "" ? abs : rel;
|
|
101
100
|
}
|