@linzumi/cli 1.0.116 → 1.0.117
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 +1 -1
- package/dist/index.js +359 -359
- package/package.json +1 -1
- package/scripts/build-onboarding-demo-evidence.mjs +186 -0
- package/scripts/build-onboarding-discovery-parity-oracle.mjs +380 -0
- package/scripts/build-onboarding-flow-parity-oracle.mjs +562 -0
- package/scripts/build-onboarding-gateway-parity-oracle.mjs +313 -0
- package/scripts/build-onboarding-support-parity-oracle.mjs +484 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 13, onboarding).
|
|
3
|
+
// Relationship: Builds the TS-side PARITY ORACLE for the signup-flow port -
|
|
4
|
+
// the interactive first-run flow. ONE esbuild bundle imports the REAL
|
|
5
|
+
// signup-flow module exports behind the cluster 1/2 stdin/stdout batch
|
|
6
|
+
// driver (a JSON array of cases in, a JSON array of results out). The
|
|
7
|
+
// ReScript package's OnboardingFlowParityConformance drives the SAME
|
|
8
|
+
// inputs through src/onboarding/CommanderOnboarding* and asserts
|
|
9
|
+
// byte-equal terminal transcripts, deep-equal recorded side effects, and
|
|
10
|
+
// byte-equal debug launch payloads.
|
|
11
|
+
//
|
|
12
|
+
// The crown op is `flow_walk`: run the REAL runSignupFlow end-to-end with
|
|
13
|
+
// fully scripted deps (prompt answer queues, scripted preflight tools,
|
|
14
|
+
// scripted discovery/task/server/launcher seams, an in-memory draft
|
|
15
|
+
// store, a fixed-width fake terminal) and return every byte the flow
|
|
16
|
+
// wrote plus every recorded side effect. `flow_walk_tty` goes one layer
|
|
17
|
+
// deeper: NO scripted prompts - the flow renders through the REAL
|
|
18
|
+
// @inquirer/prompts + the raw-keypress project picker over scripted
|
|
19
|
+
// PassThrough TTYs, driven by a seeded key schedule (the cluster 11
|
|
20
|
+
// interactive-parity pattern). `picker_walk` drives the raw project
|
|
21
|
+
// picker alone through a key schedule.
|
|
22
|
+
//
|
|
23
|
+
// Determinism: clocks never reach rendered output (the draft store
|
|
24
|
+
// normalizes updatedAt; auth writes use pinned timestamps); scripted
|
|
25
|
+
// scenarios use absolute scratch paths so the fs/git probes the flow runs
|
|
26
|
+
// inline (project resolution, git emails) behave identically for both
|
|
27
|
+
// impls over the same trees; each case runs in a fresh module-cache
|
|
28
|
+
// process, and walk cases clear the git-head cache via the exported hook.
|
|
29
|
+
//
|
|
30
|
+
// SECRETS: scripted tokens are synthetic; the launch payload op exists
|
|
31
|
+
// precisely to pin the [redacted] behavior byte-for-byte.
|
|
32
|
+
//
|
|
33
|
+
// The output is a local test artifact (.differential/ is gitignored, never
|
|
34
|
+
// published) and stays unminified for debuggability.
|
|
35
|
+
import { mkdir } from 'node:fs/promises';
|
|
36
|
+
import { dirname, join } from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
import { build } from 'esbuild';
|
|
39
|
+
|
|
40
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
41
|
+
|
|
42
|
+
const driverSource = `
|
|
43
|
+
import { PassThrough } from 'node:stream';
|
|
44
|
+
import {
|
|
45
|
+
runSignupFlow,
|
|
46
|
+
signupHelpText,
|
|
47
|
+
signupPromptCancelMessage,
|
|
48
|
+
parseSignupTuiOptions,
|
|
49
|
+
signupAuthMatchesServiceUrl,
|
|
50
|
+
comparableServiceUrl,
|
|
51
|
+
autocompletePathSuggestions,
|
|
52
|
+
signupProjectPickerFrameForTest,
|
|
53
|
+
projectPickerKeyIsPrintableForTest,
|
|
54
|
+
toggleProjectPickerSelectionForTest,
|
|
55
|
+
signupEmailPickerChoiceNamesForTest,
|
|
56
|
+
signupSuggestedEmailIsUsefulForTest,
|
|
57
|
+
signupSpinnerLinesForTest,
|
|
58
|
+
signupTaskSuggestionWaitMessageForTest,
|
|
59
|
+
signupLaunchPayloadForTest,
|
|
60
|
+
clearSignupGitHeadSummaryCacheForTest,
|
|
61
|
+
signupProjectGitHeadForPathForTest,
|
|
62
|
+
signupDiscoverProjectsForStartForTest,
|
|
63
|
+
signupReadTaskCacheForTest,
|
|
64
|
+
resolveSignupCodexCommandForTest,
|
|
65
|
+
signupCodexTaskSuggestionProcessForTest,
|
|
66
|
+
signupSuggestTasksForProjectForTest,
|
|
67
|
+
runProjectPickerPromptForTest,
|
|
68
|
+
} from './src/signupFlow';
|
|
69
|
+
import { SignupServerError } from './src/signupServerClient';
|
|
70
|
+
|
|
71
|
+
const orNull = (value) => (value === undefined ? null : value);
|
|
72
|
+
|
|
73
|
+
// --- Scripted terminal ------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
function makeFakeWriter(columns) {
|
|
76
|
+
let bytes = '';
|
|
77
|
+
return {
|
|
78
|
+
writer: {
|
|
79
|
+
columns: columns === null ? undefined : columns,
|
|
80
|
+
write: (chunk) => {
|
|
81
|
+
bytes += chunk;
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
transcript: () => bytes,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Scripted prompts ---------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
function makeScriptedPrompts(script, log) {
|
|
92
|
+
let index = 0;
|
|
93
|
+
const next = (kind, detail) => {
|
|
94
|
+
const entry = script[index];
|
|
95
|
+
index += 1;
|
|
96
|
+
if (entry === undefined) {
|
|
97
|
+
throw new Error('prompt script exhausted at ' + kind);
|
|
98
|
+
}
|
|
99
|
+
if (entry.kind !== kind) {
|
|
100
|
+
throw new Error('prompt script expected ' + entry.kind + ' got ' + kind);
|
|
101
|
+
}
|
|
102
|
+
log.push({ kind, detail });
|
|
103
|
+
if (entry.reject !== undefined) {
|
|
104
|
+
throw new Error(entry.reject);
|
|
105
|
+
}
|
|
106
|
+
return entry.value;
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
input: async (args) => next('input', { message: args.message, defaultValue: args.defaultValue }),
|
|
110
|
+
projectPicker: async (args) =>
|
|
111
|
+
next('projectPicker', {
|
|
112
|
+
message: args.message,
|
|
113
|
+
projectPaths: args.projects.map((project) => project.path),
|
|
114
|
+
defaultSelectedPaths: args.defaultSelectedPaths,
|
|
115
|
+
}),
|
|
116
|
+
starterProjectPicker: async (args) =>
|
|
117
|
+
next('starterProjectPicker', {
|
|
118
|
+
message: args.message,
|
|
119
|
+
projectPaths: args.projects.map((project) => project.path),
|
|
120
|
+
defaultValue: args.defaultValue,
|
|
121
|
+
}),
|
|
122
|
+
emailPicker: async (args) =>
|
|
123
|
+
next('emailPicker', {
|
|
124
|
+
suggestedEmails: args.suggestedEmails,
|
|
125
|
+
defaultValue: args.defaultValue,
|
|
126
|
+
}),
|
|
127
|
+
checkbox: async (args) =>
|
|
128
|
+
next('checkbox', {
|
|
129
|
+
message: args.message,
|
|
130
|
+
choices: args.choices,
|
|
131
|
+
required: args.required,
|
|
132
|
+
minSelected: args.minSelected,
|
|
133
|
+
maxSelected: orNull(args.maxSelected),
|
|
134
|
+
}),
|
|
135
|
+
confirm: async (args) => next('confirm', { message: args.message, defaultValue: args.defaultValue }),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Scripted server client -----------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
function scriptedFailure(spec) {
|
|
142
|
+
if (spec.serverCode !== undefined) {
|
|
143
|
+
// The real client's structured-code error carries the code AS the
|
|
144
|
+
// message (signupServerClient's error split).
|
|
145
|
+
return new SignupServerError({
|
|
146
|
+
status: spec.status ?? 422,
|
|
147
|
+
code: spec.serverCode,
|
|
148
|
+
message: spec.serverCode,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return new Error(spec.message ?? 'scripted failure');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function makeScriptedClient(script, log) {
|
|
155
|
+
const take = (leg, detail) => {
|
|
156
|
+
const queue = script[leg] ?? [];
|
|
157
|
+
const entry = queue.shift();
|
|
158
|
+
log.push({ leg, detail });
|
|
159
|
+
if (entry === undefined) {
|
|
160
|
+
throw new Error('server script exhausted at ' + leg);
|
|
161
|
+
}
|
|
162
|
+
if (entry.failure !== undefined) {
|
|
163
|
+
throw scriptedFailure(entry.failure);
|
|
164
|
+
}
|
|
165
|
+
return entry.value;
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
startEmailCode: async (args) => take('startEmailCode', args),
|
|
169
|
+
verifyEmailCode: async (args) => take('verifyEmailCode', args),
|
|
170
|
+
validateAuth: async (args) => take('validateAuth', { accessToken: '[recorded]' }),
|
|
171
|
+
completeMissionControl: async (args) => take('completeMissionControl', args),
|
|
172
|
+
startMissionControlTasks: async (args) => take('startMissionControlTasks', args),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Scripted stores + seams -------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
function makeScriptedDraftStore(initial, writes) {
|
|
179
|
+
let draft = initial ?? undefined;
|
|
180
|
+
return {
|
|
181
|
+
read: () => draft,
|
|
182
|
+
write: (next) => {
|
|
183
|
+
draft = next;
|
|
184
|
+
writes.push({ ...next, updatedAt: '[normalized]' });
|
|
185
|
+
},
|
|
186
|
+
clear: () => {
|
|
187
|
+
draft = undefined;
|
|
188
|
+
writes.push('cleared');
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function makePreflightRuntime(spec) {
|
|
194
|
+
const tools = spec.tools ?? {};
|
|
195
|
+
return {
|
|
196
|
+
cwd: spec.cwd,
|
|
197
|
+
homeDir: spec.homeDir,
|
|
198
|
+
probeTool: async (command) => {
|
|
199
|
+
const tool = tools[command];
|
|
200
|
+
if (tool === undefined) {
|
|
201
|
+
return { command, available: false };
|
|
202
|
+
}
|
|
203
|
+
return { command, available: tool.available, version: tool.version ?? undefined };
|
|
204
|
+
},
|
|
205
|
+
readGitEmail: async () => (spec.gitEmail === undefined ? undefined : spec.gitEmail),
|
|
206
|
+
discoverCodeRoots: async () => spec.codeRoots ?? [],
|
|
207
|
+
checkBrowserHandoff: async () => spec.browserHandoff !== false,
|
|
208
|
+
...(spec.bundledCodexBin === undefined
|
|
209
|
+
? {}
|
|
210
|
+
: { resolveBundledCodexBin: () => spec.bundledCodexBin }),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function reviveProjects(projects) {
|
|
215
|
+
return (projects ?? []).map((project) => ({
|
|
216
|
+
path: project.path,
|
|
217
|
+
language: project.language,
|
|
218
|
+
source: project.source,
|
|
219
|
+
...(project.gitHead === undefined || project.gitHead === null
|
|
220
|
+
? {}
|
|
221
|
+
: { gitHead: project.gitHead }),
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --- flow_walk -------------------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
async function handleFlowWalk(testCase) {
|
|
228
|
+
clearSignupGitHeadSummaryCacheForTest();
|
|
229
|
+
const { writer, transcript } = makeFakeWriter(testCase.columns ?? 80);
|
|
230
|
+
const promptLog = [];
|
|
231
|
+
const serverLog = [];
|
|
232
|
+
const draftWrites = [];
|
|
233
|
+
const authWrites = [];
|
|
234
|
+
const completionWrites = [];
|
|
235
|
+
const launcherCalls = [];
|
|
236
|
+
const openedUrls = [];
|
|
237
|
+
const scriptedClient =
|
|
238
|
+
testCase.server === undefined ? false : makeScriptedClient(testCase.server, serverLog);
|
|
239
|
+
const pinnedNow = '2026-07-14T00:00:00.000Z';
|
|
240
|
+
let outcome = { ok: true };
|
|
241
|
+
try {
|
|
242
|
+
await runSignupFlow({
|
|
243
|
+
input: { isTTY: testCase.inputIsTty === true },
|
|
244
|
+
output: writer,
|
|
245
|
+
prompts: makeScriptedPrompts(testCase.prompts ?? [], promptLog),
|
|
246
|
+
preflight: makePreflightRuntime(testCase.preflight),
|
|
247
|
+
projectDiscovery: {
|
|
248
|
+
discoverProjects: async () => reviveProjects(testCase.projects),
|
|
249
|
+
},
|
|
250
|
+
taskSuggestion: {
|
|
251
|
+
suggestTasks: async () => testCase.suggestedTasks ?? [],
|
|
252
|
+
},
|
|
253
|
+
forceSignup: testCase.forceSignup === true,
|
|
254
|
+
serviceUrl: testCase.serviceUrl,
|
|
255
|
+
...(testCase.codeServerBin === undefined ? {} : { codeServerBin: testCase.codeServerBin }),
|
|
256
|
+
openBrowser: testCase.openBrowser !== false,
|
|
257
|
+
debugLaunchPayload: testCase.debugLaunchPayload !== false,
|
|
258
|
+
openUrl: async (url) => {
|
|
259
|
+
openedUrls.push(url);
|
|
260
|
+
if (testCase.openUrlFails === true) {
|
|
261
|
+
throw new Error('scripted open failure');
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
readSignupAuth: () => (testCase.existingAuth === undefined ? undefined : testCase.existingAuth),
|
|
265
|
+
readConfiguredProjectPaths: () => testCase.configuredPaths ?? [],
|
|
266
|
+
signupServerClient: scriptedClient,
|
|
267
|
+
commanderLauncher: async (args) => {
|
|
268
|
+
launcherCalls.push(args);
|
|
269
|
+
const launch = testCase.launch;
|
|
270
|
+
if (launch === undefined) {
|
|
271
|
+
throw new Error('scripted launcher without a launch result');
|
|
272
|
+
}
|
|
273
|
+
if (launch.throws !== undefined) {
|
|
274
|
+
throw new Error(launch.throws);
|
|
275
|
+
}
|
|
276
|
+
return launch;
|
|
277
|
+
},
|
|
278
|
+
signupDraftStore:
|
|
279
|
+
testCase.draft === undefined && testCase.useDraftStore !== true
|
|
280
|
+
? false
|
|
281
|
+
: makeScriptedDraftStore(testCase.draft, draftWrites),
|
|
282
|
+
signupTaskCacheStore: false,
|
|
283
|
+
retryPolicy: false,
|
|
284
|
+
writeSignupAuth: (args) => {
|
|
285
|
+
authWrites.push(args.email);
|
|
286
|
+
return {
|
|
287
|
+
email: args.email,
|
|
288
|
+
serviceUrl: testCase.serviceUrl,
|
|
289
|
+
createdAt: pinnedNow,
|
|
290
|
+
updatedAt: pinnedNow,
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
writeSignupCompletion: (args) => {
|
|
294
|
+
completionWrites.push({
|
|
295
|
+
...args,
|
|
296
|
+
accessToken: '[recorded]',
|
|
297
|
+
refreshToken: '[recorded]',
|
|
298
|
+
});
|
|
299
|
+
return {
|
|
300
|
+
email: args.email,
|
|
301
|
+
serviceUrl: args.serviceUrl,
|
|
302
|
+
accessToken: args.accessToken,
|
|
303
|
+
refreshToken: args.refreshToken,
|
|
304
|
+
...(args.expiresInSeconds === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: { expiresInSeconds: args.expiresInSeconds }),
|
|
307
|
+
...(args.userId === undefined ? {} : { userId: args.userId }),
|
|
308
|
+
...(args.workspaceSlug === undefined ? {} : { workspaceSlug: args.workspaceSlug }),
|
|
309
|
+
...(args.launchUrl === undefined ? {} : { launchUrl: args.launchUrl }),
|
|
310
|
+
createdAt: pinnedNow,
|
|
311
|
+
updatedAt: pinnedNow,
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
} catch (error) {
|
|
316
|
+
outcome = { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
outcome,
|
|
320
|
+
transcript: transcript(),
|
|
321
|
+
promptLog,
|
|
322
|
+
serverLog,
|
|
323
|
+
draftWrites,
|
|
324
|
+
authWrites,
|
|
325
|
+
completionWrites,
|
|
326
|
+
launcherCalls,
|
|
327
|
+
openedUrls,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// --- Key-schedule plumbing (flow_walk_tty + picker_walk) ------------------------------------------
|
|
332
|
+
|
|
333
|
+
function makeFakeTtyInput() {
|
|
334
|
+
const stream = new PassThrough();
|
|
335
|
+
stream.isTTY = true;
|
|
336
|
+
stream.setRawMode = () => stream;
|
|
337
|
+
return stream;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const NAMED_KEYS = {
|
|
341
|
+
up: '\\u001b[A',
|
|
342
|
+
down: '\\u001b[B',
|
|
343
|
+
left: '\\u001b[D',
|
|
344
|
+
right: '\\u001b[C',
|
|
345
|
+
tab: '\\t',
|
|
346
|
+
enter: '\\r',
|
|
347
|
+
space: ' ',
|
|
348
|
+
backspace: '\\u007f',
|
|
349
|
+
'ctrl-c': '\\u0003',
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
function keyBytes(key) {
|
|
353
|
+
if (key.startsWith('text:')) {
|
|
354
|
+
return key.slice(5);
|
|
355
|
+
}
|
|
356
|
+
const bytes = NAMED_KEYS[key];
|
|
357
|
+
if (bytes === undefined) {
|
|
358
|
+
throw new Error('unknown scheduled key ' + key);
|
|
359
|
+
}
|
|
360
|
+
return bytes;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const settle = () => new Promise((resolve) => setTimeout(resolve, 15));
|
|
364
|
+
|
|
365
|
+
async function driveSchedule(input, schedule) {
|
|
366
|
+
for (const key of schedule) {
|
|
367
|
+
await settle();
|
|
368
|
+
input.write(keyBytes(key));
|
|
369
|
+
}
|
|
370
|
+
await settle();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function handlePickerWalk(testCase) {
|
|
374
|
+
clearSignupGitHeadSummaryCacheForTest();
|
|
375
|
+
const input = makeFakeTtyInput();
|
|
376
|
+
const { writer, transcript } = makeFakeWriter(testCase.columns ?? 80);
|
|
377
|
+
// The outcome handler attaches BEFORE the schedule drives: a ctrl-c
|
|
378
|
+
// cancellation rejects mid-schedule, and an unhandled rejection would
|
|
379
|
+
// take the whole oracle process down.
|
|
380
|
+
const guarded = runProjectPickerPromptForTest(input, writer, {
|
|
381
|
+
message: testCase.message,
|
|
382
|
+
projects: reviveProjects(testCase.projects),
|
|
383
|
+
defaultSelectedPaths: testCase.defaultSelectedPaths ?? [],
|
|
384
|
+
}).then(
|
|
385
|
+
(selected) => ({ ok: true, selected }),
|
|
386
|
+
(error) => ({ ok: false, message: error instanceof Error ? error.message : String(error) })
|
|
387
|
+
);
|
|
388
|
+
await driveSchedule(input, testCase.schedule ?? []);
|
|
389
|
+
return { outcome: await guarded, transcript: transcript() };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// --- Unit ops ----------------------------------------------------------------------------------------
|
|
393
|
+
|
|
394
|
+
async function handle(testCase) {
|
|
395
|
+
switch (testCase.op) {
|
|
396
|
+
case 'help_text':
|
|
397
|
+
return signupHelpText();
|
|
398
|
+
case 'cancel_message':
|
|
399
|
+
return orNull(signupPromptCancelMessage(new Error(testCase.message)));
|
|
400
|
+
case 'parse_options':
|
|
401
|
+
try {
|
|
402
|
+
const options = parseSignupTuiOptions(testCase.args);
|
|
403
|
+
return {
|
|
404
|
+
ok: true,
|
|
405
|
+
serviceUrl: options.serviceUrl,
|
|
406
|
+
forceSignup: options.forceSignup,
|
|
407
|
+
codeServerBin: orNull(options.codeServerBin),
|
|
408
|
+
openBrowser: options.openBrowser,
|
|
409
|
+
debugLaunchPayload: options.debugLaunchPayload,
|
|
410
|
+
};
|
|
411
|
+
} catch (error) {
|
|
412
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
413
|
+
}
|
|
414
|
+
case 'auth_matches':
|
|
415
|
+
return signupAuthMatchesServiceUrl(testCase.auth, testCase.serviceUrl);
|
|
416
|
+
case 'comparable_url':
|
|
417
|
+
return comparableServiceUrl(testCase.value);
|
|
418
|
+
case 'autocomplete':
|
|
419
|
+
return autocompletePathSuggestions(testCase.pathInput);
|
|
420
|
+
case 'picker_frame':
|
|
421
|
+
return signupProjectPickerFrameForTest({
|
|
422
|
+
columns: testCase.columns,
|
|
423
|
+
projects: reviveProjects(testCase.projects),
|
|
424
|
+
selectedPaths: testCase.selectedPaths,
|
|
425
|
+
});
|
|
426
|
+
case 'printable_key':
|
|
427
|
+
return projectPickerKeyIsPrintableForTest(
|
|
428
|
+
testCase.value === null ? undefined : testCase.value
|
|
429
|
+
);
|
|
430
|
+
case 'toggle_selection':
|
|
431
|
+
return toggleProjectPickerSelectionForTest({
|
|
432
|
+
pathInput: testCase.state.pathInput,
|
|
433
|
+
cursor: testCase.state.cursor,
|
|
434
|
+
highlightIndex: testCase.state.highlightIndex,
|
|
435
|
+
selectedPaths: testCase.state.selectedPaths,
|
|
436
|
+
highlightedPath:
|
|
437
|
+
testCase.state.highlightedPath === null ? undefined : testCase.state.highlightedPath,
|
|
438
|
+
});
|
|
439
|
+
case 'email_picker_choices':
|
|
440
|
+
return signupEmailPickerChoiceNamesForTest(testCase.suggestedEmails);
|
|
441
|
+
case 'useful_email':
|
|
442
|
+
return signupSuggestedEmailIsUsefulForTest(testCase.email);
|
|
443
|
+
case 'spinner_lines':
|
|
444
|
+
return signupSpinnerLinesForTest(testCase.mark, testCase.message, testCase.columns);
|
|
445
|
+
case 'wait_message':
|
|
446
|
+
return signupTaskSuggestionWaitMessageForTest(testCase.projectPath);
|
|
447
|
+
case 'launch_payload':
|
|
448
|
+
return JSON.stringify(signupLaunchPayloadForTest(testCase.state), null, 2);
|
|
449
|
+
case 'project_git_head': {
|
|
450
|
+
clearSignupGitHeadSummaryCacheForTest();
|
|
451
|
+
return orNull(signupProjectGitHeadForPathForTest(testCase.path));
|
|
452
|
+
}
|
|
453
|
+
case 'discover_projects': {
|
|
454
|
+
clearSignupGitHeadSummaryCacheForTest();
|
|
455
|
+
return signupDiscoverProjectsForStartForTest({
|
|
456
|
+
homeDir: testCase.homeDir,
|
|
457
|
+
cwd: testCase.cwd,
|
|
458
|
+
}).map((project) => ({
|
|
459
|
+
path: project.path,
|
|
460
|
+
language: project.language,
|
|
461
|
+
source: project.source,
|
|
462
|
+
gitHead: orNull(project.gitHead),
|
|
463
|
+
}));
|
|
464
|
+
}
|
|
465
|
+
case 'read_task_cache':
|
|
466
|
+
return signupReadTaskCacheForTest(testCase.path);
|
|
467
|
+
case 'resolve_codex_command':
|
|
468
|
+
try {
|
|
469
|
+
return {
|
|
470
|
+
ok: true,
|
|
471
|
+
command: await resolveSignupCodexCommandForTest({
|
|
472
|
+
env: testCase.env,
|
|
473
|
+
homeDir: testCase.homeDir,
|
|
474
|
+
executablePaths: testCase.executablePaths,
|
|
475
|
+
}),
|
|
476
|
+
};
|
|
477
|
+
} catch (error) {
|
|
478
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
479
|
+
}
|
|
480
|
+
case 'codex_process': {
|
|
481
|
+
const spec = signupCodexTaskSuggestionProcessForTest(testCase.args);
|
|
482
|
+
return {
|
|
483
|
+
command: spec.command,
|
|
484
|
+
args: spec.args,
|
|
485
|
+
cwd: spec.cwd,
|
|
486
|
+
stdin: spec.stdin,
|
|
487
|
+
timeoutMs: spec.timeoutMs,
|
|
488
|
+
env: orNull(spec.env),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
case 'suggest_tasks_cached': {
|
|
492
|
+
const { writer, transcript } = makeFakeWriter(testCase.columns ?? 80);
|
|
493
|
+
const cacheWrites = [];
|
|
494
|
+
const runnerCalls = [];
|
|
495
|
+
const store = {
|
|
496
|
+
read: (key) => {
|
|
497
|
+
runnerCalls.push({ read: key });
|
|
498
|
+
const entry = (testCase.cache ?? {})[key];
|
|
499
|
+
return entry === undefined ? undefined : entry;
|
|
500
|
+
},
|
|
501
|
+
write: (key, tasks) => {
|
|
502
|
+
cacheWrites.push({ key, tasks });
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
const tasks = await signupSuggestTasksForProjectForTest({
|
|
506
|
+
project: reviveProjects([testCase.project])[0],
|
|
507
|
+
output: writer,
|
|
508
|
+
taskCacheStore: store,
|
|
509
|
+
codexTaskRunner: async () => testCase.runnerTasks ?? [],
|
|
510
|
+
});
|
|
511
|
+
return { tasks, transcript: transcript(), cacheWrites, runnerCalls };
|
|
512
|
+
}
|
|
513
|
+
case 'flow_walk':
|
|
514
|
+
return handleFlowWalk(testCase);
|
|
515
|
+
case 'picker_walk':
|
|
516
|
+
return handlePickerWalk(testCase);
|
|
517
|
+
default:
|
|
518
|
+
return { threw: true, message: 'unknown op ' + String(testCase.op) };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
let stdin = '';
|
|
523
|
+
process.stdin.setEncoding('utf8');
|
|
524
|
+
for await (const chunk of process.stdin) {
|
|
525
|
+
stdin += chunk;
|
|
526
|
+
}
|
|
527
|
+
const cases = JSON.parse(stdin);
|
|
528
|
+
const results = [];
|
|
529
|
+
for (const testCase of cases) {
|
|
530
|
+
results.push(await handle(testCase));
|
|
531
|
+
}
|
|
532
|
+
process.stdout.write(JSON.stringify(results));
|
|
533
|
+
`;
|
|
534
|
+
|
|
535
|
+
await mkdir(join(packageRoot, '.differential'), { recursive: true });
|
|
536
|
+
|
|
537
|
+
// Renamed banner import (the build-auth-parity-oracle.mjs convention): CJS
|
|
538
|
+
// transitive deps (@inquirer's yoctocolors) require() node builtins at
|
|
539
|
+
// module scope, and a bare `createRequire` name would collide with modules
|
|
540
|
+
// that import it themselves.
|
|
541
|
+
const banner = {
|
|
542
|
+
js: "import { createRequire as __onboardingFlowOracleCreateRequire } from 'node:module';\nconst require = __onboardingFlowOracleCreateRequire(import.meta.url);",
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
await build({
|
|
546
|
+
banner,
|
|
547
|
+
stdin: {
|
|
548
|
+
contents: driverSource,
|
|
549
|
+
resolveDir: packageRoot,
|
|
550
|
+
sourcefile: 'onboarding-flow-parity-driver.ts',
|
|
551
|
+
loader: 'ts',
|
|
552
|
+
},
|
|
553
|
+
bundle: true,
|
|
554
|
+
platform: 'node',
|
|
555
|
+
target: 'node20',
|
|
556
|
+
format: 'esm',
|
|
557
|
+
outfile: join(packageRoot, '.differential/onboarding-flow-parity-oracle.mjs'),
|
|
558
|
+
minify: false,
|
|
559
|
+
sourcemap: false,
|
|
560
|
+
legalComments: 'none',
|
|
561
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
562
|
+
});
|