@wolfstar/cli 0.1.0-next-20260904111115
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/LICENSE +201 -0
- package/README.md +151 -0
- package/dist/args-Ch1KXUMn.js +24 -0
- package/dist/args-Ch1KXUMn.js.map +1 -0
- package/dist/build-Ct6zabKg.js +22 -0
- package/dist/build-Ct6zabKg.js.map +1 -0
- package/dist/build-DU76s-Ec.js +39 -0
- package/dist/build-DU76s-Ec.js.map +1 -0
- package/dist/builders-BzLMIm9Y.js +44 -0
- package/dist/builders-BzLMIm9Y.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen-DjPOn7cp.js +95 -0
- package/dist/codegen-DjPOn7cp.js.map +1 -0
- package/dist/codegen-mYkL-GBf.js +36 -0
- package/dist/codegen-mYkL-GBf.js.map +1 -0
- package/dist/commands-BkGKs_Bg.js +88 -0
- package/dist/commands-BkGKs_Bg.js.map +1 -0
- package/dist/commands-Dw2LFWV-.js +233 -0
- package/dist/commands-Dw2LFWV-.js.map +1 -0
- package/dist/dev-DWg8l23a.js +604 -0
- package/dist/dev-DWg8l23a.js.map +1 -0
- package/dist/dev-QUxuLcXj.js +30 -0
- package/dist/dev-QUxuLcXj.js.map +1 -0
- package/dist/errors-Bket6XFn.js +50 -0
- package/dist/errors-Bket6XFn.js.map +1 -0
- package/dist/external-Bk4EESdT.js +59 -0
- package/dist/external-Bk4EESdT.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/info-B-8rAIn6.js +106 -0
- package/dist/info-B-8rAIn6.js.map +1 -0
- package/dist/info-DRTYYvVB.js +30 -0
- package/dist/info-DRTYYvVB.js.map +1 -0
- package/dist/none-DEo7zEr9.js +50 -0
- package/dist/none-DEo7zEr9.js.map +1 -0
- package/dist/output-mode-ChrOf0LO.js +46 -0
- package/dist/output-mode-ChrOf0LO.js.map +1 -0
- package/dist/plain-CEe4X_Cz.js +43 -0
- package/dist/plain-CEe4X_Cz.js.map +1 -0
- package/dist/prepare-BXcm2Ukp.js +3 -0
- package/dist/prepare-CVf88_Or.js +36 -0
- package/dist/prepare-CVf88_Or.js.map +1 -0
- package/dist/prepare-D5NZ0w4t.js +68 -0
- package/dist/prepare-D5NZ0w4t.js.map +1 -0
- package/dist/process-supervisor-CJOdmp0W.js +128 -0
- package/dist/process-supervisor-CJOdmp0W.js.map +1 -0
- package/dist/project-DHF3qdm-.js +66 -0
- package/dist/project-DHF3qdm-.js.map +1 -0
- package/dist/tsc-BqZfDTD0.js +133 -0
- package/dist/tsc-BqZfDTD0.js.map +1 -0
- package/dist/tsc-CovVAtfo.js +3 -0
- package/dist/tsdown-DlAs0rSI.js +104 -0
- package/dist/tsdown-DlAs0rSI.js.map +1 -0
- package/dist/tui-BTRFsLQR.js +770 -0
- package/dist/tui-BTRFsLQR.js.map +1 -0
- package/dist/tunnel-B8Id4-lR.js +205 -0
- package/dist/tunnel-B8Id4-lR.js.map +1 -0
- package/dist/version-DN6Uho-Y.js +30 -0
- package/dist/version-DN6Uho-Y.js.map +1 -0
- package/dist/vite-D3vYFsqa.js +93 -0
- package/dist/vite-D3vYFsqa.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { n as ExitCode, t as CliError } from "./errors-Bket6XFn.js";
|
|
2
|
+
import { i as shouldUseColor } from "./output-mode-ChrOf0LO.js";
|
|
3
|
+
import { n as resolveCwd } from "./args-Ch1KXUMn.js";
|
|
4
|
+
import { displayPath, loadStarsConfig } from "@wolfstar/http-framework/config";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
import { createColors } from "colorette";
|
|
7
|
+
import { generateAutoImportsDts } from "@wolfstar/http-framework/auto-imports";
|
|
8
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
|
|
10
|
+
//#region src/lib/tasks/prepare.ts
|
|
11
|
+
/**
|
|
12
|
+
* Generates `imports.dts` (see {@link StarsImportsConfig} in `@wolfstar/http-framework/config`), the way `nuxt
|
|
13
|
+
* prepare` regenerates `.nuxt/imports.d.ts`. Run automatically by `stars dev` and `stars build` before the first
|
|
14
|
+
* build; `--check` fails instead of writing, for CI.
|
|
15
|
+
*/
|
|
16
|
+
async function runPrepare(options) {
|
|
17
|
+
const stdout = options.stdout ?? process.stdout;
|
|
18
|
+
const colors = createColors({ useColor: shouldUseColor() && !options.json });
|
|
19
|
+
const config = await loadStarsConfig({
|
|
20
|
+
cwd: resolveCwd(options),
|
|
21
|
+
configFile: options.config
|
|
22
|
+
});
|
|
23
|
+
const result = await prepareAutoImports(config, Boolean(options.check));
|
|
24
|
+
if (options.json) stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
25
|
+
else if (!result.enabled) stdout.write(`${colors.dim("stars")} nothing to prepare (auto imports are disabled)\n`);
|
|
26
|
+
else {
|
|
27
|
+
const paint = result.status === "outdated" ? colors.red : colors.green;
|
|
28
|
+
stdout.write(`${colors.dim("stars")} imports: ${paint(result.status)} ${displayPath(config.root, result.dts)}\n`);
|
|
29
|
+
}
|
|
30
|
+
if (result.status === "outdated") throw new CliError("The auto imports declaration file is out of date, run `stars prepare` to update it.", {
|
|
31
|
+
code: "PREPARE_OUTDATED",
|
|
32
|
+
exitCode: ExitCode.Error
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Regenerates `imports.dts` unconditionally, used by `stars dev`/`stars build` before the first build so the
|
|
37
|
+
* declaration file exists (and is current) even when the user never ran `stars prepare` themselves.
|
|
38
|
+
*/
|
|
39
|
+
async function prepareAutoImports(config, check = false) {
|
|
40
|
+
if (!config.imports.enabled) return {
|
|
41
|
+
enabled: false,
|
|
42
|
+
dts: null,
|
|
43
|
+
status: null
|
|
44
|
+
};
|
|
45
|
+
const { dirs, presets, exclude, dts } = config.imports;
|
|
46
|
+
const content = await generateAutoImportsDts({
|
|
47
|
+
root: config.root,
|
|
48
|
+
dirs,
|
|
49
|
+
presets,
|
|
50
|
+
exclude
|
|
51
|
+
});
|
|
52
|
+
if (check) return {
|
|
53
|
+
enabled: true,
|
|
54
|
+
dts,
|
|
55
|
+
status: await readFile(dts, "utf-8").catch(() => null) === content ? "up-to-date" : "outdated"
|
|
56
|
+
};
|
|
57
|
+
await mkdir(dirname(dts), { recursive: true });
|
|
58
|
+
await writeFile(dts, content);
|
|
59
|
+
return {
|
|
60
|
+
enabled: true,
|
|
61
|
+
dts,
|
|
62
|
+
status: "written"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { runPrepare as n, prepareAutoImports as t };
|
|
68
|
+
//# sourceMappingURL=prepare-D5NZ0w4t.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepare-D5NZ0w4t.js","names":[],"sources":["../src/lib/tasks/prepare.ts"],"sourcesContent":["import { generateAutoImportsDts } from '@wolfstar/http-framework/auto-imports';\nimport { displayPath, loadStarsConfig, type ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport { createColors } from 'colorette';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport type { ProjectArgs } from '../args.js';\nimport { resolveCwd } from '../args.js';\nimport { CliError, ExitCode } from '../errors.js';\nimport { shouldUseColor } from '../output-mode.js';\n\nexport interface PrepareTaskOptions extends ProjectArgs {\n\tcheck?: boolean;\n\tjson?: boolean;\n\tstdout?: NodeJS.WritableStream;\n}\n\nexport type PrepareResult =\n\t| { enabled: false; dts: null; status: null }\n\t| { enabled: true; dts: string; status: 'written' | 'up-to-date' | 'outdated' };\n\n/**\n * Generates `imports.dts` (see {@link StarsImportsConfig} in `@wolfstar/http-framework/config`), the way `nuxt\n * prepare` regenerates `.nuxt/imports.d.ts`. Run automatically by `stars dev` and `stars build` before the first\n * build; `--check` fails instead of writing, for CI.\n */\nexport async function runPrepare(options: PrepareTaskOptions): Promise<void> {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst colors = createColors({ useColor: shouldUseColor() && !options.json });\n\tconst config = await loadStarsConfig({ cwd: resolveCwd(options), configFile: options.config });\n\tconst result = await prepareAutoImports(config, Boolean(options.check));\n\n\tif (options.json) {\n\t\tstdout.write(`${JSON.stringify(result, null, 2)}\\n`);\n\t} else if (!result.enabled) {\n\t\tstdout.write(`${colors.dim('stars')} nothing to prepare (auto imports are disabled)\\n`);\n\t} else {\n\t\tconst paint = result.status === 'outdated' ? colors.red : colors.green;\n\t\tstdout.write(`${colors.dim('stars')} imports: ${paint(result.status)} ${displayPath(config.root, result.dts)}\\n`);\n\t}\n\n\tif (result.status === 'outdated') {\n\t\tthrow new CliError('The auto imports declaration file is out of date, run `stars prepare` to update it.', {\n\t\t\tcode: 'PREPARE_OUTDATED',\n\t\t\texitCode: ExitCode.Error\n\t\t});\n\t}\n}\n\n/**\n * Regenerates `imports.dts` unconditionally, used by `stars dev`/`stars build` before the first build so the\n * declaration file exists (and is current) even when the user never ran `stars prepare` themselves.\n */\nexport async function prepareAutoImports(config: ResolvedStarsConfig, check = false): Promise<PrepareResult> {\n\tif (!config.imports.enabled) return { enabled: false, dts: null, status: null };\n\n\tconst { dirs, presets, exclude, dts } = config.imports;\n\tconst content = await generateAutoImportsDts({ root: config.root, dirs, presets, exclude });\n\n\tif (check) {\n\t\tconst existing = await readFile(dts, 'utf-8').catch(() => null);\n\t\treturn { enabled: true, dts, status: existing === content ? 'up-to-date' : 'outdated' };\n\t}\n\n\tawait mkdir(dirname(dts), { recursive: true });\n\tawait writeFile(dts, content);\n\treturn { enabled: true, dts, status: 'written' };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyBA,eAAsB,WAAW,SAA4C;CAC5E,MAAM,SAAS,QAAQ,UAAU,QAAQ;CACzC,MAAM,SAAS,aAAa,EAAE,UAAU,eAAe,KAAK,CAAC,QAAQ,KAAK,CAAC;CAC3E,MAAM,SAAS,MAAM,gBAAgB;EAAE,KAAK,WAAW,OAAO;EAAG,YAAY,QAAQ;CAAO,CAAC;CAC7F,MAAM,SAAS,MAAM,mBAAmB,QAAQ,QAAQ,QAAQ,KAAK,CAAC;CAEtE,IAAI,QAAQ,MACX,OAAO,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,GAAG;MAC7C,IAAI,CAAC,OAAO,SAClB,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,kDAAkD;MAChF;EACN,MAAM,QAAQ,OAAO,WAAW,aAAa,OAAO,MAAM,OAAO;EACjE,OAAO,MAAM,GAAG,OAAO,IAAI,OAAO,EAAE,YAAY,MAAM,OAAO,MAAM,EAAE,GAAG,YAAY,OAAO,MAAM,OAAO,GAAG,EAAE,GAAG;CACjH;CAEA,IAAI,OAAO,WAAW,YACrB,MAAM,IAAI,SAAS,uFAAuF;EACzG,MAAM;EACN,UAAU,SAAS;CACpB,CAAC;AAEH;;;;;AAMA,eAAsB,mBAAmB,QAA6B,QAAQ,OAA+B;CAC5G,IAAI,CAAC,OAAO,QAAQ,SAAS,OAAO;EAAE,SAAS;EAAO,KAAK;EAAM,QAAQ;CAAK;CAE9E,MAAM,EAAE,MAAM,SAAS,SAAS,QAAQ,OAAO;CAC/C,MAAM,UAAU,MAAM,uBAAuB;EAAE,MAAM,OAAO;EAAM;EAAM;EAAS;CAAQ,CAAC;CAE1F,IAAI,OAEH,OAAO;EAAE,SAAS;EAAM;EAAK,QAAQ,MADd,SAAS,KAAK,OAAO,CAAC,CAAC,YAAY,IAAI,MACZ,UAAU,eAAe;CAAW;CAGvF,MAAM,MAAM,QAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;CAC7C,MAAM,UAAU,KAAK,OAAO;CAC5B,OAAO;EAAE,SAAS;EAAM;EAAK,QAAQ;CAAU;AAChD"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/process-supervisor.ts
|
|
5
|
+
/**
|
|
6
|
+
* Owns a single child process: starts it, streams its output line by line and stops it gracefully.
|
|
7
|
+
*/
|
|
8
|
+
var ProcessSupervisor = class extends EventEmitter {
|
|
9
|
+
options;
|
|
10
|
+
#child = null;
|
|
11
|
+
#state = "idle";
|
|
12
|
+
#startedAt = null;
|
|
13
|
+
#stopping = null;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
super();
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
get state() {
|
|
19
|
+
return this.#state;
|
|
20
|
+
}
|
|
21
|
+
get pid() {
|
|
22
|
+
return this.#child?.pid ?? null;
|
|
23
|
+
}
|
|
24
|
+
get startedAt() {
|
|
25
|
+
return this.#startedAt;
|
|
26
|
+
}
|
|
27
|
+
get running() {
|
|
28
|
+
return this.#state === "starting" || this.#state === "running";
|
|
29
|
+
}
|
|
30
|
+
start() {
|
|
31
|
+
if (this.#child) throw new Error("The process is already running, call stop() or restart() first.");
|
|
32
|
+
const { command, args, cwd, env } = this.options;
|
|
33
|
+
this.#setState("starting");
|
|
34
|
+
const child = spawn(command, args, {
|
|
35
|
+
cwd,
|
|
36
|
+
env,
|
|
37
|
+
stdio: [
|
|
38
|
+
"ignore",
|
|
39
|
+
"pipe",
|
|
40
|
+
"pipe"
|
|
41
|
+
],
|
|
42
|
+
windowsHide: true
|
|
43
|
+
});
|
|
44
|
+
this.#child = child;
|
|
45
|
+
this.#startedAt = Date.now();
|
|
46
|
+
let requested = false;
|
|
47
|
+
const stdout = createLineSplitter((line) => this.emit("stdout", line));
|
|
48
|
+
const stderr = createLineSplitter((line) => this.emit("stderr", line));
|
|
49
|
+
child.stdout?.on("data", stdout.push);
|
|
50
|
+
child.stderr?.on("data", stderr.push);
|
|
51
|
+
child.once("spawn", () => {
|
|
52
|
+
if (this.#child === child) this.#setState("running");
|
|
53
|
+
});
|
|
54
|
+
child.once("error", (error) => {
|
|
55
|
+
this.emit("error", error);
|
|
56
|
+
});
|
|
57
|
+
child.once("exit", (code, signal) => {
|
|
58
|
+
stdout.flush();
|
|
59
|
+
stderr.flush();
|
|
60
|
+
requested = this.#state === "stopping";
|
|
61
|
+
this.#child = null;
|
|
62
|
+
this.#startedAt = null;
|
|
63
|
+
this.#setState(requested || code === 0 ? "stopped" : "crashed");
|
|
64
|
+
this.emit("exit", {
|
|
65
|
+
code,
|
|
66
|
+
signal,
|
|
67
|
+
requested
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Sends `SIGTERM`, escalates to `SIGKILL` after `killTimeout` and resolves once the process exited.
|
|
73
|
+
*/
|
|
74
|
+
stop() {
|
|
75
|
+
const child = this.#child;
|
|
76
|
+
if (!child) return Promise.resolve();
|
|
77
|
+
if (this.#stopping) return this.#stopping;
|
|
78
|
+
this.#setState("stopping");
|
|
79
|
+
this.#stopping = new Promise((resolve) => {
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
82
|
+
}, this.options.killTimeout);
|
|
83
|
+
timer.unref();
|
|
84
|
+
child.once("exit", () => {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
this.#stopping = null;
|
|
87
|
+
resolve();
|
|
88
|
+
});
|
|
89
|
+
if (!child.kill("SIGTERM")) {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
this.#stopping = null;
|
|
92
|
+
resolve();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return this.#stopping;
|
|
96
|
+
}
|
|
97
|
+
async restart() {
|
|
98
|
+
await this.stop();
|
|
99
|
+
this.start();
|
|
100
|
+
}
|
|
101
|
+
#setState(state) {
|
|
102
|
+
if (this.#state === state) return;
|
|
103
|
+
this.#state = state;
|
|
104
|
+
this.emit("state", state);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Splits a byte stream into complete lines, keeping the trailing partial line until the next chunk.
|
|
109
|
+
*/
|
|
110
|
+
function createLineSplitter(onLine) {
|
|
111
|
+
let remainder = "";
|
|
112
|
+
return {
|
|
113
|
+
push(chunk) {
|
|
114
|
+
remainder += chunk.toString();
|
|
115
|
+
const lines = remainder.split(/\r?\n/);
|
|
116
|
+
remainder = lines.pop() ?? "";
|
|
117
|
+
for (const line of lines) onLine(line);
|
|
118
|
+
},
|
|
119
|
+
flush() {
|
|
120
|
+
if (remainder.length > 0) onLine(remainder);
|
|
121
|
+
remainder = "";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { createLineSplitter as n, ProcessSupervisor as t };
|
|
128
|
+
//# sourceMappingURL=process-supervisor-CJOdmp0W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-supervisor-CJOdmp0W.js","names":["#state","#child","#startedAt","#setState","#stopping"],"sources":["../src/lib/process-supervisor.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\n\nexport type ProcessState = 'idle' | 'starting' | 'running' | 'stopping' | 'stopped' | 'crashed';\n\nexport interface ProcessExit {\n\tcode: number | null;\n\tsignal: NodeJS.Signals | null;\n\t/** Whether the exit was requested through {@link ProcessSupervisor.stop}. */\n\trequested: boolean;\n}\n\nexport interface ProcessSupervisorOptions {\n\tcommand: string;\n\targs: readonly string[];\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n\t/** Milliseconds to wait after `SIGTERM` before sending `SIGKILL`. */\n\tkillTimeout: number;\n}\n\nexport interface ProcessSupervisorEvents {\n\tstate: [state: ProcessState];\n\tstdout: [line: string];\n\tstderr: [line: string];\n\texit: [exit: ProcessExit];\n\terror: [error: Error];\n}\n\n/**\n * Owns a single child process: starts it, streams its output line by line and stops it gracefully.\n */\nexport class ProcessSupervisor extends EventEmitter<ProcessSupervisorEvents> {\n\t#child: ChildProcess | null = null;\n\t#state: ProcessState = 'idle';\n\t#startedAt: number | null = null;\n\t#stopping: Promise<void> | null = null;\n\n\tpublic constructor(private readonly options: ProcessSupervisorOptions) {\n\t\tsuper();\n\t}\n\n\tpublic get state(): ProcessState {\n\t\treturn this.#state;\n\t}\n\n\tpublic get pid(): number | null {\n\t\treturn this.#child?.pid ?? null;\n\t}\n\n\tpublic get startedAt(): number | null {\n\t\treturn this.#startedAt;\n\t}\n\n\tpublic get running(): boolean {\n\t\treturn this.#state === 'starting' || this.#state === 'running';\n\t}\n\n\tpublic start(): void {\n\t\tif (this.#child) throw new Error('The process is already running, call stop() or restart() first.');\n\n\t\tconst { command, args, cwd, env } = this.options;\n\t\tthis.#setState('starting');\n\n\t\tconst child = spawn(command, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });\n\t\tthis.#child = child;\n\t\tthis.#startedAt = Date.now();\n\n\t\tlet requested = false;\n\t\tconst stdout = createLineSplitter((line) => this.emit('stdout', line));\n\t\tconst stderr = createLineSplitter((line) => this.emit('stderr', line));\n\t\tchild.stdout?.on('data', stdout.push);\n\t\tchild.stderr?.on('data', stderr.push);\n\n\t\tchild.once('spawn', () => {\n\t\t\tif (this.#child === child) this.#setState('running');\n\t\t});\n\n\t\tchild.once('error', (error) => {\n\t\t\tthis.emit('error', error);\n\t\t});\n\n\t\tchild.once('exit', (code, signal) => {\n\t\t\tstdout.flush();\n\t\t\tstderr.flush();\n\t\t\trequested = this.#state === 'stopping';\n\t\t\tthis.#child = null;\n\t\t\tthis.#startedAt = null;\n\t\t\tthis.#setState(requested || code === 0 ? 'stopped' : 'crashed');\n\t\t\tthis.emit('exit', { code, signal, requested });\n\t\t});\n\t}\n\n\t/**\n\t * Sends `SIGTERM`, escalates to `SIGKILL` after `killTimeout` and resolves once the process exited.\n\t */\n\tpublic stop(): Promise<void> {\n\t\tconst child = this.#child;\n\t\tif (!child) return Promise.resolve();\n\t\tif (this.#stopping) return this.#stopping;\n\n\t\tthis.#setState('stopping');\n\t\tthis.#stopping = new Promise<void>((resolve) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tif (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL');\n\t\t\t}, this.options.killTimeout);\n\t\t\ttimer.unref();\n\n\t\t\tchild.once('exit', () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tthis.#stopping = null;\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tif (!child.kill('SIGTERM')) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tthis.#stopping = null;\n\t\t\t\tresolve();\n\t\t\t}\n\t\t});\n\n\t\treturn this.#stopping;\n\t}\n\n\tpublic async restart(): Promise<void> {\n\t\tawait this.stop();\n\t\tthis.start();\n\t}\n\n\t#setState(state: ProcessState): void {\n\t\tif (this.#state === state) return;\n\t\tthis.#state = state;\n\t\tthis.emit('state', state);\n\t}\n}\n\n/**\n * Splits a byte stream into complete lines, keeping the trailing partial line until the next chunk.\n */\nexport function createLineSplitter(onLine: (line: string) => void) {\n\tlet remainder = '';\n\treturn {\n\t\tpush(chunk: Buffer | string) {\n\t\t\tremainder += chunk.toString();\n\t\t\tconst lines = remainder.split(/\\r?\\n/);\n\t\t\tremainder = lines.pop() ?? '';\n\t\t\tfor (const line of lines) onLine(line);\n\t\t},\n\t\tflush() {\n\t\t\tif (remainder.length > 0) onLine(remainder);\n\t\t\tremainder = '';\n\t\t}\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { t as CliError } from "./errors-Bket6XFn.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
//#region src/lib/project.ts
|
|
8
|
+
/**
|
|
9
|
+
* Resolves a module id from the project root, the way the project itself would.
|
|
10
|
+
*/
|
|
11
|
+
function resolveFromProject(root, id) {
|
|
12
|
+
try {
|
|
13
|
+
return createRequire(join(root, "package.json")).resolve(id);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Imports a module from the project root, failing with an actionable error when it is not installed.
|
|
20
|
+
*/
|
|
21
|
+
async function importFromProject(root, id, hint) {
|
|
22
|
+
const resolved = resolveFromProject(root, id);
|
|
23
|
+
if (!resolved) throw new CliError(`"${id}" is not installed in ${root}`, {
|
|
24
|
+
code: "DEPENDENCY_MISSING",
|
|
25
|
+
hint
|
|
26
|
+
});
|
|
27
|
+
return await import(pathToFileURL(resolved).href);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolves an executable a package declares in its `package.json#bin`, from the project's own `node_modules`.
|
|
31
|
+
*
|
|
32
|
+
* Going through `bin` rather than a hardcoded path is what makes this work across layouts: TypeScript 7 hides
|
|
33
|
+
* `lib/tsc.js` behind its `exports` map, and packages move their entry points between releases.
|
|
34
|
+
*/
|
|
35
|
+
function resolveBinary(root, packageName, binName) {
|
|
36
|
+
const packageJsonPath = resolveFromProject(root, `${packageName}/package.json`);
|
|
37
|
+
if (!packageJsonPath) return null;
|
|
38
|
+
try {
|
|
39
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
40
|
+
const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.[binName];
|
|
41
|
+
return bin ? join(dirname(packageJsonPath), bin) : null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Finds the version of an installed package by walking `node_modules` up from `root`.
|
|
48
|
+
*/
|
|
49
|
+
function findInstalledVersion(root, name) {
|
|
50
|
+
let directory = root;
|
|
51
|
+
for (;;) {
|
|
52
|
+
const file = join(directory, "node_modules", name, "package.json");
|
|
53
|
+
if (existsSync(file)) try {
|
|
54
|
+
return JSON.parse(readFileSync(file, "utf-8")).version ?? null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const parent = dirname(directory);
|
|
59
|
+
if (parent === directory) return null;
|
|
60
|
+
directory = parent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { resolveFromProject as i, importFromProject as n, resolveBinary as r, findInstalledVersion as t };
|
|
66
|
+
//# sourceMappingURL=project-DHF3qdm-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-DHF3qdm-.js","names":[],"sources":["../src/lib/project.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { CliError } from './errors.js';\n\n/**\n * Resolves a module id from the project root, the way the project itself would.\n */\nexport function resolveFromProject(root: string, id: string): string | null {\n\ttry {\n\t\treturn createRequire(join(root, 'package.json')).resolve(id);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Imports a module from the project root, failing with an actionable error when it is not installed.\n */\nexport async function importFromProject<T>(root: string, id: string, hint: string): Promise<T> {\n\tconst resolved = resolveFromProject(root, id);\n\tif (!resolved) {\n\t\tthrow new CliError(`\"${id}\" is not installed in ${root}`, { code: 'DEPENDENCY_MISSING', hint });\n\t}\n\n\treturn (await import(pathToFileURL(resolved).href)) as T;\n}\n\n/**\n * Resolves an executable a package declares in its `package.json#bin`, from the project's own `node_modules`.\n *\n * Going through `bin` rather than a hardcoded path is what makes this work across layouts: TypeScript 7 hides\n * `lib/tsc.js` behind its `exports` map, and packages move their entry points between releases.\n */\nexport function resolveBinary(root: string, packageName: string, binName: string): string | null {\n\tconst packageJsonPath = resolveFromProject(root, `${packageName}/package.json`);\n\tif (!packageJsonPath) return null;\n\n\ttry {\n\t\tconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as { bin?: string | Record<string, string> };\n\t\tconst bin = typeof packageJson.bin === 'string' ? packageJson.bin : packageJson.bin?.[binName];\n\t\treturn bin ? join(dirname(packageJsonPath), bin) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Finds the version of an installed package by walking `node_modules` up from `root`.\n */\nexport function findInstalledVersion(root: string, name: string): string | null {\n\tlet directory = root;\n\tfor (;;) {\n\t\tconst file = join(directory, 'node_modules', name, 'package.json');\n\t\tif (existsSync(file)) {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(readFileSync(file, 'utf-8')) as { version?: string };\n\t\t\t\treturn parsed.version ?? null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\tconst parent = dirname(directory);\n\t\tif (parent === directory) return null;\n\t\tdirectory = parent;\n\t}\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,mBAAmB,MAAc,IAA2B;CAC3E,IAAI;EACH,OAAO,cAAc,KAAK,MAAM,cAAc,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC5D,QAAQ;EACP,OAAO;CACR;AACD;;;;AAKA,eAAsB,kBAAqB,MAAc,IAAY,MAA0B;CAC9F,MAAM,WAAW,mBAAmB,MAAM,EAAE;CAC5C,IAAI,CAAC,UACJ,MAAM,IAAI,SAAS,IAAI,GAAG,wBAAwB,QAAQ;EAAE,MAAM;EAAsB;CAAK,CAAC;CAG/F,OAAQ,MAAM,OAAO,cAAc,QAAQ,CAAC,CAAC;AAC9C;;;;;;;AAQA,SAAgB,cAAc,MAAc,aAAqB,SAAgC;CAChG,MAAM,kBAAkB,mBAAmB,MAAM,GAAG,YAAY,cAAc;CAC9E,IAAI,CAAC,iBAAiB,OAAO;CAE7B,IAAI;EACH,MAAM,cAAc,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;EACrE,MAAM,MAAM,OAAO,YAAY,QAAQ,WAAW,YAAY,MAAM,YAAY,MAAM;EACtF,OAAO,MAAM,KAAK,QAAQ,eAAe,GAAG,GAAG,IAAI;CACpD,QAAQ;EACP,OAAO;CACR;AACD;;;;AAKA,SAAgB,qBAAqB,MAAc,MAA6B;CAC/E,IAAI,YAAY;CAChB,SAAS;EACR,MAAM,OAAO,KAAK,WAAW,gBAAgB,MAAM,cAAc;EACjE,IAAI,WAAW,IAAI,GAClB,IAAI;GAEH,OADe,KAAK,MAAM,aAAa,MAAM,OAAO,CACxC,CAAC,CAAC,WAAW;EAC1B,QAAQ;GACP,OAAO;EACR;EAGD,MAAM,SAAS,QAAQ,SAAS;EAChC,IAAI,WAAW,WAAW,OAAO;EACjC,YAAY;CACb;AACD"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { t as CliError } from "./errors-Bket6XFn.js";
|
|
2
|
+
import { n as createLineSplitter } from "./process-supervisor-CJOdmp0W.js";
|
|
3
|
+
import { r as resolveBinary } from "./project-DHF3qdm-.js";
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
//#region src/lib/builders/tsc.ts
|
|
8
|
+
const INSTALL_HINT = "Install it with `pnpm add -D typescript`, or set `build.tool` to 'tsdown' or 'none'.";
|
|
9
|
+
const WATCH_START = /Starting (incremental )?compilation/;
|
|
10
|
+
const WATCH_DONE = /Found (\d+) errors?\. Watching for file changes/;
|
|
11
|
+
const ERROR_LINE = /error TS\d+:/;
|
|
12
|
+
/**
|
|
13
|
+
* Builds through the project's `tsc -b`, parsing its output to report progress.
|
|
14
|
+
*/
|
|
15
|
+
var TscBuilder = class extends EventEmitter {
|
|
16
|
+
config;
|
|
17
|
+
tool = "tsc";
|
|
18
|
+
#child = null;
|
|
19
|
+
#startedAt = 0;
|
|
20
|
+
#firstError = null;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
super();
|
|
23
|
+
this.config = config;
|
|
24
|
+
}
|
|
25
|
+
build() {
|
|
26
|
+
const child = this.#spawn([]);
|
|
27
|
+
this.#begin();
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
child.once("exit", (code) => {
|
|
30
|
+
this.#child = null;
|
|
31
|
+
resolve(this.#finish(code === 0 ? null : this.#firstError ?? `tsc exited with code ${code}`));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
watch() {
|
|
36
|
+
this.#spawn(["--watch", "--preserveWatchOutput"]);
|
|
37
|
+
this.#begin();
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
close() {
|
|
41
|
+
const child = this.#child;
|
|
42
|
+
this.#child = null;
|
|
43
|
+
if (!child || child.exitCode !== null) return Promise.resolve();
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
child.once("exit", () => resolve());
|
|
46
|
+
child.kill();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
#spawn(extraArgs) {
|
|
50
|
+
const tsc = resolveTscBinary(this.config.root);
|
|
51
|
+
if (!tsc) throw new CliError(`"typescript" is not installed in ${this.config.root}`, {
|
|
52
|
+
code: "DEPENDENCY_MISSING",
|
|
53
|
+
hint: INSTALL_HINT
|
|
54
|
+
});
|
|
55
|
+
const child = spawn(process.execPath, [
|
|
56
|
+
tsc,
|
|
57
|
+
"-b",
|
|
58
|
+
this.config.build.tsconfig,
|
|
59
|
+
"--pretty",
|
|
60
|
+
"false",
|
|
61
|
+
...extraArgs
|
|
62
|
+
], {
|
|
63
|
+
cwd: this.config.root,
|
|
64
|
+
env: process.env,
|
|
65
|
+
stdio: [
|
|
66
|
+
"ignore",
|
|
67
|
+
"pipe",
|
|
68
|
+
"pipe"
|
|
69
|
+
],
|
|
70
|
+
windowsHide: true
|
|
71
|
+
});
|
|
72
|
+
this.#child = child;
|
|
73
|
+
const handleLine = (line) => this.#handleLine(line);
|
|
74
|
+
const stdout = createLineSplitter(handleLine);
|
|
75
|
+
const stderr = createLineSplitter(handleLine);
|
|
76
|
+
child.stdout?.on("data", stdout.push);
|
|
77
|
+
child.stderr?.on("data", stderr.push);
|
|
78
|
+
child.once("exit", () => {
|
|
79
|
+
stdout.flush();
|
|
80
|
+
stderr.flush();
|
|
81
|
+
});
|
|
82
|
+
child.once("error", (error) => this.emit("log", "error", error.message));
|
|
83
|
+
return child;
|
|
84
|
+
}
|
|
85
|
+
#handleLine(line) {
|
|
86
|
+
const text = line.trim();
|
|
87
|
+
if (text.length === 0) return;
|
|
88
|
+
if (WATCH_START.test(text)) {
|
|
89
|
+
if (this.#startedAt === 0) this.#begin();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (ERROR_LINE.test(text)) {
|
|
93
|
+
this.#firstError ??= text;
|
|
94
|
+
this.emit("log", "error", text);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const done = WATCH_DONE.exec(text);
|
|
98
|
+
if (done) {
|
|
99
|
+
const errors = Number(done[1]);
|
|
100
|
+
this.emit("log", errors === 0 ? "success" : "error", text);
|
|
101
|
+
this.#finish(errors === 0 ? null : this.#firstError ?? text);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.emit("log", "info", text);
|
|
105
|
+
}
|
|
106
|
+
#begin() {
|
|
107
|
+
this.#firstError = null;
|
|
108
|
+
this.#startedAt = performance.now();
|
|
109
|
+
this.emit("start");
|
|
110
|
+
}
|
|
111
|
+
#finish(message) {
|
|
112
|
+
const outcome = {
|
|
113
|
+
ok: message === null,
|
|
114
|
+
durationMs: Math.round(performance.now() - this.#startedAt),
|
|
115
|
+
message
|
|
116
|
+
};
|
|
117
|
+
this.#startedAt = 0;
|
|
118
|
+
this.emit(outcome.ok ? "success" : "failure", outcome);
|
|
119
|
+
return outcome;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Locates the `tsc` script of the project's TypeScript through its `package.json#bin`, which works for both the
|
|
124
|
+
* classic `lib/tsc.js` layout (TypeScript ≤ 6) and the native `bin/tsc` launcher (TypeScript 7, whose `exports` map
|
|
125
|
+
* hides `lib/`).
|
|
126
|
+
*/
|
|
127
|
+
function resolveTscBinary(root) {
|
|
128
|
+
return resolveBinary(root, "typescript", "tsc");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
export { resolveTscBinary as n, TscBuilder as t };
|
|
133
|
+
//# sourceMappingURL=tsc-BqZfDTD0.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsc-BqZfDTD0.js","names":["#spawn","#begin","#child","#finish","#firstError","#handleLine","#startedAt"],"sources":["../src/lib/builders/tsc.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\nimport type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport { CliError } from '../errors.js';\nimport { createLineSplitter } from '../process-supervisor.js';\nimport { resolveBinary } from '../project.js';\nimport type { Builder, BuilderEvents, BuildOutcome } from './types.js';\n\nconst INSTALL_HINT = \"Install it with `pnpm add -D typescript`, or set `build.tool` to 'tsdown' or 'none'.\";\nconst WATCH_START = /Starting (incremental )?compilation/;\nconst WATCH_DONE = /Found (\\d+) errors?\\. Watching for file changes/;\nconst ERROR_LINE = /error TS\\d+:/;\n\n/**\n * Builds through the project's `tsc -b`, parsing its output to report progress.\n */\nexport class TscBuilder extends EventEmitter<BuilderEvents> implements Builder {\n\tpublic readonly tool = 'tsc' as const;\n\t#child: ChildProcess | null = null;\n\t#startedAt = 0;\n\t#firstError: string | null = null;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic build(): Promise<BuildOutcome> {\n\t\tconst child = this.#spawn([]);\n\t\tthis.#begin();\n\n\t\treturn new Promise((resolve) => {\n\t\t\tchild.once('exit', (code) => {\n\t\t\t\tthis.#child = null;\n\t\t\t\tresolve(this.#finish(code === 0 ? null : (this.#firstError ?? `tsc exited with code ${code}`)));\n\t\t\t});\n\t\t});\n\t}\n\n\tpublic watch(): Promise<void> {\n\t\tthis.#spawn(['--watch', '--preserveWatchOutput']);\n\t\tthis.#begin();\n\t\treturn Promise.resolve();\n\t}\n\n\tpublic close(): Promise<void> {\n\t\tconst child = this.#child;\n\t\tthis.#child = null;\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#spawn(extraArgs: string[]): ChildProcess {\n\t\tconst tsc = resolveTscBinary(this.config.root);\n\t\tif (!tsc) throw new CliError(`\"typescript\" is not installed in ${this.config.root}`, { code: 'DEPENDENCY_MISSING', hint: INSTALL_HINT });\n\n\t\tconst child = spawn(process.execPath, [tsc, '-b', this.config.build.tsconfig!, '--pretty', 'false', ...extraArgs], {\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\n\t\tconst handleLine = (line: string) => this.#handleLine(line);\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\t\tchild.once('exit', () => {\n\t\t\tstdout.flush();\n\t\t\tstderr.flush();\n\t\t});\n\t\tchild.once('error', (error) => this.emit('log', 'error', error.message));\n\n\t\treturn child;\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\tif (this.#startedAt === 0) this.#begin();\n\t\t\treturn;\n\t\t}\n\n\t\tif (ERROR_LINE.test(text)) {\n\t\t\tthis.#firstError ??= 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);\n\t\tif (done) {\n\t\t\tconst errors = Number(done[1]);\n\t\t\tthis.emit('log', errors === 0 ? 'success' : 'error', text);\n\t\t\tthis.#finish(errors === 0 ? null : (this.#firstError ?? text));\n\t\t\treturn;\n\t\t}\n\n\t\tthis.emit('log', 'info', text);\n\t}\n\n\t#begin(): void {\n\t\tthis.#firstError = null;\n\t\tthis.#startedAt = performance.now();\n\t\tthis.emit('start');\n\t}\n\n\t#finish(message: string | null): BuildOutcome {\n\t\tconst outcome: BuildOutcome = { ok: message === null, durationMs: Math.round(performance.now() - this.#startedAt), message };\n\t\tthis.#startedAt = 0;\n\t\tthis.emit(outcome.ok ? 'success' : 'failure', outcome);\n\t\treturn outcome;\n\t}\n}\n\n/**\n * Locates the `tsc` script of the project's TypeScript through its `package.json#bin`, which works for both the\n * classic `lib/tsc.js` layout (TypeScript ≤ 6) and the native `bin/tsc` launcher (TypeScript 7, whose `exports` map\n * hides `lib/`).\n */\nexport function resolveTscBinary(root: string): string | null {\n\treturn resolveBinary(root, 'typescript', 'tsc');\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { n as importFromProject } from "./project-DHF3qdm-.js";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/builders/tsdown.ts
|
|
5
|
+
const INSTALL_HINT = "Install it with `pnpm add -D tsdown`, or set `build.tool` to 'tsc' or 'none'.";
|
|
6
|
+
/**
|
|
7
|
+
* Builds through the project's own `tsdown`, honouring its `tsdown.config.*`.
|
|
8
|
+
*/
|
|
9
|
+
var TsdownBuilder = class extends EventEmitter {
|
|
10
|
+
config;
|
|
11
|
+
tool = "tsdown";
|
|
12
|
+
#bundles = [];
|
|
13
|
+
#hadError = false;
|
|
14
|
+
#startedAt = 0;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super();
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
async build() {
|
|
20
|
+
const tsdown = await this.#load();
|
|
21
|
+
this.#begin();
|
|
22
|
+
try {
|
|
23
|
+
this.#bundles = await tsdown.build({
|
|
24
|
+
cwd: this.config.root,
|
|
25
|
+
customLogger: this.#logger()
|
|
26
|
+
});
|
|
27
|
+
return this.#finish(this.#hadError ? "The build reported errors" : null);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return this.#finish(error instanceof Error ? error.message : String(error));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async watch() {
|
|
33
|
+
const tsdown = await this.#load();
|
|
34
|
+
this.#begin();
|
|
35
|
+
this.#bundles = await tsdown.build({
|
|
36
|
+
cwd: this.config.root,
|
|
37
|
+
watch: true,
|
|
38
|
+
customLogger: this.#logger(),
|
|
39
|
+
hooks: { "build:prepare": () => {
|
|
40
|
+
if (this.#startedAt === 0) this.#begin();
|
|
41
|
+
} },
|
|
42
|
+
onSuccess: () => {
|
|
43
|
+
this.#finish(null);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async close() {
|
|
48
|
+
const bundles = this.#bundles;
|
|
49
|
+
this.#bundles = [];
|
|
50
|
+
await Promise.allSettled(bundles.map((bundle) => bundle[Symbol.asyncDispose]()));
|
|
51
|
+
}
|
|
52
|
+
#load() {
|
|
53
|
+
return importFromProject(this.config.root, "tsdown", INSTALL_HINT);
|
|
54
|
+
}
|
|
55
|
+
#begin() {
|
|
56
|
+
this.#hadError = false;
|
|
57
|
+
this.#startedAt = performance.now();
|
|
58
|
+
this.emit("start");
|
|
59
|
+
}
|
|
60
|
+
#finish(message) {
|
|
61
|
+
const outcome = {
|
|
62
|
+
ok: message === null && !this.#hadError,
|
|
63
|
+
durationMs: Math.round(performance.now() - this.#startedAt),
|
|
64
|
+
message
|
|
65
|
+
};
|
|
66
|
+
this.#startedAt = 0;
|
|
67
|
+
this.emit(outcome.ok ? "success" : "failure", outcome);
|
|
68
|
+
return outcome;
|
|
69
|
+
}
|
|
70
|
+
#logger() {
|
|
71
|
+
const log = (level, args) => {
|
|
72
|
+
const text = args.filter((argument) => argument !== void 0 && argument !== null && argument !== false && argument !== "").map((argument) => argument instanceof Error ? argument.stack ?? argument.message : String(argument)).join(" ");
|
|
73
|
+
this.emit("log", level, text);
|
|
74
|
+
};
|
|
75
|
+
const warned = /* @__PURE__ */ new Set();
|
|
76
|
+
return {
|
|
77
|
+
level: "info",
|
|
78
|
+
info: (...args) => log("info", args),
|
|
79
|
+
warn: (...args) => log("warn", args),
|
|
80
|
+
warnOnce: (...args) => {
|
|
81
|
+
const key = args.map(String).join(" ");
|
|
82
|
+
if (warned.has(key)) return;
|
|
83
|
+
warned.add(key);
|
|
84
|
+
log("warn", args);
|
|
85
|
+
},
|
|
86
|
+
error: (...args) => {
|
|
87
|
+
this.#hadError = true;
|
|
88
|
+
log("error", args);
|
|
89
|
+
if (this.#startedAt !== 0 && this.#bundles.length > 0) this.#finish(errorSummary(args));
|
|
90
|
+
},
|
|
91
|
+
success: (...args) => log("success", args),
|
|
92
|
+
clearScreen: () => {}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
function errorSummary(args) {
|
|
97
|
+
const first = args[0];
|
|
98
|
+
if (first instanceof Error) return first.message.split("\n")[0] ?? "Build failed";
|
|
99
|
+
return String(first ?? "Build failed").split("\n")[0] ?? "Build failed";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { TsdownBuilder };
|
|
104
|
+
//# sourceMappingURL=tsdown-DlAs0rSI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsdown-DlAs0rSI.js","names":["#load","#begin","#bundles","#logger","#finish","#hadError","#startedAt"],"sources":["../src/lib/builders/tsdown.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport { importFromProject } from '../project.js';\nimport type { Builder, BuilderEvents, BuildOutcome } from './types.js';\n\ntype TsdownModule = typeof import('tsdown');\ntype TsdownLogger = import('tsdown').Logger;\ntype TsdownBundle = import('tsdown').TsdownBundle;\n\nconst INSTALL_HINT = \"Install it with `pnpm add -D tsdown`, or set `build.tool` to 'tsc' or 'none'.\";\n\n/**\n * Builds through the project's own `tsdown`, honouring its `tsdown.config.*`.\n */\nexport class TsdownBuilder extends EventEmitter<BuilderEvents> implements Builder {\n\tpublic readonly tool = 'tsdown' as const;\n\t#bundles: TsdownBundle[] = [];\n\t#hadError = false;\n\t#startedAt = 0;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic async build(): Promise<BuildOutcome> {\n\t\tconst tsdown = await this.#load();\n\t\tthis.#begin();\n\n\t\ttry {\n\t\t\tthis.#bundles = await tsdown.build({ cwd: this.config.root, customLogger: this.#logger() });\n\t\t\treturn this.#finish(this.#hadError ? 'The build reported errors' : null);\n\t\t} catch (error) {\n\t\t\treturn this.#finish(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tpublic async watch(): Promise<void> {\n\t\tconst tsdown = await this.#load();\n\n\t\tthis.#begin();\n\t\tthis.#bundles = await tsdown.build({\n\t\t\tcwd: this.config.root,\n\t\t\twatch: true,\n\t\t\tcustomLogger: this.#logger(),\n\t\t\thooks: {\n\t\t\t\t'build:prepare': () => {\n\t\t\t\t\tif (this.#startedAt === 0) this.#begin();\n\t\t\t\t}\n\t\t\t},\n\t\t\tonSuccess: () => {\n\t\t\t\tthis.#finish(null);\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tconst bundles = this.#bundles;\n\t\tthis.#bundles = [];\n\t\tawait Promise.allSettled(bundles.map((bundle) => bundle[Symbol.asyncDispose]()));\n\t}\n\n\t#load(): Promise<TsdownModule> {\n\t\treturn importFromProject<TsdownModule>(this.config.root, 'tsdown', INSTALL_HINT);\n\t}\n\n\t#begin(): void {\n\t\tthis.#hadError = false;\n\t\tthis.#startedAt = performance.now();\n\t\tthis.emit('start');\n\t}\n\n\t#finish(message: string | null): BuildOutcome {\n\t\tconst outcome: BuildOutcome = {\n\t\t\tok: message === null && !this.#hadError,\n\t\t\tdurationMs: Math.round(performance.now() - this.#startedAt),\n\t\t\tmessage\n\t\t};\n\t\tthis.#startedAt = 0;\n\t\tthis.emit(outcome.ok ? 'success' : 'failure', outcome);\n\t\treturn outcome;\n\t}\n\n\t#logger(): TsdownLogger {\n\t\tconst log = (level: 'info' | 'warn' | 'error' | 'success', args: unknown[]) => {\n\t\t\t// tsdown passes its (possibly undefined) name label and other blanks as separate arguments.\n\t\t\tconst text = args\n\t\t\t\t.filter((argument) => argument !== undefined && argument !== null && argument !== false && argument !== '')\n\t\t\t\t.map((argument) => (argument instanceof Error ? (argument.stack ?? argument.message) : String(argument)))\n\t\t\t\t.join(' ');\n\t\t\tthis.emit('log', level, text);\n\t\t};\n\n\t\tconst warned = new Set<string>();\n\t\treturn {\n\t\t\tlevel: 'info',\n\t\t\tinfo: (...args) => log('info', args),\n\t\t\twarn: (...args) => log('warn', args),\n\t\t\twarnOnce: (...args) => {\n\t\t\t\tconst key = args.map(String).join(' ');\n\t\t\t\tif (warned.has(key)) return;\n\t\t\t\twarned.add(key);\n\t\t\t\tlog('warn', args);\n\t\t\t},\n\t\t\terror: (...args) => {\n\t\t\t\tthis.#hadError = true;\n\t\t\t\tlog('error', args);\n\t\t\t\t// In watch mode tsdown never calls `onSuccess` after an error, report the failure now.\n\t\t\t\tif (this.#startedAt !== 0 && this.#bundles.length > 0) this.#finish(errorSummary(args));\n\t\t\t},\n\t\t\tsuccess: (...args) => log('success', args),\n\t\t\tclearScreen: () => {}\n\t\t};\n\t}\n}\n\nfunction errorSummary(args: unknown[]): string {\n\tconst first = args[0];\n\tif (first instanceof Error) return first.message.split('\\n')[0] ?? 'Build failed';\n\treturn String(first ?? 'Build failed').split('\\n')[0] ?? 'Build failed';\n}\n"],"mappings":"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"}
|