@wolfstar/cli 2.6.1 → 2.7.0-next-20261009115919
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/README.md +6 -2
- package/dist/{_shared-CyzTojMA.js → _shared-Bqw7h4O0.js} +3 -3
- package/dist/{_shared-CyzTojMA.js.map → _shared-Bqw7h4O0.js.map} +1 -1
- package/dist/{bridge-Cbm0jX9h.js → bridge-DQ3dZ9pm.js} +2 -2
- package/dist/{bridge-Cbm0jX9h.js.map → bridge-DQ3dZ9pm.js.map} +1 -1
- package/dist/{build-BbIEbOu3.js → build-BYlgPxAE.js} +5 -5
- package/dist/{build-BbIEbOu3.js.map → build-BYlgPxAE.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/{codegen-C2PY7RXA.js → codegen-CiDXuBHu.js} +5 -5
- package/dist/{codegen-C2PY7RXA.js.map → codegen-CiDXuBHu.js.map} +1 -1
- package/dist/{commands-C-i3qYOz.js → commands-CURkZI0f.js} +5 -5
- package/dist/{commands-C-i3qYOz.js.map → commands-CURkZI0f.js.map} +1 -1
- package/dist/commands-Chpk8BR8.js +16 -0
- package/dist/{commands-D9i-ZgpA.js.map → commands-Chpk8BR8.js.map} +1 -1
- package/dist/commands-R2m3xweq.js +3 -0
- package/dist/{completions-DA-NZAcY.js → completions-DBipETDu.js} +3 -3
- package/dist/{completions-DA-NZAcY.js.map → completions-DBipETDu.js.map} +1 -1
- package/dist/{dev-BLlgQwl6.js → dev-GvuLQzWu.js} +10 -10
- package/dist/{dev-BLlgQwl6.js.map → dev-GvuLQzWu.js.map} +1 -1
- package/dist/{diagnostics-Dn3CXa8J.js → diagnostics-BKc--u_3.js} +5 -1
- package/dist/diagnostics-BKc--u_3.js.map +1 -0
- package/dist/{doctor-CMGfbXS5.js → doctor-C_lTMEjG.js} +28 -10
- package/dist/doctor-C_lTMEjG.js.map +1 -0
- package/dist/{hooks-D_RVS0ba.js → hooks-DqlScRZA.js} +29 -4
- package/dist/{hooks-D_RVS0ba.js.map → hooks-DqlScRZA.js.map} +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{info-C_YDcVul.js → info-BX-As3qN.js} +5 -4
- package/dist/info-BX-As3qN.js.map +1 -0
- package/dist/{local-commands-CGBjc8KJ.js → local-commands-DkWl97zR.js} +4 -4
- package/dist/{local-commands-CGBjc8KJ.js.map → local-commands-DkWl97zR.js.map} +1 -1
- package/dist/{locales-BzyEMQnX.js → locales-CnVIXIip.js} +5 -5
- package/dist/{locales-BzyEMQnX.js.map → locales-CnVIXIip.js.map} +1 -1
- package/dist/{nitro-DufOgx8s.js → nitro-BWRIMUd8.js} +2 -2
- package/dist/{nitro-DufOgx8s.js.map → nitro-BWRIMUd8.js.map} +1 -1
- package/dist/{prepare-DKH0UIYA.js → prepare-6abxyJeD.js} +4 -4
- package/dist/{prepare-DKH0UIYA.js.map → prepare-6abxyJeD.js.map} +1 -1
- package/dist/{project-DBqQMFCk.js → project-EJyKro-M.js} +2 -2
- package/dist/{project-DBqQMFCk.js.map → project-EJyKro-M.js.map} +1 -1
- package/dist/{run-Bs6e9_6S.js → run-DlctD5rb.js} +2 -2
- package/dist/{run-Bs6e9_6S.js.map → run-DlctD5rb.js.map} +1 -1
- package/dist/{tsc-BArammxe.js → tsc-6su4cT6K.js} +3 -3
- package/dist/{tsc-BArammxe.js.map → tsc-6su4cT6K.js.map} +1 -1
- package/dist/tsc-DK53_ZWE.js +3 -0
- package/dist/{tsdown-2i5h88vT.js → tsdown-DP5VrWx4.js} +2 -2
- package/dist/{tsdown-2i5h88vT.js.map → tsdown-DP5VrWx4.js.map} +1 -1
- package/dist/{tui-BOkOx13K.js → tui-C8MjzsKS.js} +2 -2
- package/dist/{tui-BOkOx13K.js.map → tui-C8MjzsKS.js.map} +1 -1
- package/dist/{tunnel-BNV5cuJz.js → tunnel-D3WxypOt.js} +193 -93
- package/dist/tunnel-D3WxypOt.js.map +1 -0
- package/dist/{typecheck-p_2rj9lL.js → typecheck-BIscCUNQ.js} +5 -5
- package/dist/{typecheck-p_2rj9lL.js.map → typecheck-BIscCUNQ.js.map} +1 -1
- package/dist/{typechecker-C3UOI2LF.js → typechecker-zjnzC2L_.js} +4 -4
- package/dist/{typechecker-C3UOI2LF.js.map → typechecker-zjnzC2L_.js.map} +1 -1
- package/dist/{vite-C5CN3xUu.js → vite-9yEVMHY_.js} +2 -2
- package/dist/{vite-C5CN3xUu.js.map → vite-9yEVMHY_.js.map} +1 -1
- package/package.json +9 -5
- package/dist/commands-BfNA1-eZ.js +0 -3
- package/dist/commands-D9i-ZgpA.js +0 -16
- package/dist/diagnostics-Dn3CXa8J.js.map +0 -1
- package/dist/doctor-CMGfbXS5.js.map +0 -1
- package/dist/info-C_YDcVul.js.map +0 -1
- package/dist/tsc-9yHyx0L6.js +0 -3
- package/dist/tunnel-BNV5cuJz.js.map +0 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as cliDiagnostics } from "./diagnostics-BKc--u_3.js";
|
|
2
|
+
import { n as importFromProject } from "./project-EJyKro-M.js";
|
|
3
|
+
import { a as readProjectVariable } from "./hooks-DqlScRZA.js";
|
|
4
|
+
import { DEFAULT_TUNNEL_PROVIDER } from "@wolfstar/schema";
|
|
5
|
+
import { Diagnostic } from "nostics";
|
|
2
6
|
import { EventEmitter } from "node:events";
|
|
3
7
|
import { startTunnel } from "untun";
|
|
4
8
|
|
|
5
|
-
//#region src/dev/tunnel.ts
|
|
9
|
+
//#region src/dev/tunnel-providers.ts
|
|
6
10
|
const SIGNALS = [
|
|
7
11
|
"SIGINT",
|
|
8
12
|
"SIGTERM",
|
|
@@ -12,24 +16,163 @@ function isCloudflaredExit(reason) {
|
|
|
12
16
|
return reason instanceof Error && reason.message.startsWith("cloudflared exited (");
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
19
|
+
* The `cloudflared` quick tunnel, opened through `untun` (no account, a new hostname on every run).
|
|
20
|
+
*
|
|
21
|
+
* `untun` is wrapped rather than changed: it adds process signal listeners and leaves a promise unhandled whenever
|
|
22
|
+
* `cloudflared` exits, both of which would end `stars dev`, so they are neutralised here.
|
|
23
|
+
*/
|
|
24
|
+
var CloudflaredProvider = class {
|
|
25
|
+
name = "cloudflared";
|
|
26
|
+
#startTunnel;
|
|
27
|
+
#rejectionGuard = null;
|
|
28
|
+
constructor(start = startTunnel) {
|
|
29
|
+
this.#startTunnel = start;
|
|
30
|
+
}
|
|
31
|
+
async open(target, context) {
|
|
32
|
+
this.#installRejectionGuard(context);
|
|
33
|
+
let tunnel;
|
|
34
|
+
try {
|
|
35
|
+
tunnel = await this.#startWithoutSignalHandlers(target);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this.#releaseRejectionGuard();
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
if (!tunnel) {
|
|
41
|
+
this.#releaseRejectionGuard();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const opened = tunnel;
|
|
45
|
+
return {
|
|
46
|
+
getURL: async () => {
|
|
47
|
+
try {
|
|
48
|
+
return await opened.getURL();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.#releaseRejectionGuard();
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
close: async () => {
|
|
55
|
+
try {
|
|
56
|
+
await opened.close();
|
|
57
|
+
} finally {
|
|
58
|
+
this.#releaseRejectionGuard();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* `untun` adds its own `SIGINT`/`SIGTERM`/`SIGHUP` listeners that call `process.exit()` once cloudflared is closed,
|
|
65
|
+
* which races `stars dev`'s graceful shutdown (it closes the tunnel through {@link TunnelHandle.close} anyway), so they
|
|
66
|
+
* are removed.
|
|
67
|
+
*/
|
|
68
|
+
async #startWithoutSignalHandlers(target) {
|
|
69
|
+
const before = SIGNALS.map((signal) => new Set(process.listeners(signal)));
|
|
70
|
+
try {
|
|
71
|
+
return await this.#startTunnel({
|
|
72
|
+
url: target,
|
|
73
|
+
acceptCloudflareNotice: true
|
|
74
|
+
});
|
|
75
|
+
} finally {
|
|
76
|
+
SIGNALS.forEach((signal, index) => {
|
|
77
|
+
for (const listener of process.listeners(signal)) if (!before[index].has(listener)) process.off(signal, listener);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* `untun` rejects a promise per connection when cloudflared exits and never handles it (and its location pattern
|
|
83
|
+
* does not match lowercase codes such as `mxp03`, so that promise is never settled before), which Node reports as an
|
|
84
|
+
* unhandled rejection that ends the process, whenever the tunnel closes. The guard swallows that one error for as
|
|
85
|
+
* long as this tunnel exists; anything else it sees is thrown again, as if it was not installed.
|
|
86
|
+
*/
|
|
87
|
+
#installRejectionGuard(context) {
|
|
88
|
+
if (this.#rejectionGuard) return;
|
|
89
|
+
const guard = (reason) => {
|
|
90
|
+
if (!isCloudflaredExit(reason)) {
|
|
91
|
+
if (process.listeners("unhandledRejection").length === 1) throw reason;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (context.onStopped(reason)) this.#releaseRejectionGuard();
|
|
95
|
+
};
|
|
96
|
+
this.#rejectionGuard = guard;
|
|
97
|
+
process.on("unhandledRejection", guard);
|
|
98
|
+
}
|
|
99
|
+
/** Node reports an unhandled rejection after the microtasks that follow it, so the guard has to outlive them. */
|
|
100
|
+
#releaseRejectionGuard() {
|
|
101
|
+
const guard = this.#rejectionGuard;
|
|
102
|
+
this.#rejectionGuard = null;
|
|
103
|
+
if (guard) setImmediate(() => process.off("unhandledRejection", guard));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const NGROK_PACKAGE = "@ngrok/ngrok";
|
|
107
|
+
const NGROK_AUTHTOKEN_VARIABLE = "NGROK_AUTHTOKEN";
|
|
108
|
+
const NGROK_INSTALL_HINT = `Install it with \`pnpm add -D ${NGROK_PACKAGE}\`, or remove \`provider: 'ngrok'\` from \`dev.tunnel\`.`;
|
|
109
|
+
/** Imports `@ngrok/ngrok` from the project, where it is an optional peer of the CLI. */
|
|
110
|
+
const loadNgrok = async (root) => {
|
|
111
|
+
const imported = await importFromProject(root, NGROK_PACKAGE, NGROK_INSTALL_HINT);
|
|
112
|
+
return typeof imported.forward === "function" ? imported : imported.default;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* ngrok through its official Node SDK. It runs in-process (no binary is downloaded), authenticates with
|
|
116
|
+
* `NGROK_AUTHTOKEN` and can bind a domain reserved in the account.
|
|
117
|
+
*/
|
|
118
|
+
var NgrokProvider = class {
|
|
119
|
+
name = "ngrok";
|
|
120
|
+
#load;
|
|
121
|
+
constructor(load = loadNgrok) {
|
|
122
|
+
this.#load = load;
|
|
123
|
+
}
|
|
124
|
+
async open(target, { config, tunnel }) {
|
|
125
|
+
const ngrok = await this.#load(config.root);
|
|
126
|
+
const authtoken = readProjectVariable(config, process.env, [NGROK_AUTHTOKEN_VARIABLE], { fresh: true });
|
|
127
|
+
if (!authtoken) throw cliDiagnostics.NGROK_AUTHTOKEN_MISSING({});
|
|
128
|
+
const listener = await ngrok.forward({
|
|
129
|
+
addr: ngrokAddress(target),
|
|
130
|
+
authtoken,
|
|
131
|
+
...tunnel.domain ? { domain: tunnel.domain } : {}
|
|
132
|
+
});
|
|
133
|
+
const url = listener.url();
|
|
134
|
+
if (!url) {
|
|
135
|
+
await listener.close();
|
|
136
|
+
throw new Error("ngrok did not report a URL for the tunnel");
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
getURL: () => Promise.resolve(url),
|
|
140
|
+
close: () => listener.close()
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
/** ngrok forwards to `host:port`; a full `http` URL is reduced to it, anything else (`https`, a socket) is passed on. */
|
|
145
|
+
function ngrokAddress(target) {
|
|
146
|
+
try {
|
|
147
|
+
const url = new URL(target);
|
|
148
|
+
if (url.protocol === "http:") return `${url.hostname}:${url.port || 80}`;
|
|
149
|
+
} catch {}
|
|
150
|
+
return target;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/dev/tunnel.ts
|
|
155
|
+
/**
|
|
15
156
|
* Exposes the bot's interactions endpoint publicly while `stars dev` runs.
|
|
16
157
|
*
|
|
17
|
-
* `dev.tunnel: true` opens a
|
|
18
|
-
* URL is only probed, since the user already serves it. Writing
|
|
19
|
-
* through `dev.tunnel.updateEndpoint`, because it edits a live application.
|
|
158
|
+
* `dev.tunnel: true` opens a quick tunnel through the configured provider: `cloudflared` via `untun` by default (it
|
|
159
|
+
* changes on every run), or `ngrok`. A configured https URL is only probed, since the user already serves it. Writing
|
|
160
|
+
* the URL to the Discord application is opt-in through `dev.tunnel.updateEndpoint`, because it edits a live application.
|
|
20
161
|
*/
|
|
21
162
|
var Tunnel = class extends EventEmitter {
|
|
22
163
|
config;
|
|
23
|
-
#
|
|
164
|
+
#providers;
|
|
24
165
|
#tunnel = null;
|
|
25
166
|
#state = "off";
|
|
26
167
|
#url = null;
|
|
27
168
|
#wanted = false;
|
|
28
|
-
#rejectionGuard = null;
|
|
29
169
|
constructor(config, options = {}) {
|
|
30
170
|
super();
|
|
31
171
|
this.config = config;
|
|
32
|
-
this.#
|
|
172
|
+
this.#providers = {
|
|
173
|
+
cloudflared: new CloudflaredProvider(options.startTunnel),
|
|
174
|
+
ngrok: new NgrokProvider(options.loadNgrok)
|
|
175
|
+
};
|
|
33
176
|
}
|
|
34
177
|
get state() {
|
|
35
178
|
return this.#state;
|
|
@@ -43,13 +186,15 @@ var Tunnel = class extends EventEmitter {
|
|
|
43
186
|
const configured = this.config.dev.tunnel;
|
|
44
187
|
const tunnel = forceQuick && configured.mode === "off" ? {
|
|
45
188
|
mode: "quick",
|
|
189
|
+
provider: DEFAULT_TUNNEL_PROVIDER,
|
|
190
|
+
domain: null,
|
|
46
191
|
path: "/",
|
|
47
192
|
updateEndpoint: false
|
|
48
193
|
} : configured;
|
|
49
194
|
if (tunnel.mode === "off") return;
|
|
50
195
|
this.#wanted = true;
|
|
51
196
|
this.#setState("starting", null);
|
|
52
|
-
const url = tunnel.mode === "url" ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel();
|
|
197
|
+
const url = tunnel.mode === "url" ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel(tunnel);
|
|
53
198
|
if (!url || !this.#wanted) return;
|
|
54
199
|
this.#setState("up", endpointUrl(url, tunnel.path));
|
|
55
200
|
this.emit("log", "success", `Tunnel ready at ${this.#url}`);
|
|
@@ -64,93 +209,52 @@ var Tunnel = class extends EventEmitter {
|
|
|
64
209
|
const tunnel = this.#tunnel;
|
|
65
210
|
this.#tunnel = null;
|
|
66
211
|
this.#setState("off", null);
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
await tunnel.close();
|
|
70
|
-
} finally {
|
|
71
|
-
this.#releaseRejectionGuard();
|
|
72
|
-
}
|
|
212
|
+
await tunnel?.close();
|
|
73
213
|
}
|
|
74
214
|
async #useConfiguredUrl(tunnel) {
|
|
75
215
|
if (!await probe(endpointUrl(tunnel.url, tunnel.path))) this.emit("log", "warn", `${tunnel.url} did not answer; make sure it forwards to ${this.config.dev.url ?? "the bot"}`);
|
|
76
216
|
return tunnel.url;
|
|
77
217
|
}
|
|
78
|
-
async #openQuickTunnel() {
|
|
218
|
+
async #openQuickTunnel(tunnel) {
|
|
79
219
|
const target = this.config.dev.url;
|
|
80
220
|
if (!target) {
|
|
81
221
|
this.emit("log", "error", "A quick tunnel needs `dev.url` to know what to forward to");
|
|
82
222
|
this.#setState("failed", null);
|
|
83
223
|
return null;
|
|
84
224
|
}
|
|
85
|
-
|
|
86
|
-
this
|
|
225
|
+
const provider = this.#providers[tunnel.provider];
|
|
226
|
+
this.emit("log", "info", `Opening a ${provider.name} quick tunnel…`);
|
|
87
227
|
try {
|
|
88
|
-
const
|
|
228
|
+
const handle = await provider.open(target, {
|
|
229
|
+
config: this.config,
|
|
230
|
+
tunnel,
|
|
231
|
+
onStopped: (error) => this.#stoppedUnexpectedly(provider.name, error)
|
|
232
|
+
});
|
|
89
233
|
if (!this.#wanted) {
|
|
90
|
-
await
|
|
91
|
-
this.#releaseRejectionGuard();
|
|
234
|
+
await handle?.close();
|
|
92
235
|
return null;
|
|
93
236
|
}
|
|
94
|
-
if (!
|
|
95
|
-
this.emit("log", "error",
|
|
237
|
+
if (!handle) {
|
|
238
|
+
this.emit("log", "error", `${provider.name} setup was cancelled`);
|
|
96
239
|
this.#setState("failed", null);
|
|
97
|
-
this.#releaseRejectionGuard();
|
|
98
240
|
return null;
|
|
99
241
|
}
|
|
100
|
-
this.#tunnel =
|
|
101
|
-
return await
|
|
242
|
+
this.#tunnel = handle;
|
|
243
|
+
return await handle.getURL();
|
|
102
244
|
} catch (error) {
|
|
103
|
-
this.#releaseRejectionGuard();
|
|
104
245
|
if (!this.#wanted) return null;
|
|
105
|
-
this.emit("log", "error",
|
|
246
|
+
this.emit("log", "error", `${provider.name} failed: ${describeError(error)}`);
|
|
106
247
|
this.#setState("failed", null);
|
|
107
248
|
return null;
|
|
108
249
|
}
|
|
109
250
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return await this.#startTunnel({
|
|
118
|
-
url: target,
|
|
119
|
-
acceptCloudflareNotice: true
|
|
120
|
-
});
|
|
121
|
-
} finally {
|
|
122
|
-
SIGNALS.forEach((signal, index) => {
|
|
123
|
-
for (const listener of process.listeners(signal)) if (!before[index].has(listener)) process.off(signal, listener);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* `untun` rejects a promise per connection when cloudflared exits and never handles it (and its location pattern
|
|
129
|
-
* does not match lowercase codes such as `mxp03`, so that promise is never settled before), which Node reports as an
|
|
130
|
-
* unhandled rejection that ends the process, whenever the tunnel closes. The guard swallows that one error for as
|
|
131
|
-
* long as this tunnel exists; anything else it sees is thrown again, as if it was not installed.
|
|
132
|
-
*/
|
|
133
|
-
#installRejectionGuard() {
|
|
134
|
-
if (this.#rejectionGuard) return;
|
|
135
|
-
const guard = (reason) => {
|
|
136
|
-
if (!isCloudflaredExit(reason)) {
|
|
137
|
-
if (process.listeners("unhandledRejection").length === 1) throw reason;
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (this.#state !== "up" || !this.#wanted) return;
|
|
141
|
-
this.#tunnel = null;
|
|
142
|
-
this.emit("log", "error", `cloudflared stopped unexpectedly: ${reason.message}`);
|
|
143
|
-
this.#setState("failed", null);
|
|
144
|
-
this.#releaseRejectionGuard();
|
|
145
|
-
};
|
|
146
|
-
this.#rejectionGuard = guard;
|
|
147
|
-
process.on("unhandledRejection", guard);
|
|
148
|
-
}
|
|
149
|
-
/** Node reports an unhandled rejection after the microtasks that follow it, so the guard has to outlive them. */
|
|
150
|
-
#releaseRejectionGuard() {
|
|
151
|
-
const guard = this.#rejectionGuard;
|
|
152
|
-
this.#rejectionGuard = null;
|
|
153
|
-
if (guard) setImmediate(() => process.off("unhandledRejection", guard));
|
|
251
|
+
/** Whether the report was about the tunnel in use: one that is up and still wanted. */
|
|
252
|
+
#stoppedUnexpectedly(provider, error) {
|
|
253
|
+
if (this.#state !== "up" || !this.#wanted) return false;
|
|
254
|
+
this.#tunnel = null;
|
|
255
|
+
this.emit("log", "error", `${provider} stopped unexpectedly: ${error.message}`);
|
|
256
|
+
this.#setState("failed", null);
|
|
257
|
+
return true;
|
|
154
258
|
}
|
|
155
259
|
/**
|
|
156
260
|
* Points the Discord application's interactions endpoint at the tunnel. Discord validates the endpoint with a
|
|
@@ -205,32 +309,28 @@ async function probe(url) {
|
|
|
205
309
|
* way the bot itself does once it starts.
|
|
206
310
|
*/
|
|
207
311
|
function readDiscordCredentials(config, env = process.env) {
|
|
208
|
-
const
|
|
209
|
-
const read = (...keys) => {
|
|
210
|
-
for (const key of keys) {
|
|
211
|
-
const value = config.dev.env[key] ?? env[key] ?? fromFiles[key];
|
|
212
|
-
if (value) return value;
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
};
|
|
216
|
-
const token = read("DISCORD_TOKEN", "TOKEN");
|
|
312
|
+
const token = readProjectVariable(config, env, ["DISCORD_TOKEN", "TOKEN"]);
|
|
217
313
|
if (!token) return null;
|
|
218
314
|
return {
|
|
219
315
|
token,
|
|
220
|
-
applicationId:
|
|
316
|
+
applicationId: readProjectVariable(config, env, [
|
|
317
|
+
"DISCORD_APPLICATION_ID",
|
|
318
|
+
"APPLICATION_ID",
|
|
319
|
+
"DISCORD_CLIENT_ID",
|
|
320
|
+
"CLIENT_ID"
|
|
321
|
+
])
|
|
221
322
|
};
|
|
222
323
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return envFileCache.values;
|
|
324
|
+
/** How a quick tunnel is described to the user, for `stars info` and `stars doctor`. */
|
|
325
|
+
function describeQuickTunnel(tunnel) {
|
|
326
|
+
return `${tunnel.provider} quick tunnel${tunnel.domain ? ` on ${tunnel.domain}` : ""}`;
|
|
327
|
+
}
|
|
328
|
+
/** A diagnostic's own advice is part of the message, since the dev log has no place to show it separately. */
|
|
329
|
+
function describeError(error) {
|
|
330
|
+
if (error instanceof Diagnostic) return error.fix ? `${error.message}. ${error.fix}` : error.message;
|
|
331
|
+
return error instanceof Error ? error.message : String(error);
|
|
232
332
|
}
|
|
233
333
|
|
|
234
334
|
//#endregion
|
|
235
|
-
export {
|
|
236
|
-
//# sourceMappingURL=tunnel-
|
|
335
|
+
export { NGROK_AUTHTOKEN_VARIABLE as a, readDiscordCredentials as i, describeQuickTunnel as n, NGROK_INSTALL_HINT as o, endpointUrl as r, NGROK_PACKAGE as s, Tunnel as t };
|
|
336
|
+
//# sourceMappingURL=tunnel-D3WxypOt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel-D3WxypOt.js","names":[],"sources":["../src/dev/tunnel-providers.ts","../src/dev/tunnel.ts"],"sourcesContent":["import type { ResolvedStarsConfig, ResolvedTunnelConfig, StarsTunnelProvider } from '@wolfstar/schema';\nimport { startTunnel, type Tunnel as UntunTunnel } from 'untun';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { readProjectVariable } from '../utils/project-env.js';\nimport { importFromProject } from '../utils/project.js';\n\nexport type QuickTunnelConfig = Extract<ResolvedTunnelConfig, { mode: 'quick' }>;\n\n/** A tunnel a provider opened: where it is reachable, and how to take it down. */\nexport interface TunnelHandle {\n\tgetURL(): Promise<string>;\n\tclose(): Promise<void>;\n}\n\nexport interface TunnelOpenContext {\n\tconfig: ResolvedStarsConfig;\n\ttunnel: QuickTunnelConfig;\n\t/**\n\t * Reports that the tunnel ended without being closed. Returns whether it was handled (the tunnel was up and wanted),\n\t * so the provider knows whether it is done watching for it.\n\t */\n\tonStopped(error: Error): boolean;\n}\n\n/** A service that exposes the bot's local address publicly. */\nexport interface TunnelProvider {\n\treadonly name: StarsTunnelProvider;\n\t/**\n\t * Opens a tunnel to `target`. Resolves to `undefined` when the user cancelled the setup, and rejects when the tunnel\n\t * cannot be opened; either way nothing stays open.\n\t */\n\topen(target: string, context: TunnelOpenContext): Promise<TunnelHandle | undefined>;\n}\n\nconst SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'] as const;\n\nfunction isCloudflaredExit(reason: unknown): reason is Error {\n\treturn reason instanceof Error && reason.message.startsWith('cloudflared exited (');\n}\n\n/**\n * The `cloudflared` quick tunnel, opened through `untun` (no account, a new hostname on every run).\n *\n * `untun` is wrapped rather than changed: it adds process signal listeners and leaves a promise unhandled whenever\n * `cloudflared` exits, both of which would end `stars dev`, so they are neutralised here.\n */\nexport class CloudflaredProvider implements TunnelProvider {\n\tpublic readonly name = 'cloudflared';\n\t#startTunnel: typeof startTunnel;\n\t#rejectionGuard: ((reason: unknown) => void) | null = null;\n\n\tpublic constructor(start: typeof startTunnel = startTunnel) {\n\t\tthis.#startTunnel = start;\n\t}\n\n\tpublic async open(target: string, context: TunnelOpenContext): Promise<TunnelHandle | undefined> {\n\t\tthis.#installRejectionGuard(context);\n\n\t\tlet tunnel: UntunTunnel | undefined;\n\t\ttry {\n\t\t\ttunnel = await this.#startWithoutSignalHandlers(target);\n\t\t} catch (error) {\n\t\t\tthis.#releaseRejectionGuard();\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (!tunnel) {\n\t\t\tthis.#releaseRejectionGuard();\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst opened = tunnel;\n\t\treturn {\n\t\t\tgetURL: async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await opened.getURL();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.#releaseRejectionGuard();\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\tclose: async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait opened.close();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.#releaseRejectionGuard();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * `untun` adds its own `SIGINT`/`SIGTERM`/`SIGHUP` listeners that call `process.exit()` once cloudflared is closed,\n\t * which races `stars dev`'s graceful shutdown (it closes the tunnel through {@link TunnelHandle.close} anyway), so they\n\t * are removed.\n\t */\n\tasync #startWithoutSignalHandlers(target: string): Promise<UntunTunnel | undefined> {\n\t\tconst before = SIGNALS.map((signal) => new Set(process.listeners(signal)));\n\t\ttry {\n\t\t\treturn await this.#startTunnel({ url: target, acceptCloudflareNotice: true });\n\t\t} finally {\n\t\t\tSIGNALS.forEach((signal, index) => {\n\t\t\t\tfor (const listener of process.listeners(signal)) {\n\t\t\t\t\tif (!before[index]!.has(listener)) process.off(signal, listener);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * `untun` rejects a promise per connection when cloudflared exits and never handles it (and its location pattern\n\t * does not match lowercase codes such as `mxp03`, so that promise is never settled before), which Node reports as an\n\t * unhandled rejection that ends the process, whenever the tunnel closes. The guard swallows that one error for as\n\t * long as this tunnel exists; anything else it sees is thrown again, as if it was not installed.\n\t */\n\t#installRejectionGuard(context: TunnelOpenContext): void {\n\t\tif (this.#rejectionGuard) return;\n\n\t\tconst guard = (reason: unknown): void => {\n\t\t\tif (!isCloudflaredExit(reason)) {\n\t\t\t\t// The guard is the only listener, so without this the rejection would be ignored instead of ending the process.\n\t\t\t\tif (process.listeners('unhandledRejection').length === 1) throw reason;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (context.onStopped(reason)) this.#releaseRejectionGuard();\n\t\t};\n\n\t\tthis.#rejectionGuard = guard;\n\t\tprocess.on('unhandledRejection', guard);\n\t}\n\n\t/** Node reports an unhandled rejection after the microtasks that follow it, so the guard has to outlive them. */\n\t#releaseRejectionGuard(): void {\n\t\tconst guard = this.#rejectionGuard;\n\t\tthis.#rejectionGuard = null;\n\t\tif (guard) setImmediate(() => process.off('unhandledRejection', guard));\n\t}\n}\n\n/** What the CLI uses of `@ngrok/ngrok`, so the package itself is never a dependency of the CLI. */\nexport interface NgrokListener {\n\turl(): string | null;\n\tclose(): Promise<void>;\n}\n\nexport interface NgrokModule {\n\tforward(options: { addr: string | number; authtoken: string; domain?: string }): Promise<NgrokListener>;\n}\n\n/** Loads `@ngrok/ngrok`; replaced in tests. */\nexport type NgrokLoader = (root: string) => Promise<NgrokModule>;\n\nexport const NGROK_PACKAGE = '@ngrok/ngrok';\nexport const NGROK_AUTHTOKEN_VARIABLE = 'NGROK_AUTHTOKEN';\nexport const NGROK_INSTALL_HINT = `Install it with \\`pnpm add -D ${NGROK_PACKAGE}\\`, or remove \\`provider: 'ngrok'\\` from \\`dev.tunnel\\`.`;\n\n/** Imports `@ngrok/ngrok` from the project, where it is an optional peer of the CLI. */\nexport const loadNgrok: NgrokLoader = async (root) => {\n\tconst imported = await importFromProject<NgrokModule & { default?: NgrokModule }>(root, NGROK_PACKAGE, NGROK_INSTALL_HINT);\n\treturn typeof imported.forward === 'function' ? imported : imported.default!;\n};\n\n/**\n * ngrok through its official Node SDK. It runs in-process (no binary is downloaded), authenticates with\n * `NGROK_AUTHTOKEN` and can bind a domain reserved in the account.\n */\nexport class NgrokProvider implements TunnelProvider {\n\tpublic readonly name = 'ngrok';\n\t#load: NgrokLoader;\n\n\tpublic constructor(load: NgrokLoader = loadNgrok) {\n\t\tthis.#load = load;\n\t}\n\n\tpublic async open(target: string, { config, tunnel }: TunnelOpenContext): Promise<TunnelHandle> {\n\t\tconst ngrok = await this.#load(config.root);\n\t\tconst authtoken = readProjectVariable(config, process.env, [NGROK_AUTHTOKEN_VARIABLE], { fresh: true });\n\t\tif (!authtoken) throw cliDiagnostics.NGROK_AUTHTOKEN_MISSING({});\n\n\t\tconst listener = await ngrok.forward({ addr: ngrokAddress(target), authtoken, ...(tunnel.domain ? { domain: tunnel.domain } : {}) });\n\t\tconst url = listener.url();\n\t\tif (!url) {\n\t\t\tawait listener.close();\n\t\t\tthrow new Error('ngrok did not report a URL for the tunnel');\n\t\t}\n\n\t\treturn { getURL: () => Promise.resolve(url), close: () => listener.close() };\n\t}\n}\n\n/** ngrok forwards to `host:port`; a full `http` URL is reduced to it, anything else (`https`, a socket) is passed on. */\nexport function ngrokAddress(target: string): string {\n\ttry {\n\t\tconst url = new URL(target);\n\t\tif (url.protocol === 'http:') return `${url.hostname}:${url.port || 80}`;\n\t} catch {\n\t\t// Not a URL, so already an address.\n\t}\n\n\treturn target;\n}\n","import { DEFAULT_TUNNEL_PROVIDER, type ResolvedStarsConfig, type ResolvedTunnelConfig, type StarsTunnelProvider } from '@wolfstar/schema';\nimport { EventEmitter } from 'node:events';\nimport { Diagnostic } from 'nostics';\nimport type { startTunnel } from 'untun';\nimport type { LogLevel } from '../utils/log-buffer.js';\nimport { readProjectVariable } from '../utils/project-env.js';\nimport {\n\tCloudflaredProvider,\n\tNgrokProvider,\n\ttype NgrokLoader,\n\ttype QuickTunnelConfig,\n\ttype TunnelHandle,\n\ttype TunnelProvider\n} from './tunnel-providers.js';\n\nexport type TunnelState = 'off' | 'starting' | 'up' | 'failed';\n\nexport interface TunnelEvents {\n\tlog: [level: LogLevel, text: string];\n\tstate: [state: TunnelState, url: string | null];\n}\n\nexport interface TunnelOptions {\n\t/** Override for tests: how the `cloudflared` provider opens its tunnel. */\n\tstartTunnel?: typeof startTunnel;\n\t/** Override for tests: how the `ngrok` provider loads `@ngrok/ngrok`. */\n\tloadNgrok?: NgrokLoader;\n}\n\n/**\n * Exposes the bot's interactions endpoint publicly while `stars dev` runs.\n *\n * `dev.tunnel: true` opens a quick tunnel through the configured provider: `cloudflared` via `untun` by default (it\n * changes on every run), or `ngrok`. A configured https URL is only probed, since the user already serves it. Writing\n * the URL to the Discord application is opt-in through `dev.tunnel.updateEndpoint`, because it edits a live application.\n */\nexport class Tunnel extends EventEmitter<TunnelEvents> {\n\t#providers: Record<StarsTunnelProvider, TunnelProvider>;\n\t#tunnel: TunnelHandle | null = null;\n\t#state: TunnelState = 'off';\n\t#url: string | null = null;\n\t#wanted = false;\n\n\tpublic constructor(\n\t\tprivate readonly config: ResolvedStarsConfig,\n\t\toptions: TunnelOptions = {}\n\t) {\n\t\tsuper();\n\t\tthis.#providers = { cloudflared: new CloudflaredProvider(options.startTunnel), ngrok: new NgrokProvider(options.loadNgrok) };\n\t}\n\n\tpublic get state(): TunnelState {\n\t\treturn this.#state;\n\t}\n\n\t/** The public URL of the interactions endpoint, `null` until the tunnel is up. */\n\tpublic get url(): string | null {\n\t\treturn this.#url;\n\t}\n\n\tpublic async start(forceQuick = false): Promise<void> {\n\t\tif (this.#state === 'starting' || this.#state === 'up') return;\n\t\tconst configured = this.config.dev.tunnel;\n\t\tconst tunnel: ResolvedTunnelConfig =\n\t\t\tforceQuick && configured.mode === 'off'\n\t\t\t\t? { mode: 'quick', provider: DEFAULT_TUNNEL_PROVIDER, domain: null, path: '/', updateEndpoint: false }\n\t\t\t\t: configured;\n\t\tif (tunnel.mode === 'off') return;\n\n\t\tthis.#wanted = true;\n\t\tthis.#setState('starting', null);\n\t\tconst url = tunnel.mode === 'url' ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel(tunnel);\n\t\tif (!url || !this.#wanted) return;\n\n\t\tthis.#setState('up', endpointUrl(url, tunnel.path));\n\t\tthis.emit('log', 'success', `Tunnel ready at ${this.#url}`);\n\t\tif (tunnel.updateEndpoint) await this.#updateInteractionsEndpoint(this.#url!);\n\t}\n\n\t/** Toggles the configured tunnel, opening a quick tunnel when the project did not configure one. */\n\tpublic toggle(): Promise<void> {\n\t\treturn this.#state === 'starting' || this.#state === 'up' ? this.close() : this.start(true);\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tthis.#wanted = false;\n\t\tconst tunnel = this.#tunnel;\n\t\tthis.#tunnel = null;\n\t\tthis.#setState('off', null);\n\t\t// Without a tunnel, `#openQuickTunnel` is still waiting for one and closes it when it arrives.\n\t\tawait tunnel?.close();\n\t}\n\n\tasync #useConfiguredUrl(tunnel: Extract<ResolvedTunnelConfig, { mode: 'url' }>): Promise<string | null> {\n\t\tconst reachable = await probe(endpointUrl(tunnel.url, tunnel.path));\n\t\tif (!reachable) {\n\t\t\tthis.emit('log', 'warn', `${tunnel.url} did not answer; make sure it forwards to ${this.config.dev.url ?? 'the bot'}`);\n\t\t}\n\n\t\treturn tunnel.url;\n\t}\n\n\tasync #openQuickTunnel(tunnel: QuickTunnelConfig): Promise<string | null> {\n\t\tconst target = this.config.dev.url;\n\t\tif (!target) {\n\t\t\tthis.emit('log', 'error', 'A quick tunnel needs `dev.url` to know what to forward to');\n\t\t\tthis.#setState('failed', null);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst provider = this.#providers[tunnel.provider];\n\t\tthis.emit('log', 'info', `Opening a ${provider.name} quick tunnel…`);\n\n\t\ttry {\n\t\t\tconst handle = await provider.open(target, {\n\t\t\t\tconfig: this.config,\n\t\t\t\ttunnel,\n\t\t\t\tonStopped: (error) => this.#stoppedUnexpectedly(provider.name, error)\n\t\t\t});\n\t\t\tif (!this.#wanted) {\n\t\t\t\tawait handle?.close();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (!handle) {\n\t\t\t\tthis.emit('log', 'error', `${provider.name} setup was cancelled`);\n\t\t\t\tthis.#setState('failed', null);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tthis.#tunnel = handle;\n\t\t\treturn await handle.getURL();\n\t\t} catch (error) {\n\t\t\t// Closing a tunnel can make its process exit, which a provider may report as a failure: that is not one.\n\t\t\tif (!this.#wanted) return null;\n\t\t\tthis.emit('log', 'error', `${provider.name} failed: ${describeError(error)}`);\n\t\t\tthis.#setState('failed', null);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/** Whether the report was about the tunnel in use: one that is up and still wanted. */\n\t#stoppedUnexpectedly(provider: StarsTunnelProvider, error: Error): boolean {\n\t\tif (this.#state !== 'up' || !this.#wanted) return false;\n\t\tthis.#tunnel = null;\n\t\tthis.emit('log', 'error', `${provider} stopped unexpectedly: ${error.message}`);\n\t\tthis.#setState('failed', null);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Points the Discord application's interactions endpoint at the tunnel. Discord validates the endpoint with a\n\t * signed ping before accepting it, so the bot has to be up already — this runs after the first successful build.\n\t */\n\tasync #updateInteractionsEndpoint(url: string): Promise<void> {\n\t\tconst credentials = readDiscordCredentials(this.config);\n\t\tif (!credentials) {\n\t\t\tthis.emit('log', 'warn', 'Skipped the interactions endpoint update: DISCORD_TOKEN is not set');\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await fetch('https://discord.com/api/v10/applications/@me', {\n\t\t\t\tmethod: 'PATCH',\n\t\t\t\theaders: { authorization: `Bot ${credentials.token}`, 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ interactions_endpoint_url: url }),\n\t\t\t\tsignal: AbortSignal.timeout(10_000)\n\t\t\t});\n\n\t\t\tif (response.ok) this.emit('log', 'success', `Interactions endpoint set to ${url}`);\n\t\t\telse\n\t\t\t\tthis.emit(\n\t\t\t\t\t'log',\n\t\t\t\t\t'error',\n\t\t\t\t\t`Discord rejected the interactions endpoint (${response.status}): ${(await response.text()).slice(0, 300)}`\n\t\t\t\t);\n\t\t} catch (error) {\n\t\t\tthis.emit('log', 'error', `Failed to update the interactions endpoint: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\t#setState(state: TunnelState, url: string | null): void {\n\t\tthis.#state = state;\n\t\tthis.#url = url;\n\t\tthis.emit('state', state, url);\n\t}\n}\n\n/**\n * Joins the tunnel's origin with the path the interactions endpoint is served on.\n */\nexport function endpointUrl(origin: string, path: string): string {\n\treturn path === '/' ? origin : new URL(path, origin.endsWith('/') ? origin : `${origin}/`).href;\n}\n\nasync function probe(url: string): Promise<boolean> {\n\ttry {\n\t\tconst response = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) });\n\t\treturn response.status < 500;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport interface DiscordCredentials {\n\ttoken: string;\n\tapplicationId: string | null;\n}\n\n/**\n * Reads the Discord credentials the CLI needs from the environment, falling back to the project's `.env` files the\n * way the bot itself does once it starts.\n */\nexport function readDiscordCredentials(config: ResolvedStarsConfig, env: NodeJS.ProcessEnv = process.env): DiscordCredentials | null {\n\tconst token = readProjectVariable(config, env, ['DISCORD_TOKEN', 'TOKEN']);\n\tif (!token) return null;\n\n\treturn {\n\t\ttoken,\n\t\tapplicationId: readProjectVariable(config, env, ['DISCORD_APPLICATION_ID', 'APPLICATION_ID', 'DISCORD_CLIENT_ID', 'CLIENT_ID'])\n\t};\n}\n\n/** How a quick tunnel is described to the user, for `stars info` and `stars doctor`. */\nexport function describeQuickTunnel(tunnel: QuickTunnelConfig): string {\n\treturn `${tunnel.provider} quick tunnel${tunnel.domain ? ` on ${tunnel.domain}` : ''}`;\n}\n\n/** A diagnostic's own advice is part of the message, since the dev log has no place to show it separately. */\nfunction describeError(error: unknown): string {\n\tif (error instanceof Diagnostic) return error.fix ? `${error.message}. ${error.fix}` : error.message;\n\treturn error instanceof Error ? error.message : String(error);\n}\n"],"mappings":";;;;;;;;;AAkCA,MAAM,UAAU;CAAC;CAAU;CAAW;AAAQ;AAE9C,SAAS,kBAAkB,QAAkC;CAC5D,OAAO,kBAAkB,SAAS,OAAO,QAAQ,WAAW,sBAAsB;AACnF;;;;;;;AAQA,IAAa,sBAAb,MAA2D;CAC1D,AAAgB,OAAO;CACvB;CACA,kBAAsD;CAEtD,AAAO,YAAY,QAA4B,aAAa;EAC3D,KAAK,eAAe;CACrB;CAEA,MAAa,KAAK,QAAgB,SAA+D;EAChG,KAAK,uBAAuB,OAAO;EAEnC,IAAI;EACJ,IAAI;GACH,SAAS,MAAM,KAAK,4BAA4B,MAAM;EACvD,SAAS,OAAO;GACf,KAAK,uBAAuB;GAC5B,MAAM;EACP;EAEA,IAAI,CAAC,QAAQ;GACZ,KAAK,uBAAuB;GAC5B;EACD;EAEA,MAAM,SAAS;EACf,OAAO;GACN,QAAQ,YAAY;IACnB,IAAI;KACH,OAAO,MAAM,OAAO,OAAO;IAC5B,SAAS,OAAO;KACf,KAAK,uBAAuB;KAC5B,MAAM;IACP;GACD;GACA,OAAO,YAAY;IAClB,IAAI;KACH,MAAM,OAAO,MAAM;IACpB,UAAU;KACT,KAAK,uBAAuB;IAC7B;GACD;EACD;CACD;;;;;;CAOA,MAAM,4BAA4B,QAAkD;EACnF,MAAM,SAAS,QAAQ,KAAK,WAAW,IAAI,IAAI,QAAQ,UAAU,MAAM,CAAC,CAAC;EACzE,IAAI;GACH,OAAO,MAAM,KAAK,aAAa;IAAE,KAAK;IAAQ,wBAAwB;GAAK,CAAC;EAC7E,UAAU;GACT,QAAQ,SAAS,QAAQ,UAAU;IAClC,KAAK,MAAM,YAAY,QAAQ,UAAU,MAAM,GAC9C,IAAI,CAAC,OAAO,MAAM,CAAE,IAAI,QAAQ,GAAG,QAAQ,IAAI,QAAQ,QAAQ;GAEjE,CAAC;EACF;CACD;;;;;;;CAQA,uBAAuB,SAAkC;EACxD,IAAI,KAAK,iBAAiB;EAE1B,MAAM,SAAS,WAA0B;GACxC,IAAI,CAAC,kBAAkB,MAAM,GAAG;IAE/B,IAAI,QAAQ,UAAU,oBAAoB,CAAC,CAAC,WAAW,GAAG,MAAM;IAChE;GACD;GAEA,IAAI,QAAQ,UAAU,MAAM,GAAG,KAAK,uBAAuB;EAC5D;EAEA,KAAK,kBAAkB;EACvB,QAAQ,GAAG,sBAAsB,KAAK;CACvC;;CAGA,yBAA+B;EAC9B,MAAM,QAAQ,KAAK;EACnB,KAAK,kBAAkB;EACvB,IAAI,OAAO,mBAAmB,QAAQ,IAAI,sBAAsB,KAAK,CAAC;CACvE;AACD;AAeA,MAAa,gBAAgB;AAC7B,MAAa,2BAA2B;AACxC,MAAa,qBAAqB,iCAAiC,cAAc;;AAGjF,MAAa,YAAyB,OAAO,SAAS;CACrD,MAAM,WAAW,MAAM,kBAA2D,MAAM,eAAe,kBAAkB;CACzH,OAAO,OAAO,SAAS,YAAY,aAAa,WAAW,SAAS;AACrE;;;;;AAMA,IAAa,gBAAb,MAAqD;CACpD,AAAgB,OAAO;CACvB;CAEA,AAAO,YAAY,OAAoB,WAAW;EACjD,KAAK,QAAQ;CACd;CAEA,MAAa,KAAK,QAAgB,EAAE,QAAQ,UAAoD;EAC/F,MAAM,QAAQ,MAAM,KAAK,MAAM,OAAO,IAAI;EAC1C,MAAM,YAAY,oBAAoB,QAAQ,QAAQ,KAAK,CAAC,wBAAwB,GAAG,EAAE,OAAO,KAAK,CAAC;EACtG,IAAI,CAAC,WAAW,MAAM,eAAe,wBAAwB,CAAC,CAAC;EAE/D,MAAM,WAAW,MAAM,MAAM,QAAQ;GAAE,MAAM,aAAa,MAAM;GAAG;GAAW,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;EAAG,CAAC;EACnI,MAAM,MAAM,SAAS,IAAI;EACzB,IAAI,CAAC,KAAK;GACT,MAAM,SAAS,MAAM;GACrB,MAAM,IAAI,MAAM,2CAA2C;EAC5D;EAEA,OAAO;GAAE,cAAc,QAAQ,QAAQ,GAAG;GAAG,aAAa,SAAS,MAAM;EAAE;CAC5E;AACD;;AAGA,SAAgB,aAAa,QAAwB;CACpD,IAAI;EACH,MAAM,MAAM,IAAI,IAAI,MAAM;EAC1B,IAAI,IAAI,aAAa,SAAS,OAAO,GAAG,IAAI,SAAS,GAAG,IAAI,QAAQ;CACrE,QAAQ,CAER;CAEA,OAAO;AACR;;;;;;;;;;;ACrKA,IAAa,SAAb,cAA4B,aAA2B;CAQpC;CAPlB;CACA,UAA+B;CAC/B,SAAsB;CACtB,OAAsB;CACtB,UAAU;CAEV,AAAO,YACN,AAAiB,QACjB,UAAyB,CAAC,GACzB;EACD,MAAM;EAHW;EAIjB,KAAK,aAAa;GAAE,aAAa,IAAI,oBAAoB,QAAQ,WAAW;GAAG,OAAO,IAAI,cAAc,QAAQ,SAAS;EAAE;CAC5H;CAEA,IAAW,QAAqB;EAC/B,OAAO,KAAK;CACb;;CAGA,IAAW,MAAqB;EAC/B,OAAO,KAAK;CACb;CAEA,MAAa,MAAM,aAAa,OAAsB;EACrD,IAAI,KAAK,WAAW,cAAc,KAAK,WAAW,MAAM;EACxD,MAAM,aAAa,KAAK,OAAO,IAAI;EACnC,MAAM,SACL,cAAc,WAAW,SAAS,QAC/B;GAAE,MAAM;GAAS,UAAU;GAAyB,QAAQ;GAAM,MAAM;GAAK,gBAAgB;EAAM,IACnG;EACJ,IAAI,OAAO,SAAS,OAAO;EAE3B,KAAK,UAAU;EACf,KAAK,UAAU,YAAY,IAAI;EAC/B,MAAM,MAAM,OAAO,SAAS,QAAQ,MAAM,KAAK,kBAAkB,MAAM,IAAI,MAAM,KAAK,iBAAiB,MAAM;EAC7G,IAAI,CAAC,OAAO,CAAC,KAAK,SAAS;EAE3B,KAAK,UAAU,MAAM,YAAY,KAAK,OAAO,IAAI,CAAC;EAClD,KAAK,KAAK,OAAO,WAAW,mBAAmB,KAAK,MAAM;EAC1D,IAAI,OAAO,gBAAgB,MAAM,KAAK,4BAA4B,KAAK,IAAK;CAC7E;;CAGA,AAAO,SAAwB;EAC9B,OAAO,KAAK,WAAW,cAAc,KAAK,WAAW,OAAO,KAAK,MAAM,IAAI,KAAK,MAAM,IAAI;CAC3F;CAEA,MAAa,QAAuB;EACnC,KAAK,UAAU;EACf,MAAM,SAAS,KAAK;EACpB,KAAK,UAAU;EACf,KAAK,UAAU,OAAO,IAAI;EAE1B,MAAM,QAAQ,MAAM;CACrB;CAEA,MAAM,kBAAkB,QAAgF;EAEvG,IAAI,CAAC,MADmB,MAAM,YAAY,OAAO,KAAK,OAAO,IAAI,CAAC,GAEjE,KAAK,KAAK,OAAO,QAAQ,GAAG,OAAO,IAAI,4CAA4C,KAAK,OAAO,IAAI,OAAO,WAAW;EAGtH,OAAO,OAAO;CACf;CAEA,MAAM,iBAAiB,QAAmD;EACzE,MAAM,SAAS,KAAK,OAAO,IAAI;EAC/B,IAAI,CAAC,QAAQ;GACZ,KAAK,KAAK,OAAO,SAAS,2DAA2D;GACrF,KAAK,UAAU,UAAU,IAAI;GAC7B,OAAO;EACR;EAEA,MAAM,WAAW,KAAK,WAAW,OAAO;EACxC,KAAK,KAAK,OAAO,QAAQ,aAAa,SAAS,KAAK,eAAe;EAEnE,IAAI;GACH,MAAM,SAAS,MAAM,SAAS,KAAK,QAAQ;IAC1C,QAAQ,KAAK;IACb;IACA,YAAY,UAAU,KAAK,qBAAqB,SAAS,MAAM,KAAK;GACrE,CAAC;GACD,IAAI,CAAC,KAAK,SAAS;IAClB,MAAM,QAAQ,MAAM;IACpB,OAAO;GACR;GACA,IAAI,CAAC,QAAQ;IACZ,KAAK,KAAK,OAAO,SAAS,GAAG,SAAS,KAAK,qBAAqB;IAChE,KAAK,UAAU,UAAU,IAAI;IAC7B,OAAO;GACR;GAEA,KAAK,UAAU;GACf,OAAO,MAAM,OAAO,OAAO;EAC5B,SAAS,OAAO;GAEf,IAAI,CAAC,KAAK,SAAS,OAAO;GAC1B,KAAK,KAAK,OAAO,SAAS,GAAG,SAAS,KAAK,WAAW,cAAc,KAAK,GAAG;GAC5E,KAAK,UAAU,UAAU,IAAI;GAC7B,OAAO;EACR;CACD;;CAGA,qBAAqB,UAA+B,OAAuB;EAC1E,IAAI,KAAK,WAAW,QAAQ,CAAC,KAAK,SAAS,OAAO;EAClD,KAAK,UAAU;EACf,KAAK,KAAK,OAAO,SAAS,GAAG,SAAS,yBAAyB,MAAM,SAAS;EAC9E,KAAK,UAAU,UAAU,IAAI;EAC7B,OAAO;CACR;;;;;CAMA,MAAM,4BAA4B,KAA4B;EAC7D,MAAM,cAAc,uBAAuB,KAAK,MAAM;EACtD,IAAI,CAAC,aAAa;GACjB,KAAK,KAAK,OAAO,QAAQ,oEAAoE;GAC7F;EACD;EAEA,IAAI;GACH,MAAM,WAAW,MAAM,MAAM,gDAAgD;IAC5E,QAAQ;IACR,SAAS;KAAE,eAAe,OAAO,YAAY;KAAS,gBAAgB;IAAmB;IACzF,MAAM,KAAK,UAAU,EAAE,2BAA2B,IAAI,CAAC;IACvD,QAAQ,YAAY,QAAQ,GAAM;GACnC,CAAC;GAED,IAAI,SAAS,IAAI,KAAK,KAAK,OAAO,WAAW,gCAAgC,KAAK;QAEjF,KAAK,KACJ,OACA,SACA,+CAA+C,SAAS,OAAO,MAAM,MAAM,SAAS,KAAK,EAAC,CAAE,MAAM,GAAG,GAAG,GACzG;EACF,SAAS,OAAO;GACf,KAAK,KAAK,OAAO,SAAS,+CAA+C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;EAClI;CACD;CAEA,UAAU,OAAoB,KAA0B;EACvD,KAAK,SAAS;EACd,KAAK,OAAO;EACZ,KAAK,KAAK,SAAS,OAAO,GAAG;CAC9B;AACD;;;;AAKA,SAAgB,YAAY,QAAgB,MAAsB;CACjE,OAAO,SAAS,MAAM,SAAS,IAAI,IAAI,MAAM,OAAO,SAAS,GAAG,IAAI,SAAS,GAAG,OAAO,EAAE,CAAC,CAAC;AAC5F;AAEA,eAAe,MAAM,KAA+B;CACnD,IAAI;EAEH,QAAO,MADgB,MAAM,KAAK;GAAE,QAAQ;GAAQ,QAAQ,YAAY,QAAQ,GAAI;EAAE,CAAC,EACxE,CAAC,SAAS;CAC1B,QAAQ;EACP,OAAO;CACR;AACD;;;;;AAWA,SAAgB,uBAAuB,QAA6B,MAAyB,QAAQ,KAAgC;CACpI,MAAM,QAAQ,oBAAoB,QAAQ,KAAK,CAAC,iBAAiB,OAAO,CAAC;CACzE,IAAI,CAAC,OAAO,OAAO;CAEnB,OAAO;EACN;EACA,eAAe,oBAAoB,QAAQ,KAAK;GAAC;GAA0B;GAAkB;GAAqB;EAAW,CAAC;CAC/H;AACD;;AAGA,SAAgB,oBAAoB,QAAmC;CACtE,OAAO,GAAG,OAAO,SAAS,eAAe,OAAO,SAAS,OAAO,OAAO,WAAW;AACnF;;AAGA,SAAS,cAAc,OAAwB;CAC9C,IAAI,iBAAiB,YAAY,OAAO,MAAM,MAAM,GAAG,MAAM,QAAQ,IAAI,MAAM,QAAQ,MAAM;CAC7F,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC7D"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { i as shouldUseColor } from "./output-mode-Bp7Da8vJ.js";
|
|
2
|
-
import { t as cliDiagnostics } from "./diagnostics-
|
|
3
|
-
import { d as
|
|
4
|
-
import { n as reportWarnings, t as prepareProject } from "./_shared-
|
|
5
|
-
import { n as resolveTypecheckCommand } from "./typechecker-
|
|
2
|
+
import { t as cliDiagnostics } from "./diagnostics-BKc--u_3.js";
|
|
3
|
+
import { d as projectArgs, f as resolveCwd, n as loadProject } from "./hooks-DqlScRZA.js";
|
|
4
|
+
import { n as reportWarnings, t as prepareProject } from "./_shared-Bqw7h4O0.js";
|
|
5
|
+
import { n as resolveTypecheckCommand } from "./typechecker-zjnzC2L_.js";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
7
7
|
import { displayPath } from "@wolfstar/schema";
|
|
8
8
|
import { createColors } from "colorette";
|
|
@@ -63,4 +63,4 @@ var typecheck_default = defineCommand({
|
|
|
63
63
|
|
|
64
64
|
//#endregion
|
|
65
65
|
export { typecheck_default as default };
|
|
66
|
-
//# sourceMappingURL=typecheck-
|
|
66
|
+
//# sourceMappingURL=typecheck-BIscCUNQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typecheck-
|
|
1
|
+
{"version":3,"file":"typecheck-BIscCUNQ.js","names":[],"sources":["../src/commands/typecheck.ts"],"sourcesContent":["import { displayPath } from '@wolfstar/schema';\nimport { defineCommand } from 'citty';\nimport { createColors } from 'colorette';\nimport { spawn } from 'node:child_process';\nimport { projectArgs, resolveCwd, type ProjectArgs } from '../utils/args.js';\nimport { resolveTypecheckCommand } from '../dev/typechecker.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport { loadProject } from '../utils/hooks.js';\nimport { shouldUseColor } from '../utils/output-mode.js';\nimport { prepareProject, reportWarnings } from './_shared.js';\n\nexport interface TypecheckTaskOptions extends ProjectArgs {\n\tstdout?: NodeJS.WritableStream;\n}\n\n/**\n * Type-checks the project once, the way `nuxt typecheck` does: regenerates `.stars/` first, then runs the project's own\n * checker (`dev.typecheck.checker`, `golar` or `tsc`) on every project: the generated app and node configs from\n * compatibility version 6, the single tsconfig below it. A project root `tsconfig.json` that only references them is\n * never passed to `tsc -p`, which would check nothing.\n */\nexport async function runTypecheck(options: TypecheckTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() });\n\tconst { config, hooks } = await loadProject({ cwd: resolveCwd(options), configFile: options.config });\n\tawait reportWarnings(config, (text) => process.stderr.write(`${text}\\n`), { production: false });\n\tawait prepareProject(config, hooks);\n\n\tconst { projects } = config.dev.typecheck;\n\tif (projects.length === 0) throw cliDiagnostics.TYPECHECK_NO_PROJECT({ root: config.root });\n\n\tconst failed: string[] = [];\n\tfor (const project of projects) {\n\t\tconst { command, args, node } = resolveTypecheckCommand(config, { project, once: true });\n\t\tstdout.write(`${colors.dim('stars')} typecheck ${displayPath(config.root, project)}\\n`);\n\t\tconst code = await new Promise<number | null>((resolve, reject) => {\n\t\t\tconst child = spawn(node ? process.execPath : command, node ? [command, ...args] : [...args], {\n\t\t\t\tcwd: config.root,\n\t\t\t\tenv: process.env,\n\t\t\t\tstdio: 'inherit',\n\t\t\t\twindowsHide: true\n\t\t\t});\n\t\t\tchild.once('error', reject);\n\t\t\tchild.once('exit', resolve);\n\t\t});\n\t\tif (code !== 0) failed.push(displayPath(config.root, project));\n\t}\n\n\tif (failed.length > 0) throw cliDiagnostics.TYPECHECK_FAILED({ projects: failed.join(', ') });\n\tstdout.write(`${colors.dim('stars')} ${colors.green('no type errors')}\\n`);\n}\n\nexport default defineCommand({\n\tmeta: {\n\t\tname: 'typecheck',\n\t\tdescription: 'Type-check the project, including the generated app and node TypeScript configurations'\n\t},\n\targs: { ...projectArgs },\n\tasync run({ args }) {\n\t\tawait runTypecheck({ config: args.config, cwd: args.cwd });\n\t}\n});\n"],"mappings":";;;;;;;;;;;;;;;;;AAqBA,eAAsB,aAAa,SAA8C;CAChF,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,EAAE,CAAC;CAC1D,MAAM,EAAE,QAAQ,UAAU,MAAM,YAAY;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CACpG,MAAM,eAAe,SAAS,SAAS,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG,GAAG,EAAE,YAAY,MAAM,CAAC;CAC/F,MAAM,eAAe,QAAQ,KAAK;CAElC,MAAM,EAAE,aAAa,OAAO,IAAI;CAChC,IAAI,SAAS,WAAW,GAAG,MAAM,eAAe,qBAAqB,EAAE,MAAM,OAAO,KAAK,CAAC;CAE1F,MAAM,SAAmB,CAAC;CAC1B,KAAK,MAAM,WAAW,UAAU;EAC/B,MAAM,EAAE,SAAS,MAAM,SAAS,wBAAwB,QAAQ;GAAE;GAAS,MAAM;EAAK,CAAC;EACvF,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,aAAa,YAAY,OAAO,MAAM,OAAO,EAAE,GAAG;EAWtF,IAAI,MAVe,IAAI,SAAwB,SAAS,WAAW;GAClE,MAAM,QAAQ,MAAM,OAAO,QAAQ,WAAW,SAAS,OAAO,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,IAAI,GAAG;IAC7F,KAAK,OAAO;IACZ,KAAK,QAAQ;IACb,OAAO;IACP,aAAa;GACd,CAAC;GACD,MAAM,KAAK,SAAS,MAAM;GAC1B,MAAM,KAAK,QAAQ,OAAO;EAC3B,CAAC,MACY,GAAG,OAAO,KAAK,YAAY,OAAO,MAAM,OAAO,CAAC;CAC9D;CAEA,IAAI,OAAO,SAAS,GAAG,MAAM,eAAe,iBAAiB,EAAE,UAAU,OAAO,KAAK,IAAI,EAAE,CAAC;CAC5F,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,GAAG,OAAO,MAAM,gBAAgB,EAAE,GAAG;AAC1E;AAEA,wBAAe,cAAc;CAC5B,MAAM;EACL,MAAM;EACN,aAAa;CACd;CACA,MAAM,EAAE,GAAG,YAAY;CACvB,MAAM,IAAI,EAAE,QAAQ;EACnB,MAAM,aAAa;GAAE,QAAQ,KAAK;GAAQ,KAAK,KAAK;EAAI,CAAC;CAC1D;AACD,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as cliDiagnostics } from "./diagnostics-
|
|
2
|
-
import { r as resolveBinary } from "./project-
|
|
3
|
-
import { i as createLineSplitter, n as resolveTscBinary } from "./tsc-
|
|
1
|
+
import { t as cliDiagnostics } from "./diagnostics-BKc--u_3.js";
|
|
2
|
+
import { r as resolveBinary } from "./project-EJyKro-M.js";
|
|
3
|
+
import { i as createLineSplitter, n as resolveTscBinary } from "./tsc-6su4cT6K.js";
|
|
4
4
|
import { EventEmitter } from "node:events";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
|
|
@@ -210,4 +210,4 @@ function missing(config, name, install) {
|
|
|
210
210
|
|
|
211
211
|
//#endregion
|
|
212
212
|
export { resolveTypecheckCommand as n, Typechecker as t };
|
|
213
|
-
//# sourceMappingURL=typechecker-
|
|
213
|
+
//# sourceMappingURL=typechecker-zjnzC2L_.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typechecker-C3UOI2LF.js","names":[],"sources":["../src/dev/typechecker.ts"],"sourcesContent":["import type { ResolvedStarsConfig, StarsTypechecker } from '@wolfstar/schema';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\nimport type { Diagnostic } from 'nostics';\nimport { resolveTscBinary } from '../builders/tsc.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport type { LogLevel } from '../utils/log-buffer.js';\nimport { createLineSplitter } from '../utils/process-supervisor.js';\nimport { resolveBinary } from '../utils/project.js';\n\nconst WATCH_START = /Starting (incremental )?compilation/;\nconst WATCH_DONE = /Found (\\d+) errors?\\. Watching for file changes/;\nconst ONE_SHOT_DONE = /Found (\\d+) errors?/;\nconst ERROR_LINE = /error TS\\d+:/;\n\nexport type TypecheckState = 'idle' | 'checking' | 'ok' | 'failed';\n\nexport interface TypecheckerEvents {\n\tlog: [level: LogLevel, text: string];\n\tstate: [state: TypecheckState, errors: number];\n}\n\nexport interface TypecheckCommand {\n\t/** The executable, and whether it is a script `node` has to run. */\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly node: boolean;\n\t/** Whether the checker watches the project itself, or has to be re-run after every build. */\n\treadonly watch: boolean;\n}\n\n/**\n * Runs a type checker next to the bot and reports type errors on their own channel, without ever blocking builds or\n * restarts: `stars dev` keeps running whatever the build tool produced, the way Seedcord's `hmr.typecheck` and\n * Vite's `checker` plugin do.\n *\n * `tsc` and `golar` (which forwards to TypeScript) watch the project themselves; `tsz` has no watch mode, so\n * {@link Typechecker.check} re-runs it after every build instead.\n */\nexport class Typechecker extends EventEmitter<TypecheckerEvents> {\n\t#child: ChildProcess | null = null;\n\t#state: TypecheckState = 'idle';\n\t#errors = 0;\n\t#pending = false;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic get state(): TypecheckState {\n\t\treturn this.#state;\n\t}\n\n\tpublic get errors(): number {\n\t\treturn this.#errors;\n\t}\n\n\t/** Whether the checker re-runs per build instead of watching on its own. */\n\tpublic get oneShot(): boolean {\n\t\treturn this.config.dev.typecheck.enabled && !resolveTypecheckCommand(this.config).watch;\n\t}\n\n\tpublic start(): void {\n\t\tif (!this.config.dev.typecheck.enabled) return;\n\t\tthis.#run();\n\t}\n\n\t/**\n\t * Re-runs a checker that cannot watch. Called after every successful build; a run already in flight is replaced,\n\t * so a fast series of builds only leaves the last check standing.\n\t */\n\tpublic check(): void {\n\t\tif (!this.oneShot) return;\n\t\tif (this.#child) {\n\t\t\tthis.#pending = true;\n\t\t\tthis.#child.kill();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#run();\n\t}\n\n\tpublic close(): Promise<void> {\n\t\tconst child = this.#child;\n\t\tthis.#child = null;\n\t\tthis.#pending = false;\n\t\tif (!child || child.exitCode !== null) return Promise.resolve();\n\n\t\treturn new Promise((resolve) => {\n\t\t\tchild.once('exit', () => resolve());\n\t\t\tchild.kill();\n\t\t});\n\t}\n\n\t#run(): void {\n\t\tconst { command, args, node, watch } = resolveTypecheckCommand(this.config);\n\t\tconst child = spawn(node ? process.execPath : command, node ? [command, ...args] : [...args], {\n\t\t\tcwd: this.config.root,\n\t\t\tenv: process.env,\n\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\twindowsHide: true\n\t\t});\n\t\tthis.#child = child;\n\t\tthis.#setState('checking', 0);\n\n\t\tlet errors = 0;\n\t\tconst handleLine = (line: string) => {\n\t\t\tif (ERROR_LINE.test(line)) errors++;\n\t\t\tthis.#handleLine(line);\n\t\t};\n\n\t\tconst stdout = createLineSplitter(handleLine);\n\t\tconst stderr = createLineSplitter(handleLine);\n\t\tchild.stdout?.on('data', stdout.push);\n\t\tchild.stderr?.on('data', stderr.push);\n\n\t\tchild.once('exit', (code) => {\n\t\t\tstdout.flush();\n\t\t\tstderr.flush();\n\t\t\tthis.#child = null;\n\n\t\t\tif (!watch && this.#state === 'checking') {\n\t\t\t\t// A one-shot checker only reports through its exit code when it prints no summary line.\n\t\t\t\tthis.#setState(code === 0 && errors === 0 ? 'ok' : 'failed', errors);\n\t\t\t}\n\n\t\t\tif (this.#pending) {\n\t\t\t\tthis.#pending = false;\n\t\t\t\tthis.#run();\n\t\t\t}\n\t\t});\n\t\tchild.once('error', (error: NodeJS.ErrnoException) => {\n\t\t\tthis.#child = null;\n\t\t\tthis.emit('log', 'error', error.code === 'ENOENT' ? `${command} is not installed` : error.message);\n\t\t\tthis.#setState('failed', this.#errors);\n\t\t});\n\t}\n\n\t#handleLine(line: string): void {\n\t\tconst text = line.trim();\n\t\tif (text.length === 0) return;\n\n\t\tif (WATCH_START.test(text)) {\n\t\t\tthis.#setState('checking', 0);\n\t\t\treturn;\n\t\t}\n\n\t\tif (ERROR_LINE.test(text)) {\n\t\t\tthis.emit('log', 'error', text);\n\t\t\treturn;\n\t\t}\n\n\t\tconst done = WATCH_DONE.exec(text) ?? ONE_SHOT_DONE.exec(text);\n\t\tif (done) {\n\t\t\tconst errors = Number(done[1]);\n\t\t\tthis.#setState(errors === 0 ? 'ok' : 'failed', errors);\n\t\t\tthis.emit('log', errors === 0 ? 'success' : 'error', errors === 0 ? 'No type errors' : `${errors} type error${errors === 1 ? '' : 's'}`);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.emit('log', 'info', text);\n\t}\n\n\t#setState(state: TypecheckState, errors: number): void {\n\t\tthis.#state = state;\n\t\tthis.#errors = errors;\n\t\tthis.emit('state', state, errors);\n\t}\n}\n\n/**\n * Builds the command line of the configured type checker, resolved from the project's own `node_modules`.\n *\n * @throws {Diagnostic} when the checker is not installed in the project.\n */\nexport function resolveTypecheckCommand(config: ResolvedStarsConfig, options: { project?: string; once?: boolean } = {}): TypecheckCommand {\n\tconst { checker, tsconfig } = config.dev.typecheck;\n\tconst project = options.project ?? tsconfig!;\n\t// `stars typecheck` checks a project once, with the checker's own output; `stars dev` watches it quietly.\n\tconst flags = options.once ? ONCE_ARGS : WATCH_ARGS;\n\tconst watch = !options.once;\n\n\tswitch (checker) {\n\t\tcase 'golar': {\n\t\t\t// `golar tsc` forwards everything after it to the TypeScript CLI, watch mode included.\n\t\t\tconst golar = resolveBinary(config.root, 'golar', 'golar');\n\t\t\tif (!golar) throw missing(config, 'golar', 'Install it with `pnpm add -D golar`');\n\n\t\t\treturn { command: golar, args: ['tsc', ...flags, '-p', project], node: true, watch };\n\t\t}\n\n\t\tcase 'tsz': {\n\t\t\t// `tsz` is an early, tsc-compatible checker with no watch mode; `try-tsz` is its comparison harness.\n\t\t\tconst tsz = resolveBinary(config.root, '@mohsen-azimi/tsz-dev', 'tsz');\n\t\t\tif (tsz) return { command: tsz, args: ['--noEmit', '-p', project], node: true, watch: false };\n\n\t\t\tconst tryTsz = resolveBinary(config.root, 'try-tsz', 'try-tsz');\n\t\t\tif (tryTsz) return { command: tryTsz, args: ['-p', project], node: true, watch: false };\n\n\t\t\tthrow missing(config, 'tsz', 'Install it with `pnpm add -D @mohsen-azimi/tsz-dev` (or `try-tsz`)');\n\t\t}\n\n\t\tdefault: {\n\t\t\tconst tsc = resolveTscBinary(config.root);\n\t\t\tif (!tsc) throw missing(config, 'typescript', 'Install it with `pnpm add -D typescript`');\n\n\t\t\treturn { command: tsc, args: [...flags, '-p', project], node: true, watch };\n\t\t}\n\t}\n}\n\nconst ONCE_ARGS = ['--noEmit'] as const;\nconst WATCH_ARGS = ['--noEmit', '--watch', '--preserveWatchOutput', '--pretty', 'false'] as const;\n\nfunction missing(config: ResolvedStarsConfig, name: string, install: string): Diagnostic {\n\treturn cliDiagnostics.DEPENDENCY_MISSING({\n\t\tname,\n\t\troot: config.root,\n\t\thint: `${install}, pick another \\`dev.typecheck.checker\\`, or set \\`dev.typecheck\\` to false.`\n\t});\n}\n\nexport type { StarsTypechecker };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"typechecker-zjnzC2L_.js","names":[],"sources":["../src/dev/typechecker.ts"],"sourcesContent":["import type { ResolvedStarsConfig, StarsTypechecker } from '@wolfstar/schema';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\nimport type { Diagnostic } from 'nostics';\nimport { resolveTscBinary } from '../builders/tsc.js';\nimport { cliDiagnostics } from '../utils/diagnostics.js';\nimport type { LogLevel } from '../utils/log-buffer.js';\nimport { createLineSplitter } from '../utils/process-supervisor.js';\nimport { resolveBinary } from '../utils/project.js';\n\nconst WATCH_START = /Starting (incremental )?compilation/;\nconst WATCH_DONE = /Found (\\d+) errors?\\. Watching for file changes/;\nconst ONE_SHOT_DONE = /Found (\\d+) errors?/;\nconst ERROR_LINE = /error TS\\d+:/;\n\nexport type TypecheckState = 'idle' | 'checking' | 'ok' | 'failed';\n\nexport interface TypecheckerEvents {\n\tlog: [level: LogLevel, text: string];\n\tstate: [state: TypecheckState, errors: number];\n}\n\nexport interface TypecheckCommand {\n\t/** The executable, and whether it is a script `node` has to run. */\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly node: boolean;\n\t/** Whether the checker watches the project itself, or has to be re-run after every build. */\n\treadonly watch: boolean;\n}\n\n/**\n * Runs a type checker next to the bot and reports type errors on their own channel, without ever blocking builds or\n * restarts: `stars dev` keeps running whatever the build tool produced, the way Seedcord's `hmr.typecheck` and\n * Vite's `checker` plugin do.\n *\n * `tsc` and `golar` (which forwards to TypeScript) watch the project themselves; `tsz` has no watch mode, so\n * {@link Typechecker.check} re-runs it after every build instead.\n */\nexport class Typechecker extends EventEmitter<TypecheckerEvents> {\n\t#child: ChildProcess | null = null;\n\t#state: TypecheckState = 'idle';\n\t#errors = 0;\n\t#pending = false;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic get state(): TypecheckState {\n\t\treturn this.#state;\n\t}\n\n\tpublic get errors(): number {\n\t\treturn this.#errors;\n\t}\n\n\t/** Whether the checker re-runs per build instead of watching on its own. */\n\tpublic get oneShot(): boolean {\n\t\treturn this.config.dev.typecheck.enabled && !resolveTypecheckCommand(this.config).watch;\n\t}\n\n\tpublic start(): void {\n\t\tif (!this.config.dev.typecheck.enabled) return;\n\t\tthis.#run();\n\t}\n\n\t/**\n\t * Re-runs a checker that cannot watch. Called after every successful build; a run already in flight is replaced,\n\t * so a fast series of builds only leaves the last check standing.\n\t */\n\tpublic check(): void {\n\t\tif (!this.oneShot) return;\n\t\tif (this.#child) {\n\t\t\tthis.#pending = true;\n\t\t\tthis.#child.kill();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#run();\n\t}\n\n\tpublic close(): Promise<void> {\n\t\tconst child = this.#child;\n\t\tthis.#child = null;\n\t\tthis.#pending = false;\n\t\tif (!child || child.exitCode !== null) return Promise.resolve();\n\n\t\treturn new Promise((resolve) => {\n\t\t\tchild.once('exit', () => resolve());\n\t\t\tchild.kill();\n\t\t});\n\t}\n\n\t#run(): void {\n\t\tconst { command, args, node, watch } = resolveTypecheckCommand(this.config);\n\t\tconst child = spawn(node ? process.execPath : command, node ? [command, ...args] : [...args], {\n\t\t\tcwd: this.config.root,\n\t\t\tenv: process.env,\n\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\twindowsHide: true\n\t\t});\n\t\tthis.#child = child;\n\t\tthis.#setState('checking', 0);\n\n\t\tlet errors = 0;\n\t\tconst handleLine = (line: string) => {\n\t\t\tif (ERROR_LINE.test(line)) errors++;\n\t\t\tthis.#handleLine(line);\n\t\t};\n\n\t\tconst stdout = createLineSplitter(handleLine);\n\t\tconst stderr = createLineSplitter(handleLine);\n\t\tchild.stdout?.on('data', stdout.push);\n\t\tchild.stderr?.on('data', stderr.push);\n\n\t\tchild.once('exit', (code) => {\n\t\t\tstdout.flush();\n\t\t\tstderr.flush();\n\t\t\tthis.#child = null;\n\n\t\t\tif (!watch && this.#state === 'checking') {\n\t\t\t\t// A one-shot checker only reports through its exit code when it prints no summary line.\n\t\t\t\tthis.#setState(code === 0 && errors === 0 ? 'ok' : 'failed', errors);\n\t\t\t}\n\n\t\t\tif (this.#pending) {\n\t\t\t\tthis.#pending = false;\n\t\t\t\tthis.#run();\n\t\t\t}\n\t\t});\n\t\tchild.once('error', (error: NodeJS.ErrnoException) => {\n\t\t\tthis.#child = null;\n\t\t\tthis.emit('log', 'error', error.code === 'ENOENT' ? `${command} is not installed` : error.message);\n\t\t\tthis.#setState('failed', this.#errors);\n\t\t});\n\t}\n\n\t#handleLine(line: string): void {\n\t\tconst text = line.trim();\n\t\tif (text.length === 0) return;\n\n\t\tif (WATCH_START.test(text)) {\n\t\t\tthis.#setState('checking', 0);\n\t\t\treturn;\n\t\t}\n\n\t\tif (ERROR_LINE.test(text)) {\n\t\t\tthis.emit('log', 'error', text);\n\t\t\treturn;\n\t\t}\n\n\t\tconst done = WATCH_DONE.exec(text) ?? ONE_SHOT_DONE.exec(text);\n\t\tif (done) {\n\t\t\tconst errors = Number(done[1]);\n\t\t\tthis.#setState(errors === 0 ? 'ok' : 'failed', errors);\n\t\t\tthis.emit('log', errors === 0 ? 'success' : 'error', errors === 0 ? 'No type errors' : `${errors} type error${errors === 1 ? '' : 's'}`);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.emit('log', 'info', text);\n\t}\n\n\t#setState(state: TypecheckState, errors: number): void {\n\t\tthis.#state = state;\n\t\tthis.#errors = errors;\n\t\tthis.emit('state', state, errors);\n\t}\n}\n\n/**\n * Builds the command line of the configured type checker, resolved from the project's own `node_modules`.\n *\n * @throws {Diagnostic} when the checker is not installed in the project.\n */\nexport function resolveTypecheckCommand(config: ResolvedStarsConfig, options: { project?: string; once?: boolean } = {}): TypecheckCommand {\n\tconst { checker, tsconfig } = config.dev.typecheck;\n\tconst project = options.project ?? tsconfig!;\n\t// `stars typecheck` checks a project once, with the checker's own output; `stars dev` watches it quietly.\n\tconst flags = options.once ? ONCE_ARGS : WATCH_ARGS;\n\tconst watch = !options.once;\n\n\tswitch (checker) {\n\t\tcase 'golar': {\n\t\t\t// `golar tsc` forwards everything after it to the TypeScript CLI, watch mode included.\n\t\t\tconst golar = resolveBinary(config.root, 'golar', 'golar');\n\t\t\tif (!golar) throw missing(config, 'golar', 'Install it with `pnpm add -D golar`');\n\n\t\t\treturn { command: golar, args: ['tsc', ...flags, '-p', project], node: true, watch };\n\t\t}\n\n\t\tcase 'tsz': {\n\t\t\t// `tsz` is an early, tsc-compatible checker with no watch mode; `try-tsz` is its comparison harness.\n\t\t\tconst tsz = resolveBinary(config.root, '@mohsen-azimi/tsz-dev', 'tsz');\n\t\t\tif (tsz) return { command: tsz, args: ['--noEmit', '-p', project], node: true, watch: false };\n\n\t\t\tconst tryTsz = resolveBinary(config.root, 'try-tsz', 'try-tsz');\n\t\t\tif (tryTsz) return { command: tryTsz, args: ['-p', project], node: true, watch: false };\n\n\t\t\tthrow missing(config, 'tsz', 'Install it with `pnpm add -D @mohsen-azimi/tsz-dev` (or `try-tsz`)');\n\t\t}\n\n\t\tdefault: {\n\t\t\tconst tsc = resolveTscBinary(config.root);\n\t\t\tif (!tsc) throw missing(config, 'typescript', 'Install it with `pnpm add -D typescript`');\n\n\t\t\treturn { command: tsc, args: [...flags, '-p', project], node: true, watch };\n\t\t}\n\t}\n}\n\nconst ONCE_ARGS = ['--noEmit'] as const;\nconst WATCH_ARGS = ['--noEmit', '--watch', '--preserveWatchOutput', '--pretty', 'false'] as const;\n\nfunction missing(config: ResolvedStarsConfig, name: string, install: string): Diagnostic {\n\treturn cliDiagnostics.DEPENDENCY_MISSING({\n\t\tname,\n\t\troot: config.root,\n\t\thint: `${install}, pick another \\`dev.typecheck.checker\\`, or set \\`dev.typecheck\\` to false.`\n\t});\n}\n\nexport type { StarsTypechecker };\n"],"mappings":";;;;;;;AAUA,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,gBAAgB;AACtB,MAAM,aAAa;;;;;;;;;AA0BnB,IAAa,cAAb,cAAiC,aAAgC;CAM5B;CALpC,SAA8B;CAC9B,SAAyB;CACzB,UAAU;CACV,WAAW;CAEX,AAAO,YAAY,AAAiB,QAA6B;EAChE,MAAM;EAD6B;CAEpC;CAEA,IAAW,QAAwB;EAClC,OAAO,KAAK;CACb;CAEA,IAAW,SAAiB;EAC3B,OAAO,KAAK;CACb;;CAGA,IAAW,UAAmB;EAC7B,OAAO,KAAK,OAAO,IAAI,UAAU,WAAW,CAAC,wBAAwB,KAAK,MAAM,CAAC,CAAC;CACnF;CAEA,AAAO,QAAc;EACpB,IAAI,CAAC,KAAK,OAAO,IAAI,UAAU,SAAS;EACxC,KAAK,KAAK;CACX;;;;;CAMA,AAAO,QAAc;EACpB,IAAI,CAAC,KAAK,SAAS;EACnB,IAAI,KAAK,QAAQ;GAChB,KAAK,WAAW;GAChB,KAAK,OAAO,KAAK;GACjB;EACD;EAEA,KAAK,KAAK;CACX;CAEA,AAAO,QAAuB;EAC7B,MAAM,QAAQ,KAAK;EACnB,KAAK,SAAS;EACd,KAAK,WAAW;EAChB,IAAI,CAAC,SAAS,MAAM,aAAa,MAAM,OAAO,QAAQ,QAAQ;EAE9D,OAAO,IAAI,SAAS,YAAY;GAC/B,MAAM,KAAK,cAAc,QAAQ,CAAC;GAClC,MAAM,KAAK;EACZ,CAAC;CACF;CAEA,OAAa;EACZ,MAAM,EAAE,SAAS,MAAM,MAAM,UAAU,wBAAwB,KAAK,MAAM;EAC1E,MAAM,QAAQ,MAAM,OAAO,QAAQ,WAAW,SAAS,OAAO,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,IAAI,GAAG;GAC7F,KAAK,KAAK,OAAO;GACjB,KAAK,QAAQ;GACb,OAAO;IAAC;IAAU;IAAQ;GAAM;GAChC,aAAa;EACd,CAAC;EACD,KAAK,SAAS;EACd,KAAK,UAAU,YAAY,CAAC;EAE5B,IAAI,SAAS;EACb,MAAM,cAAc,SAAiB;GACpC,IAAI,WAAW,KAAK,IAAI,GAAG;GAC3B,KAAK,YAAY,IAAI;EACtB;EAEA,MAAM,SAAS,mBAAmB,UAAU;EAC5C,MAAM,SAAS,mBAAmB,UAAU;EAC5C,MAAM,QAAQ,GAAG,QAAQ,OAAO,IAAI;EACpC,MAAM,QAAQ,GAAG,QAAQ,OAAO,IAAI;EAEpC,MAAM,KAAK,SAAS,SAAS;GAC5B,OAAO,MAAM;GACb,OAAO,MAAM;GACb,KAAK,SAAS;GAEd,IAAI,CAAC,SAAS,KAAK,WAAW,YAE7B,KAAK,UAAU,SAAS,KAAK,WAAW,IAAI,OAAO,UAAU,MAAM;GAGpE,IAAI,KAAK,UAAU;IAClB,KAAK,WAAW;IAChB,KAAK,KAAK;GACX;EACD,CAAC;EACD,MAAM,KAAK,UAAU,UAAiC;GACrD,KAAK,SAAS;GACd,KAAK,KAAK,OAAO,SAAS,MAAM,SAAS,WAAW,GAAG,QAAQ,qBAAqB,MAAM,OAAO;GACjG,KAAK,UAAU,UAAU,KAAK,OAAO;EACtC,CAAC;CACF;CAEA,YAAY,MAAoB;EAC/B,MAAM,OAAO,KAAK,KAAK;EACvB,IAAI,KAAK,WAAW,GAAG;EAEvB,IAAI,YAAY,KAAK,IAAI,GAAG;GAC3B,KAAK,UAAU,YAAY,CAAC;GAC5B;EACD;EAEA,IAAI,WAAW,KAAK,IAAI,GAAG;GAC1B,KAAK,KAAK,OAAO,SAAS,IAAI;GAC9B;EACD;EAEA,MAAM,OAAO,WAAW,KAAK,IAAI,KAAK,cAAc,KAAK,IAAI;EAC7D,IAAI,MAAM;GACT,MAAM,SAAS,OAAO,KAAK,EAAE;GAC7B,KAAK,UAAU,WAAW,IAAI,OAAO,UAAU,MAAM;GACrD,KAAK,KAAK,OAAO,WAAW,IAAI,YAAY,SAAS,WAAW,IAAI,mBAAmB,GAAG,OAAO,aAAa,WAAW,IAAI,KAAK,KAAK;GACvI;EACD;EAEA,KAAK,KAAK,OAAO,QAAQ,IAAI;CAC9B;CAEA,UAAU,OAAuB,QAAsB;EACtD,KAAK,SAAS;EACd,KAAK,UAAU;EACf,KAAK,KAAK,SAAS,OAAO,MAAM;CACjC;AACD;;;;;;AAOA,SAAgB,wBAAwB,QAA6B,UAAgD,CAAC,GAAqB;CAC1I,MAAM,EAAE,SAAS,aAAa,OAAO,IAAI;CACzC,MAAM,UAAU,QAAQ,WAAW;CAEnC,MAAM,QAAQ,QAAQ,OAAO,YAAY;CACzC,MAAM,QAAQ,CAAC,QAAQ;CAEvB,QAAQ,SAAR;EACC,KAAK,SAAS;GAEb,MAAM,QAAQ,cAAc,OAAO,MAAM,SAAS,OAAO;GACzD,IAAI,CAAC,OAAO,MAAM,QAAQ,QAAQ,SAAS,qCAAqC;GAEhF,OAAO;IAAE,SAAS;IAAO,MAAM;KAAC;KAAO,GAAG;KAAO;KAAM;IAAO;IAAG,MAAM;IAAM;GAAM;EACpF;EAEA,KAAK,OAAO;GAEX,MAAM,MAAM,cAAc,OAAO,MAAM,yBAAyB,KAAK;GACrE,IAAI,KAAK,OAAO;IAAE,SAAS;IAAK,MAAM;KAAC;KAAY;KAAM;IAAO;IAAG,MAAM;IAAM,OAAO;GAAM;GAE5F,MAAM,SAAS,cAAc,OAAO,MAAM,WAAW,SAAS;GAC9D,IAAI,QAAQ,OAAO;IAAE,SAAS;IAAQ,MAAM,CAAC,MAAM,OAAO;IAAG,MAAM;IAAM,OAAO;GAAM;GAEtF,MAAM,QAAQ,QAAQ,OAAO,oEAAoE;EAClG;EAEA,SAAS;GACR,MAAM,MAAM,iBAAiB,OAAO,IAAI;GACxC,IAAI,CAAC,KAAK,MAAM,QAAQ,QAAQ,cAAc,0CAA0C;GAExF,OAAO;IAAE,SAAS;IAAK,MAAM;KAAC,GAAG;KAAO;KAAM;IAAO;IAAG,MAAM;IAAM;GAAM;EAC3E;CACD;AACD;AAEA,MAAM,YAAY,CAAC,UAAU;AAC7B,MAAM,aAAa;CAAC;CAAY;CAAW;CAAyB;CAAY;AAAO;AAEvF,SAAS,QAAQ,QAA6B,MAAc,SAA6B;CACxF,OAAO,eAAe,mBAAmB;EACxC;EACA,MAAM,OAAO;EACb,MAAM,GAAG,QAAQ;CAClB,CAAC;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as pluginRegistrations } from "./plugin-registrations-Dr_BicfC.js";
|
|
2
|
-
import { n as importFromProject } from "./project-
|
|
2
|
+
import { n as importFromProject } from "./project-EJyKro-M.js";
|
|
3
3
|
import { ViteBuilder as ViteBuilder$1 } from "@wolfstar/vite-server";
|
|
4
4
|
|
|
5
5
|
//#region src/builders/vite.ts
|
|
@@ -15,4 +15,4 @@ var ViteBuilder = class extends ViteBuilder$1 {
|
|
|
15
15
|
|
|
16
16
|
//#endregion
|
|
17
17
|
export { ViteBuilder };
|
|
18
|
-
//# sourceMappingURL=vite-
|
|
18
|
+
//# sourceMappingURL=vite-9yEVMHY_.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-
|
|
1
|
+
{"version":3,"file":"vite-9yEVMHY_.js","names":["ServerBuilder"],"sources":["../src/builders/vite.ts"],"sourcesContent":["import { ViteBuilder as ServerBuilder } from '@wolfstar/vite-server';\nimport type { ResolvedStarsConfig } from '@wolfstar/schema';\nimport { pluginRegistrations } from '../utils/plugin-registrations.js';\nimport { importFromProject } from '../utils/project.js';\n\n/** Connect the server integration to the CLI's diagnostics and plugin discovery. */\nexport class ViteBuilder extends ServerBuilder {\n\tpublic constructor(config: ResolvedStarsConfig) {\n\t\tsuper(config, { importFromProject, pluginRegistrations });\n\t}\n}\n"],"mappings":";;;;;;AAMA,IAAa,cAAb,cAAiCA,cAAc;CAC9C,AAAO,YAAY,QAA6B;EAC/C,MAAM,QAAQ;GAAE;GAAmB;EAAoB,CAAC;CACzD;AACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wolfstar/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0-next-20261009115919",
|
|
4
4
|
"description": "The stars command line interface for @wolfstar/http-framework projects: typed configuration, dev server, build and project info",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@clack/prompts": "^1.8.1",
|
|
58
58
|
"@sapphire/ts-config": "^5.0.3",
|
|
59
|
-
"@wolfstar/kit": "^0.2.
|
|
60
|
-
"@wolfstar/nitro-server": "^0.2.
|
|
61
|
-
"@wolfstar/schema": "^0.
|
|
62
|
-
"@wolfstar/vite-server": "^0.4.
|
|
59
|
+
"@wolfstar/kit": "^0.2.1-next-20261009115919",
|
|
60
|
+
"@wolfstar/nitro-server": "^0.2.10-next-20261009115919",
|
|
61
|
+
"@wolfstar/schema": "^0.10.0-next-20261009115919",
|
|
62
|
+
"@wolfstar/vite-server": "^0.4.5-next-20261009115919",
|
|
63
63
|
"chokidar": "^5.0.0",
|
|
64
64
|
"citty": "^0.2.2",
|
|
65
65
|
"colorette": "^2.0.20",
|
|
@@ -83,12 +83,16 @@
|
|
|
83
83
|
"vitest": "^5.0.3"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
|
+
"@ngrok/ngrok": ">=1.0.0",
|
|
86
87
|
"nitro": ">=3.0.0-0",
|
|
87
88
|
"tsdown": ">=0.15.0",
|
|
88
89
|
"typescript": ">=5.0.0",
|
|
89
90
|
"vite": ">=6.0.0"
|
|
90
91
|
},
|
|
91
92
|
"peerDependenciesMeta": {
|
|
93
|
+
"@ngrok/ngrok": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
92
96
|
"nitro": {
|
|
93
97
|
"optional": true
|
|
94
98
|
},
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
//#region src/commands/index.ts
|
|
2
|
-
const commands = {
|
|
3
|
-
dev: () => import("./dev-BLlgQwl6.js").then((module) => module.default),
|
|
4
|
-
build: () => import("./build-BbIEbOu3.js").then((module) => module.default),
|
|
5
|
-
info: () => import("./info-C_YDcVul.js").then((module) => module.default),
|
|
6
|
-
codegen: () => import("./codegen-C2PY7RXA.js").then((module) => module.default),
|
|
7
|
-
prepare: () => import("./prepare-DKH0UIYA.js").then((module) => module.default),
|
|
8
|
-
typecheck: () => import("./typecheck-p_2rj9lL.js").then((module) => module.default),
|
|
9
|
-
commands: () => import("./commands-C-i3qYOz.js").then((module) => module.default),
|
|
10
|
-
doctor: () => import("./doctor-CMGfbXS5.js").then((module) => module.default),
|
|
11
|
-
completions: () => import("./completions-DA-NZAcY.js").then((module) => module.default)
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
//#endregion
|
|
15
|
-
export { commands as t };
|
|
16
|
-
//# sourceMappingURL=commands-D9i-ZgpA.js.map
|