@wolfstar/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +10 -14
  2. package/dist/{build-DkMulM4f.js → build-B1YfbqX3.js} +2 -2
  3. package/dist/{build-DkMulM4f.js.map → build-B1YfbqX3.js.map} +1 -1
  4. package/dist/{build-DbGs9-s8.js → build-vpcF5bwc.js} +3 -2
  5. package/dist/build-vpcF5bwc.js.map +1 -0
  6. package/dist/cli.js +3 -3
  7. package/dist/{commands-BkZNThm8.js → commands-DPGDL7ft.js} +3 -3
  8. package/dist/commands-DPGDL7ft.js.map +1 -0
  9. package/dist/{commands-DwqViJ2V.js → commands-hpnmaBM2.js} +4 -4
  10. package/dist/{commands-DwqViJ2V.js.map → commands-hpnmaBM2.js.map} +1 -1
  11. package/dist/{dev-C6HogNjR.js → dev-Dh1Euxk1.js} +34 -6
  12. package/dist/dev-Dh1Euxk1.js.map +1 -0
  13. package/dist/{dev-BazJrPZ3.js → dev-hT1WR1Kh.js} +2 -2
  14. package/dist/{dev-BazJrPZ3.js.map → dev-hT1WR1Kh.js.map} +1 -1
  15. package/dist/external-Bk4EESdT.js.map +1 -1
  16. package/dist/index.d.ts +10 -10
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/locales-B3mRenhY.js +104 -0
  19. package/dist/locales-B3mRenhY.js.map +1 -0
  20. package/dist/log-buffer-D-nj2ZEU.js.map +1 -1
  21. package/dist/none-DEo7zEr9.js.map +1 -1
  22. package/dist/process-supervisor-CJOdmp0W.js.map +1 -1
  23. package/dist/tsc-BqZfDTD0.js.map +1 -1
  24. package/dist/{tsdown-Do8vjnIK.js → tsdown-DN3PQTJf.js} +8 -8
  25. package/dist/tsdown-DN3PQTJf.js.map +1 -0
  26. package/dist/{tui-C2P91TgW.js → tui-BCGQrMQo.js} +10 -1
  27. package/dist/tui-BCGQrMQo.js.map +1 -0
  28. package/dist/{tunnel-B8Id4-lR.js → tunnel-mGJe2zuE.js} +20 -7
  29. package/dist/tunnel-mGJe2zuE.js.map +1 -0
  30. package/dist/vite-27ZbreDz.js.map +1 -1
  31. package/package.json +6 -6
  32. package/dist/build-DbGs9-s8.js.map +0 -1
  33. package/dist/builders-NkAoPRdw.js +0 -44
  34. package/dist/builders-NkAoPRdw.js.map +0 -1
  35. package/dist/commands-BkZNThm8.js.map +0 -1
  36. package/dist/dev-C6HogNjR.js.map +0 -1
  37. package/dist/tsdown-Do8vjnIK.js.map +0 -1
  38. package/dist/tui-C2P91TgW.js.map +0 -1
  39. package/dist/tunnel-B8Id4-lR.js.map +0 -1
