kaizenai 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-BBs80KD-.js +623 -0
- package/dist/client/assets/index-CkCgyLNq.css +32 -0
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/agent.ts +58 -19
- package/src/server/cli-runtime.ts +210 -168
- package/src/server/cli-supervisor.ts +58 -42
- package/src/server/event-store.ts +119 -18
- package/src/server/events.ts +8 -0
- package/src/server/restart.ts +44 -18
- package/src/server/ws-router.ts +35 -10
- package/src/shared/protocol.ts +5 -1
- package/src/shared/types.ts +12 -0
- package/dist/client/assets/index-BI5hJ6Xa.js +0 -598
- package/dist/client/assets/index-BJ10I6WX.css +0 -32
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import process from
|
|
2
|
-
import { spawnSync } from
|
|
3
|
-
import { hasCommand, spawnDetached } from
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { hasCommand, spawnDetached } from './process-utils';
|
|
4
4
|
import {
|
|
5
5
|
APP_NAME,
|
|
6
6
|
CLI_COMMAND,
|
|
@@ -8,71 +8,83 @@ import {
|
|
|
8
8
|
getDataDirDisplay,
|
|
9
9
|
LOG_PREFIX,
|
|
10
10
|
PACKAGE_NAME,
|
|
11
|
-
} from
|
|
12
|
-
import type { UpdateInstallErrorCode } from
|
|
13
|
-
import { PROD_SERVER_PORT } from
|
|
14
|
-
import {
|
|
11
|
+
} from '../shared/branding';
|
|
12
|
+
import type { UpdateInstallErrorCode } from '../shared/types';
|
|
13
|
+
import { PROD_SERVER_PORT } from '../shared/ports';
|
|
14
|
+
import {
|
|
15
|
+
CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR,
|
|
16
|
+
CLI_SUPPRESS_OPEN_ONCE_ENV_VAR,
|
|
17
|
+
shouldSkipStartupUpdateOnce,
|
|
18
|
+
} from './restart';
|
|
15
19
|
|
|
16
20
|
export interface CliOptions {
|
|
17
|
-
port: number
|
|
18
|
-
host: string
|
|
19
|
-
openBrowser: boolean
|
|
20
|
-
strictPort: boolean
|
|
21
|
+
port: number;
|
|
22
|
+
host: string;
|
|
23
|
+
openBrowser: boolean;
|
|
24
|
+
strictPort: boolean;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export interface CliUpdateOptions {
|
|
24
|
-
version: string
|
|
25
|
-
fetchLatestVersion: (packageName: string) => Promise<string
|
|
26
|
-
installVersion: (
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
version: string;
|
|
29
|
+
fetchLatestVersion: (packageName: string) => Promise<string>;
|
|
30
|
+
installVersion: (
|
|
31
|
+
packageName: string,
|
|
32
|
+
version: string
|
|
33
|
+
) => UpdateInstallAttemptResult;
|
|
34
|
+
argv: string[];
|
|
35
|
+
command: string;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
export interface StartedCli {
|
|
32
|
-
kind:
|
|
33
|
-
stop: () => Promise<void
|
|
39
|
+
kind: 'started';
|
|
40
|
+
stop: () => Promise<void>;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export interface RestartingCli {
|
|
37
|
-
kind:
|
|
38
|
-
reason:
|
|
44
|
+
kind: 'restarting';
|
|
45
|
+
reason: 'startup_update' | 'ui_update';
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export interface ExitedCli {
|
|
42
|
-
kind:
|
|
43
|
-
code: number
|
|
49
|
+
kind: 'exited';
|
|
50
|
+
code: number;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
export type CliRunResult = StartedCli | RestartingCli | ExitedCli
|
|
53
|
+
export type CliRunResult = StartedCli | RestartingCli | ExitedCli;
|
|
47
54
|
|
|
48
55
|
export interface CliRuntimeDeps {
|
|
49
|
-
version: string
|
|
50
|
-
bunVersion: string
|
|
51
|
-
allowSelfUpdate?: boolean
|
|
52
|
-
startServer: (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
version: string;
|
|
57
|
+
bunVersion: string;
|
|
58
|
+
allowSelfUpdate?: boolean;
|
|
59
|
+
startServer: (
|
|
60
|
+
options: CliOptions & {
|
|
61
|
+
update: CliUpdateOptions;
|
|
62
|
+
onMigrationProgress?: (message: string) => void;
|
|
63
|
+
}
|
|
64
|
+
) => Promise<{ port: number; stop: () => Promise<void> }>;
|
|
65
|
+
fetchLatestVersion: (packageName: string) => Promise<string>;
|
|
66
|
+
installVersion: (
|
|
67
|
+
packageName: string,
|
|
68
|
+
version: string
|
|
69
|
+
) => UpdateInstallAttemptResult;
|
|
70
|
+
openUrl: (url: string) => void;
|
|
71
|
+
log: (message: string) => void;
|
|
72
|
+
warn: (message: string) => void;
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
export interface UpdateInstallAttemptResult {
|
|
64
|
-
ok: boolean
|
|
65
|
-
errorCode: UpdateInstallErrorCode | null
|
|
66
|
-
userTitle: string | null
|
|
67
|
-
userMessage: string | null
|
|
76
|
+
ok: boolean;
|
|
77
|
+
errorCode: UpdateInstallErrorCode | null;
|
|
78
|
+
userTitle: string | null;
|
|
79
|
+
userMessage: string | null;
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
type ParsedArgs =
|
|
71
|
-
| { kind:
|
|
72
|
-
| { kind:
|
|
73
|
-
| { kind:
|
|
83
|
+
| { kind: 'run'; options: CliOptions }
|
|
84
|
+
| { kind: 'help' }
|
|
85
|
+
| { kind: 'version' };
|
|
74
86
|
|
|
75
|
-
const MINIMUM_BUN_VERSION =
|
|
87
|
+
const MINIMUM_BUN_VERSION = '1.3.5';
|
|
76
88
|
|
|
77
89
|
function printHelp() {
|
|
78
90
|
console.log(`${APP_NAME} — local-only project chat UI
|
|
@@ -87,144 +99,161 @@ Options:
|
|
|
87
99
|
--strict-port Fail instead of trying another port
|
|
88
100
|
--no-open Don't open browser automatically
|
|
89
101
|
--version Print version and exit
|
|
90
|
-
--help Show this help message`)
|
|
102
|
+
--help Show this help message`);
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
export function parseArgs(argv: string[]): ParsedArgs {
|
|
94
|
-
let port = PROD_SERVER_PORT
|
|
95
|
-
let host =
|
|
96
|
-
let openBrowser = true
|
|
97
|
-
let strictPort = false
|
|
106
|
+
let port = PROD_SERVER_PORT;
|
|
107
|
+
let host = '127.0.0.1';
|
|
108
|
+
let openBrowser = true;
|
|
109
|
+
let strictPort = false;
|
|
98
110
|
|
|
99
111
|
for (let index = 0; index < argv.length; index += 1) {
|
|
100
|
-
const arg = argv[index]
|
|
101
|
-
if (arg ===
|
|
102
|
-
return { kind:
|
|
112
|
+
const arg = argv[index];
|
|
113
|
+
if (arg === '--version' || arg === '-v') {
|
|
114
|
+
return { kind: 'version' };
|
|
103
115
|
}
|
|
104
|
-
if (arg ===
|
|
105
|
-
return { kind:
|
|
116
|
+
if (arg === '--help' || arg === '-h') {
|
|
117
|
+
return { kind: 'help' };
|
|
106
118
|
}
|
|
107
|
-
if (arg ===
|
|
108
|
-
const next = argv[index + 1]
|
|
109
|
-
if (!next) throw new Error(
|
|
110
|
-
port = Number(next)
|
|
111
|
-
index += 1
|
|
112
|
-
continue
|
|
119
|
+
if (arg === '--port') {
|
|
120
|
+
const next = argv[index + 1];
|
|
121
|
+
if (!next) throw new Error('Missing value for --port');
|
|
122
|
+
port = Number(next);
|
|
123
|
+
index += 1;
|
|
124
|
+
continue;
|
|
113
125
|
}
|
|
114
|
-
if (arg ===
|
|
115
|
-
const next = argv[index + 1]
|
|
116
|
-
if (!next || next.startsWith(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
if (arg === '--host') {
|
|
127
|
+
const next = argv[index + 1];
|
|
128
|
+
if (!next || next.startsWith('-'))
|
|
129
|
+
throw new Error('Missing value for --host');
|
|
130
|
+
host = next;
|
|
131
|
+
index += 1;
|
|
132
|
+
continue;
|
|
120
133
|
}
|
|
121
|
-
if (arg ===
|
|
122
|
-
host =
|
|
123
|
-
continue
|
|
134
|
+
if (arg === '--remote') {
|
|
135
|
+
host = '0.0.0.0';
|
|
136
|
+
continue;
|
|
124
137
|
}
|
|
125
|
-
if (arg ===
|
|
126
|
-
openBrowser = false
|
|
127
|
-
continue
|
|
138
|
+
if (arg === '--no-open') {
|
|
139
|
+
openBrowser = false;
|
|
140
|
+
continue;
|
|
128
141
|
}
|
|
129
|
-
if (arg ===
|
|
130
|
-
strictPort = true
|
|
131
|
-
continue
|
|
142
|
+
if (arg === '--strict-port') {
|
|
143
|
+
strictPort = true;
|
|
144
|
+
continue;
|
|
132
145
|
}
|
|
133
|
-
if (!arg.startsWith(
|
|
146
|
+
if (!arg.startsWith('-'))
|
|
147
|
+
throw new Error(`Unexpected positional argument: ${arg}`);
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
return {
|
|
137
|
-
kind:
|
|
151
|
+
kind: 'run',
|
|
138
152
|
options: {
|
|
139
153
|
port,
|
|
140
154
|
host,
|
|
141
155
|
openBrowser,
|
|
142
156
|
strictPort,
|
|
143
157
|
},
|
|
144
|
-
}
|
|
158
|
+
};
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
export function compareVersions(currentVersion: string, latestVersion: string) {
|
|
148
|
-
const currentParts = normalizeVersion(currentVersion)
|
|
149
|
-
const latestParts = normalizeVersion(latestVersion)
|
|
150
|
-
const length = Math.max(currentParts.length, latestParts.length)
|
|
162
|
+
const currentParts = normalizeVersion(currentVersion);
|
|
163
|
+
const latestParts = normalizeVersion(latestVersion);
|
|
164
|
+
const length = Math.max(currentParts.length, latestParts.length);
|
|
151
165
|
|
|
152
166
|
for (let index = 0; index < length; index += 1) {
|
|
153
|
-
const current = currentParts[index] ?? 0
|
|
154
|
-
const latest = latestParts[index] ?? 0
|
|
155
|
-
if (current === latest) continue
|
|
156
|
-
return current < latest ? -1 : 1
|
|
167
|
+
const current = currentParts[index] ?? 0;
|
|
168
|
+
const latest = latestParts[index] ?? 0;
|
|
169
|
+
if (current === latest) continue;
|
|
170
|
+
return current < latest ? -1 : 1;
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
return 0
|
|
173
|
+
return 0;
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
function normalizeVersion(version: string) {
|
|
163
177
|
return version
|
|
164
178
|
.trim()
|
|
165
|
-
.replace(/^v/i,
|
|
166
|
-
.split(
|
|
167
|
-
.split(
|
|
179
|
+
.replace(/^v/i, '')
|
|
180
|
+
.split('-')[0]
|
|
181
|
+
.split('.')
|
|
168
182
|
.map((part) => Number.parseInt(part, 10))
|
|
169
|
-
.filter((part) => Number.isFinite(part))
|
|
183
|
+
.filter((part) => Number.isFinite(part));
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
async function maybeSelfUpdate(_argv: string[], deps: CliRuntimeDeps) {
|
|
173
|
-
if (process.env[DISABLE_SELF_UPDATE_ENV_VAR] ===
|
|
174
|
-
return null
|
|
187
|
+
if (process.env[DISABLE_SELF_UPDATE_ENV_VAR] === '1') {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
shouldSkipStartupUpdateOnce(
|
|
193
|
+
process.env[CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR]
|
|
194
|
+
)
|
|
195
|
+
) {
|
|
196
|
+
deps.warn(
|
|
197
|
+
`${LOG_PREFIX} skipping startup update after restart to avoid an update loop`
|
|
198
|
+
);
|
|
199
|
+
return null;
|
|
175
200
|
}
|
|
176
201
|
|
|
177
|
-
deps.log(`${LOG_PREFIX} checking for updates`)
|
|
202
|
+
deps.log(`${LOG_PREFIX} checking for updates`);
|
|
178
203
|
|
|
179
|
-
let latestVersion: string
|
|
204
|
+
let latestVersion: string;
|
|
180
205
|
try {
|
|
181
|
-
latestVersion = await deps.fetchLatestVersion(PACKAGE_NAME)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
deps.warn(`${LOG_PREFIX} update check failed, continuing current version`)
|
|
206
|
+
latestVersion = await deps.fetchLatestVersion(PACKAGE_NAME);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
deps.warn(`${LOG_PREFIX} update check failed, continuing current version`);
|
|
185
209
|
if (error instanceof Error && error.message) {
|
|
186
|
-
deps.warn(`${LOG_PREFIX} ${error.message}`)
|
|
210
|
+
deps.warn(`${LOG_PREFIX} ${error.message}`);
|
|
187
211
|
}
|
|
188
|
-
return null
|
|
212
|
+
return null;
|
|
189
213
|
}
|
|
190
214
|
|
|
191
215
|
if (!latestVersion || compareVersions(deps.version, latestVersion) >= 0) {
|
|
192
|
-
return null
|
|
216
|
+
return null;
|
|
193
217
|
}
|
|
194
218
|
|
|
195
|
-
deps.log(`${LOG_PREFIX} installing ${PACKAGE_NAME}@${latestVersion}`)
|
|
196
|
-
const installResult = deps.installVersion(PACKAGE_NAME, latestVersion)
|
|
219
|
+
deps.log(`${LOG_PREFIX} installing ${PACKAGE_NAME}@${latestVersion}`);
|
|
220
|
+
const installResult = deps.installVersion(PACKAGE_NAME, latestVersion);
|
|
197
221
|
if (!installResult.ok) {
|
|
198
|
-
deps.warn(`${LOG_PREFIX} update failed, continuing current version`)
|
|
222
|
+
deps.warn(`${LOG_PREFIX} update failed, continuing current version`);
|
|
199
223
|
if (installResult.userMessage) {
|
|
200
|
-
deps.warn(`${LOG_PREFIX} ${installResult.userMessage}`)
|
|
224
|
+
deps.warn(`${LOG_PREFIX} ${installResult.userMessage}`);
|
|
201
225
|
}
|
|
202
|
-
return null
|
|
226
|
+
return null;
|
|
203
227
|
}
|
|
204
228
|
|
|
205
|
-
deps.log(`${LOG_PREFIX} restarting into updated version`)
|
|
206
|
-
return
|
|
229
|
+
deps.log(`${LOG_PREFIX} restarting into updated version`);
|
|
230
|
+
return 'startup_update';
|
|
207
231
|
}
|
|
208
232
|
|
|
209
|
-
export async function runCli(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
233
|
+
export async function runCli(
|
|
234
|
+
argv: string[],
|
|
235
|
+
deps: CliRuntimeDeps
|
|
236
|
+
): Promise<CliRunResult> {
|
|
237
|
+
const parsedArgs = parseArgs(argv);
|
|
238
|
+
if (parsedArgs.kind === 'version') {
|
|
239
|
+
deps.log(deps.version);
|
|
240
|
+
return { kind: 'exited', code: 0 };
|
|
214
241
|
}
|
|
215
|
-
if (parsedArgs.kind ===
|
|
216
|
-
printHelp()
|
|
217
|
-
return { kind:
|
|
242
|
+
if (parsedArgs.kind === 'help') {
|
|
243
|
+
printHelp();
|
|
244
|
+
return { kind: 'exited', code: 0 };
|
|
218
245
|
}
|
|
219
246
|
if (compareVersions(deps.bunVersion, MINIMUM_BUN_VERSION) < 0) {
|
|
220
|
-
deps.warn(
|
|
221
|
-
|
|
247
|
+
deps.warn(
|
|
248
|
+
`${LOG_PREFIX} Bun ${MINIMUM_BUN_VERSION}+ is required for the embedded terminal. Current Bun: ${deps.bunVersion}`
|
|
249
|
+
);
|
|
250
|
+
return { kind: 'exited', code: 1 };
|
|
222
251
|
}
|
|
223
252
|
|
|
224
253
|
if (deps.allowSelfUpdate !== false) {
|
|
225
|
-
const shouldRestart = await maybeSelfUpdate(argv, deps)
|
|
254
|
+
const shouldRestart = await maybeSelfUpdate(argv, deps);
|
|
226
255
|
if (shouldRestart !== null) {
|
|
227
|
-
return { kind:
|
|
256
|
+
return { kind: 'restarting', reason: shouldRestart };
|
|
228
257
|
}
|
|
229
258
|
}
|
|
230
259
|
|
|
@@ -238,96 +267,109 @@ export async function runCli(argv: string[], deps: CliRuntimeDeps): Promise<CliR
|
|
|
238
267
|
argv,
|
|
239
268
|
command: CLI_COMMAND,
|
|
240
269
|
},
|
|
241
|
-
})
|
|
242
|
-
const bindHost = parsedArgs.options.host
|
|
243
|
-
const displayHost =
|
|
244
|
-
|
|
270
|
+
});
|
|
271
|
+
const bindHost = parsedArgs.options.host;
|
|
272
|
+
const displayHost =
|
|
273
|
+
bindHost === '127.0.0.1' || bindHost === '0.0.0.0' ? 'localhost' : bindHost;
|
|
274
|
+
const launchUrl = `http://${displayHost}:${port}`;
|
|
245
275
|
|
|
246
|
-
deps.log(`${LOG_PREFIX} listening on http://${bindHost}:${port}`)
|
|
247
|
-
deps.log(`${LOG_PREFIX} data dir: ${getDataDirDisplay()}`)
|
|
276
|
+
deps.log(`${LOG_PREFIX} listening on http://${bindHost}:${port}`);
|
|
277
|
+
deps.log(`${LOG_PREFIX} data dir: ${getDataDirDisplay()}`);
|
|
248
278
|
|
|
249
|
-
const suppressOpenBrowser =
|
|
279
|
+
const suppressOpenBrowser =
|
|
280
|
+
process.env[CLI_SUPPRESS_OPEN_ONCE_ENV_VAR] === '1';
|
|
250
281
|
if (parsedArgs.options.openBrowser && !suppressOpenBrowser) {
|
|
251
|
-
deps.openUrl(launchUrl)
|
|
282
|
+
deps.openUrl(launchUrl);
|
|
252
283
|
}
|
|
253
284
|
|
|
254
285
|
return {
|
|
255
|
-
kind:
|
|
286
|
+
kind: 'started',
|
|
256
287
|
stop,
|
|
257
|
-
}
|
|
288
|
+
};
|
|
258
289
|
}
|
|
259
290
|
|
|
260
291
|
export function openUrl(url: string) {
|
|
261
|
-
const platform = process.platform
|
|
262
|
-
if (platform ===
|
|
263
|
-
spawnDetached(
|
|
264
|
-
} else if (platform ===
|
|
265
|
-
spawnDetached(
|
|
292
|
+
const platform = process.platform;
|
|
293
|
+
if (platform === 'darwin') {
|
|
294
|
+
spawnDetached('open', [url]);
|
|
295
|
+
} else if (platform === 'win32') {
|
|
296
|
+
spawnDetached('cmd', ['/c', 'start', '', url]);
|
|
266
297
|
} else {
|
|
267
|
-
spawnDetached(
|
|
298
|
+
spawnDetached('xdg-open', [url]);
|
|
268
299
|
}
|
|
269
|
-
console.log(`${LOG_PREFIX} opened in default browser`)
|
|
300
|
+
console.log(`${LOG_PREFIX} opened in default browser`);
|
|
270
301
|
}
|
|
271
302
|
|
|
272
303
|
export async function fetchLatestPackageVersion(packageName: string) {
|
|
273
|
-
const response = await fetch(
|
|
304
|
+
const response = await fetch(
|
|
305
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`
|
|
306
|
+
);
|
|
274
307
|
if (!response.ok) {
|
|
275
|
-
throw new Error(`registry returned ${response.status}`)
|
|
308
|
+
throw new Error(`registry returned ${response.status}`);
|
|
276
309
|
}
|
|
277
310
|
|
|
278
|
-
const payload = await response.json() as { version?: unknown }
|
|
279
|
-
if (typeof payload.version !==
|
|
280
|
-
throw new Error(
|
|
311
|
+
const payload = (await response.json()) as { version?: unknown };
|
|
312
|
+
if (typeof payload.version !== 'string' || !payload.version.trim()) {
|
|
313
|
+
throw new Error('registry response did not include a version');
|
|
281
314
|
}
|
|
282
315
|
|
|
283
|
-
return payload.version
|
|
316
|
+
return payload.version;
|
|
284
317
|
}
|
|
285
318
|
|
|
286
|
-
export function classifyInstallVersionFailure(
|
|
287
|
-
|
|
288
|
-
|
|
319
|
+
export function classifyInstallVersionFailure(
|
|
320
|
+
output: string
|
|
321
|
+
): UpdateInstallAttemptResult {
|
|
322
|
+
const normalizedOutput = output.trim();
|
|
323
|
+
if (
|
|
324
|
+
/No version matching .* found|failed to resolve/i.test(normalizedOutput)
|
|
325
|
+
) {
|
|
289
326
|
return {
|
|
290
327
|
ok: false,
|
|
291
|
-
errorCode:
|
|
292
|
-
userTitle:
|
|
293
|
-
userMessage:
|
|
294
|
-
|
|
328
|
+
errorCode: 'version_not_live_yet',
|
|
329
|
+
userTitle: 'Update not live yet',
|
|
330
|
+
userMessage:
|
|
331
|
+
'This update is still propagating. Try again in a few minutes.',
|
|
332
|
+
};
|
|
295
333
|
}
|
|
296
334
|
|
|
297
335
|
return {
|
|
298
336
|
ok: false,
|
|
299
|
-
errorCode:
|
|
300
|
-
userTitle:
|
|
301
|
-
userMessage:
|
|
302
|
-
}
|
|
337
|
+
errorCode: 'install_failed',
|
|
338
|
+
userTitle: 'Update failed',
|
|
339
|
+
userMessage: 'Kaizen could not install the update. Try again later.',
|
|
340
|
+
};
|
|
303
341
|
}
|
|
304
342
|
|
|
305
343
|
export function installPackageVersion(packageName: string, version: string) {
|
|
306
|
-
if (!hasCommand(
|
|
344
|
+
if (!hasCommand('bun')) {
|
|
307
345
|
return {
|
|
308
346
|
ok: false,
|
|
309
|
-
errorCode:
|
|
310
|
-
userTitle:
|
|
311
|
-
userMessage:
|
|
312
|
-
} satisfies UpdateInstallAttemptResult
|
|
347
|
+
errorCode: 'command_missing',
|
|
348
|
+
userTitle: 'Bun not found',
|
|
349
|
+
userMessage: 'Kaizen could not find Bun to install the update.',
|
|
350
|
+
} satisfies UpdateInstallAttemptResult;
|
|
313
351
|
}
|
|
314
352
|
|
|
315
|
-
const result = spawnSync(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
353
|
+
const result = spawnSync(
|
|
354
|
+
'bun',
|
|
355
|
+
['install', '-g', `${packageName}@${version}`],
|
|
356
|
+
{
|
|
357
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
358
|
+
encoding: 'utf8',
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
const stdout = result.stdout ?? '';
|
|
362
|
+
const stderr = result.stderr ?? '';
|
|
363
|
+
if (stdout) process.stdout.write(stdout);
|
|
364
|
+
if (stderr) process.stderr.write(stderr);
|
|
323
365
|
if (result.status === 0) {
|
|
324
366
|
return {
|
|
325
367
|
ok: true,
|
|
326
368
|
errorCode: null,
|
|
327
369
|
userTitle: null,
|
|
328
370
|
userMessage: null,
|
|
329
|
-
} satisfies UpdateInstallAttemptResult
|
|
371
|
+
} satisfies UpdateInstallAttemptResult;
|
|
330
372
|
}
|
|
331
373
|
|
|
332
|
-
return classifyInstallVersionFailure(`${stdout}\n${stderr}`)
|
|
374
|
+
return classifyInstallVersionFailure(`${stdout}\n${stderr}`);
|
|
333
375
|
}
|
|
@@ -1,81 +1,97 @@
|
|
|
1
|
-
import process from
|
|
2
|
-
import { spawn } from
|
|
3
|
-
import { CLI_COMMAND, LOG_PREFIX } from
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { CLI_COMMAND, LOG_PREFIX } from '../shared/branding';
|
|
4
4
|
import {
|
|
5
5
|
CLI_CHILD_ARGS_ENV_VAR,
|
|
6
6
|
CLI_CHILD_COMMAND_ENV_VAR,
|
|
7
7
|
CLI_CHILD_MODE,
|
|
8
8
|
CLI_CHILD_MODE_ENV_VAR,
|
|
9
|
+
CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR,
|
|
9
10
|
CLI_SUPPRESS_OPEN_ONCE_ENV_VAR,
|
|
11
|
+
isStartupUpdateRestart,
|
|
10
12
|
isUiUpdateRestart,
|
|
11
13
|
parseChildArgsEnv,
|
|
12
14
|
shouldRestartCliProcess,
|
|
13
|
-
} from
|
|
15
|
+
} from './restart';
|
|
14
16
|
|
|
15
17
|
interface ChildExit {
|
|
16
|
-
code: number | null
|
|
17
|
-
signal: NodeJS.Signals | null
|
|
18
|
+
code: number | null;
|
|
19
|
+
signal: NodeJS.Signals | null;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function getChildProcessSpec() {
|
|
21
|
-
const command = process.env[CLI_CHILD_COMMAND_ENV_VAR] || CLI_COMMAND
|
|
22
|
-
const args = parseChildArgsEnv(process.env[CLI_CHILD_ARGS_ENV_VAR])
|
|
23
|
-
return { command, args }
|
|
23
|
+
const command = process.env[CLI_CHILD_COMMAND_ENV_VAR] || CLI_COMMAND;
|
|
24
|
+
const args = parseChildArgsEnv(process.env[CLI_CHILD_ARGS_ENV_VAR]);
|
|
25
|
+
return { command, args };
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
function spawnChild(argv: string[]) {
|
|
27
|
-
const childProcess = getChildProcessSpec()
|
|
28
|
-
const suppressOpenThisChild = suppressOpenOnNextChild
|
|
29
|
-
|
|
29
|
+
const childProcess = getChildProcessSpec();
|
|
30
|
+
const suppressOpenThisChild = suppressOpenOnNextChild;
|
|
31
|
+
const skipStartupUpdateThisChild = skipStartupUpdateOnNextChild;
|
|
32
|
+
suppressOpenOnNextChild = false;
|
|
33
|
+
skipStartupUpdateOnNextChild = false;
|
|
30
34
|
return new Promise<ChildExit>((resolve, reject) => {
|
|
31
35
|
const child = spawn(childProcess.command, [...childProcess.args, ...argv], {
|
|
32
|
-
stdio:
|
|
36
|
+
stdio: 'inherit',
|
|
33
37
|
env: {
|
|
34
38
|
...process.env,
|
|
35
39
|
[CLI_CHILD_MODE_ENV_VAR]: CLI_CHILD_MODE,
|
|
36
|
-
...(suppressOpenThisChild
|
|
40
|
+
...(suppressOpenThisChild
|
|
41
|
+
? { [CLI_SUPPRESS_OPEN_ONCE_ENV_VAR]: '1' }
|
|
42
|
+
: {}),
|
|
43
|
+
...(skipStartupUpdateThisChild
|
|
44
|
+
? { [CLI_SKIP_STARTUP_UPDATE_ONCE_ENV_VAR]: '1' }
|
|
45
|
+
: {}),
|
|
37
46
|
},
|
|
38
|
-
})
|
|
47
|
+
});
|
|
39
48
|
|
|
40
49
|
const forwardSignal = (signal: NodeJS.Signals) => {
|
|
41
|
-
if (child.exitCode !== null) return
|
|
42
|
-
child.kill(signal)
|
|
43
|
-
}
|
|
50
|
+
if (child.exitCode !== null) return;
|
|
51
|
+
child.kill(signal);
|
|
52
|
+
};
|
|
44
53
|
|
|
45
54
|
const onSigint = () => {
|
|
46
|
-
forwardSignal(
|
|
47
|
-
}
|
|
55
|
+
forwardSignal('SIGINT');
|
|
56
|
+
};
|
|
48
57
|
const onSigterm = () => {
|
|
49
|
-
forwardSignal(
|
|
50
|
-
}
|
|
58
|
+
forwardSignal('SIGTERM');
|
|
59
|
+
};
|
|
51
60
|
|
|
52
|
-
process.on(
|
|
53
|
-
process.on(
|
|
61
|
+
process.on('SIGINT', onSigint);
|
|
62
|
+
process.on('SIGTERM', onSigterm);
|
|
54
63
|
|
|
55
|
-
child.once(
|
|
56
|
-
process.off(
|
|
57
|
-
process.off(
|
|
58
|
-
reject(error)
|
|
59
|
-
})
|
|
64
|
+
child.once('error', (error) => {
|
|
65
|
+
process.off('SIGINT', onSigint);
|
|
66
|
+
process.off('SIGTERM', onSigterm);
|
|
67
|
+
reject(error);
|
|
68
|
+
});
|
|
60
69
|
|
|
61
|
-
child.once(
|
|
62
|
-
process.off(
|
|
63
|
-
process.off(
|
|
64
|
-
resolve({ code, signal })
|
|
65
|
-
})
|
|
66
|
-
})
|
|
70
|
+
child.once('exit', (code, signal) => {
|
|
71
|
+
process.off('SIGINT', onSigint);
|
|
72
|
+
process.off('SIGTERM', onSigterm);
|
|
73
|
+
resolve({ code, signal });
|
|
74
|
+
});
|
|
75
|
+
});
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
const argv = process.argv.slice(2)
|
|
70
|
-
let suppressOpenOnNextChild = false
|
|
78
|
+
const argv = process.argv.slice(2);
|
|
79
|
+
let suppressOpenOnNextChild = false;
|
|
80
|
+
let skipStartupUpdateOnNextChild = false;
|
|
71
81
|
|
|
72
82
|
while (true) {
|
|
73
|
-
const result = await spawnChild(argv)
|
|
83
|
+
const result = await spawnChild(argv);
|
|
74
84
|
if (shouldRestartCliProcess(result.code, result.signal)) {
|
|
75
|
-
suppressOpenOnNextChild = isUiUpdateRestart(result.code, result.signal)
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
suppressOpenOnNextChild = isUiUpdateRestart(result.code, result.signal);
|
|
86
|
+
skipStartupUpdateOnNextChild = isStartupUpdateRestart(
|
|
87
|
+
result.code,
|
|
88
|
+
result.signal
|
|
89
|
+
);
|
|
90
|
+
console.log(
|
|
91
|
+
`${LOG_PREFIX} supervisor restarting ${CLI_COMMAND} in the same terminal session`
|
|
92
|
+
);
|
|
93
|
+
continue;
|
|
78
94
|
}
|
|
79
95
|
|
|
80
|
-
process.exit(result.code ?? (result.signal ? 1 : 0))
|
|
96
|
+
process.exit(result.code ?? (result.signal ? 1 : 0));
|
|
81
97
|
}
|