@wolfstar/cli 0.6.1 → 0.7.0-next-20260918190835
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/{build-B0DNtlI8.js → build-BouwhG0y.js} +2 -2
- package/dist/{build-B0DNtlI8.js.map → build-BouwhG0y.js.map} +1 -1
- package/dist/{build-D4A4OlYR.js → build-CqVlp9y0.js} +2 -2
- package/dist/{build-D4A4OlYR.js.map → build-CqVlp9y0.js.map} +1 -1
- package/dist/cli.js +4 -4
- package/dist/{codegen-DHQH83M3.js → codegen-Do2h4Hl8.js} +2 -2
- package/dist/{codegen-DHQH83M3.js.map → codegen-Do2h4Hl8.js.map} +1 -1
- package/dist/{codegen-1kW_OnrL.js → codegen-y8gc147n.js} +2 -2
- package/dist/{codegen-1kW_OnrL.js.map → codegen-y8gc147n.js.map} +1 -1
- package/dist/{dev-CwJg6pSQ.js → dev-BgpW3Gsr.js} +9 -3
- package/dist/dev-BgpW3Gsr.js.map +1 -0
- package/dist/{dev-C-QKrdnz.js → dev-Dx9emKiU.js} +42 -8
- package/dist/dev-Dx9emKiU.js.map +1 -0
- package/dist/index.d.ts +42 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/{info-BrxKCcUW.js → info-Bvt1Jl88.js} +2 -2
- package/dist/{info-BrxKCcUW.js.map → info-Bvt1Jl88.js.map} +1 -1
- package/dist/{info-rYRLITnv.js → info-pX-M8R11.js} +2 -2
- package/dist/{info-rYRLITnv.js.map → info-pX-M8R11.js.map} +1 -1
- package/dist/{locales-DZCKWWcq.js → locales-C4_03V54.js} +2 -2
- package/dist/{locales-DZCKWWcq.js.map → locales-C4_03V54.js.map} +1 -1
- package/dist/theme-Bosd2Kk2.js +151 -0
- package/dist/theme-Bosd2Kk2.js.map +1 -0
- package/dist/tsc-D3FXjM6P.js +3 -0
- package/dist/{tsc-BTuFXw3O.js → tsc-D56BJ6lA.js} +8 -1
- package/dist/tsc-D56BJ6lA.js.map +1 -0
- package/dist/{tui-BCGQrMQo.js → tui-O94OH1w8.js} +151 -30
- package/dist/tui-O94OH1w8.js.map +1 -0
- package/package.json +2 -2
- package/dist/dev-C-QKrdnz.js.map +0 -1
- package/dist/dev-CwJg6pSQ.js.map +0 -1
- package/dist/tsc-BTuFXw3O.js.map +0 -1
- package/dist/tsc-Ba29w8eS.js +0 -3
- package/dist/tui-BCGQrMQo.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales-
|
|
1
|
+
{"version":3,"file":"locales-C4_03V54.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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/theme.ts
|
|
6
|
+
/** Every theme a user can pick, in the order the picker lists them. `auto` follows the terminal background. */
|
|
7
|
+
const THEME_SETTINGS = [
|
|
8
|
+
"auto",
|
|
9
|
+
"dark",
|
|
10
|
+
"light",
|
|
11
|
+
"dark-daltonized",
|
|
12
|
+
"light-daltonized",
|
|
13
|
+
"dark-ansi",
|
|
14
|
+
"light-ansi"
|
|
15
|
+
];
|
|
16
|
+
const THEMES = {
|
|
17
|
+
dark: {
|
|
18
|
+
brand: "#4ade80",
|
|
19
|
+
url: "#22d3ee",
|
|
20
|
+
tunnel: "#e879f9",
|
|
21
|
+
busy: "#facc15",
|
|
22
|
+
success: "#4ade80",
|
|
23
|
+
warning: "#facc15",
|
|
24
|
+
error: "#f87171",
|
|
25
|
+
text: void 0,
|
|
26
|
+
onBright: "#000000",
|
|
27
|
+
onError: "#000000"
|
|
28
|
+
},
|
|
29
|
+
light: {
|
|
30
|
+
brand: "#15803d",
|
|
31
|
+
url: "#0e7490",
|
|
32
|
+
tunnel: "#a21caf",
|
|
33
|
+
busy: "#a16207",
|
|
34
|
+
success: "#15803d",
|
|
35
|
+
warning: "#a16207",
|
|
36
|
+
error: "#b91c1c",
|
|
37
|
+
text: void 0,
|
|
38
|
+
onBright: "#ffffff",
|
|
39
|
+
onError: "#ffffff"
|
|
40
|
+
},
|
|
41
|
+
"dark-daltonized": {
|
|
42
|
+
brand: "#56b4e9",
|
|
43
|
+
url: "#56b4e9",
|
|
44
|
+
tunnel: "#cc79a7",
|
|
45
|
+
busy: "#f0e442",
|
|
46
|
+
success: "#56b4e9",
|
|
47
|
+
warning: "#e69f00",
|
|
48
|
+
error: "#ff8a3d",
|
|
49
|
+
text: void 0,
|
|
50
|
+
onBright: "#000000",
|
|
51
|
+
onError: "#000000"
|
|
52
|
+
},
|
|
53
|
+
"light-daltonized": {
|
|
54
|
+
brand: "#0072b2",
|
|
55
|
+
url: "#0072b2",
|
|
56
|
+
tunnel: "#8e3b76",
|
|
57
|
+
busy: "#8a6500",
|
|
58
|
+
success: "#0072b2",
|
|
59
|
+
warning: "#8a6500",
|
|
60
|
+
error: "#b84a00",
|
|
61
|
+
text: void 0,
|
|
62
|
+
onBright: "#ffffff",
|
|
63
|
+
onError: "#ffffff"
|
|
64
|
+
},
|
|
65
|
+
"dark-ansi": {
|
|
66
|
+
brand: "greenBright",
|
|
67
|
+
url: "cyanBright",
|
|
68
|
+
tunnel: "magentaBright",
|
|
69
|
+
busy: "yellowBright",
|
|
70
|
+
success: "greenBright",
|
|
71
|
+
warning: "yellowBright",
|
|
72
|
+
error: "redBright",
|
|
73
|
+
text: void 0,
|
|
74
|
+
onBright: "black",
|
|
75
|
+
onError: "black"
|
|
76
|
+
},
|
|
77
|
+
"light-ansi": {
|
|
78
|
+
brand: "green",
|
|
79
|
+
url: "cyan",
|
|
80
|
+
tunnel: "magenta",
|
|
81
|
+
busy: "yellow",
|
|
82
|
+
success: "green",
|
|
83
|
+
warning: "yellow",
|
|
84
|
+
error: "red",
|
|
85
|
+
text: void 0,
|
|
86
|
+
onBright: "white",
|
|
87
|
+
onError: "white"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
function isThemeSetting(value) {
|
|
91
|
+
return typeof value === "string" && THEME_SETTINGS.includes(value);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Guesses whether the terminal background is light from `COLORFGBG` (`foreground;background`, set by rxvt, Konsole and
|
|
95
|
+
* others). Without it the terminal is assumed dark, which matches most developer setups.
|
|
96
|
+
*/
|
|
97
|
+
function detectTerminalBackground(env = process.env) {
|
|
98
|
+
const background = env.COLORFGBG?.split(";").at(-1);
|
|
99
|
+
if (background === void 0 || !/^\d+$/.test(background)) return "dark";
|
|
100
|
+
const index = Number(background);
|
|
101
|
+
return index === 7 || index === 15 ? "light" : "dark";
|
|
102
|
+
}
|
|
103
|
+
function resolveTheme(setting, env = process.env) {
|
|
104
|
+
return setting === "auto" ? detectTerminalBackground(env) : setting;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Precedence: `--theme` › `STARS_THEME` › the saved preference › `auto`. An unknown `STARS_THEME` is ignored, since an
|
|
108
|
+
* environment variable should not stop the dev server; an unknown `--theme` is the caller's to reject.
|
|
109
|
+
*/
|
|
110
|
+
function resolveThemeSetting({ flag, env = process.env, saved }) {
|
|
111
|
+
if (isThemeSetting(flag)) return flag;
|
|
112
|
+
const fromEnv = env.STARS_THEME?.trim().toLowerCase();
|
|
113
|
+
if (isThemeSetting(fromEnv)) return fromEnv;
|
|
114
|
+
return saved ?? "auto";
|
|
115
|
+
}
|
|
116
|
+
/** Where preferences live: `$STARS_CONFIG_DIR`, else `$XDG_CONFIG_HOME/stars`, `%APPDATA%\stars` or `~/.config/stars`. */
|
|
117
|
+
function preferencesPath(env = process.env) {
|
|
118
|
+
const directory = env.STARS_CONFIG_DIR ?? join((env.XDG_CONFIG_HOME || (process.platform === "win32" ? env.APPDATA : void 0)) ?? join(homedir(), ".config"), "stars");
|
|
119
|
+
return join(directory, "preferences.json");
|
|
120
|
+
}
|
|
121
|
+
function readSavedTheme(env = process.env) {
|
|
122
|
+
try {
|
|
123
|
+
const { theme } = JSON.parse(readFileSync(preferencesPath(env), "utf8"));
|
|
124
|
+
return isThemeSetting(theme) ? theme : void 0;
|
|
125
|
+
} catch {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Persists the theme, keeping any other preference in the file. Returns whether it could be written. */
|
|
130
|
+
function saveTheme(setting, env = process.env) {
|
|
131
|
+
const path = preferencesPath(env);
|
|
132
|
+
try {
|
|
133
|
+
let existing = {};
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
136
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) existing = parsed;
|
|
137
|
+
} catch {}
|
|
138
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
139
|
+
writeFileSync(path, `${JSON.stringify({
|
|
140
|
+
...existing,
|
|
141
|
+
theme: setting
|
|
142
|
+
}, null, " ")}\n`);
|
|
143
|
+
return true;
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
export { readSavedTheme as a, saveTheme as c, isThemeSetting as i, THEME_SETTINGS as n, resolveTheme as o, detectTerminalBackground as r, resolveThemeSetting as s, THEMES as t };
|
|
151
|
+
//# sourceMappingURL=theme-Bosd2Kk2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-Bosd2Kk2.js","names":[],"sources":["../src/lib/theme.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\n/** Every theme a user can pick, in the order the picker lists them. `auto` follows the terminal background. */\nexport const THEME_SETTINGS = ['auto', 'dark', 'light', 'dark-daltonized', 'light-daltonized', 'dark-ansi', 'light-ansi'] as const;\nexport type ThemeSetting = (typeof THEME_SETTINGS)[number];\nexport type ThemeName = Exclude<ThemeSetting, 'auto'>;\n\n/**\n * Semantic colour roles the TUI paints with, instead of hardcoded colour names. `undefined` leaves the terminal's own\n * foreground colour, which is the only legible choice for body text on both dark and light backgrounds.\n */\nexport interface Theme {\n\t/** The wordmark, progress bar and selection marker. */\n\tbrand: string;\n\turl: string;\n\ttunnel: string;\n\t/** Something is still building or starting. */\n\tbusy: string;\n\tsuccess: string;\n\twarning: string;\n\terror: string;\n\ttext: string | undefined;\n\t/** Foreground of a badge painted on `success`, `warning` or `busy`. */\n\tonBright: string;\n\t/** Foreground of a badge painted on `error`. */\n\tonError: string;\n}\n\nexport type ThemeToken = keyof Theme;\n\nexport const THEMES: Readonly<Record<ThemeName, Theme>> = {\n\tdark: {\n\t\tbrand: '#4ade80',\n\t\turl: '#22d3ee',\n\t\ttunnel: '#e879f9',\n\t\tbusy: '#facc15',\n\t\tsuccess: '#4ade80',\n\t\twarning: '#facc15',\n\t\terror: '#f87171',\n\t\ttext: undefined,\n\t\tonBright: '#000000',\n\t\tonError: '#000000'\n\t},\n\tlight: {\n\t\tbrand: '#15803d',\n\t\turl: '#0e7490',\n\t\ttunnel: '#a21caf',\n\t\tbusy: '#a16207',\n\t\tsuccess: '#15803d',\n\t\twarning: '#a16207',\n\t\terror: '#b91c1c',\n\t\ttext: undefined,\n\t\tonBright: '#ffffff',\n\t\tonError: '#ffffff'\n\t},\n\t// Okabe-Ito palette: blue/orange carry the good/bad distinction instead of green/red.\n\t'dark-daltonized': {\n\t\tbrand: '#56b4e9',\n\t\turl: '#56b4e9',\n\t\ttunnel: '#cc79a7',\n\t\tbusy: '#f0e442',\n\t\tsuccess: '#56b4e9',\n\t\twarning: '#e69f00',\n\t\terror: '#ff8a3d',\n\t\ttext: undefined,\n\t\tonBright: '#000000',\n\t\tonError: '#000000'\n\t},\n\t'light-daltonized': {\n\t\tbrand: '#0072b2',\n\t\turl: '#0072b2',\n\t\ttunnel: '#8e3b76',\n\t\tbusy: '#8a6500',\n\t\tsuccess: '#0072b2',\n\t\twarning: '#8a6500',\n\t\terror: '#b84a00',\n\t\ttext: undefined,\n\t\tonBright: '#ffffff',\n\t\tonError: '#ffffff'\n\t},\n\t// The 16 ANSI colours only, so the terminal's own palette decides what they look like.\n\t'dark-ansi': {\n\t\tbrand: 'greenBright',\n\t\turl: 'cyanBright',\n\t\ttunnel: 'magentaBright',\n\t\tbusy: 'yellowBright',\n\t\tsuccess: 'greenBright',\n\t\twarning: 'yellowBright',\n\t\terror: 'redBright',\n\t\ttext: undefined,\n\t\tonBright: 'black',\n\t\tonError: 'black'\n\t},\n\t'light-ansi': {\n\t\tbrand: 'green',\n\t\turl: 'cyan',\n\t\ttunnel: 'magenta',\n\t\tbusy: 'yellow',\n\t\tsuccess: 'green',\n\t\twarning: 'yellow',\n\t\terror: 'red',\n\t\ttext: undefined,\n\t\tonBright: 'white',\n\t\tonError: 'white'\n\t}\n};\n\nexport function isThemeSetting(value: unknown): value is ThemeSetting {\n\treturn typeof value === 'string' && (THEME_SETTINGS as readonly string[]).includes(value);\n}\n\n/**\n * Guesses whether the terminal background is light from `COLORFGBG` (`foreground;background`, set by rxvt, Konsole and\n * others). Without it the terminal is assumed dark, which matches most developer setups.\n */\nexport function detectTerminalBackground(env: NodeJS.ProcessEnv = process.env): 'dark' | 'light' {\n\tconst background = env.COLORFGBG?.split(';').at(-1);\n\tif (background === undefined || !/^\\d+$/.test(background)) return 'dark';\n\tconst index = Number(background);\n\treturn index === 7 || index === 15 ? 'light' : 'dark';\n}\n\nexport function resolveTheme(setting: ThemeSetting, env: NodeJS.ProcessEnv = process.env): ThemeName {\n\treturn setting === 'auto' ? detectTerminalBackground(env) : setting;\n}\n\nexport interface ThemeSettingSources {\n\t/** `--theme`. */\n\tflag?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** The theme saved by the picker. */\n\tsaved?: ThemeSetting;\n}\n\n/**\n * Precedence: `--theme` › `STARS_THEME` › the saved preference › `auto`. An unknown `STARS_THEME` is ignored, since an\n * environment variable should not stop the dev server; an unknown `--theme` is the caller's to reject.\n */\nexport function resolveThemeSetting({ flag, env = process.env, saved }: ThemeSettingSources): ThemeSetting {\n\tif (isThemeSetting(flag)) return flag;\n\tconst fromEnv = env.STARS_THEME?.trim().toLowerCase();\n\tif (isThemeSetting(fromEnv)) return fromEnv;\n\treturn saved ?? 'auto';\n}\n\n/** Where preferences live: `$STARS_CONFIG_DIR`, else `$XDG_CONFIG_HOME/stars`, `%APPDATA%\\stars` or `~/.config/stars`. */\nexport function preferencesPath(env: NodeJS.ProcessEnv = process.env): string {\n\tconst directory =\n\t\tenv.STARS_CONFIG_DIR ??\n\t\tjoin((env.XDG_CONFIG_HOME || (process.platform === 'win32' ? env.APPDATA : undefined)) ?? join(homedir(), '.config'), 'stars');\n\treturn join(directory, 'preferences.json');\n}\n\nexport function readSavedTheme(env: NodeJS.ProcessEnv = process.env): ThemeSetting | undefined {\n\ttry {\n\t\tconst { theme } = JSON.parse(readFileSync(preferencesPath(env), 'utf8')) as { theme?: unknown };\n\t\treturn isThemeSetting(theme) ? theme : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Persists the theme, keeping any other preference in the file. Returns whether it could be written. */\nexport function saveTheme(setting: ThemeSetting, env: NodeJS.ProcessEnv = process.env): boolean {\n\tconst path = preferencesPath(env);\n\ttry {\n\t\tlet existing: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));\n\t\t\tif (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) existing = parsed as Record<string, unknown>;\n\t\t} catch {\n\t\t\t// No file yet, or a corrupt one: start over.\n\t\t}\n\t\tmkdirSync(dirname(path), { recursive: true });\n\t\twriteFileSync(path, `${JSON.stringify({ ...existing, theme: setting }, null, '\\t')}\\n`);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":";;;;;;AAKA,MAAa,iBAAiB;CAAC;CAAQ;CAAQ;CAAS;CAAmB;CAAoB;CAAa;AAAY;AA2BxH,MAAa,SAA6C;CACzD,MAAM;EACL,OAAO;EACP,KAAK;EACL,QAAQ;EACR,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;CACV;CACA,OAAO;EACN,OAAO;EACP,KAAK;EACL,QAAQ;EACR,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;CACV;CAEA,mBAAmB;EAClB,OAAO;EACP,KAAK;EACL,QAAQ;EACR,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;CACV;CACA,oBAAoB;EACnB,OAAO;EACP,KAAK;EACL,QAAQ;EACR,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;CACV;CAEA,aAAa;EACZ,OAAO;EACP,KAAK;EACL,QAAQ;EACR,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;CACV;CACA,cAAc;EACb,OAAO;EACP,KAAK;EACL,QAAQ;EACR,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;CACV;AACD;AAEA,SAAgB,eAAe,OAAuC;CACrE,OAAO,OAAO,UAAU,YAAa,eAAqC,SAAS,KAAK;AACzF;;;;;AAMA,SAAgB,yBAAyB,MAAyB,QAAQ,KAAuB;CAChG,MAAM,aAAa,IAAI,WAAW,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE;CAClD,IAAI,eAAe,UAAa,CAAC,QAAQ,KAAK,UAAU,GAAG,OAAO;CAClE,MAAM,QAAQ,OAAO,UAAU;CAC/B,OAAO,UAAU,KAAK,UAAU,KAAK,UAAU;AAChD;AAEA,SAAgB,aAAa,SAAuB,MAAyB,QAAQ,KAAgB;CACpG,OAAO,YAAY,SAAS,yBAAyB,GAAG,IAAI;AAC7D;;;;;AAcA,SAAgB,oBAAoB,EAAE,MAAM,MAAM,QAAQ,KAAK,SAA4C;CAC1G,IAAI,eAAe,IAAI,GAAG,OAAO;CACjC,MAAM,UAAU,IAAI,aAAa,KAAK,CAAC,CAAC,YAAY;CACpD,IAAI,eAAe,OAAO,GAAG,OAAO;CACpC,OAAO,SAAS;AACjB;;AAGA,SAAgB,gBAAgB,MAAyB,QAAQ,KAAa;CAC7E,MAAM,YACL,IAAI,oBACJ,MAAM,IAAI,oBAAoB,QAAQ,aAAa,UAAU,IAAI,UAAU,YAAe,KAAK,QAAQ,GAAG,SAAS,GAAG,OAAO;CAC9H,OAAO,KAAK,WAAW,kBAAkB;AAC1C;AAEA,SAAgB,eAAe,MAAyB,QAAQ,KAA+B;CAC9F,IAAI;EACH,MAAM,EAAE,UAAU,KAAK,MAAM,aAAa,gBAAgB,GAAG,GAAG,MAAM,CAAC;EACvE,OAAO,eAAe,KAAK,IAAI,QAAQ;CACxC,QAAQ;EACP;CACD;AACD;;AAGA,SAAgB,UAAU,SAAuB,MAAyB,QAAQ,KAAc;CAC/F,MAAM,OAAO,gBAAgB,GAAG;CAChC,IAAI;EACH,IAAI,WAAoC,CAAC;EACzC,IAAI;GACH,MAAM,SAAkB,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;GAC7D,IAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG,WAAW;EAChF,QAAQ,CAER;EACA,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC5C,cAAc,MAAM,GAAG,KAAK,UAAU;GAAE,GAAG;GAAU,OAAO;EAAQ,GAAG,MAAM,GAAI,EAAE,GAAG;EACtF,OAAO;CACR,QAAQ;EACP,OAAO;CACR;AACD"}
|
|
@@ -96,6 +96,13 @@ var ProcessSupervisor = class extends EventEmitter {
|
|
|
96
96
|
});
|
|
97
97
|
return this.#stopping;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Sends `SIGKILL` right away, without waiting for the process to exit. Used to force a shutdown that is taking too long.
|
|
101
|
+
*/
|
|
102
|
+
kill() {
|
|
103
|
+
const child = this.#child;
|
|
104
|
+
if (child && child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
105
|
+
}
|
|
99
106
|
async restart() {
|
|
100
107
|
await this.stop();
|
|
101
108
|
this.start();
|
|
@@ -252,4 +259,4 @@ function resolveTscBinary(root) {
|
|
|
252
259
|
|
|
253
260
|
//#endregion
|
|
254
261
|
export { createLineSplitter as i, resolveTscBinary as n, ProcessSupervisor as r, TscBuilder as t };
|
|
255
|
-
//# sourceMappingURL=tsc-
|
|
262
|
+
//# sourceMappingURL=tsc-D56BJ6lA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsc-D56BJ6lA.js","names":[],"sources":["../src/lib/process-supervisor.ts","../src/lib/builders/tsc.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\t/**\n\t * Sends `SIGKILL` right away, without waiting for the process to exit. Used to force a shutdown that is taking too long.\n\t */\n\tpublic kill(): void {\n\t\tconst child = this.#child;\n\t\tif (child && child.exitCode === null && child.signalCode === null) child.kill('SIGKILL');\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","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":";;;;;;;;;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;;;;CAKA,AAAO,OAAa;EACnB,MAAM,QAAQ,KAAK;EACnB,IAAI,SAAS,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM,MAAM,KAAK,SAAS;CACxF;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;;;;ACzJA,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,aAAa;;;;AAKnB,IAAa,aAAb,cAAgC,aAA+C;CAM1C;CALpC,AAAgB,OAAO;CACvB,SAA8B;CAC9B,aAAa;CACb,cAA6B;CAE7B,AAAO,YAAY,AAAiB,QAA6B;EAChE,MAAM;EAD6B;CAEpC;CAEA,AAAO,QAA+B;EACrC,MAAM,QAAQ,KAAK,OAAO,CAAC,CAAC;EAC5B,KAAK,OAAO;EAEZ,OAAO,IAAI,SAAS,YAAY;GAC/B,MAAM,KAAK,SAAS,SAAS;IAC5B,KAAK,SAAS;IACd,QAAQ,KAAK,QAAQ,SAAS,IAAI,OAAQ,KAAK,eAAe,wBAAwB,MAAO,CAAC;GAC/F,CAAC;EACF,CAAC;CACF;CAEA,AAAO,QAAuB;EAC7B,KAAK,OAAO,CAAC,WAAW,uBAAuB,CAAC;EAChD,KAAK,OAAO;EACZ,OAAO,QAAQ,QAAQ;CACxB;CAEA,AAAO,QAAuB;EAC7B,MAAM,QAAQ,KAAK;EACnB,KAAK,SAAS;EACd,IAAI,CAAC,SAAS,MAAM,aAAa,MAAM,OAAO,QAAQ,QAAQ;EAE9D,OAAO,IAAI,SAAS,YAAY;GAC/B,MAAM,KAAK,cAAc,QAAQ,CAAC;GAClC,MAAM,KAAK;EACZ,CAAC;CACF;CAEA,OAAO,WAAmC;EACzC,MAAM,MAAM,iBAAiB,KAAK,OAAO,IAAI;EAC7C,IAAI,CAAC,KAAK,MAAM,IAAI,SAAS,oCAAoC,KAAK,OAAO,QAAQ;GAAE,MAAM;GAAsB,MAAM;EAAa,CAAC;EAEvI,MAAM,QAAQ,MAAM,QAAQ,UAAU;GAAC;GAAK;GAAM,KAAK,OAAO,MAAM;GAAW;GAAY;GAAS,GAAG;EAAS,GAAG;GAClH,KAAK,KAAK,OAAO;GACjB,KAAK,QAAQ;GACb,OAAO;IAAC;IAAU;IAAQ;GAAM;GAChC,aAAa;EACd,CAAC;EACD,KAAK,SAAS;EAEd,MAAM,cAAc,SAAiB,KAAK,YAAY,IAAI;EAC1D,MAAM,SAAS,mBAAmB,UAAU;EAC5C,MAAM,SAAS,mBAAmB,UAAU;EAC5C,MAAM,QAAQ,GAAG,QAAQ,OAAO,IAAI;EACpC,MAAM,QAAQ,GAAG,QAAQ,OAAO,IAAI;EACpC,MAAM,KAAK,cAAc;GACxB,OAAO,MAAM;GACb,OAAO,MAAM;EACd,CAAC;EACD,MAAM,KAAK,UAAU,UAAU,KAAK,KAAK,OAAO,SAAS,MAAM,OAAO,CAAC;EAEvE,OAAO;CACR;CAEA,YAAY,MAAoB;EAC/B,MAAM,OAAO,KAAK,KAAK;EACvB,IAAI,KAAK,WAAW,GAAG;EAEvB,IAAI,YAAY,KAAK,IAAI,GAAG;GAC3B,IAAI,KAAK,eAAe,GAAG,KAAK,OAAO;GACvC;EACD;EAEA,IAAI,WAAW,KAAK,IAAI,GAAG;GAC1B,KAAK,gBAAgB;GACrB,KAAK,KAAK,OAAO,SAAS,IAAI;GAC9B;EACD;EAEA,MAAM,OAAO,WAAW,KAAK,IAAI;EACjC,IAAI,MAAM;GACT,MAAM,SAAS,OAAO,KAAK,EAAE;GAC7B,KAAK,KAAK,OAAO,WAAW,IAAI,YAAY,SAAS,IAAI;GACzD,KAAK,QAAQ,WAAW,IAAI,OAAQ,KAAK,eAAe,IAAK;GAC7D;EACD;EAEA,KAAK,KAAK,OAAO,QAAQ,IAAI;CAC9B;CAEA,SAAe;EACd,KAAK,cAAc;EACnB,KAAK,aAAa,YAAY,IAAI;EAClC,KAAK,KAAK,OAAO;CAClB;CAEA,QAAQ,SAAsC;EAC7C,MAAM,UAAwB;GAAE,IAAI,YAAY;GAAM,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,UAAU;GAAG;EAAQ;EAC3H,KAAK,aAAa;EAClB,KAAK,KAAK,QAAQ,KAAK,YAAY,WAAW,OAAO;EACrD,OAAO;CACR;AACD;;;;;;AAOA,SAAgB,iBAAiB,MAA6B;CAC7D,OAAO,cAAc,MAAM,cAAc,KAAK;AAC/C"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t as readOwnPackageJson } from "./version-DN6Uho-Y.js";
|
|
2
|
+
import { n as THEME_SETTINGS, o as resolveTheme, r as detectTerminalBackground, t as THEMES } from "./theme-Bosd2Kk2.js";
|
|
2
3
|
import { n as classifyAppLine, r as isErrorDetail } from "./log-buffer-D-nj2ZEU.js";
|
|
3
4
|
import { execFile } from "node:child_process";
|
|
4
5
|
import { stripVTControlCharacters } from "node:util";
|
|
@@ -9,22 +10,31 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
9
10
|
|
|
10
11
|
//#region src/renderers/ink/theme.tsx
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
* component reads
|
|
13
|
+
* `stars dev` decides once whether the UI may use colour (`NO_COLOR`, `FORCE_COLOR`, TTY detection) and which theme
|
|
14
|
+
* to paint with; every component reads both from here instead of re-deriving them.
|
|
14
15
|
*/
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const ThemeContext = createContext({
|
|
17
|
+
color: true,
|
|
18
|
+
theme: "dark"
|
|
19
|
+
});
|
|
20
|
+
function ThemeProvider({ color, theme, children }) {
|
|
21
|
+
return /* @__PURE__ */ jsx(ThemeContext, {
|
|
22
|
+
value: {
|
|
23
|
+
color,
|
|
24
|
+
theme
|
|
25
|
+
},
|
|
19
26
|
children
|
|
20
27
|
});
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
|
-
* Returns a colour
|
|
30
|
+
* Returns a theme colour only when colour is enabled, so `<Text color={usePaint()('success')}>` degrades to plain text.
|
|
24
31
|
*/
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
return (
|
|
32
|
+
function usePaint() {
|
|
33
|
+
const { color, theme } = useContext(ThemeContext);
|
|
34
|
+
return (token) => color ? THEMES[theme][token] : void 0;
|
|
35
|
+
}
|
|
36
|
+
function useColorEnabled() {
|
|
37
|
+
return useContext(ThemeContext).color;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
//#endregion
|
|
@@ -34,6 +44,7 @@ const KEYS = [
|
|
|
34
44
|
["o", "open the local URL in a browser"],
|
|
35
45
|
["t", "toggle the public tunnel"],
|
|
36
46
|
["i", "show versions, URLs and session info"],
|
|
47
|
+
["T", "pick a colour theme (dark, light, colour-blind friendly, ANSI)"],
|
|
37
48
|
["l", "open the logs (scroll, filter by source or level)"],
|
|
38
49
|
["e", "jump to the last error, keeping its context"],
|
|
39
50
|
["c / Ctrl+L", "clear the log history"],
|
|
@@ -42,7 +53,7 @@ const KEYS = [
|
|
|
42
53
|
];
|
|
43
54
|
/** The static keys reference, opened with `?` from the pinned panel. */
|
|
44
55
|
function HelpOverlay({ height, width, onClose }) {
|
|
45
|
-
const paint =
|
|
56
|
+
const paint = usePaint();
|
|
46
57
|
useInput((input, key) => {
|
|
47
58
|
if (!key.ctrl && (key.escape || [
|
|
48
59
|
"q",
|
|
@@ -68,7 +79,7 @@ function HelpOverlay({ height, width, onClose }) {
|
|
|
68
79
|
" ",
|
|
69
80
|
/* @__PURE__ */ jsx(Text, {
|
|
70
81
|
bold: true,
|
|
71
|
-
color: paint("
|
|
82
|
+
color: paint("text"),
|
|
72
83
|
children: key.padEnd(14)
|
|
73
84
|
}),
|
|
74
85
|
/* @__PURE__ */ jsx(Text, {
|
|
@@ -127,6 +138,11 @@ const PANEL_HINTS = [
|
|
|
127
138
|
label: "info",
|
|
128
139
|
priority: 50
|
|
129
140
|
},
|
|
141
|
+
{
|
|
142
|
+
key: "T",
|
|
143
|
+
label: "theme",
|
|
144
|
+
priority: 30
|
|
145
|
+
},
|
|
130
146
|
{
|
|
131
147
|
key: "l",
|
|
132
148
|
label: "logs",
|
|
@@ -175,7 +191,7 @@ function Hints({ hints, width }) {
|
|
|
175
191
|
const group = (entry) => entry.source === "app" ? "runtime" : entry.source === "build" || entry.source === "tsc" ? "build" : "cli";
|
|
176
192
|
/** Filterable history, using Nuxt's keys and selecting the last error without discarding its context. */
|
|
177
193
|
function LogBrowser({ service, height, width, lastError = false, onClose, onCopy }) {
|
|
178
|
-
const paint =
|
|
194
|
+
const paint = usePaint();
|
|
179
195
|
const [sources, setSources] = useState({
|
|
180
196
|
cli: true,
|
|
181
197
|
build: true,
|
|
@@ -294,10 +310,10 @@ function LogBrowser({ service, height, width, lastError = false, onClose, onCopy
|
|
|
294
310
|
}), visible.map(({ entry, text }, i) => /* @__PURE__ */ jsxs(Text, {
|
|
295
311
|
wrap: "truncate-end",
|
|
296
312
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
297
|
-
color: paint("
|
|
313
|
+
color: paint("brand"),
|
|
298
314
|
children: entry.id === selected ? "▎ " : " "
|
|
299
315
|
}), /* @__PURE__ */ jsx(Text, {
|
|
300
|
-
color: paint(entry.level === "error" ? "
|
|
316
|
+
color: paint(entry.level === "error" ? "error" : entry.level === "warn" ? "warning" : entry.level === "success" ? "success" : "text"),
|
|
301
317
|
dimColor: isErrorDetail(entry),
|
|
302
318
|
children: text
|
|
303
319
|
})]
|
|
@@ -474,7 +490,7 @@ function InfoOverlay({ service, height, width, onClose }) {
|
|
|
474
490
|
//#region src/renderers/ink/components/Panel.tsx
|
|
475
491
|
/** Nuxt's wordmark / URLs / progress / status / hints layout, with Stars branding. */
|
|
476
492
|
function Panel({ status, config, counters, frame, elapsedMs, width, height, confirmQuit }) {
|
|
477
|
-
const paint =
|
|
493
|
+
const paint = usePaint();
|
|
478
494
|
const state = describeBadge(status);
|
|
479
495
|
const busy = state.badge !== "ready" && state.badge !== "error";
|
|
480
496
|
const badge = !busy && counters.errors > 0 ? "error" : state.badge;
|
|
@@ -489,7 +505,7 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
489
505
|
wrap: "truncate-end",
|
|
490
506
|
children: [" ", line === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
491
507
|
/* @__PURE__ */ jsx(Text, {
|
|
492
|
-
color: paint("
|
|
508
|
+
color: paint("brand"),
|
|
493
509
|
children: [..."·✦★✦"].map((cell, i) => /* @__PURE__ */ jsx(Text, {
|
|
494
510
|
dimColor: busy && i !== frame % 4,
|
|
495
511
|
children: cell
|
|
@@ -498,7 +514,7 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
498
514
|
" ",
|
|
499
515
|
/* @__PURE__ */ jsx(Text, {
|
|
500
516
|
bold: true,
|
|
501
|
-
color: paint("
|
|
517
|
+
color: paint("brand"),
|
|
502
518
|
children: "Stars"
|
|
503
519
|
}),
|
|
504
520
|
/* @__PURE__ */ jsx(Text, {
|
|
@@ -506,7 +522,7 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
506
522
|
children: ` ${readOwnPackageJson().version}`
|
|
507
523
|
})
|
|
508
524
|
] }) : /* @__PURE__ */ jsx(Text, {
|
|
509
|
-
color: paint("
|
|
525
|
+
color: paint("brand"),
|
|
510
526
|
children: line
|
|
511
527
|
})]
|
|
512
528
|
}), index === 0 && width >= 60 && badge === "ready" && status.progress.readyMs !== null && /* @__PURE__ */ jsx(Text, {
|
|
@@ -530,12 +546,12 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
530
546
|
children: "Local "
|
|
531
547
|
}),
|
|
532
548
|
/* @__PURE__ */ jsx(Text, {
|
|
533
|
-
color: paint("
|
|
549
|
+
color: paint("url"),
|
|
534
550
|
dimColor: busy,
|
|
535
551
|
children: status.url
|
|
536
552
|
}),
|
|
537
553
|
busy && /* @__PURE__ */ jsx(Text, {
|
|
538
|
-
color: paint("
|
|
554
|
+
color: paint("busy"),
|
|
539
555
|
children: ` ${[
|
|
540
556
|
"⠋",
|
|
541
557
|
"⠙",
|
|
@@ -563,7 +579,7 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
563
579
|
children: "Tunnel "
|
|
564
580
|
}),
|
|
565
581
|
/* @__PURE__ */ jsx(Text, {
|
|
566
|
-
color: paint("
|
|
582
|
+
color: paint("tunnel"),
|
|
567
583
|
children: status.tunnelUrl
|
|
568
584
|
})
|
|
569
585
|
]
|
|
@@ -580,7 +596,7 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
580
596
|
children: [
|
|
581
597
|
" ",
|
|
582
598
|
/* @__PURE__ */ jsx(Text, {
|
|
583
|
-
color: paint("
|
|
599
|
+
color: paint("brand"),
|
|
584
600
|
children: "━".repeat(filled)
|
|
585
601
|
}),
|
|
586
602
|
/* @__PURE__ */ jsxs(Text, {
|
|
@@ -593,12 +609,12 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
593
609
|
children: [
|
|
594
610
|
" ",
|
|
595
611
|
counters.warnings > 0 && /* @__PURE__ */ jsx(Text, {
|
|
596
|
-
color: paint("
|
|
612
|
+
color: paint("warning"),
|
|
597
613
|
children: `⚠ ${counters.warnings} ${plural(counters.warnings, "warning")} `
|
|
598
614
|
}),
|
|
599
615
|
counters.errors > 0 && /* @__PURE__ */ jsx(Text, {
|
|
600
616
|
bold: true,
|
|
601
|
-
color: paint("
|
|
617
|
+
color: paint("error"),
|
|
602
618
|
children: `✖ ${counters.errors} ${plural(counters.errors, "error")} `
|
|
603
619
|
}),
|
|
604
620
|
counters.errors === 0 && counters.warnings === 0 && /* @__PURE__ */ jsx(Text, {
|
|
@@ -620,8 +636,8 @@ function Panel({ status, config, counters, frame, elapsedMs, width, height, conf
|
|
|
620
636
|
" ",
|
|
621
637
|
/* @__PURE__ */ jsx(Text, {
|
|
622
638
|
bold: true,
|
|
623
|
-
backgroundColor: paint(confirmQuit || busy ? "
|
|
624
|
-
color: paint(badge === "error" && !confirmQuit ? "
|
|
639
|
+
backgroundColor: paint(confirmQuit || busy ? "busy" : badge === "error" ? "error" : "success"),
|
|
640
|
+
color: paint(badge === "error" && !confirmQuit ? "onError" : "onBright"),
|
|
625
641
|
children: ` ${confirmQuit ? "QUIT?" : badge === "restarting" ? "RESTART" : badge.toUpperCase()} `
|
|
626
642
|
}),
|
|
627
643
|
" ",
|
|
@@ -754,16 +770,101 @@ function openDevUrl(value) {
|
|
|
754
770
|
return new Promise((resolve, reject) => execFile(command, [...args], (error) => error ? reject(/* @__PURE__ */ new Error(`Could not open the browser: ${error.message}`)) : resolve()));
|
|
755
771
|
}
|
|
756
772
|
|
|
773
|
+
//#endregion
|
|
774
|
+
//#region src/renderers/ink/components/ThemeOverlay.tsx
|
|
775
|
+
const DESCRIPTIONS = {
|
|
776
|
+
auto: "match the terminal background",
|
|
777
|
+
dark: "for dark terminals",
|
|
778
|
+
light: "for light terminals",
|
|
779
|
+
"dark-daltonized": "dark, colour-blind friendly (blue/orange)",
|
|
780
|
+
"light-daltonized": "light, colour-blind friendly (blue/orange)",
|
|
781
|
+
"dark-ansi": "dark, only your terminal palette",
|
|
782
|
+
"light-ansi": "light, only your terminal palette"
|
|
783
|
+
};
|
|
784
|
+
/** Like Claude Code's `/theme`: arrows preview live, `Enter` keeps the choice and saves it, `Esc` restores. */
|
|
785
|
+
function ThemeOverlay({ current, height, width, onPreview, onSelect, onClose }) {
|
|
786
|
+
const color = useColorEnabled();
|
|
787
|
+
const [index, setIndex] = useState(Math.max(0, THEME_SETTINGS.indexOf(current)));
|
|
788
|
+
const move = (delta) => {
|
|
789
|
+
const next = (index + delta + THEME_SETTINGS.length) % THEME_SETTINGS.length;
|
|
790
|
+
setIndex(next);
|
|
791
|
+
onPreview(THEME_SETTINGS[next]);
|
|
792
|
+
};
|
|
793
|
+
useInput((input, key) => {
|
|
794
|
+
if (key.ctrl) return;
|
|
795
|
+
if (key.escape || input === "q" || input === "T") return onClose();
|
|
796
|
+
if (key.return) return onSelect(THEME_SETTINGS[index]);
|
|
797
|
+
if (key.upArrow || input === "k") return move(-1);
|
|
798
|
+
if (key.downArrow || input === "j") return move(1);
|
|
799
|
+
});
|
|
800
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
801
|
+
flexDirection: "column",
|
|
802
|
+
height,
|
|
803
|
+
children: [
|
|
804
|
+
/* @__PURE__ */ jsx(Text, {
|
|
805
|
+
bold: true,
|
|
806
|
+
children: " colour theme"
|
|
807
|
+
}),
|
|
808
|
+
/* @__PURE__ */ jsx(Text, {
|
|
809
|
+
dimColor: true,
|
|
810
|
+
children: "─".repeat(width)
|
|
811
|
+
}),
|
|
812
|
+
THEME_SETTINGS.slice(0, Math.max(0, height - 3)).map((setting, i) => {
|
|
813
|
+
const swatch = THEMES[setting === "auto" ? detectTerminalBackground() : setting];
|
|
814
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
815
|
+
wrap: "truncate-end",
|
|
816
|
+
children: [
|
|
817
|
+
i === index ? " ▎ " : " ",
|
|
818
|
+
/* @__PURE__ */ jsx(Text, {
|
|
819
|
+
bold: i === index,
|
|
820
|
+
children: setting.padEnd(18)
|
|
821
|
+
}),
|
|
822
|
+
color && /* @__PURE__ */ jsxs(Text, { children: [
|
|
823
|
+
/* @__PURE__ */ jsx(Text, {
|
|
824
|
+
color: swatch.success,
|
|
825
|
+
children: "● "
|
|
826
|
+
}),
|
|
827
|
+
/* @__PURE__ */ jsx(Text, {
|
|
828
|
+
color: swatch.warning,
|
|
829
|
+
children: "● "
|
|
830
|
+
}),
|
|
831
|
+
/* @__PURE__ */ jsx(Text, {
|
|
832
|
+
color: swatch.error,
|
|
833
|
+
children: "● "
|
|
834
|
+
}),
|
|
835
|
+
/* @__PURE__ */ jsx(Text, {
|
|
836
|
+
color: swatch.url,
|
|
837
|
+
children: "● "
|
|
838
|
+
})
|
|
839
|
+
] }),
|
|
840
|
+
/* @__PURE__ */ jsx(Text, {
|
|
841
|
+
dimColor: true,
|
|
842
|
+
children: ` ${DESCRIPTIONS[setting]}${setting === current ? " (current)" : ""}`
|
|
843
|
+
})
|
|
844
|
+
]
|
|
845
|
+
}, setting);
|
|
846
|
+
}),
|
|
847
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
|
|
848
|
+
/* @__PURE__ */ jsx(Text, {
|
|
849
|
+
dimColor: true,
|
|
850
|
+
children: " ↑/↓ preview · enter save · esc cancel"
|
|
851
|
+
})
|
|
852
|
+
]
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
757
856
|
//#endregion
|
|
758
857
|
//#region src/renderers/ink/DevApp.tsx
|
|
759
858
|
/**
|
|
760
859
|
* A bottom-aligned panel in the normal buffer, with logs folded away. Only the browsable overlays use the
|
|
761
860
|
* alternate screen, preserving the terminal's history and the panel when they close.
|
|
762
861
|
*/
|
|
763
|
-
function DevApp({ service, color, reducedMotion, onQuit, onViewChange, onCopy }) {
|
|
862
|
+
function DevApp({ service, color, theme, reducedMotion, onQuit, onViewChange, onCopy, onThemeSave }) {
|
|
764
863
|
const { columns, rows } = useWindowSize();
|
|
765
864
|
const [view, setView] = useState("panel");
|
|
766
865
|
const [confirmQuit, setConfirmQuit] = useState(false);
|
|
866
|
+
const [saved, setSaved] = useState(theme);
|
|
867
|
+
const [preview, setPreview] = useState(theme);
|
|
767
868
|
const status = useDevStatus(service);
|
|
768
869
|
const counters = useLogCounters(service);
|
|
769
870
|
const badge = describeBadge(status).badge;
|
|
@@ -800,12 +901,14 @@ function DevApp({ service, color, reducedMotion, onQuit, onViewChange, onCopy })
|
|
|
800
901
|
service.toggleTunnel();
|
|
801
902
|
return;
|
|
802
903
|
case "i": return changeView("info");
|
|
904
|
+
case "T": return changeView("theme");
|
|
803
905
|
case "h":
|
|
804
906
|
case "?": return changeView("help");
|
|
805
907
|
}
|
|
806
908
|
});
|
|
807
|
-
return /* @__PURE__ */ jsx(
|
|
909
|
+
return /* @__PURE__ */ jsx(ThemeProvider, {
|
|
808
910
|
color,
|
|
911
|
+
theme: resolveTheme(preview),
|
|
809
912
|
children: /* @__PURE__ */ jsxs(Box, {
|
|
810
913
|
width,
|
|
811
914
|
height,
|
|
@@ -831,6 +934,22 @@ function DevApp({ service, color, reducedMotion, onQuit, onViewChange, onCopy })
|
|
|
831
934
|
width,
|
|
832
935
|
onClose: () => changeView("panel")
|
|
833
936
|
}),
|
|
937
|
+
view === "theme" && /* @__PURE__ */ jsx(ThemeOverlay, {
|
|
938
|
+
current: saved,
|
|
939
|
+
height,
|
|
940
|
+
width,
|
|
941
|
+
onPreview: setPreview,
|
|
942
|
+
onSelect: (setting) => {
|
|
943
|
+
setSaved(setting);
|
|
944
|
+
setPreview(setting);
|
|
945
|
+
onThemeSave(setting);
|
|
946
|
+
changeView("panel");
|
|
947
|
+
},
|
|
948
|
+
onClose: () => {
|
|
949
|
+
setPreview(saved);
|
|
950
|
+
changeView("panel");
|
|
951
|
+
}
|
|
952
|
+
}),
|
|
834
953
|
view === "panel" && /* @__PURE__ */ jsx(Panel, {
|
|
835
954
|
status,
|
|
836
955
|
config: service.config,
|
|
@@ -909,6 +1028,8 @@ function createTuiRenderer(service, options = {}) {
|
|
|
909
1028
|
const instance = render(/* @__PURE__ */ jsx(DevApp, {
|
|
910
1029
|
service,
|
|
911
1030
|
color: options.color ?? false,
|
|
1031
|
+
theme: options.theme ?? "auto",
|
|
1032
|
+
onThemeSave: options.onThemeSave ?? (() => {}),
|
|
912
1033
|
reducedMotion: options.reducedMotion ?? false,
|
|
913
1034
|
onQuit: quit,
|
|
914
1035
|
onViewChange: switchView,
|
|
@@ -944,4 +1065,4 @@ function createTuiRenderer(service, options = {}) {
|
|
|
944
1065
|
|
|
945
1066
|
//#endregion
|
|
946
1067
|
export { createTuiRenderer };
|
|
947
|
-
//# sourceMappingURL=tui-
|
|
1068
|
+
//# sourceMappingURL=tui-O94OH1w8.js.map
|