@@ -0,0 +1,104 @@
1
+ import { t as CliError } from "./errors-Bket6XFn.js";
2
+ import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
3
+ import { dirname, join, relative } from "node:path";
4
+ import { EventEmitter } from "node:events";
5
+
6
+ //#region src/lib/builders/index.ts
7
+ /**
8
+ * Fails on experiments the CLI cannot honour yet, before a command gets far enough to look like it worked.
9
+ *
10
+ * Called by `stars dev`/`stars build` up front, since a project whose build tool needs no build at all would
11
+ * otherwise return early and never reach {@link createBuilder}.
12
+ */
13
+ function assertSupportedExperiments(config) {
14
+ if (config.experimental.enableNitro) throw new CliError("`experimental.enableNitro` is not implemented yet", {
15
+ code: "EXPERIMENT_UNAVAILABLE",
16
+ hint: "Nitro needs the framework's Fetch adapter (wolfstar-project/stars-components#81); until it lands, use `build.tool` 'tsdown', 'vite' or 'tsc'."
17
+ });
18
+ }
19
+ async function createBuilder(config) {
20
+ assertSupportedExperiments(config);
21
+ if (config.experimental.enableExternalVite) {
22
+ const { ExternalBuilder } = await import("./external-Bk4EESdT.js");
23
+ return new ExternalBuilder(config);
24
+ }
25
+ switch (config.build.tool) {
26
+ case "tsdown": {
27
+ const { TsdownBuilder } = await import("./tsdown-DN3PQTJf.js");
28
+ return new TsdownBuilder(config);
29
+ }
30
+ case "vite": {
31
+ const { ViteBuilder } = await import("./vite-27ZbreDz.js");
32
+ return new ViteBuilder(config);
33
+ }
34
+ case "tsc": {
35
+ const { TscBuilder } = await import("./tsc-CovVAtfo.js");
36
+ return new TscBuilder(config);
37
+ }
38
+ case "none": {
39
+ const { NoneBuilder } = await import("./none-DEo7zEr9.js");
40
+ return new NoneBuilder(config);
41
+ }
42
+ }
43
+ }
44
+
45
+ //#endregion
46
+ //#region src/lib/locales.ts
47
+ /** Keeps the conventional `src/locales` data directory next to the compiled application. */
48
+ var Locales = class extends EventEmitter {
49
+ config;
50
+ source;
51
+ destination;
52
+ #watcher = null;
53
+ constructor(config) {
54
+ super();
55
+ this.config = config;
56
+ this.source = join(config.root, "src", "locales");
57
+ this.destination = join(config.build.outDir, "locales");
58
+ }
59
+ copy() {
60
+ if (this.config.build.tool === "none" || !existsSync(this.source)) return false;
61
+ mkdirSync(this.destination, { recursive: true });
62
+ cpSync(this.source, this.destination, {
63
+ recursive: true,
64
+ force: true
65
+ });
66
+ return true;
67
+ }
68
+ async watch() {
69
+ if (this.config.build.tool === "none" || !existsSync(this.source) || this.#watcher) return;
70
+ const { watch } = await import("chokidar");
71
+ const watcher = watch(this.source, { ignoreInitial: true });
72
+ this.#watcher = watcher;
73
+ watcher.on("all", (event, path) => {
74
+ try {
75
+ const target = join(this.destination, relative(this.source, path));
76
+ if (event === "unlink" || event === "unlinkDir") rmSync(target, {
77
+ recursive: event === "unlinkDir",
78
+ force: true
79
+ });
80
+ else if (existsSync(path)) {
81
+ mkdirSync(dirname(target), { recursive: true });
82
+ cpSync(path, target, {
83
+ recursive: event === "addDir",
84
+ force: true
85
+ });
86
+ }
87
+ this.emit("change");
88
+ } catch (error) {
89
+ this.emit("log", "error", `Failed to copy locales: ${error instanceof Error ? error.message : String(error)}`);
90
+ }
91
+ });
92
+ watcher.on("error", (error) => this.emit("log", "error", error instanceof Error ? error.message : String(error)));
93
+ await new Promise((resolve) => watcher.once("ready", resolve));
94
+ }
95
+ async close() {
96
+ const watcher = this.#watcher;
97
+ this.#watcher = null;
98
+ await watcher?.close();
99
+ }
100
+ };
101
+
102
+ //#endregion
103
+ export { assertSupportedExperiments as n, createBuilder as r, Locales as t };
104
+ //# sourceMappingURL=locales-B3mRenhY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locales-B3mRenhY.js","names":[],"sources":["../src/lib/builders/index.ts","../src/lib/locales.ts"],"sourcesContent":["import type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport { CliError } from '../errors.js';\nimport type { Builder } from './types.js';\n\n/**\n * Fails on experiments the CLI cannot honour yet, before a command gets far enough to look like it worked.\n *\n * Called by `stars dev`/`stars build` up front, since a project whose build tool needs no build at all would\n * otherwise return early and never reach {@link createBuilder}.\n */\nexport function assertSupportedExperiments(config: ResolvedStarsConfig): void {\n\tif (config.experimental.enableNitro) {\n\t\tthrow new CliError('`experimental.enableNitro` is not implemented yet', {\n\t\t\tcode: 'EXPERIMENT_UNAVAILABLE',\n\t\t\thint: \"Nitro needs the framework's Fetch adapter (wolfstar-project/stars-components#81); until it lands, use `build.tool` 'tsdown', 'vite' or 'tsc'.\"\n\t\t});\n\t}\n}\n\nexport async function createBuilder(config: ResolvedStarsConfig): Promise<Builder> {\n\tassertSupportedExperiments(config);\n\n\t// The project runs its own build, so the CLI only watches what it writes (see `ExternalBuilder`).\n\tif (config.experimental.enableExternalVite) {\n\t\tconst { ExternalBuilder } = await import('./external.js');\n\t\treturn new ExternalBuilder(config);\n\t}\n\n\tswitch (config.build.tool) {\n\t\tcase 'tsdown': {\n\t\t\tconst { TsdownBuilder } = await import('./tsdown.js');\n\t\t\treturn new TsdownBuilder(config);\n\t\t}\n\t\tcase 'vite': {\n\t\t\tconst { ViteBuilder } = await import('./vite.js');\n\t\t\treturn new ViteBuilder(config);\n\t\t}\n\t\tcase 'tsc': {\n\t\t\tconst { TscBuilder } = await import('./tsc.js');\n\t\t\treturn new TscBuilder(config);\n\t\t}\n\t\tcase 'none': {\n\t\t\tconst { NoneBuilder } = await import('./none.js');\n\t\t\treturn new NoneBuilder(config);\n\t\t}\n\t}\n}\n\nexport type { Builder, BuilderEvents, BuildOutcome } from './types.js';\n","import { EventEmitter } from 'node:events';\nimport { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { dirname, join, relative } from 'node:path';\nimport type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport type { LogLevel } from './log-buffer.js';\n\nexport interface LocalesEvents {\n\tchange: [];\n\tlog: [level: LogLevel, text: string];\n}\n\n/** Keeps the conventional `src/locales` data directory next to the compiled application. */\nexport class Locales extends EventEmitter<LocalesEvents> {\n\treadonly source: string;\n\treadonly destination: string;\n\t#watcher: { close(): Promise<void> } | null = null;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t\tthis.source = join(config.root, 'src', 'locales');\n\t\tthis.destination = join(config.build.outDir, 'locales');\n\t}\n\n\tpublic copy(): boolean {\n\t\tif (this.config.build.tool === 'none' || !existsSync(this.source)) return false;\n\t\tmkdirSync(this.destination, { recursive: true });\n\t\tcpSync(this.source, this.destination, { recursive: true, force: true });\n\t\treturn true;\n\t}\n\n\tpublic async watch(): Promise<void> {\n\t\tif (this.config.build.tool === 'none' || !existsSync(this.source) || this.#watcher) return;\n\t\tconst { watch } = await import('chokidar');\n\t\tconst watcher = watch(this.source, { ignoreInitial: true });\n\t\tthis.#watcher = watcher;\n\n\t\twatcher.on('all', (event, path) => {\n\t\t\ttry {\n\t\t\t\tconst target = join(this.destination, relative(this.source, path));\n\t\t\t\tif (event === 'unlink' || event === 'unlinkDir') rmSync(target, { recursive: event === 'unlinkDir', force: true });\n\t\t\t\telse if (existsSync(path)) {\n\t\t\t\t\tmkdirSync(dirname(target), { recursive: true });\n\t\t\t\t\tcpSync(path, target, { recursive: event === 'addDir', force: true });\n\t\t\t\t}\n\t\t\t\tthis.emit('change');\n\t\t\t} catch (error) {\n\t\t\t\tthis.emit('log', 'error', `Failed to copy locales: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t}\n\t\t});\n\t\twatcher.on('error', (error) => this.emit('log', 'error', error instanceof Error ? error.message : String(error)));\n\t\tawait new Promise<void>((resolve) => watcher.once('ready', resolve));\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tconst watcher = this.#watcher;\n\t\tthis.#watcher = null;\n\t\tawait watcher?.close();\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAUA,SAAgB,2BAA2B,QAAmC;CAC7E,IAAI,OAAO,aAAa,aACvB,MAAM,IAAI,SAAS,qDAAqD;EACvE,MAAM;EACN,MAAM;CACP,CAAC;AAEH;AAEA,eAAsB,cAAc,QAA+C;CAClF,2BAA2B,MAAM;CAGjC,IAAI,OAAO,aAAa,oBAAoB;EAC3C,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,OAAO,IAAI,gBAAgB,MAAM;CAClC;CAEA,QAAQ,OAAO,MAAM,MAArB;EACC,KAAK,UAAU;GACd,MAAM,EAAE,kBAAkB,MAAM,OAAO;GACvC,OAAO,IAAI,cAAc,MAAM;EAChC;EACA,KAAK,QAAQ;GACZ,MAAM,EAAE,gBAAgB,MAAM,OAAO;GACrC,OAAO,IAAI,YAAY,MAAM;EAC9B;EACA,KAAK,OAAO;GACX,MAAM,EAAE,eAAe,MAAM,OAAO;GACpC,OAAO,IAAI,WAAW,MAAM;EAC7B;EACA,KAAK,QAAQ;GACZ,MAAM,EAAE,gBAAgB,MAAM,OAAO;GACrC,OAAO,IAAI,YAAY,MAAM;EAC9B;CACD;AACD;;;;;AClCA,IAAa,UAAb,cAA6B,aAA4B;CAKpB;CAJpC,AAAS;CACT,AAAS;CACT,WAA8C;CAE9C,AAAO,YAAY,AAAiB,QAA6B;EAChE,MAAM;EAD6B;EAEnC,KAAK,SAAS,KAAK,OAAO,MAAM,OAAO,SAAS;EAChD,KAAK,cAAc,KAAK,OAAO,MAAM,QAAQ,SAAS;CACvD;CAEA,AAAO,OAAgB;EACtB,IAAI,KAAK,OAAO,MAAM,SAAS,UAAU,CAAC,WAAW,KAAK,MAAM,GAAG,OAAO;EAC1E,UAAU,KAAK,aAAa,EAAE,WAAW,KAAK,CAAC;EAC/C,OAAO,KAAK,QAAQ,KAAK,aAAa;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EACtE,OAAO;CACR;CAEA,MAAa,QAAuB;EACnC,IAAI,KAAK,OAAO,MAAM,SAAS,UAAU,CAAC,WAAW,KAAK,MAAM,KAAK,KAAK,UAAU;EACpF,MAAM,EAAE,UAAU,MAAM,OAAO;EAC/B,MAAM,UAAU,MAAM,KAAK,QAAQ,EAAE,eAAe,KAAK,CAAC;EAC1D,KAAK,WAAW;EAEhB,QAAQ,GAAG,QAAQ,OAAO,SAAS;GAClC,IAAI;IACH,MAAM,SAAS,KAAK,KAAK,aAAa,SAAS,KAAK,QAAQ,IAAI,CAAC;IACjE,IAAI,UAAU,YAAY,UAAU,aAAa,OAAO,QAAQ;KAAE,WAAW,UAAU;KAAa,OAAO;IAAK,CAAC;SAC5G,IAAI,WAAW,IAAI,GAAG;KAC1B,UAAU,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;KAC9C,OAAO,MAAM,QAAQ;MAAE,WAAW,UAAU;MAAU,OAAO;KAAK,CAAC;IACpE;IACA,KAAK,KAAK,QAAQ;GACnB,SAAS,OAAO;IACf,KAAK,KAAK,OAAO,SAAS,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;GAC9G;EACD,CAAC;EACD,QAAQ,GAAG,UAAU,UAAU,KAAK,KAAK,OAAO,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC;EAChH,MAAM,IAAI,SAAe,YAAY,QAAQ,KAAK,SAAS,OAAO,CAAC;CACpE;CAEA,MAAa,QAAuB;EACnC,MAAM,UAAU,KAAK;EACrB,KAAK,WAAW;EAChB,MAAM,SAAS,MAAM;CACtB;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"log-buffer-D-nj2ZEU.js","names":["#nextId","#entries"],"sources":["../src/lib/log-buffer.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { stripVTControlCharacters } from 'node:util';\n\nexport type LogSource = 'stars' | 'build' | 'app' | 'tsc' | 'tunnel';\nexport type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error';\n\nexport interface LogEntry {\n\treadonly id: number;\n\treadonly time: number;\n\treadonly source: LogSource;\n\treadonly level: LogLevel;\n\treadonly text: string;\n}\n\nexport interface LogFilter {\n\tsource?: LogSource | null;\n\tlevel?: LogLevel | null;\n\tquery?: string | null;\n}\n\nexport type LogInput = Omit<LogEntry, 'id' | 'time'>;\n\n/**\n * A bounded, in-memory log store shared by the dev service and its renderers.\n */\nexport class LogBuffer extends EventEmitter<{ entry: [LogEntry]; clear: [] }> {\n\t#entries: LogEntry[] = [];\n\t#nextId = 1;\n\n\tpublic constructor(public readonly capacity = 2000) {\n\t\tsuper();\n\t}\n\n\tpublic push(input: LogInput): LogEntry {\n\t\tconst entry: LogEntry = { id: this.#nextId++, time: Date.now(), ...input };\n\t\tthis.#entries.push(entry);\n\t\tif (this.#entries.length > this.capacity) this.#entries.splice(0, this.#entries.length - this.capacity);\n\t\tthis.emit('entry', entry);\n\t\treturn entry;\n\t}\n\n\tpublic entries(): readonly LogEntry[] {\n\t\treturn this.#entries;\n\t}\n\n\tpublic filter(filter: LogFilter): LogEntry[] {\n\t\tconst query = filter.query?.toLowerCase() ?? null;\n\t\treturn this.#entries.filter(\n\t\t\t(entry) =>\n\t\t\t\t(!filter.source || entry.source === filter.source) &&\n\t\t\t\t(!filter.level || matchesLevel(entry.level, filter.level)) &&\n\t\t\t\t(!query || entry.text.toLowerCase().includes(query))\n\t\t);\n\t}\n\n\tpublic clear(): void {\n\t\tthis.#entries = [];\n\t\tthis.emit('clear');\n\t}\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = { debug: 0, info: 1, success: 1, warn: 2, error: 3 };\n\n/**\n * Whether `level` is at least as severe as `minimum`.\n */\nexport function matchesLevel(level: LogLevel, minimum: LogLevel): boolean {\n\treturn LEVEL_ORDER[level] >= LEVEL_ORDER[minimum];\n}\n\n/**\n * Whether an application stderr line only continues the preceding JavaScript error. Stack frames and the fields\n * Node prints after an `Error` are useful log lines, but they must not each increment the panel's error counter.\n */\nexport function isErrorDetail(entry: Pick<LogEntry, 'source' | 'level' | 'text'>): boolean {\n\tif (entry.source !== 'app' || entry.level !== 'error') return false;\n\tconst text = stripVTControlCharacters(entry.text);\n\treturn /^\\s+at\\s/.test(text) || /^\\s+(?:type|path|code|cause|errno|syscall|address|port):/.test(text) || /^\\s*}\\s*$/.test(text) || !text.trim();\n}\n\n/** stderr is a transport, not a severity: Node and many loggers also send warnings there. */\nexport function classifyAppLine(text: string, fallback: LogLevel): LogLevel {\n\tconst plain = stripVTControlCharacters(text);\n\tif (/\\b(?:Error|ERROR|FATAL)\\b|\\bERR_[A-Z_]+\\b/.test(plain)) return 'error';\n\tif (/\\b(?:Warning|WARN|WARNING|DeprecationWarning|ExperimentalWarning)\\b/.test(plain) || /^\\(node:\\d+\\).*warning/i.test(plain)) return 'warn';\n\tif (/^\\s*(?:\\(Use .*--trace-warnings|\\[(?:info|debug)\\]|INFO\\b|DEBUG\\b|[◇ℹ✔])/.test(plain)) return 'info';\n\treturn fallback;\n}\n"],"mappings":";;;;;;;AAyBA,IAAa,YAAb,cAA+B,aAA+C;CAI1C;CAHnC,WAAuB,CAAC;CACxB,UAAU;CAEV,AAAO,YAAY,AAAgB,WAAW,KAAM;EACnD,MAAM;EAD4B;CAEnC;CAEA,AAAO,KAAK,OAA2B;EACtC,MAAM,QAAkB;GAAE,IAAI,KAAKA;GAAW,MAAM,KAAK,IAAI;GAAG,GAAG;EAAM;EACzE,KAAKC,SAAS,KAAK,KAAK;EACxB,IAAI,KAAKA,SAAS,SAAS,KAAK,UAAU,KAAKA,SAAS,OAAO,GAAG,KAAKA,SAAS,SAAS,KAAK,QAAQ;EACtG,KAAK,KAAK,SAAS,KAAK;EACxB,OAAO;CACR;CAEA,AAAO,UAA+B;EACrC,OAAO,KAAKA;CACb;CAEA,AAAO,OAAO,QAA+B;EAC5C,MAAM,QAAQ,OAAO,OAAO,YAAY,KAAK;EAC7C,OAAO,KAAKA,SAAS,QACnB,WACC,CAAC,OAAO,UAAU,MAAM,WAAW,OAAO,YAC1C,CAAC,OAAO,SAAS,aAAa,MAAM,OAAO,OAAO,KAAK,OACvD,CAAC,SAAS,MAAM,KAAK,YAAY,CAAC,CAAC,SAAS,KAAK,EACpD;CACD;CAEA,AAAO,QAAc;EACpB,KAAKA,WAAW,CAAC;EACjB,KAAK,KAAK,OAAO;CAClB;AACD;AAEA,MAAM,cAAwC;CAAE,OAAO;CAAG,MAAM;CAAG,SAAS;CAAG,MAAM;CAAG,OAAO;AAAE;;;;AAKjG,SAAgB,aAAa,OAAiB,SAA4B;CACzE,OAAO,YAAY,UAAU,YAAY;AAC1C;;;;;AAMA,SAAgB,cAAc,OAA6D;CAC1F,IAAI,MAAM,WAAW,SAAS,MAAM,UAAU,SAAS,OAAO;CAC9D,MAAM,OAAO,yBAAyB,MAAM,IAAI;CAChD,OAAO,WAAW,KAAK,IAAI,KAAK,2DAA2D,KAAK,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK;AAC/I;;AAGA,SAAgB,gBAAgB,MAAc,UAA8B;CAC3E,MAAM,QAAQ,yBAAyB,IAAI;CAC3C,IAAI,4CAA4C,KAAK,KAAK,GAAG,OAAO;CACpE,IAAI,sEAAsE,KAAK,KAAK,KAAK,0BAA0B,KAAK,KAAK,GAAG,OAAO;CACvI,IAAI,2EAA2E,KAAK,KAAK,GAAG,OAAO;CACnG,OAAO;AACR"}
1
+ {"version":3,"file":"log-buffer-D-nj2ZEU.js","names":[],"sources":["../src/lib/log-buffer.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { stripVTControlCharacters } from 'node:util';\n\nexport type LogSource = 'stars' | 'build' | 'app' | 'tsc' | 'tunnel';\nexport type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error';\n\nexport interface LogEntry {\n\treadonly id: number;\n\treadonly time: number;\n\treadonly source: LogSource;\n\treadonly level: LogLevel;\n\treadonly text: string;\n}\n\nexport interface LogFilter {\n\tsource?: LogSource | null;\n\tlevel?: LogLevel | null;\n\tquery?: string | null;\n}\n\nexport type LogInput = Omit<LogEntry, 'id' | 'time'>;\n\n/**\n * A bounded, in-memory log store shared by the dev service and its renderers.\n */\nexport class LogBuffer extends EventEmitter<{ entry: [LogEntry]; clear: [] }> {\n\t#entries: LogEntry[] = [];\n\t#nextId = 1;\n\n\tpublic constructor(public readonly capacity = 2000) {\n\t\tsuper();\n\t}\n\n\tpublic push(input: LogInput): LogEntry {\n\t\tconst entry: LogEntry = { id: this.#nextId++, time: Date.now(), ...input };\n\t\tthis.#entries.push(entry);\n\t\tif (this.#entries.length > this.capacity) this.#entries.splice(0, this.#entries.length - this.capacity);\n\t\tthis.emit('entry', entry);\n\t\treturn entry;\n\t}\n\n\tpublic entries(): readonly LogEntry[] {\n\t\treturn this.#entries;\n\t}\n\n\tpublic filter(filter: LogFilter): LogEntry[] {\n\t\tconst query = filter.query?.toLowerCase() ?? null;\n\t\treturn this.#entries.filter(\n\t\t\t(entry) =>\n\t\t\t\t(!filter.source || entry.source === filter.source) &&\n\t\t\t\t(!filter.level || matchesLevel(entry.level, filter.level)) &&\n\t\t\t\t(!query || entry.text.toLowerCase().includes(query))\n\t\t);\n\t}\n\n\tpublic clear(): void {\n\t\tthis.#entries = [];\n\t\tthis.emit('clear');\n\t}\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = { debug: 0, info: 1, success: 1, warn: 2, error: 3 };\n\n/**\n * Whether `level` is at least as severe as `minimum`.\n */\nexport function matchesLevel(level: LogLevel, minimum: LogLevel): boolean {\n\treturn LEVEL_ORDER[level] >= LEVEL_ORDER[minimum];\n}\n\n/**\n * Whether an application stderr line only continues the preceding JavaScript error. Stack frames and the fields\n * Node prints after an `Error` are useful log lines, but they must not each increment the panel's error counter.\n */\nexport function isErrorDetail(entry: Pick<LogEntry, 'source' | 'level' | 'text'>): boolean {\n\tif (entry.source !== 'app' || entry.level !== 'error') return false;\n\tconst text = stripVTControlCharacters(entry.text);\n\treturn /^\\s+at\\s/.test(text) || /^\\s+(?:type|path|code|cause|errno|syscall|address|port):/.test(text) || /^\\s*}\\s*$/.test(text) || !text.trim();\n}\n\n/** stderr is a transport, not a severity: Node and many loggers also send warnings there. */\nexport function classifyAppLine(text: string, fallback: LogLevel): LogLevel {\n\tconst plain = stripVTControlCharacters(text);\n\tif (/\\b(?:Error|ERROR|FATAL)\\b|\\bERR_[A-Z_]+\\b/.test(plain)) return 'error';\n\tif (/\\b(?:Warning|WARN|WARNING|DeprecationWarning|ExperimentalWarning)\\b/.test(plain) || /^\\(node:\\d+\\).*warning/i.test(plain)) return 'warn';\n\tif (/^\\s*(?:\\(Use .*--trace-warnings|\\[(?:info|debug)\\]|INFO\\b|DEBUG\\b|[◇ℹ✔])/.test(plain)) return 'info';\n\treturn fallback;\n}\n"],"mappings":";;;;;;;AAyBA,IAAa,YAAb,cAA+B,aAA+C;CAI1C;CAHnC,WAAuB,CAAC;CACxB,UAAU;CAEV,AAAO,YAAY,AAAgB,WAAW,KAAM;EACnD,MAAM;EAD4B;CAEnC;CAEA,AAAO,KAAK,OAA2B;EACtC,MAAM,QAAkB;GAAE,IAAI,KAAK;GAAW,MAAM,KAAK,IAAI;GAAG,GAAG;EAAM;EACzE,KAAK,SAAS,KAAK,KAAK;EACxB,IAAI,KAAK,SAAS,SAAS,KAAK,UAAU,KAAK,SAAS,OAAO,GAAG,KAAK,SAAS,SAAS,KAAK,QAAQ;EACtG,KAAK,KAAK,SAAS,KAAK;EACxB,OAAO;CACR;CAEA,AAAO,UAA+B;EACrC,OAAO,KAAK;CACb;CAEA,AAAO,OAAO,QAA+B;EAC5C,MAAM,QAAQ,OAAO,OAAO,YAAY,KAAK;EAC7C,OAAO,KAAK,SAAS,QACnB,WACC,CAAC,OAAO,UAAU,MAAM,WAAW,OAAO,YAC1C,CAAC,OAAO,SAAS,aAAa,MAAM,OAAO,OAAO,KAAK,OACvD,CAAC,SAAS,MAAM,KAAK,YAAY,CAAC,CAAC,SAAS,KAAK,EACpD;CACD;CAEA,AAAO,QAAc;EACpB,KAAK,WAAW,CAAC;EACjB,KAAK,KAAK,OAAO;CAClB;AACD;AAEA,MAAM,cAAwC;CAAE,OAAO;CAAG,MAAM;CAAG,SAAS;CAAG,MAAM;CAAG,OAAO;AAAE;;;;AAKjG,SAAgB,aAAa,OAAiB,SAA4B;CACzE,OAAO,YAAY,UAAU,YAAY;AAC1C;;;;;AAMA,SAAgB,cAAc,OAA6D;CAC1F,IAAI,MAAM,WAAW,SAAS,MAAM,UAAU,SAAS,OAAO;CAC9D,MAAM,OAAO,yBAAyB,MAAM,IAAI;CAChD,OAAO,WAAW,KAAK,IAAI,KAAK,2DAA2D,KAAK,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK;AAC/I;;AAGA,SAAgB,gBAAgB,MAAc,UAA8B;CAC3E,MAAM,QAAQ,yBAAyB,IAAI;CAC3C,IAAI,4CAA4C,KAAK,KAAK,GAAG,OAAO;CACpE,IAAI,sEAAsE,KAAK,KAAK,KAAK,0BAA0B,KAAK,KAAK,GAAG,OAAO;CACvI,IAAI,2EAA2E,KAAK,KAAK,GAAG,OAAO;CACnG,OAAO;AACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"none-DEo7zEr9.js","names":["#watcher"],"sources":["../src/lib/builders/none.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport type { Builder, BuilderEvents, BuildOutcome } from './types.js';\n\n/**\n * No build step: watches the configured paths and reports every change as an instant successful \"build\".\n */\nexport class NoneBuilder extends EventEmitter<BuilderEvents> implements Builder {\n\tpublic readonly tool = 'none' as const;\n\t#watcher: { close(): Promise<void> } | null = null;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic build(): Promise<BuildOutcome> {\n\t\tthis.emit('start');\n\t\tconst outcome: BuildOutcome = { ok: true, durationMs: 0, message: null };\n\t\tthis.emit('success', outcome);\n\t\treturn Promise.resolve(outcome);\n\t}\n\n\tpublic async watch(): Promise<void> {\n\t\tconst { watch } = await import('chokidar');\n\t\tconst watcher = watch([...this.config.dev.watch], {\n\t\t\tignored: [...this.config.dev.ignore],\n\t\t\tignoreInitial: true,\n\t\t\tcwd: this.config.root\n\t\t});\n\t\tthis.#watcher = watcher;\n\n\t\twatcher.on('all', (event, path) => {\n\t\t\tthis.emit('log', 'info', `${event}: ${path}`);\n\t\t\tvoid this.build();\n\t\t});\n\t\twatcher.on('error', (error) => this.emit('log', 'error', error instanceof Error ? error.message : String(error)));\n\n\t\tawait new Promise<void>((resolve) => watcher.once('ready', resolve));\n\t\tawait this.build();\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tconst watcher = this.#watcher;\n\t\tthis.#watcher = null;\n\t\tawait watcher?.close();\n\t}\n}\n"],"mappings":";;;;;;AAOA,IAAa,cAAb,cAAiC,aAA+C;CAI3C;CAHpC,AAAgB,OAAO;CACvB,WAA8C;CAE9C,AAAO,YAAY,AAAiB,QAA6B;EAChE,MAAM;EAD6B;CAEpC;CAEA,AAAO,QAA+B;EACrC,KAAK,KAAK,OAAO;EACjB,MAAM,UAAwB;GAAE,IAAI;GAAM,YAAY;GAAG,SAAS;EAAK;EACvE,KAAK,KAAK,WAAW,OAAO;EAC5B,OAAO,QAAQ,QAAQ,OAAO;CAC/B;CAEA,MAAa,QAAuB;EACnC,MAAM,EAAE,UAAU,MAAM,OAAO;EAC/B,MAAM,UAAU,MAAM,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,GAAG;GACjD,SAAS,CAAC,GAAG,KAAK,OAAO,IAAI,MAAM;GACnC,eAAe;GACf,KAAK,KAAK,OAAO;EAClB,CAAC;EACD,KAAKA,WAAW;EAEhB,QAAQ,GAAG,QAAQ,OAAO,SAAS;GAClC,KAAK,KAAK,OAAO,QAAQ,GAAG,MAAM,IAAI,MAAM;GAC5C,AAAK,KAAK,MAAM;EACjB,CAAC;EACD,QAAQ,GAAG,UAAU,UAAU,KAAK,KAAK,OAAO,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC;EAEhH,MAAM,IAAI,SAAe,YAAY,QAAQ,KAAK,SAAS,OAAO,CAAC;EACnE,MAAM,KAAK,MAAM;CAClB;CAEA,MAAa,QAAuB;EACnC,MAAM,UAAU,KAAKA;EACrB,KAAKA,WAAW;EAChB,MAAM,SAAS,MAAM;CACtB;AACD"}
1
+ {"version":3,"file":"none-DEo7zEr9.js","names":[],"sources":["../src/lib/builders/none.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport type { Builder, BuilderEvents, BuildOutcome } from './types.js';\n\n/**\n * No build step: watches the configured paths and reports every change as an instant successful \"build\".\n */\nexport class NoneBuilder extends EventEmitter<BuilderEvents> implements Builder {\n\tpublic readonly tool = 'none' as const;\n\t#watcher: { close(): Promise<void> } | null = null;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\n\t}\n\n\tpublic build(): Promise<BuildOutcome> {\n\t\tthis.emit('start');\n\t\tconst outcome: BuildOutcome = { ok: true, durationMs: 0, message: null };\n\t\tthis.emit('success', outcome);\n\t\treturn Promise.resolve(outcome);\n\t}\n\n\tpublic async watch(): Promise<void> {\n\t\tconst { watch } = await import('chokidar');\n\t\tconst watcher = watch([...this.config.dev.watch], {\n\t\t\tignored: [...this.config.dev.ignore],\n\t\t\tignoreInitial: true,\n\t\t\tcwd: this.config.root\n\t\t});\n\t\tthis.#watcher = watcher;\n\n\t\twatcher.on('all', (event, path) => {\n\t\t\tthis.emit('log', 'info', `${event}: ${path}`);\n\t\t\tvoid this.build();\n\t\t});\n\t\twatcher.on('error', (error) => this.emit('log', 'error', error instanceof Error ? error.message : String(error)));\n\n\t\tawait new Promise<void>((resolve) => watcher.once('ready', resolve));\n\t\tawait this.build();\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tconst watcher = this.#watcher;\n\t\tthis.#watcher = null;\n\t\tawait watcher?.close();\n\t}\n}\n"],"mappings":";;;;;;AAOA,IAAa,cAAb,cAAiC,aAA+C;CAI3C;CAHpC,AAAgB,OAAO;CACvB,WAA8C;CAE9C,AAAO,YAAY,AAAiB,QAA6B;EAChE,MAAM;EAD6B;CAEpC;CAEA,AAAO,QAA+B;EACrC,KAAK,KAAK,OAAO;EACjB,MAAM,UAAwB;GAAE,IAAI;GAAM,YAAY;GAAG,SAAS;EAAK;EACvE,KAAK,KAAK,WAAW,OAAO;EAC5B,OAAO,QAAQ,QAAQ,OAAO;CAC/B;CAEA,MAAa,QAAuB;EACnC,MAAM,EAAE,UAAU,MAAM,OAAO;EAC/B,MAAM,UAAU,MAAM,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,GAAG;GACjD,SAAS,CAAC,GAAG,KAAK,OAAO,IAAI,MAAM;GACnC,eAAe;GACf,KAAK,KAAK,OAAO;EAClB,CAAC;EACD,KAAK,WAAW;EAEhB,QAAQ,GAAG,QAAQ,OAAO,SAAS;GAClC,KAAK,KAAK,OAAO,QAAQ,GAAG,MAAM,IAAI,MAAM;GAC5C,AAAK,KAAK,MAAM;EACjB,CAAC;EACD,QAAQ,GAAG,UAAU,UAAU,KAAK,KAAK,OAAO,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC;EAEhH,MAAM,IAAI,SAAe,YAAY,QAAQ,KAAK,SAAS,OAAO,CAAC;EACnE,MAAM,KAAK,MAAM;CAClB;CAEA,MAAa,QAAuB;EACnC,MAAM,UAAU,KAAK;EACrB,KAAK,WAAW;EAChB,MAAM,SAAS,MAAM;CACtB;AACD"}
@@ -1 +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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"}
1
+ {"version":3,"file":"process-supervisor-CJOdmp0W.js","names":[],"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":";;;;;;;AAgCA,IAAa,oBAAb,cAAuC,aAAsC;CAMxC;CALpC,SAA8B;CAC9B,SAAuB;CACvB,aAA4B;CAC5B,YAAkC;CAElC,AAAO,YAAY,AAAiB,SAAmC;EACtE,MAAM;EAD6B;CAEpC;CAEA,IAAW,QAAsB;EAChC,OAAO,KAAK;CACb;CAEA,IAAW,MAAqB;EAC/B,OAAO,KAAK,QAAQ,OAAO;CAC5B;CAEA,IAAW,YAA2B;EACrC,OAAO,KAAK;CACb;CAEA,IAAW,UAAmB;EAC7B,OAAO,KAAK,WAAW,cAAc,KAAK,WAAW;CACtD;CAEA,AAAO,QAAc;EACpB,IAAI,KAAK,QAAQ,MAAM,IAAI,MAAM,iEAAiE;EAElG,MAAM,EAAE,SAAS,MAAM,KAAK,QAAQ,KAAK;EACzC,KAAK,UAAU,UAAU;EAEzB,MAAM,QAAQ,MAAM,SAAS,MAAM;GAAE;GAAK;GAAK,OAAO;IAAC;IAAU;IAAQ;GAAM;GAAG,aAAa;EAAK,CAAC;EACrG,KAAK,SAAS;EACd,KAAK,aAAa,KAAK,IAAI;EAE3B,IAAI,YAAY;EAChB,MAAM,SAAS,oBAAoB,SAAS,KAAK,KAAK,UAAU,IAAI,CAAC;EACrE,MAAM,SAAS,oBAAoB,SAAS,KAAK,KAAK,UAAU,IAAI,CAAC;EACrE,MAAM,QAAQ,GAAG,QAAQ,OAAO,IAAI;EACpC,MAAM,QAAQ,GAAG,QAAQ,OAAO,IAAI;EAEpC,MAAM,KAAK,eAAe;GACzB,IAAI,KAAK,WAAW,OAAO,KAAK,UAAU,SAAS;EACpD,CAAC;EAED,MAAM,KAAK,UAAU,UAAU;GAC9B,KAAK,KAAK,SAAS,KAAK;EACzB,CAAC;EAED,MAAM,KAAK,SAAS,MAAM,WAAW;GACpC,OAAO,MAAM;GACb,OAAO,MAAM;GACb,YAAY,KAAK,WAAW;GAC5B,KAAK,SAAS;GACd,KAAK,aAAa;GAClB,KAAK,UAAU,aAAa,SAAS,IAAI,YAAY,SAAS;GAC9D,KAAK,KAAK,QAAQ;IAAE;IAAM;IAAQ;GAAU,CAAC;EAC9C,CAAC;CACF;;;;CAKA,AAAO,OAAsB;EAC5B,MAAM,QAAQ,KAAK;EACnB,IAAI,CAAC,OAAO,OAAO,QAAQ,QAAQ;EACnC,IAAI,KAAK,WAAW,OAAO,KAAK;EAEhC,KAAK,UAAU,UAAU;EACzB,KAAK,YAAY,IAAI,SAAe,YAAY;GAC/C,MAAM,QAAQ,iBAAiB;IAC9B,IAAI,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM,MAAM,KAAK,SAAS;GAC/E,GAAG,KAAK,QAAQ,WAAW;GAC3B,MAAM,MAAM;GAEZ,MAAM,KAAK,cAAc;IACxB,aAAa,KAAK;IAClB,KAAK,YAAY;IACjB,QAAQ;GACT,CAAC;GAED,IAAI,CAAC,MAAM,KAAK,SAAS,GAAG;IAC3B,aAAa,KAAK;IAClB,KAAK,YAAY;IACjB,QAAQ;GACT;EACD,CAAC;EAED,OAAO,KAAK;CACb;CAEA,MAAa,UAAyB;EACrC,MAAM,KAAK,KAAK;EAChB,KAAK,MAAM;CACZ;CAEA,UAAU,OAA2B;EACpC,IAAI,KAAK,WAAW,OAAO;EAC3B,KAAK,SAAS;EACd,KAAK,KAAK,SAAS,KAAK;CACzB;AACD;;;;AAKA,SAAgB,mBAAmB,QAAgC;CAClE,IAAI,YAAY;CAChB,OAAO;EACN,KAAK,OAAwB;GAC5B,aAAa,MAAM,SAAS;GAC5B,MAAM,QAAQ,UAAU,MAAM,OAAO;GACrC,YAAY,MAAM,IAAI,KAAK;GAC3B,KAAK,MAAM,QAAQ,OAAO,OAAO,IAAI;EACtC;EACA,QAAQ;GACP,IAAI,UAAU,SAAS,GAAG,OAAO,SAAS;GAC1C,YAAY;EACb;CACD;AACD"}
@@ -1 +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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"}
1
+ {"version":3,"file":"tsc-BqZfDTD0.js","names":[],"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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"}
@@ -23,7 +23,7 @@ const RELATIVE_PATH = /^\.\.?[/\\]/;
23
23
  var TsdownBuilder = class extends EventEmitter {
24
24
  config;
25
25
  tool = "tsdown";
26
- #bundles = [];
26
+ #handle = null;
27
27
  #hadError = false;
28
28
  #startedAt = 0;
29
29
  constructor(config) {
@@ -35,7 +35,7 @@ var TsdownBuilder = class extends EventEmitter {
35
35
  const options = await this.#options();
36
36
  this.#begin();
37
37
  try {
38
- this.#bundles = await tsdown.build(options);
38
+ this.#handle = await tsdown.build(options);
39
39
  return this.#finish(this.#hadError ? "The build reported errors" : null);
40
40
  } catch (error) {
41
41
  return this.#finish(error instanceof Error ? error.message : String(error));
@@ -45,12 +45,12 @@ var TsdownBuilder = class extends EventEmitter {
45
45
  const tsdown = await this.#load();
46
46
  const options = await this.#options();
47
47
  this.#begin();
48
- this.#bundles = await tsdown.build(this.#watchOptions(options));
48
+ this.#handle = await tsdown.build(this.#watchOptions(options));
49
49
  }
50
50
  async close() {
51
- const bundles = this.#bundles;
52
- this.#bundles = [];
53
- await Promise.allSettled(bundles.map((bundle) => bundle[Symbol.asyncDispose]()));
51
+ const handle = this.#handle;
52
+ this.#handle = null;
53
+ await handle?.watch.close();
54
54
  }
55
55
  #load() {
56
56
  return importFromProject(this.config.root, "tsdown", INSTALL_HINT);
@@ -220,7 +220,7 @@ var TsdownBuilder = class extends EventEmitter {
220
220
  error: (...args) => {
221
221
  this.#hadError = true;
222
222
  log("error", args);
223
- if (this.#startedAt !== 0 && this.#bundles.length > 0) this.#finish(errorSummary(args));
223
+ if (this.#startedAt !== 0) this.#finish(errorSummary(args));
224
224
  },
225
225
  success: () => {},
226
226
  clearScreen: () => {}
@@ -239,4 +239,4 @@ function errorSummary(args) {
239
239
 
240
240
  //#endregion
241
241
  export { TsdownBuilder };
242
- //# sourceMappingURL=tsdown-Do8vjnIK.js.map
242
+ //# sourceMappingURL=tsdown-DN3PQTJf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsdown-DN3PQTJf.js","names":[],"sources":["../src/lib/builders/tsdown.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { dirname, extname, relative, resolve } from 'node:path';\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 TsdownHandle = import('tsdown').TsdownHandle;\ntype TsdownInlineConfig = NonNullable<Parameters<TsdownModule['build']>[0]>;\ntype TsdownOptions = Record<string, unknown>;\ntype AnyFunction = (...args: never[]) => unknown;\n\nconst INSTALL_HINT = \"Install it with `pnpm add -D tsdown`, or set `build.tool` to 'tsc' or 'none'.\";\nconst TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.mts', '.cts']);\n/** `./src/lib` and `../shared` are paths; `preact/compat` is a module id and must be left alone. */\nconst RELATIVE_PATH = /^\\.\\.?[/\\\\]/;\n\n/**\n * Builds through the project's own `tsdown`, configured from `stars.config`.\n *\n * From `future.compatibilityVersion` 4 on this is the whole configuration: the entry's directory, `build.outDir` and\n * `build.tsconfig` produce the defaults a bot needs, the project's `tsdown` block is layered on top, and the auto\n * imports plugin is wired in — one configuration file instead of two. At 3 the project's own `tsdown.config.*` is\n * still loaded and everything here is merged over it by `tsdown` itself: scalars from `stars.config` win, and\n * `plugins` are appended rather than replaced.\n */\nexport class TsdownBuilder extends EventEmitter<BuilderEvents> implements Builder {\n\tpublic readonly tool = 'tsdown' as const;\n\t#handle: TsdownHandle | null = null;\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\tconst options = await this.#options();\n\t\tthis.#begin();\n\n\t\ttry {\n\t\t\tthis.#handle = await tsdown.build(options as TsdownInlineConfig);\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\t\tconst options = await this.#options();\n\n\t\tthis.#begin();\n\t\tthis.#handle = await tsdown.build(this.#watchOptions(options) as TsdownInlineConfig);\n\t}\n\n\tpublic async close(): Promise<void> {\n\t\tconst handle = this.#handle;\n\t\tthis.#handle = null;\n\t\tawait handle?.watch.close();\n\t}\n\n\t#load(): Promise<TsdownModule> {\n\t\treturn importFromProject<TsdownModule>(this.config.root, 'tsdown', INSTALL_HINT);\n\t}\n\n\t/**\n\t * The options handed to `tsdown.build()`, with the project's own `tsdown` block already layered on top of the\n\t * defaults (and of the plugins `stars` contributes).\n\t */\n\tasync #options(): Promise<TsdownOptions> {\n\t\tconst user = this.config.tsdown as TsdownOptions;\n\t\t// A `tsdown.config.*` (or `package.json#tsdown`) is the project's own; `build.configFile` is `null` from\n\t\t// compatibility version 4 on, where loading one is a configuration error rather than a fallback.\n\t\tconst defaults = this.config.build.configFile === null ? this.#defaults() : {};\n\t\tconst plugins = [...(await this.#plugins()), ...toArray(user.plugins)];\n\t\t// `plugins` and `alias` are added to rather than replaced: a project declaring one of its own would\n\t\t// otherwise silently drop the auto imports transform, or every `~`/`@` import in its sources.\n\t\tconst alias = { ...(defaults.alias as object), ...this.#alias(user.alias) };\n\n\t\treturn {\n\t\t\t...defaults,\n\t\t\t...user,\n\t\t\t...(plugins.length > 0 ? { plugins } : {}),\n\t\t\t...(Object.keys(alias).length > 0 ? { alias } : {}),\n\t\t\t// Last, and so not overridable: these are how the CLI talks to `tsdown` rather than project build options —\n\t\t\t// the project root it resolves from, its own compact progress UI, and the logger that feeds diagnostics to\n\t\t\t// the dev panel. `logLevel` also covers the few lifecycle messages tsdown writes through its global logger.\n\t\t\tcwd: this.config.root,\n\t\t\tlogLevel: 'warn',\n\t\t\tcustomLogger: this.#logger()\n\t\t};\n\t}\n\n\t/**\n\t * What `stars.config` alone says the build is — the configuration a bot would otherwise write out by hand, taken\n\t * from the ones the WolfStar bots already keep next to theirs:\n\t *\n\t * - every source file next to the entry (minus tests), emitted one-to-one (`unbundle`) so the stores keep\n\t * loading pieces from `dist/commands` and friends at runtime;\n\t * - ESM on `platform: 'node'`, with the extension `build.output` — and so `stars dev`, `package.json#main` and\n\t * `node dist/…` — expects;\n\t * - sourcemaps and treeshaking on, minification off: a bot is deployed, not shipped, so readable stack traces\n\t * are worth more than bytes;\n\t * - dependencies left in `node_modules` rather than bundled into the output;\n\t * - Nuxt's own alias prefixes: `~` and `@` for the entry's directory, `~~` and `@@` for the project root;\n\t * - no declaration files: nothing consumes a bot's `dist/`, and emitting them roughly doubles the build. A\n\t * project that wants them sets `tsdown: { dts: true }`.\n\t *\n\t * Every one of these is overridable through the project's `tsdown` block.\n\t */\n\t#defaults(): TsdownOptions {\n\t\tconst { root, entry, build } = this.config;\n\t\tconst source = dirname(entry);\n\t\tconst sourceDir = relative(root, source) || '.';\n\t\tconst extensions = TYPESCRIPT_EXTENSIONS.has(extname(entry)) ? '{ts,mts,cts}' : '{js,mjs,cjs}';\n\n\t\treturn {\n\t\t\tconfig: false,\n\t\t\tentry: [`${sourceDir}/**/*.${extensions}`, `!${sourceDir}/**/*.{test,spec}.${extensions}`],\n\t\t\t// The prefixes Nuxt gives every project, pointing at the same two places its own do: the source\n\t\t\t// directory and the project root. A project's tsconfig needs the matching `paths` for them to type.\n\t\t\talias: { '~': source, '@': source, '~~': root, '@@': root },\n\t\t\tformat: 'esm',\n\t\t\tplatform: 'node',\n\t\t\tunbundle: true,\n\t\t\toutDir: build.outDir,\n\t\t\toutExtensions: () => ({ js: extname(build.output) }),\n\t\t\tsourcemap: true,\n\t\t\tclean: true,\n\t\t\ttreeshake: true,\n\t\t\tminify: false,\n\t\t\tdts: false,\n\t\t\tdeps: { neverBundle: true },\n\t\t\t...(build.tsconfig === null ? {} : { tsconfig: build.tsconfig })\n\t\t};\n\t}\n\n\t/**\n\t * The project's own aliases, with relative targets resolved against the project root the way every other path in\n\t * `stars.config` is. Module ids (`preact/compat`) and absolute paths are passed through untouched.\n\t */\n\t#alias(alias: unknown): Record<string, unknown> {\n\t\tif (alias === null || typeof alias !== 'object' || Array.isArray(alias)) return {};\n\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(alias as Record<string, unknown>).map(([key, value]) => [\n\t\t\t\tkey,\n\t\t\t\ttypeof value === 'string' && RELATIVE_PATH.test(value) ? resolve(this.config.root, value) : value\n\t\t\t])\n\t\t);\n\t}\n\n\t/**\n\t * The auto imports transform, the way `nuxt dev` wires its own into the build rather than asking the project to.\n\t * `stars dev`/`stars build` regenerate its declaration file (`stars prepare`) before the first build.\n\t */\n\tasync #plugins(): Promise<unknown[]> {\n\t\tif (!this.config.imports.enabled) return [];\n\n\t\tconst { autoImports } = await import('@wolfstar/http-framework/auto-imports');\n\t\tconst { dirs, presets, exclude, dts } = this.config.imports;\n\t\treturn [await autoImports({ root: this.config.root, dirs, presets, exclude, dts })];\n\t}\n\n\t/**\n\t * Watch mode reports through the same events a single build does. The project's own `hooks`/`onSuccess` still\n\t * run: they are the reason a build is watched at all in some projects.\n\t */\n\t#watchOptions(options: TsdownOptions): TsdownOptions {\n\t\tconst hooks = (options.hooks ?? {}) as Record<string, unknown>;\n\t\tconst prepare = hooks['build:prepare'];\n\t\tconst before = hooks['build:before'];\n\t\tconst done = hooks['build:done'];\n\t\tconst success = options.onSuccess;\n\n\t\treturn {\n\t\t\t...options,\n\t\t\twatch: true,\n\t\t\t// tsdown's build:before runs once when the watcher is configured, not on every rebuild. Rolldown's\n\t\t\t// buildStart is the actual per-build boundary, including recovery after a failed compilation.\n\t\t\tplugins: [\n\t\t\t\t{\n\t\t\t\t\tname: 'stars:dev-progress',\n\t\t\t\t\tbuildStart: () => {\n\t\t\t\t\t\tif (this.#startedAt === 0) this.#begin();\n\t\t\t\t\t\tthis.emit('progress', 0.25, 'bundling app');\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t...toArray(options.plugins)\n\t\t\t],\n\t\t\thooks: {\n\t\t\t\t...hooks,\n\t\t\t\t'build:prepare': async (...args: never[]) => {\n\t\t\t\t\tif (this.#startedAt === 0) this.#begin();\n\t\t\t\t\tif (typeof prepare === 'function') await (prepare as AnyFunction)(...args);\n\t\t\t\t},\n\t\t\t\t'build:before': async (...args: never[]) => {\n\t\t\t\t\tif (typeof before === 'function') await (before as AnyFunction)(...args);\n\t\t\t\t},\n\t\t\t\t'build:done': async (...args: never[]) => {\n\t\t\t\t\tthis.emit('progress', 0.5, 'finishing build');\n\t\t\t\t\tif (typeof done === 'function') await (done as AnyFunction)(...args);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonSuccess: async (...args: never[]) => {\n\t\t\t\tif (typeof success === 'function') await (success as AnyFunction)(...args);\n\t\t\t\tthis.#finish(null);\n\t\t\t}\n\t\t};\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\t// `stars` owns the build progress and completion messages. Keeping tsdown at `warn` avoids copying its\n\t\t\t// entry list, target, output table and completion line into the dev session while preserving diagnostics\n\t\t\t// that need action from the project.\n\t\t\tlevel: 'warn',\n\t\t\tinfo: () => {},\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.#finish(errorSummary(args));\n\t\t\t},\n\t\t\tsuccess: () => {},\n\t\t\tclearScreen: () => {}\n\t\t};\n\t}\n}\n\nfunction toArray(value: unknown): unknown[] {\n\tif (value === undefined || value === null || value === false) return [];\n\treturn Array.isArray(value) ? value : [value];\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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"}
@@ -32,6 +32,7 @@ function useColor() {
32
32
  const KEYS = [
33
33
  ["r", "restart the bot now"],
34
34
  ["o", "open the local URL in a browser"],
35
+ ["t", "toggle the public tunnel"],
35
36
  ["i", "show versions, URLs and session info"],
36
37
  ["l", "open the logs (scroll, filter by source or level)"],
37
38
  ["e", "jump to the last error, keeping its context"],
@@ -116,6 +117,11 @@ const PANEL_HINTS = [
116
117
  label: "open",
117
118
  priority: 40
118
119
  },
120
+ {
121
+ key: "t",
122
+ label: "tunnel",
123
+ priority: 60
124
+ },
119
125
  {
120
126
  key: "i",
121
127
  label: "info",
@@ -790,6 +796,9 @@ function DevApp({ service, color, reducedMotion, onQuit, onViewChange, onCopy })
790
796
  case "o":
791
797
  openDevUrl(status.url).catch((error) => service.log("stars", "warn", error.message));
792
798
  return;
799
+ case "t":
800
+ service.toggleTunnel();
801
+ return;
793
802
  case "i": return changeView("info");
794
803
  case "h":
795
804
  case "?": return changeView("help");
@@ -935,4 +944,4 @@ function createTuiRenderer(service, options = {}) {
935
944
 
936
945
  //#endregion
937
946
  export { createTuiRenderer };
938
- //# sourceMappingURL=tui-C2P91TgW.js.map
947
+ //# sourceMappingURL=tui-BCGQrMQo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-BCGQrMQo.js","names":[],"sources":["../src/renderers/ink/theme.tsx","../src/renderers/ink/components/HelpOverlay.tsx","../src/renderers/ink/hooks/useLogVersion.ts","../src/renderers/ink/components/Hints.tsx","../src/renderers/ink/components/LogBrowser.tsx","../src/renderers/ink/hooks/useDevStatus.ts","../src/renderers/ink/hooks/useUptime.ts","../src/renderers/panel-logic.ts","../src/renderers/ink/components/InfoOverlay.tsx","../src/renderers/ink/components/Panel.tsx","../src/renderers/ink/hooks/useLogCounters.ts","../src/renderers/ink/hooks/useBuildClock.ts","../src/lib/open-url.ts","../src/renderers/ink/DevApp.tsx","../src/renderers/capture-output.ts","../src/renderers/tui.tsx"],"sourcesContent":["import { createContext, useContext, type ReactNode } from 'react';\n\n/**\n * Whether the UI may use colour. `stars dev` decides this once (`NO_COLOR`, `FORCE_COLOR`, TTY detection) and every\n * component reads it from here instead of re-deriving it.\n */\nconst ColorContext = createContext(true);\n\nexport function ColorProvider({ color, children }: { color: boolean; children: ReactNode }) {\n\treturn <ColorContext value={color}>{children}</ColorContext>;\n}\n\n/**\n * Returns a colour name only when colour is enabled, so `<Text color={useColor()('green')}>` degrades to plain text.\n */\nexport function useColor(): (name: string) => string | undefined {\n\tconst enabled = useContext(ColorContext);\n\treturn (name) => (enabled ? name : undefined);\n}\n","import { Box, Text, useInput } from 'ink';\nimport { useColor } from '../theme.js';\n\nconst KEYS: [string, string][] = [\n\t['r', 'restart the bot now'],\n\t['o', 'open the local URL in a browser'],\n\t['t', 'toggle the public tunnel'],\n\t['i', 'show versions, URLs and session info'],\n\t['l', 'open the logs (scroll, filter by source or level)'],\n\t['e', 'jump to the last error, keeping its context'],\n\t['c / Ctrl+L', 'clear the log history'],\n\t['q / Ctrl+C', 'stop the bot and quit'],\n\t['?', 'toggle this help']\n];\n\nexport interface HelpOverlayProps {\n\theight: number;\n\twidth: number;\n\tonClose: () => void;\n}\n\n/** The static keys reference, opened with `?` from the pinned panel. */\nexport function HelpOverlay({ height, width, onClose }: HelpOverlayProps) {\n\tconst paint = useColor();\n\tuseInput((input, key) => {\n\t\tif (!key.ctrl && (key.escape || ['q', 'h', '?'].includes(input))) onClose();\n\t});\n\n\treturn (\n\t\t<Box flexDirection=\"column\" height={height}>\n\t\t\t<Text bold> keyboard shortcuts</Text>\n\t\t\t<Text dimColor>{'─'.repeat(width)}</Text>\n\t\t\t{KEYS.slice(0, Math.max(0, height - 3)).map(([key, description]) => (\n\t\t\t\t<Text key={key} wrap=\"truncate-end\">\n\t\t\t\t\t{' '}\n\t\t\t\t\t<Text bold color={paint('white')}>\n\t\t\t\t\t\t{key.padEnd(14)}\n\t\t\t\t\t</Text>\n\t\t\t\t\t<Text dimColor>{description}</Text>\n\t\t\t\t</Text>\n\t\t\t))}\n\t\t\t<Box flexGrow={1} />\n\t\t\t<Text dimColor> q close</Text>\n\t\t</Box>\n\t);\n}\n","import { useEffect, useState } from 'react';\nimport type { LogBuffer } from '../../../lib/log-buffer.js';\n\n/** Bumps whenever the buffer changes, so a consumer can re-derive a filtered view without re-filtering on every render. */\nexport function useLogVersion(logs: LogBuffer): number {\n\tconst [version, setVersion] = useState(0);\n\n\tuseEffect(() => {\n\t\tconst bump = () => setVersion((current) => current + 1);\n\t\tlogs.on('entry', bump);\n\t\tlogs.on('clear', bump);\n\t\tbump();\n\t\treturn () => {\n\t\t\tlogs.off('entry', bump);\n\t\t\tlogs.off('clear', bump);\n\t\t};\n\t}, [logs]);\n\n\treturn version;\n}\n","import { Text } from 'ink';\n\nexport interface Hint {\n\tkey: string;\n\tlabel: string;\n\tpriority: number;\n}\n\nexport const PANEL_HINTS: Hint[] = [\n\t{ key: 'r', label: 'restart', priority: 80 },\n\t{ key: 'o', label: 'open', priority: 40 },\n\t{ key: 't', label: 'tunnel', priority: 60 },\n\t{ key: 'i', label: 'info', priority: 50 },\n\t{ key: 'l', label: 'logs', priority: 70 },\n\t{ key: '?', label: 'help', priority: 100 },\n\t{ key: 'q', label: 'quit', priority: 90 }\n];\n\n/** Drop the lowest-priority shortcuts first; help and the last error survive narrow panes. */\nexport function Hints({ hints, width }: { hints: readonly Hint[]; width: number }) {\n\tconst shown = [...hints];\n\tconst length = () => shown.reduce((size, hint) => size + hint.key.length + hint.label.length + 4, -2);\n\twhile (shown.length > 1 && length() > width) {\n\t\tconst weakest = shown.reduce((a, b) => (a.priority <= b.priority ? a : b));\n\t\tshown.splice(shown.indexOf(weakest), 1);\n\t}\n\treturn (\n\t\t<Text wrap=\"truncate-end\">\n\t\t\t{' '}\n\t\t\t{shown.map(({ key, label }, i) => (\n\t\t\t\t<Text key={key}>\n\t\t\t\t\t{i > 0 && <Text dimColor> · </Text>}\n\t\t\t\t\t<Text bold>{key}</Text>\n\t\t\t\t\t<Text dimColor>{` ${label}`}</Text>\n\t\t\t\t</Text>\n\t\t\t))}\n\t\t</Text>\n\t);\n}\n","import { Box, Text, useInput } from 'ink';\nimport { useMemo, useState } from 'react';\nimport { stripVTControlCharacters } from 'node:util';\nimport type { DevService } from '../../../lib/dev-service.js';\nimport { isErrorDetail, type LogEntry, type LogLevel } from '../../../lib/log-buffer.js';\nimport { useLogVersion } from '../hooks/useLogVersion.js';\nimport { useColor } from '../theme.js';\nimport { Hints } from './Hints.js';\n\nexport interface LogBrowserProps {\n\tservice: DevService;\n\theight: number;\n\twidth: number;\n\tlastError?: boolean;\n\tonClose: () => void;\n\tonCopy?: (text: string) => void;\n}\n\nconst group = (entry: LogEntry) => (entry.source === 'app' ? 'runtime' : entry.source === 'build' || entry.source === 'tsc' ? 'build' : 'cli');\n\n/** Filterable history, using Nuxt's keys and selecting the last error without discarding its context. */\nexport function LogBrowser({ service, height, width, lastError = false, onClose, onCopy }: LogBrowserProps) {\n\tconst paint = useColor();\n\tconst [sources, setSources] = useState({ cli: true, build: true, runtime: true });\n\tconst [level, setLevel] = useState<LogLevel | null>(null);\n\tconst [query, setQuery] = useState('');\n\tconst [searching, setSearching] = useState(false);\n\tconst [selected, setSelected] = useState<number | null>(() =>\n\t\tlastError ? (service.logs.entries().findLast((entry) => entry.level === 'error' && !isErrorDetail(entry))?.id ?? null) : null\n\t);\n\tconst version = useLogVersion(service.logs);\n\tconst entries = useMemo(\n\t\t() =>\n\t\t\tservice.logs\n\t\t\t\t.filter({ level })\n\t\t\t\t.filter((entry) => sources[group(entry)] && stripVTControlCharacters(entry.text).toLowerCase().includes(query.toLowerCase())),\n\t\t[service, sources, level, query, version]\n\t);\n\tconst bodyHeight = Math.max(0, height - 3);\n\tconst index = entries.findIndex((entry) => entry.id === selected);\n\tconst rows = entries.flatMap((entry) =>\n\t\tstripVTControlCharacters(entry.text)\n\t\t\t.split('\\n')\n\t\t\t.map((line, i) => ({\n\t\t\t\tentry,\n\t\t\t\ttext: i === 0 ? `${new Date(entry.time).toLocaleTimeString()} ${entry.source.padEnd(6)} ${line}` : ` ${line}`\n\t\t\t}))\n\t);\n\tconst selectedStart = rows.findIndex((row) => row.entry.id === selected);\n\tconst selectedEnd = rows.findLastIndex((row) => row.entry.id === selected) + 1;\n\tconst end =\n\t\tindex < 0 ? rows.length : Math.min(rows.length, selectedStart + Math.max(bodyHeight, Math.min(selectedEnd - selectedStart, bodyHeight)));\n\tconst visible = rows.slice(Math.max(0, end - bodyHeight), end);\n\tconst move = (delta: number) => {\n\t\tif (!entries.length) return;\n\t\tconst next = index < 0 ? (delta < 0 ? entries.length - 1 : 0) : Math.max(0, Math.min(entries.length - 1, index + delta));\n\t\tsetSelected(entries[next]!.id);\n\t};\n\n\tuseInput((input, key) => {\n\t\tif (key.ctrl) return;\n\t\tif (searching) {\n\t\t\tif (key.escape) {\n\t\t\t\tsetSearching(false);\n\t\t\t\tsetQuery('');\n\t\t\t} else if (key.return) setSearching(false);\n\t\t\telse if (key.backspace || key.delete) setQuery((value) => [...value].slice(0, -1).join(''));\n\t\t\telse if (input && !key.upArrow && !key.downArrow) setQuery((value) => value + input);\n\t\t\tsetSelected(null);\n\t\t\treturn;\n\t\t}\n\t\tif (key.escape || input === 'l' || input === 'q') return onClose();\n\t\tif (input === '/') return setSearching(true);\n\t\tif (input === 'e' || input === 'w' || input === 'a') {\n\t\t\tconst next = input === 'e' ? 'error' : input === 'w' ? 'warn' : null;\n\t\t\tsetLevel((current) => (current === next ? null : next));\n\t\t\treturn setSelected(null);\n\t\t}\n\t\tif (input === 'c' || input === 'b' || input === 'r') {\n\t\t\tconst source = input === 'c' ? 'cli' : input === 'b' ? 'build' : 'runtime';\n\t\t\tsetSources((current) => {\n\t\t\t\tconst next = { ...current, [source]: !current[source] };\n\t\t\t\treturn Object.values(next).some(Boolean) ? next : current;\n\t\t\t});\n\t\t\treturn setSelected(null);\n\t\t}\n\t\tif (input === 'x') {\n\t\t\tservice.clearLogs();\n\t\t\treturn setSelected(null);\n\t\t}\n\t\tif (key.upArrow || input === 'k') return move(-1);\n\t\tif (key.downArrow || input === 'j') return move(1);\n\t\tif (key.pageUp) return move(-Math.max(1, bodyHeight));\n\t\tif (key.pageDown) return move(Math.max(1, bodyHeight));\n\t\tif (key.home || input === 'g') return setSelected(entries[0]?.id ?? null);\n\t\tif (key.end || input === 'G') return setSelected(null);\n\t\tif ((key.return || input === 'y') && index >= 0) onCopy?.(stripVTControlCharacters(entries[index]!.text));\n\t});\n\n\tconst hints = searching\n\t\t? [\n\t\t\t\t['enter', 'apply'],\n\t\t\t\t['esc', 'cancel']\n\t\t\t]\n\t\t: [\n\t\t\t\t['↑/↓', 'select'],\n\t\t\t\t['g/G', 'top/bottom'],\n\t\t\t\t['e', 'errors'],\n\t\t\t\t['w', 'warnings'],\n\t\t\t\t['a', 'all'],\n\t\t\t\t['c/b/r', 'sources'],\n\t\t\t\t['/', 'search'],\n\t\t\t\t['x', 'clear'],\n\t\t\t\t['enter', 'copy'],\n\t\t\t\t['q', 'close']\n\t\t\t];\n\treturn (\n\t\t<Box flexDirection=\"column\" height={height}>\n\t\t\t<Text wrap=\"truncate-end\">\n\t\t\t\t{' '}\n\t\t\t\t<Text bold>logs</Text>\n\t\t\t\t<Text dimColor>{` · ${Object.entries(sources)\n\t\t\t\t\t.filter(([, shown]) => shown)\n\t\t\t\t\t.map(([source]) => source)\n\t\t\t\t\t.join(\n\t\t\t\t\t\t'+'\n\t\t\t\t\t)} · ${level ? `${level}+ only` : 'all levels'}${query || searching ? ` · search ${query}${searching ? '▏' : ''}` : ''}`}</Text>\n\t\t\t</Text>\n\t\t\t<Text dimColor>{'─'.repeat(width)}</Text>\n\t\t\t<Box flexDirection=\"column\" height={bodyHeight} overflow=\"hidden\">\n\t\t\t\t{visible.length === 0 && <Text dimColor> no matching logs</Text>}\n\t\t\t\t{visible.map(({ entry, text }, i) => (\n\t\t\t\t\t<Text key={`${entry.id}-${i}`} wrap=\"truncate-end\">\n\t\t\t\t\t\t<Text color={paint('green')}>{entry.id === selected ? '▎ ' : ' '}</Text>\n\t\t\t\t\t\t<Text\n\t\t\t\t\t\t\tcolor={paint(\n\t\t\t\t\t\t\t\tentry.level === 'error' ? 'red' : entry.level === 'warn' ? 'yellow' : entry.level === 'success' ? 'green' : 'white'\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\tdimColor={isErrorDetail(entry)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{text}\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t</Text>\n\t\t\t\t))}\n\t\t\t</Box>\n\t\t\t<Hints\n\t\t\t\twidth={width}\n\t\t\t\thints={hints.map(([key, label], i) => ({ key: key!, label: label!, priority: i === hints.length - 1 ? 100 : hints.length - i }))}\n\t\t\t/>\n\t\t</Box>\n\t);\n}\n","import { useEffect, useState } from 'react';\nimport type { DevService, DevStatus } from '../../../lib/dev-service.js';\n\n/**\n * Mirrors the service's status into React state. The service stays the single source of truth: the UI only\n * subscribes to it, exactly like the plain renderer does.\n */\nexport function useDevStatus(service: DevService): DevStatus {\n\tconst [status, setStatus] = useState<DevStatus>(() => service.status);\n\n\tuseEffect(() => {\n\t\tconst onStatus = (next: DevStatus) => setStatus(next);\n\t\tservice.on('status', onStatus);\n\t\tsetStatus(service.status);\n\t\treturn () => {\n\t\t\tservice.off('status', onStatus);\n\t\t};\n\t}, [service]);\n\n\treturn status;\n}\n","import { useEffect, useState } from 'react';\n\n/**\n * Re-renders once a second while the bot is up, so the uptime in the header keeps counting.\n */\nexport function useUptime(startedAt: number | null): number | null {\n\tconst [now, setNow] = useState(() => Date.now());\n\n\tuseEffect(() => {\n\t\tif (startedAt === null) return;\n\n\t\tsetNow(Date.now());\n\t\tconst timer = setInterval(() => setNow(Date.now()), 1000);\n\t\treturn () => clearInterval(timer);\n\t}, [startedAt]);\n\n\treturn startedAt === null ? null : now - startedAt;\n}\n","import type { DevStatus } from '../lib/dev-service.js';\n\nexport type Badge = 'starting' | 'building' | 'restarting' | 'ready' | 'error';\n\n/**\n * The badge and standing description for the panel's status row, mirroring the states Nuxt CLI v4's own dev panel\n * cycles through (`STARTING`/`BUILDING`/`RESTART`/`READY`/`ERROR`).\n */\nexport function describeBadge(status: DevStatus): { badge: Badge; note: string } {\n\tif (status.build === 'building') return { badge: status.startedAt === null ? 'starting' : 'building', note: status.progress.message };\n\tif (status.process === 'starting') return { badge: 'starting', note: 'starting the bot' };\n\tif (status.process === 'stopping') return { badge: 'restarting', note: 'restarting the bot' };\n\tif (status.build === 'failed') return { badge: 'error', note: 'build failed, waiting for changes' };\n\tif (status.process === 'crashed') return { badge: 'error', note: 'the bot crashed, press r to restart' };\n\tif (status.health === 'down') return { badge: 'error', note: 'health check failed' };\n\tif (status.typecheck === 'failed') return { badge: 'error', note: 'type check failed, press e to view it' };\n\tif (status.process === 'stopped') return { badge: 'error', note: 'the bot stopped, press r to restart' };\n\tif (status.progress.fraction < 1) return { badge: 'starting', note: status.progress.message };\n\tif (status.process === 'running') return { badge: 'ready', note: 'watching for changes' };\n\treturn { badge: 'starting', note: 'waiting for the first build' };\n}\n\n/** Formats a duration the way a dev session reads it: `mm:ss` under an hour, `h:mm:ss` above. */\nexport function formatDuration(milliseconds: number): string {\n\tconst total = Math.max(0, Math.floor(milliseconds / 1000));\n\tconst seconds = total % 60;\n\tconst minutes = Math.floor(total / 60) % 60;\n\tconst hours = Math.floor(total / 3600);\n\tconst pad = (value: number) => value.toString().padStart(2, '0');\n\n\treturn hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;\n}\n","import { Box, Text, useInput } from 'ink';\nimport type { DevService } from '../../../lib/dev-service.js';\nimport { useDevStatus } from '../hooks/useDevStatus.js';\nimport { useUptime } from '../hooks/useUptime.js';\nimport { formatDuration } from '../../panel-logic.js';\n\nexport function InfoOverlay({ service, height, width, onClose }: { service: DevService; height: number; width: number; onClose: () => void }) {\n\tconst status = useDevStatus(service);\n\tconst uptime = useUptime(status.startedAt);\n\tuseInput((input, key) => {\n\t\tif (!key.ctrl && (key.escape || ['q', 'i'].includes(input))) onClose();\n\t});\n\tconst lines = [\n\t\t['Project', service.config.packageJson?.name ?? 'stars project'],\n\t\t['Root', service.config.root],\n\t\t['Node', process.version],\n\t\t['Builder', service.builder.tool],\n\t\t['Local', status.url ?? '—'],\n\t\t['Tunnel', status.tunnelUrl ?? status.tunnel],\n\t\t['Process', `${status.process}${status.pid ? ` (${status.pid})` : ''}`],\n\t\t['Uptime', uptime === null ? '—' : formatDuration(uptime)],\n\t\t['Restarts', String(status.restarts)],\n\t\t['Health', service.config.dev.health ? status.health : 'not configured (READY reports process state)'],\n\t\t['Types', `${status.typecheck}${status.typeErrors ? ` (${status.typeErrors} errors)` : ''}`],\n\t\t['Log file', service.config.dev.logFile ?? 'disabled']\n\t];\n\treturn (\n\t\t<Box flexDirection=\"column\" height={height}>\n\t\t\t<Text bold wrap=\"truncate-end\">\n\t\t\t\t{' '}\n\t\t\t\tsession info\n\t\t\t</Text>\n\t\t\t<Text dimColor>{'─'.repeat(width)}</Text>\n\t\t\t{lines.slice(0, Math.max(0, height - 3)).map(([label, value]) => (\n\t\t\t\t<Text key={label} wrap=\"truncate-end\">\n\t\t\t\t\t{' '}\n\t\t\t\t\t<Text bold>{label!.padEnd(10)}</Text>\n\t\t\t\t\t<Text dimColor>{value}</Text>\n\t\t\t\t</Text>\n\t\t\t))}\n\t\t\t<Box flexGrow={1} />\n\t\t\t<Text dimColor> q close</Text>\n\t\t</Box>\n\t);\n}\n","import { Box, Text } from 'ink';\nimport type { ReactNode } from 'react';\nimport type { ResolvedStarsConfig } from '@wolfstar/http-framework/config';\nimport type { DevStatus } from '../../../lib/dev-service.js';\nimport { readOwnPackageJson } from '../../../lib/version.js';\nimport type { LogCounters } from '../hooks/useLogCounters.js';\nimport { describeBadge } from '../../panel-logic.js';\nimport { useColor } from '../theme.js';\nimport { Hints, PANEL_HINTS } from './Hints.js';\n\nexport interface PanelProps {\n\tstatus: DevStatus;\n\tconfig: ResolvedStarsConfig;\n\tcounters: LogCounters;\n\tframe: number;\n\telapsedMs: number;\n\twidth: number;\n\theight: number;\n\tconfirmQuit: boolean;\n}\n\n/** Nuxt's wordmark / URLs / progress / status / hints layout, with Stars branding. */\nexport function Panel({ status, config, counters, frame, elapsedMs, width, height, confirmQuit }: PanelProps) {\n\tconst paint = useColor();\n\tconst state = describeBadge(status);\n\tconst busy = state.badge !== 'ready' && state.badge !== 'error';\n\tconst badge = !busy && counters.errors > 0 ? 'error' : state.badge;\n\tconst note = badge === 'error' && state.badge === 'ready' ? 'an error was logged · press e to view it' : state.note;\n\tconst filled = Math.round(Math.min(1, Math.max(0, status.progress.fraction)) * 20);\n\tconst wordmark = config.dev.banner === false ? [] : (config.dev.banner?.flatMap((line) => line.split('\\n')) ?? [null]);\n\tconst blocks: { name: string; content: ReactNode }[] = [\n\t\t...wordmark.slice(0, 4).map((line, index) => ({\n\t\t\tname: 'wordmark',\n\t\t\tcontent: (\n\t\t\t\t<Box key={index} justifyContent=\"space-between\">\n\t\t\t\t\t<Text wrap=\"truncate-end\">\n\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t{line === null ? (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<Text color={paint('green')}>\n\t\t\t\t\t\t\t\t\t{[...'·✦★✦'].map((cell, i) => (\n\t\t\t\t\t\t\t\t\t\t<Text key={i} dimColor={busy && i !== frame % 4}>\n\t\t\t\t\t\t\t\t\t\t\t{cell}\n\t\t\t\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t<Text bold color={paint('green')}>\n\t\t\t\t\t\t\t\t\tStars\n\t\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t\t\t<Text dimColor>{` ${readOwnPackageJson().version}`}</Text>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<Text color={paint('green')}>{line}</Text>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</Text>\n\t\t\t\t\t{index === 0 && width >= 60 && badge === 'ready' && status.progress.readyMs !== null && (\n\t\t\t\t\t\t<Text dimColor>{`ready in ${(status.progress.readyMs / 1000).toFixed(2)}s `}</Text>\n\t\t\t\t\t)}\n\t\t\t\t</Box>\n\t\t\t)\n\t\t})),\n\t\t{ name: 'space', content: <Text> </Text> },\n\t\t...(status.url\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'urls',\n\t\t\t\t\t\tcontent: (\n\t\t\t\t\t\t\t<Text wrap=\"truncate-end\">\n\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t<Text dimColor>{'Local '}</Text>\n\t\t\t\t\t\t\t\t<Text color={paint('cyan')} dimColor={busy}>\n\t\t\t\t\t\t\t\t\t{status.url}\n\t\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t\t\t{busy && <Text color={paint('yellow')}>{` ${['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'][frame % 10]}`}</Text>}\n\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(status.tunnelUrl\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'urls',\n\t\t\t\t\t\tcontent: (\n\t\t\t\t\t\t\t<Text wrap=\"truncate-end\">\n\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t<Text dimColor>{'Tunnel '}</Text>\n\t\t\t\t\t\t\t\t<Text color={paint('magenta')}>{status.tunnelUrl}</Text>\n\t\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t: []),\n\t\t{ name: 'space', content: <Text> </Text> },\n\t\t{\n\t\t\tname: 'summary',\n\t\t\tcontent: busy ? (\n\t\t\t\t<Text wrap=\"truncate-end\">\n\t\t\t\t\t{' '}\n\t\t\t\t\t<Text color={paint('green')}>{'━'.repeat(filled)}</Text>\n\t\t\t\t\t<Text dimColor>\n\t\t\t\t\t\t{'━'.repeat(20 - filled)}\n\t\t\t\t\t\t{` ${Math.round(status.progress.fraction * 100)}% · ${(elapsedMs / 1000).toFixed(1)}s`}\n\t\t\t\t\t</Text>\n\t\t\t\t</Text>\n\t\t\t) : (\n\t\t\t\t<Text wrap=\"truncate-end\">\n\t\t\t\t\t{' '}\n\t\t\t\t\t{counters.warnings > 0 && (\n\t\t\t\t\t\t<Text color={paint('yellow')}>{`⚠ ${counters.warnings} ${plural(counters.warnings, 'warning')} `}</Text>\n\t\t\t\t\t)}\n\t\t\t\t\t{counters.errors > 0 && <Text bold color={paint('red')}>{`✖ ${counters.errors} ${plural(counters.errors, 'error')} `}</Text>}\n\t\t\t\t\t{counters.errors === 0 && counters.warnings === 0 && (\n\t\t\t\t\t\t<Text dimColor>\n\t\t\t\t\t\t\t{status.typecheck === 'checking'\n\t\t\t\t\t\t\t\t? 'checking types'\n\t\t\t\t\t\t\t\t: status.tunnel === 'failed'\n\t\t\t\t\t\t\t\t\t? 'tunnel failed · press l to view logs'\n\t\t\t\t\t\t\t\t\t: 'logs folded away · press l to view'}\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t)}\n\t\t\t\t</Text>\n\t\t\t)\n\t\t},\n\t\t{ name: 'space', content: <Text> </Text> },\n\t\t{\n\t\t\tname: 'status',\n\t\t\tcontent: (\n\t\t\t\t<Text wrap=\"truncate-end\">\n\t\t\t\t\t{' '}\n\t\t\t\t\t<Text\n\t\t\t\t\t\tbold\n\t\t\t\t\t\tbackgroundColor={paint(confirmQuit || busy ? 'yellow' : badge === 'error' ? 'red' : 'green')}\n\t\t\t\t\t\tcolor={paint(badge === 'error' && !confirmQuit ? 'white' : 'black')}\n\t\t\t\t\t>{` ${confirmQuit ? 'QUIT?' : badge === 'restarting' ? 'RESTART' : badge.toUpperCase()} `}</Text>\n\t\t\t\t\t{' '}\n\t\t\t\t\t<Text dimColor>{confirmQuit ? 'press y to confirm, esc to stay' : note}</Text>\n\t\t\t\t</Text>\n\t\t\t)\n\t\t},\n\t\t{\n\t\t\tname: 'hints',\n\t\t\tcontent: confirmQuit ? (\n\t\t\t\t<Text> </Text>\n\t\t\t) : (\n\t\t\t\t<Hints width={width} hints={[...(counters.errors ? [{ key: 'e', label: 'last error', priority: Infinity }] : []), ...PANEL_HINTS]} />\n\t\t\t)\n\t\t}\n\t];\n\tlet shown = blocks;\n\tconst budget = Math.max(2, Math.min(height, 14));\n\tfor (const name of ['space', 'summary', 'urls', 'wordmark']) {\n\t\tif (shown.length <= budget) break;\n\t\tshown = shown.filter((block) => block.name !== name);\n\t}\n\tshown = shown.filter((block, i, all) => block.name !== 'space' || (i > 0 && i < all.length - 1 && all[i - 1]?.name !== 'space'));\n\treturn (\n\t\t<Box flexDirection=\"column\">\n\t\t\t{shown.map((block, index) => (\n\t\t\t\t<Box key={index} flexDirection=\"column\" height={1}>\n\t\t\t\t\t{block.content}\n\t\t\t\t</Box>\n\t\t\t))}\n\t\t</Box>\n\t);\n}\n\nfunction plural(count: number, word: string): string {\n\treturn count === 1 ? word : `${word}s`;\n}\n","import { useEffect, useState } from 'react';\nimport type { DevService, DevStatus } from '../../../lib/dev-service.js';\nimport { isErrorDetail, type LogEntry } from '../../../lib/log-buffer.js';\n\nexport interface LogCounters {\n\twarnings: number;\n\terrors: number;\n}\n\n/**\n * Warnings and errors logged since the last successful build, the way Nuxt CLI v4's own panel counts them: the\n * badge's job is the current state, this line is what happened getting there.\n */\nexport function useLogCounters(service: DevService): LogCounters {\n\tconst count = () =>\n\t\tservice.logs.entries().reduce<LogCounters>(\n\t\t\t(total, entry) => ({\n\t\t\t\twarnings: total.warnings + Number(entry.level === 'warn'),\n\t\t\t\terrors: total.errors + Number(entry.level === 'error' && !isErrorDetail(entry))\n\t\t\t}),\n\t\t\t{ warnings: 0, errors: 0 }\n\t\t);\n\tconst [counters, setCounters] = useState<LogCounters>(count);\n\n\tuseEffect(() => {\n\t\tlet previousBuild: DevStatus['build'] = service.status.build;\n\t\tlet previousStart = service.status.startedAt;\n\n\t\tconst onEntry = (entry: LogEntry) => {\n\t\t\tif (entry.level === 'warn') setCounters((current) => ({ ...current, warnings: current.warnings + 1 }));\n\t\t\telse if (entry.level === 'error' && !isErrorDetail(entry)) setCounters((current) => ({ ...current, errors: current.errors + 1 }));\n\t\t};\n\n\t\tconst onStatus = (status: DevStatus) => {\n\t\t\tif (\n\t\t\t\t(status.build === 'building' && previousBuild !== 'building' && previousBuild !== 'idle') ||\n\t\t\t\t(status.lastRestartReason === 'manual' && status.startedAt !== null && status.startedAt !== previousStart)\n\t\t\t)\n\t\t\t\tsetCounters({ warnings: 0, errors: 0 });\n\t\t\tpreviousBuild = status.build;\n\t\t\tpreviousStart = status.startedAt;\n\t\t};\n\n\t\tservice.logs.on('entry', onEntry);\n\t\tconst onClear = () => setCounters({ warnings: 0, errors: 0 });\n\t\tservice.logs.on('clear', onClear);\n\t\tservice.on('status', onStatus);\n\t\tsetCounters(count());\n\t\treturn () => {\n\t\t\tservice.logs.off('entry', onEntry);\n\t\t\tservice.logs.off('clear', onClear);\n\t\t\tservice.off('status', onStatus);\n\t\t};\n\t}, [service]);\n\n\treturn counters;\n}\n","import { useEffect, useState } from 'react';\n\n/** The clock moves during a long compiler phase; the percentage only moves on real build events. */\nexport function useBuildClock(startedAt: number, active: boolean, reducedMotion: boolean) {\n\tconst [now, setNow] = useState(Date.now);\n\tuseEffect(() => {\n\t\tif (!active) return;\n\t\tsetNow(Date.now());\n\t\tconst timer = setInterval(() => setNow(Date.now()), reducedMotion ? 1000 : 120);\n\t\treturn () => clearInterval(timer);\n\t}, [startedAt, active, reducedMotion]);\n\tconst elapsedMs = Math.max(0, now - startedAt);\n\treturn { elapsedMs, frame: reducedMotion ? 0 : Math.floor(elapsedMs / 120) };\n}\n","import { execFile } from 'node:child_process';\n\n/** Called only on an explicit `o` keypress, with no shell interpolation. */\nexport function openDevUrl(value: string | null): Promise<void> {\n\tif (!value) return Promise.resolve();\n\tconst url = new URL(value);\n\tif (url.protocol !== 'http:' && url.protocol !== 'https:') return Promise.reject(new Error('Only HTTP(S) URLs can be opened.'));\n\tconst [command, args] =\n\t\tprocess.platform === 'win32'\n\t\t\t? (['rundll32', ['url.dll,FileProtocolHandler', url.href]] as const)\n\t\t\t: process.platform === 'darwin'\n\t\t\t\t? (['open', [url.href]] as const)\n\t\t\t\t: (['xdg-open', [url.href]] as const);\n\treturn new Promise((resolve, reject) =>\n\t\texecFile(command, [...args], (error) => (error ? reject(new Error(`Could not open the browser: ${error.message}`)) : resolve()))\n\t);\n}\n","import { Box, useInput, useWindowSize } from 'ink';\nimport { useState } from 'react';\nimport type { DevService } from '../../lib/dev-service.js';\nimport { HelpOverlay } from './components/HelpOverlay.js';\nimport { LogBrowser } from './components/LogBrowser.js';\nimport { InfoOverlay } from './components/InfoOverlay.js';\nimport { Panel } from './components/Panel.js';\nimport { useDevStatus } from './hooks/useDevStatus.js';\nimport { useLogCounters } from './hooks/useLogCounters.js';\nimport { useBuildClock } from './hooks/useBuildClock.js';\nimport { describeBadge } from '../panel-logic.js';\nimport { openDevUrl } from '../../lib/open-url.js';\nimport { ColorProvider } from './theme.js';\n\nexport interface DevAppProps {\n\tservice: DevService;\n\tcolor: boolean;\n\treducedMotion: boolean;\n\t/** Called when the user asks to quit, so the CLI can stop the bot and exit. */\n\tonQuit: () => void;\n\tonViewChange: (overlay: boolean) => void;\n\tonCopy: (text: string) => void;\n}\n\ntype View = 'panel' | 'logs' | 'errors' | 'help' | 'info';\n\n/**\n * A bottom-aligned panel in the normal buffer, with logs folded away. Only the browsable overlays use the\n * alternate screen, preserving the terminal's history and the panel when they close.\n */\nexport function DevApp({ service, color, reducedMotion, onQuit, onViewChange, onCopy }: DevAppProps) {\n\tconst { columns, rows } = useWindowSize();\n\tconst [view, setView] = useState<View>('panel');\n\tconst [confirmQuit, setConfirmQuit] = useState(false);\n\n\tconst status = useDevStatus(service);\n\tconst counters = useLogCounters(service);\n\tconst badge = describeBadge(status).badge;\n\tconst busy = badge !== 'ready' && badge !== 'error';\n\tconst clock = useBuildClock(status.progress.startedAt, busy && view === 'panel', reducedMotion);\n\n\tconst width = Math.max(1, columns);\n\tconst height = Math.max(2, rows - 1);\n\n\tconst changeView = (next: View) => {\n\t\tonViewChange(next !== 'panel');\n\t\tsetView(next);\n\t};\n\n\tuseInput((input, key) => {\n\t\tif (key.ctrl && input === 'c') return onQuit();\n\t\tif (key.ctrl && input === 'l') return service.clearLogs();\n\t\tif (view !== 'panel') return;\n\t\tif (confirmQuit) {\n\t\t\tif (input === 'y') return onQuit();\n\t\t\treturn setConfirmQuit(false);\n\t\t}\n\t\tif (key.ctrl && input === 'r') return void service.restart('manual');\n\t\tif (key.ctrl && input === 'd') return busy ? setConfirmQuit(true) : onQuit();\n\n\t\tswitch (input) {\n\t\t\tcase 'q':\n\t\t\t\treturn busy ? setConfirmQuit(true) : onQuit();\n\t\t\tcase 'r':\n\t\t\t\treturn void service.restart('manual');\n\t\t\tcase 'l':\n\t\t\t\treturn changeView('logs');\n\t\t\tcase 'e':\n\t\t\t\treturn changeView('errors');\n\t\t\tcase 'c':\n\t\t\t\treturn service.clearLogs();\n\t\t\tcase 'o':\n\t\t\t\treturn void openDevUrl(status.url).catch((error: Error) => service.log('stars', 'warn', error.message));\n\t\t\tcase 't':\n\t\t\t\treturn void service.toggleTunnel();\n\t\t\tcase 'i':\n\t\t\t\treturn changeView('info');\n\t\t\tcase 'h':\n\t\t\tcase '?':\n\t\t\t\treturn changeView('help');\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t});\n\n\treturn (\n\t\t<ColorProvider color={color}>\n\t\t\t<Box width={width} height={height} flexDirection=\"column\" justifyContent={view === 'panel' ? 'flex-end' : 'flex-start'}>\n\t\t\t\t{(view === 'logs' || view === 'errors') && (\n\t\t\t\t\t<LogBrowser\n\t\t\t\t\t\tservice={service}\n\t\t\t\t\t\theight={height}\n\t\t\t\t\t\twidth={width}\n\t\t\t\t\t\tlastError={view === 'errors'}\n\t\t\t\t\t\tonCopy={onCopy}\n\t\t\t\t\t\tonClose={() => changeView('panel')}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t\t{view === 'help' && <HelpOverlay height={height} width={width} onClose={() => changeView('panel')} />}\n\t\t\t\t{view === 'info' && <InfoOverlay service={service} height={height} width={width} onClose={() => changeView('panel')} />}\n\t\t\t\t{view === 'panel' && (\n\t\t\t\t\t<Panel\n\t\t\t\t\t\tstatus={status}\n\t\t\t\t\t\tconfig={service.config}\n\t\t\t\t\t\tcounters={counters}\n\t\t\t\t\t\t{...clock}\n\t\t\t\t\t\twidth={width}\n\t\t\t\t\t\theight={height}\n\t\t\t\t\t\tconfirmQuit={confirmQuit}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</Box>\n\t\t</ColorProvider>\n\t);\n}\n","import { StringDecoder } from 'node:string_decoder';\nimport type { DevService } from '../lib/dev-service.js';\nimport { classifyAppLine, type LogLevel } from '../lib/log-buffer.js';\n\n/** Capture third-party writes before build plugins run. Ink writes through a separate, unpatched sink. */\nexport function captureOutput(service: DevService, stream: NodeJS.WriteStream, fallback: LogLevel): () => void {\n\tconst original = stream.write;\n\tconst decoder = new StringDecoder('utf8');\n\tlet pending = '';\n\tconst emit = (text: string) => {\n\t\tif (text.trim()) service.log('build', classifyAppLine(text, fallback), text);\n\t};\n\tconst write: typeof stream.write = (chunk: unknown, encoding?: unknown, callback?: unknown) => {\n\t\tpending += typeof chunk === 'string' ? chunk : chunk instanceof Uint8Array ? decoder.write(Buffer.from(chunk)) : String(chunk);\n\t\tconst lines = pending.split(/\\r?\\n/);\n\t\tpending = lines.pop() ?? '';\n\t\tfor (const line of lines) emit(line);\n\t\t// A tool without newlines must not grow the capture indefinitely.\n\t\tif (pending.length > 65536) {\n\t\t\temit(pending);\n\t\t\tpending = '';\n\t\t}\n\t\tconst done = typeof encoding === 'function' ? encoding : callback;\n\t\tif (typeof done === 'function') queueMicrotask(() => done());\n\t\treturn true;\n\t};\n\tstream.write = write;\n\treturn () => {\n\t\tif (stream.write === write) stream.write = original;\n\t\temit(pending + decoder.end());\n\t\tpending = '';\n\t};\n}\n","import { render } from 'ink';\nimport type { DevService } from '../lib/dev-service.js';\nimport { DevApp } from './ink/DevApp.js';\nimport type { Renderer } from './plain.js';\nimport { captureOutput } from './capture-output.js';\nimport { isErrorDetail } from '../lib/log-buffer.js';\n\nexport interface TuiRendererOptions {\n\tstdout?: NodeJS.WriteStream;\n\tstdin?: NodeJS.ReadStream;\n\tcolor?: boolean;\n\t/** Freezes spinners, for `STARS_REDUCED_MOTION=1`. */\n\treducedMotion?: boolean;\n\t/** Caps how often Ink repaints; the default is Ink's own. */\n\tfps?: number;\n}\n\n/**\n * A normal-buffer, bottom-aligned panel and alternate-buffer overlays. External output is captured for the log\n * browser; the renderer alone writes to the terminal. Switching views and teardown restore the original buffer.\n *\n * `start()` resolves when the user quits, which is what `stars dev` waits on before shutting the bot down.\n */\nexport function createTuiRenderer(service: DevService, options: TuiRendererOptions = {}): Renderer {\n\tconst stdout = options.stdout ?? process.stdout;\n\tconst stdin = options.stdin ?? process.stdin;\n\tconst write = stdout.write.bind(stdout);\n\t// Bind methods to the real stream (resize subscriptions included), except write which bypasses capture.\n\tconst sink = new Proxy(stdout, {\n\t\tget(target, property) {\n\t\t\tif (property === 'write') return write;\n\t\t\tconst value = Reflect.get(target, property, target);\n\t\t\treturn typeof value === 'function' ? value.bind(target) : value;\n\t\t}\n\t});\n\tconst restoreOutput =\n\t\tstdout === process.stdout ? [captureOutput(service, process.stdout, 'info'), captureOutput(service, process.stderr, 'warn')] : [];\n\tlet alternate = false;\n\tlet stopped = false;\n\tconst switchView = (overlay: boolean) => {\n\t\tif (overlay === alternate || stopped) return;\n\t\tinstance.clear();\n\t\twrite(overlay ? '\\u001B[?1049h\\u001B[H' : '\\u001B[?1049l');\n\t\talternate = overlay;\n\t};\n\n\tlet quit!: () => void;\n\tconst quitting = new Promise<void>((resolve) => {\n\t\tquit = resolve;\n\t});\n\n\tconst instance = render(\n\t\t<DevApp\n\t\t\tservice={service}\n\t\t\tcolor={options.color ?? false}\n\t\t\treducedMotion={options.reducedMotion ?? false}\n\t\t\tonQuit={quit}\n\t\t\tonViewChange={switchView}\n\t\t\tonCopy={(text) => write(`\\u001B]52;c;${Buffer.from(text.slice(0, 65536)).toString('base64')}\\u0007`)}\n\t\t/>,\n\t\t{\n\t\t\tstdout: sink,\n\t\t\tstdin,\n\t\t\t// `stars dev` owns the shutdown: it stops the bot, then exits with the right code.\n\t\t\texitOnCtrlC: false,\n\t\t\tpatchConsole: false,\n\t\t\t// `createTuiRenderer` only runs once `resolveOutputMode()` already picked 'tui' (never in CI or on a\n\t\t\t// non-TTY stdout), so Ink is told to trust that instead of re-deriving it from `process.env.CI`: on a\n\t\t\t// CI runner that env var is set even for this package's own test process, which would otherwise force\n\t\t\t// Ink's non-interactive mode (no repaint until unmount) despite the fake stdin/stdout looking like a\n\t\t\t// real TTY.\n\t\t\tinteractive: true,\n\t\t\t...(options.fps === undefined ? {} : { maxFps: options.fps })\n\t\t}\n\t);\n\n\treturn {\n\t\tasync start() {\n\t\t\tawait Promise.race([quitting, instance.waitUntilExit()]);\n\t\t},\n\t\tstop() {\n\t\t\tif (stopped) return;\n\t\t\tstopped = true;\n\t\t\tif (alternate) instance.clear();\n\t\t\tinstance.unmount();\n\t\t\tif (alternate) write('\\u001B[?1049l');\n\t\t\twrite('\\u001B[?25h');\n\t\t\tfor (const restore of restoreOutput) restore();\n\t\t\t// A fatal startup must not leave the user with only a folded error and no way to read it.\n\t\t\tif (service.status.progress.readyMs === null) {\n\t\t\t\tconst error = service.logs.entries().findLast((entry) => entry.level === 'error' && !isErrorDetail(entry));\n\t\t\t\tif (error) write(`\\n${error.text}\\n`);\n\t\t\t}\n\t\t\tquit();\n\t\t}\n\t};\n}\n\nexport { formatDuration } from './panel-logic.js';\n"],"mappings":"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"}
@@ -18,6 +18,7 @@ var Tunnel = class extends EventEmitter {
18
18
  #child = null;
19
19
  #state = "off";
20
20
  #url = null;
21
+ #wanted = false;
21
22
  constructor(config) {
22
23
  super();
23
24
  this.config = config;
@@ -29,17 +30,29 @@ var Tunnel = class extends EventEmitter {
29
30
  get url() {
30
31
  return this.#url;
31
32
  }
32
- async start() {
33
- const tunnel = this.config.dev.tunnel;
33
+ async start(forceQuick = false) {
34
+ if (this.#state === "starting" || this.#state === "up") return;
35
+ const configured = this.config.dev.tunnel;
36
+ const tunnel = forceQuick && configured.mode === "off" ? {
37
+ mode: "quick",
38
+ path: "/",
39
+ updateEndpoint: false
40
+ } : configured;
34
41
  if (tunnel.mode === "off") return;
42
+ this.#wanted = true;
35
43
  this.#setState("starting", null);
36
44
  const url = tunnel.mode === "url" ? await this.#useConfiguredUrl(tunnel) : await this.#openQuickTunnel();
37
- if (!url) return;
45
+ if (!url || !this.#wanted) return;
38
46
  this.#setState("up", endpointUrl(url, tunnel.path));
39
47
  this.emit("log", "success", `Tunnel ready at ${this.#url}`);
40
48
  if (tunnel.updateEndpoint) await this.#updateInteractionsEndpoint(this.#url);
41
49
  }
50
+ /** Toggles the configured tunnel, opening a quick tunnel when the project did not configure one. */
51
+ toggle() {
52
+ return this.#state === "starting" || this.#state === "up" ? this.close() : this.start(true);
53
+ }
42
54
  close() {
55
+ this.#wanted = false;
43
56
  const child = this.#child;
44
57
  this.#child = null;
45
58
  this.#setState("off", null);
@@ -111,11 +124,11 @@ var Tunnel = class extends EventEmitter {
111
124
  stdout.flush();
112
125
  stderr.flush();
113
126
  this.#child = null;
114
- if (!settled) {
127
+ if (!settled && this.#wanted) {
115
128
  this.emit("log", "error", `cloudflared exited with code ${code} before the tunnel was up`);
116
129
  this.#setState("failed", null);
117
130
  settle(null);
118
- }
131
+ } else settle(null);
119
132
  });
120
133
  });
121
134
  }
@@ -195,11 +208,11 @@ let envFileCache = null;
195
208
  function readProjectEnvFilesCached(root) {
196
209
  if (envFileCache?.root !== root) envFileCache = {
197
210
  root,
198
- values: readProjectEnvFiles(root)
211
+ values: readProjectEnvFiles(root, "development")
199
212
  };
200
213
  return envFileCache.values;
201
214
  }
202
215
 
203
216
  //#endregion
204
217
  export { readDiscordCredentials as n, Tunnel as t };
205
- //# sourceMappingURL=tunnel-B8Id4-lR.js.map
218
+ //# sourceMappingURL=tunnel-mGJe2zuE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-mGJe2zuE.js","names":[],"sources":["../src/lib/tunnel.ts"],"sourcesContent":["import { readProjectEnvFiles, type ResolvedStarsConfig, type ResolvedTunnelConfig } from '@wolfstar/http-framework/config';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { EventEmitter } from 'node:events';\nimport type { LogLevel } from './log-buffer.js';\nimport { createLineSplitter } from './process-supervisor.js';\n\nconst QUICK_TUNNEL_URL = /https:\\/\\/[a-z0-9-]+\\.trycloudflare\\.com/i;\nconst INSTALL_HINT =\n\t'Install cloudflared (https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/), point `dev.tunnel` at an https URL you already serve, or set it to false.';\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\n/**\n * Exposes the bot's interactions endpoint publicly while `stars dev` runs.\n *\n * `dev.tunnel: true` spawns a `cloudflared` quick tunnel and reads its hostname off the process output (it changes\n * on every run); a configured https URL is only probed, since the user already serves it. Writing the URL to the\n * Discord application is opt-in through `dev.tunnel.updateEndpoint`, because it edits a live application.\n */\nexport class Tunnel extends EventEmitter<TunnelEvents> {\n\t#child: ChildProcess | null = null;\n\t#state: TunnelState = 'off';\n\t#url: string | null = null;\n\t#wanted = false;\n\n\tpublic constructor(private readonly config: ResolvedStarsConfig) {\n\t\tsuper();\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' ? { mode: 'quick', path: '/', updateEndpoint: false } : 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();\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 close(): Promise<void> {\n\t\tthis.#wanted = false;\n\t\tconst child = this.#child;\n\t\tthis.#child = null;\n\t\tthis.#setState('off', 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\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(): 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\tthis.emit('log', 'info', 'Opening a cloudflared quick tunnel…');\n\n\t\treturn new Promise<string | null>((resolve) => {\n\t\t\tlet child: ChildProcess;\n\t\t\ttry {\n\t\t\t\tchild = spawn('cloudflared', ['tunnel', '--url', target, '--no-autoupdate'], {\n\t\t\t\t\tcwd: this.config.root,\n\t\t\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t\t\t\twindowsHide: true\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\tthis.#failWithMissingBinary();\n\t\t\t\tresolve(null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.#child = child;\n\t\t\tlet settled = false;\n\t\t\tconst settle = (url: string | null) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tresolve(url);\n\t\t\t};\n\n\t\t\tconst handleLine = (line: string) => {\n\t\t\t\tconst match = QUICK_TUNNEL_URL.exec(line);\n\t\t\t\tif (match) settle(match[0]);\n\t\t\t\telse if (line.trim().length > 0) this.emit('log', 'debug', line.trim());\n\t\t\t};\n\n\t\t\tconst stdout = createLineSplitter(handleLine);\n\t\t\tconst stderr = createLineSplitter(handleLine);\n\t\t\tchild.stdout?.on('data', stdout.push);\n\t\t\tchild.stderr?.on('data', stderr.push);\n\n\t\t\tchild.once('error', (error: NodeJS.ErrnoException) => {\n\t\t\t\tif (error.code === 'ENOENT') this.#failWithMissingBinary();\n\t\t\t\telse {\n\t\t\t\t\tthis.emit('log', 'error', `cloudflared failed: ${error.message}`);\n\t\t\t\t\tthis.#setState('failed', null);\n\t\t\t\t}\n\t\t\t\tsettle(null);\n\t\t\t});\n\n\t\t\tchild.once('exit', (code) => {\n\t\t\t\tstdout.flush();\n\t\t\t\tstderr.flush();\n\t\t\t\tthis.#child = null;\n\t\t\t\tif (!settled && this.#wanted) {\n\t\t\t\t\tthis.emit('log', 'error', `cloudflared exited with code ${code} before the tunnel was up`);\n\t\t\t\t\tthis.#setState('failed', null);\n\t\t\t\t\tsettle(null);\n\t\t\t\t} else settle(null);\n\t\t\t});\n\t\t});\n\t}\n\n\t#failWithMissingBinary(): void {\n\t\tthis.emit('log', 'error', `cloudflared is not installed. ${INSTALL_HINT}`);\n\t\tthis.#setState('failed', null);\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 fromFiles = readProjectEnvFilesCached(config.root);\n\tconst read = (...keys: string[]): string | null => {\n\t\tfor (const key of keys) {\n\t\t\tconst value = config.dev.env[key] ?? env[key] ?? fromFiles[key];\n\t\t\tif (value) return value;\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst token = read('DISCORD_TOKEN', 'TOKEN');\n\tif (!token) return null;\n\n\treturn { token, applicationId: read('DISCORD_APPLICATION_ID', 'APPLICATION_ID', 'DISCORD_CLIENT_ID', 'CLIENT_ID') };\n}\n\nlet envFileCache: { root: string; values: Record<string, string> } | null = null;\n\nfunction readProjectEnvFilesCached(root: string): Record<string, string> {\n\tif (envFileCache?.root !== root) envFileCache = { root, values: readProjectEnvFiles(root, 'development') };\n\treturn envFileCache.values;\n}\n"],"mappings":"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"}