@timekast/factory 1.28.0 → 2.0.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/commands/backlog.js +52 -15
- package/dist/commands/doctor.js +210 -29
- package/dist/commands/env.js +146 -18
- package/dist/commands/invite-admin.js +62 -15
- package/dist/commands/provision.js +5820 -768
- package/dist/commands/status.js +12 -2
- package/dist/commands/ticket.js +4 -4
- package/dist/commands/update.js +97 -12
- package/dist/commands/vault.js +2627 -0
- package/dist/index.js +97 -33
- package/dist/lib/backlog-mapping.js +111 -18
- package/dist/lib/backlog-parse.js +1 -1
- package/dist/lib/claude-md.js +320 -0
- package/dist/lib/cloudflare-api.js +213 -37
- package/dist/lib/constants.js +70 -6
- package/dist/lib/env-file.js +45 -7
- package/dist/lib/env-wizard.js +66 -2
- package/dist/lib/initial-migration.js +3 -2
- package/dist/lib/neon-api.js +170 -35
- package/dist/lib/package-json.js +37 -16
- package/dist/lib/preflight.js +92 -38
- package/dist/lib/provision-preflight.js +158 -100
- package/dist/lib/provision-state.js +652 -20
- package/dist/lib/publish-engine.js +1 -1
- package/dist/lib/rail.js +400 -0
- package/dist/lib/railway-api.js +851 -0
- package/dist/lib/vault-adopt.js +1495 -0
- package/dist/lib/vault-api.js +1101 -0
- package/dist/lib/vault-config.js +21 -0
- package/dist/lib/vault-deploys.js +898 -0
- package/dist/lib/vercel-env.js +251 -31
- package/dist/lib/wrapper-wiring.js +160 -0
- package/dist/tui/env-wizard-flow.js +17 -3
- package/dist/tui/menu.js +4 -22
- package/dist/tui/provision-flow.js +68 -13
- package/package.json +1 -1
- package/dist/commands/secrets.js +0 -71
- package/dist/lib/env-merge.js +0 -238
package/dist/lib/package-json.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { CLIError } from './cli-error.js';
|
|
11
|
-
import { DEV_NEXT_GATE_FILE, DEV_NEXT_SCRIPT_CMD, DEV_NEXT_SCRIPT_NAME, E2E_SETUP_SCRIPT_CMD, E2E_SETUP_SCRIPT_FILE, E2E_SETUP_SCRIPT_NAME, EVIDENCE_VISUAL_SCRIPT_CMD, EVIDENCE_VISUAL_SCRIPT_FILE, EVIDENCE_VISUAL_SCRIPT_NAME, FACTORY_SCRIPTS, GENERATE_REFERENCE_CMD, GENERATE_REFERENCE_GATE_FILE, GENERATE_REFERENCE_NAME, GENERATOR_SCRIPTS, INVITE_ADMIN_SCRIPT_CMD, INVITE_ADMIN_SCRIPT_FILE, INVITE_ADMIN_SCRIPT_NAME, OBSOLETE_SCRIPT_CMDS, VERIFY_QUICK_SCRIPT_CMD, VERIFY_QUICK_SCRIPT_NAME, VERIFY_SCRIPT_CMD, VERIFY_SCRIPT_FILE, VERIFY_SCRIPT_NAME, PORTS_E2E_COMMENT, PORTS_E2E_COMMENT_KEY, PORTS_E2E_KEY, PORTS_KEY, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, RETIRED_SCRIPT_CMDS, SETUP_E2E_VARS_SCRIPT_CMD, SETUP_E2E_VARS_SCRIPT_FILE, SETUP_E2E_VARS_SCRIPT_NAME, SHIPPED_E2E_PORT, UPDATE_SCRIPT_NAME, VERCEL_BUILD_OBSOLETE_CMDS, VERCEL_BUILD_SCRIPT_CMD, VERCEL_BUILD_SCRIPT_NAME, } from './constants.js';
|
|
11
|
+
import { DEV_NEXT_GATE_FILE, DEV_NEXT_SCRIPT_CMD, DEV_NEXT_SCRIPT_NAME, E2E_SETUP_SCRIPT_CMD, E2E_SETUP_SCRIPT_FILE, E2E_SETUP_SCRIPT_NAME, EVIDENCE_VISUAL_SCRIPT_CMD, EVIDENCE_VISUAL_SCRIPT_FILE, EVIDENCE_VISUAL_SCRIPT_NAME, FACTORY_SCRIPTS, GENERATE_REFERENCE_CMD, GENERATE_REFERENCE_GATE_FILE, GENERATE_REFERENCE_NAME, GENERATOR_SCRIPTS, INVITE_ADMIN_SCRIPT_CMD, INVITE_ADMIN_SCRIPT_FILE, INVITE_ADMIN_SCRIPT_NAME, OBSOLETE_SCRIPT_CMDS, VERIFY_QUICK_SCRIPT_CMD, VERIFY_QUICK_SCRIPT_NAME, VERIFY_SCRIPT_CMD, VERIFY_SCRIPT_FILE, VERIFY_SCRIPT_NAME, PORTS_E2E_COMMENT, PORTS_E2E_COMMENT_KEY, PORTS_E2E_KEY, PORTS_KEY, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, RETIRED_SCRIPT_CMDS, SETUP_E2E_VARS_SCRIPT_CMD, SETUP_E2E_VARS_SCRIPT_FILE, SETUP_E2E_VARS_SCRIPT_NAME, SHIPPED_E2E_PORT, UPDATE_SCRIPT_NAME, VERCEL_BUILD_OBSOLETE_CMDS, VERCEL_BUILD_SCRIPT_CMD, VERCEL_BUILD_SCRIPT_NAME, WITH_VAULT_SCRIPT_FILE, } from './constants.js';
|
|
12
12
|
/**
|
|
13
13
|
* Auto-detect the profile to install into an existing repo from its
|
|
14
14
|
* `package.json`: a `factoryVersion` field means the repo was born from the
|
|
@@ -35,7 +35,7 @@ export function detectProfile(rootDir) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
/** Detect the indentation used by an existing JSON document (defaults to 2 spaces). */
|
|
38
|
-
function detectIndent(raw) {
|
|
38
|
+
export function detectIndent(raw) {
|
|
39
39
|
const match = raw.match(/^(\s+)"/m);
|
|
40
40
|
if (!match)
|
|
41
41
|
return 2;
|
|
@@ -57,6 +57,22 @@ export function parsePackageJson(raw) {
|
|
|
57
57
|
throw new CLIError('El `package.json` desempacado no es JSON válido; no se puede continuar de forma segura.');
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* THE one comparison every heal of a kit-owned script uses (`ensureFactoryScripts`,
|
|
62
|
+
* `healVercelBuildOrder`, the vault wiring of `wrapper-wiring.ts`): exact match, byte for byte,
|
|
63
|
+
* against the current command and the registered values the kit shipped. A variant nobody
|
|
64
|
+
* registered — a flag appended, different spacing, a step chained in front — is `own`: erring
|
|
65
|
+
* toward "leave it alone" is the safe side of this mistake.
|
|
66
|
+
*/
|
|
67
|
+
export function classifyKitScript(existing, current, shipped) {
|
|
68
|
+
if (existing === undefined)
|
|
69
|
+
return 'absent';
|
|
70
|
+
if (existing === current)
|
|
71
|
+
return 'current';
|
|
72
|
+
if (shipped.includes(existing))
|
|
73
|
+
return 'shipped';
|
|
74
|
+
return 'own';
|
|
75
|
+
}
|
|
60
76
|
/**
|
|
61
77
|
* Pure core of the script insertion: mutate `pkg.scripts` in place to ensure ALL
|
|
62
78
|
* `factory:*` convenience scripts exist (`update` / `doctor` / `status`), adding
|
|
@@ -79,18 +95,19 @@ function ensureFactoryScripts(pkg, extraScripts = {}) {
|
|
|
79
95
|
for (const [name, cmd] of Object.entries({ ...FACTORY_SCRIPTS, ...extraScripts })) {
|
|
80
96
|
const existing = scripts[name];
|
|
81
97
|
let result;
|
|
82
|
-
|
|
98
|
+
// The value is a KNOWN-OBSOLETE kit command (e.g. the old `node …mjs` that the .ts
|
|
99
|
+
// migration retired) → heal it to the current command. A genuinely dev-divergent value
|
|
100
|
+
// is a `conflict` and is left intact.
|
|
101
|
+
const kind = classifyKitScript(existing, cmd, OBSOLETE_SCRIPT_CMDS[name] ?? []);
|
|
102
|
+
if (kind === 'absent') {
|
|
83
103
|
scripts[name] = cmd;
|
|
84
104
|
changed = true;
|
|
85
105
|
result = { action: 'added' };
|
|
86
106
|
}
|
|
87
|
-
else if (
|
|
107
|
+
else if (kind === 'current') {
|
|
88
108
|
result = { action: 'already-correct' };
|
|
89
109
|
}
|
|
90
|
-
else if (
|
|
91
|
-
// The value is a KNOWN-OBSOLETE kit command (e.g. the old `node …mjs` that
|
|
92
|
-
// the .ts migration retired) — heal it to the current command. A genuinely
|
|
93
|
-
// dev-divergent value falls through to `conflict` and is left intact.
|
|
110
|
+
else if (kind === 'shipped') {
|
|
94
111
|
scripts[name] = cmd;
|
|
95
112
|
changed = true;
|
|
96
113
|
result = { action: 'healed' };
|
|
@@ -112,7 +129,8 @@ function ensureFactoryScripts(pkg, extraScripts = {}) {
|
|
|
112
129
|
*
|
|
113
130
|
* - `preflight` — when its sweep script ships (full brain).
|
|
114
131
|
* - `setup:e2e:vars` — publishes the CI E2E auth env vars (Fase 1 of the
|
|
115
|
-
* `e2e-ci-auth-env` retrofit), when `scripts/tools/setup-e2e.ts` ships (full brain)
|
|
132
|
+
* `e2e-ci-auth-env` retrofit), when `scripts/tools/setup-e2e.ts` ships (full brain) AND the
|
|
133
|
+
* vault wrapper `scripts/tools/with-vault.mjs` does too — its value runs through it (VAULT-004).
|
|
116
134
|
* - `evidence:visual` — the visual-evidence harness (`fx-visual-evidence`), gated on the
|
|
117
135
|
* harness itself (`scripts/tools/visual-evidence/`) rather than on the runner: it is what
|
|
118
136
|
* this alias needs, and a `core` derivative receives neither. Forwards to `test:e2e`, so the
|
|
@@ -152,7 +170,10 @@ export function kitLocalScripts(rootDir) {
|
|
|
152
170
|
scripts[PREFLIGHT_SCRIPT_NAME] = PREFLIGHT_SCRIPT_CMD;
|
|
153
171
|
if (has(INVITE_ADMIN_SCRIPT_FILE))
|
|
154
172
|
scripts[INVITE_ADMIN_SCRIPT_NAME] = INVITE_ADMIN_SCRIPT_CMD;
|
|
155
|
-
|
|
173
|
+
// Its value runs through the vault wrapper, so it is ALSO gated on the wrapper: without it
|
|
174
|
+
// the alias is neither inserted nor healed (a script pointing at a missing file is worse than
|
|
175
|
+
// an old one that still runs).
|
|
176
|
+
if (has(SETUP_E2E_VARS_SCRIPT_FILE) && has(WITH_VAULT_SCRIPT_FILE))
|
|
156
177
|
scripts[SETUP_E2E_VARS_SCRIPT_NAME] = SETUP_E2E_VARS_SCRIPT_CMD;
|
|
157
178
|
if (has(E2E_SETUP_SCRIPT_FILE))
|
|
158
179
|
scripts[E2E_SETUP_SCRIPT_NAME] = E2E_SETUP_SCRIPT_CMD;
|
|
@@ -401,15 +422,15 @@ export function healVercelBuildOrder(pkgPath) {
|
|
|
401
422
|
const scripts = pkg.scripts;
|
|
402
423
|
if (typeof scripts !== 'object' || scripts === null || Array.isArray(scripts))
|
|
403
424
|
return 'absent';
|
|
404
|
-
const current = scripts[VERCEL_BUILD_SCRIPT_NAME];
|
|
405
|
-
if (current === undefined)
|
|
406
|
-
return 'absent';
|
|
407
|
-
if (current === VERCEL_BUILD_SCRIPT_CMD)
|
|
408
|
-
return 'already-correct';
|
|
409
425
|
// Exact match against what the kit actually shipped, byte for byte. A dev who appended a flag
|
|
410
426
|
// — or wrote the same two halves with different spacing — keeps their command through every
|
|
411
427
|
// future update: an unlisted variant erring toward "leave it alone" is the safe side here.
|
|
412
|
-
|
|
428
|
+
const kind = classifyKitScript(scripts[VERCEL_BUILD_SCRIPT_NAME], VERCEL_BUILD_SCRIPT_CMD, VERCEL_BUILD_OBSOLETE_CMDS);
|
|
429
|
+
if (kind === 'absent')
|
|
430
|
+
return 'absent';
|
|
431
|
+
if (kind === 'current')
|
|
432
|
+
return 'already-correct';
|
|
433
|
+
if (kind === 'own')
|
|
413
434
|
return 'preserved';
|
|
414
435
|
scripts[VERCEL_BUILD_SCRIPT_NAME] = VERCEL_BUILD_SCRIPT_CMD;
|
|
415
436
|
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, indent)}\n`, 'utf8');
|
package/dist/lib/preflight.js
CHANGED
|
@@ -15,12 +15,11 @@
|
|
|
15
15
|
* --launcher` can render every step with ✅ / ❌ across all platforms (the
|
|
16
16
|
* cross-platform equivalent of the macOS launcher's `checkPrereqs`).
|
|
17
17
|
*/
|
|
18
|
-
import { readFileSync } from 'node:fs';
|
|
19
|
-
import { homedir } from 'node:os';
|
|
20
|
-
import path from 'node:path';
|
|
21
18
|
import { execa } from 'execa';
|
|
22
19
|
import { PreflightError } from './cli-error.js';
|
|
23
20
|
import { FACTORY_ORG } from './constants.js';
|
|
21
|
+
import { RailError, createRailResolver } from './rail.js';
|
|
22
|
+
import { VAULT_CONFIG } from './vault-config.js';
|
|
24
23
|
/** Minimum Node major version the kit requires (mirrors `engines.node` + the launcher AppleScript). */
|
|
25
24
|
export const MIN_NODE_MAJOR = 22;
|
|
26
25
|
/** Timeout (ms) for the GitHub API calls in the org-membership check — a slow / dead network must not hang. */
|
|
@@ -136,9 +135,10 @@ export async function runPreflight() {
|
|
|
136
135
|
await assertOrgMembership();
|
|
137
136
|
}
|
|
138
137
|
/**
|
|
139
|
-
* The methodology-rail keys the launcher expects in
|
|
140
|
-
*
|
|
141
|
-
* proposal tooling keys. Single source so the check + tests
|
|
138
|
+
* The methodology-rail keys the launcher expects in the vault rail (`rail-timekast`,
|
|
139
|
+
* read with the person's session — `fx-secrets-vault §3`). Provisioning tokens +
|
|
140
|
+
* their team/org ids + the proposal tooling keys. Single source so the check + tests
|
|
141
|
+
* agree.
|
|
142
142
|
*
|
|
143
143
|
* `NEON_ORG` is deliberately NOT here: it only names a default organization, and
|
|
144
144
|
* its absence is a valid configuration (a key that reaches exactly one org needs
|
|
@@ -153,14 +153,26 @@ export const REQUIRED_SECRET_KEYS = [
|
|
|
153
153
|
'SHORTIO_API_KEY',
|
|
154
154
|
'GAMMA_API_KEY',
|
|
155
155
|
'VERCEL_TEAM_ID',
|
|
156
|
+
// RWAY-003: `provision` asks Railway for the connected repo to discover a repo's destination
|
|
157
|
+
// (and to refuse a `--target=vercel` over a repo that lives in Railway) — in Vercel repos too.
|
|
158
|
+
'RAILWAY_TOKEN',
|
|
156
159
|
];
|
|
160
|
+
/**
|
|
161
|
+
* Time budget (ms) of the launcher's vault read. Deliberately SHORTER than the
|
|
162
|
+
* resolver's default (`RAIL_READ_TIMEOUT_MS`, 30 s): the desktop launcher waits 30 s
|
|
163
|
+
* IN TOTAL for the whole `doctor --launcher --json` document
|
|
164
|
+
* (`desktop/renderer/panels/home.tsx`), and `npx @latest` already eats part of that.
|
|
165
|
+
* A vault that does not answer must end as a `warn` "could not be determined" inside
|
|
166
|
+
* the document — not as a launcher-side "connection error" because the JSON never came.
|
|
167
|
+
*/
|
|
168
|
+
export const LAUNCHER_RAIL_TIMEOUT_MS = 10_000;
|
|
157
169
|
/** The ordered launcher labels (single source so render + tests agree). */
|
|
158
170
|
const LAUNCHER_LABELS = {
|
|
159
171
|
node: `Node ≥ ${MIN_NODE_MAJOR}`,
|
|
160
172
|
gh: 'GitHub CLI (gh) instalado',
|
|
161
173
|
auth: 'Sesión de GitHub activa',
|
|
162
174
|
org: `Membresía del org ${FACTORY_ORG}`,
|
|
163
|
-
secrets:
|
|
175
|
+
secrets: `Secrets del org en la bóveda (${VAULT_CONFIG.rail.name})`,
|
|
164
176
|
};
|
|
165
177
|
/** A confirmation message for a passing check (the `ok` branch). */
|
|
166
178
|
const OK_MESSAGES = {
|
|
@@ -168,43 +180,81 @@ const OK_MESSAGES = {
|
|
|
168
180
|
gh: 'GitHub CLI (gh) está instalado.',
|
|
169
181
|
auth: 'Tu sesión de GitHub CLI está activa.',
|
|
170
182
|
org: `Eres miembro del org ${FACTORY_ORG}.`,
|
|
171
|
-
secrets: `Las ${REQUIRED_SECRET_KEYS.length}
|
|
183
|
+
secrets: `Las ${REQUIRED_SECRET_KEYS.length} claves requeridas están en ` +
|
|
184
|
+
`\`${VAULT_CONFIG.rail.name}\` y tu sesión de la bóveda está activa.`,
|
|
172
185
|
};
|
|
186
|
+
/** The vault state skeleton — coordinates always present, everything else undetermined. */
|
|
187
|
+
function vaultState(overrides) {
|
|
188
|
+
return {
|
|
189
|
+
domain: VAULT_CONFIG.domain,
|
|
190
|
+
project: VAULT_CONFIG.rail.name,
|
|
191
|
+
session: null,
|
|
192
|
+
access: null,
|
|
193
|
+
missingKeys: null,
|
|
194
|
+
unknownReason: null,
|
|
195
|
+
...overrides,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/** Map a rail failure (other than a missing key) to the published vault state. */
|
|
199
|
+
function vaultStateForFailure(kind) {
|
|
200
|
+
switch (kind) {
|
|
201
|
+
case 'no-session':
|
|
202
|
+
return vaultState({ session: false });
|
|
203
|
+
case 'no-access':
|
|
204
|
+
return vaultState({ session: true, access: false });
|
|
205
|
+
case 'not-installed':
|
|
206
|
+
case 'timeout':
|
|
207
|
+
case 'unrecognized':
|
|
208
|
+
return vaultState({ unknownReason: kind });
|
|
209
|
+
case 'key-missing':
|
|
210
|
+
// Never reaches here (`getOptional` turns it into `null`), kept for exhaustiveness.
|
|
211
|
+
return vaultState({ session: true, access: true });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
173
214
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
215
|
+
* Build the secrets check against the vault rail: session → access → presence of
|
|
216
|
+
* each {@link REQUIRED_SECRET_KEYS}, in that order (the resolver reads the rail
|
|
217
|
+
* project ONCE; a session/access failure surfaces on the first key). NAMES ONLY —
|
|
218
|
+
* each value is dropped the moment the resolver hands it over.
|
|
219
|
+
*
|
|
220
|
+
* Never `fail`, never throws: every outcome is `ok` or `warn`. The message on a
|
|
221
|
+
* failure is the resolver's own (built from the classification, never from what
|
|
222
|
+
* the vault printed), so it names the login command with the domain, the rail
|
|
223
|
+
* project, or how to install `infisical`.
|
|
177
224
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
225
|
+
async function secretsCheck(rail) {
|
|
226
|
+
const base = { id: 'secrets', label: LAUNCHER_LABELS.secrets };
|
|
227
|
+
const missing = [];
|
|
228
|
+
try {
|
|
229
|
+
for (const key of REQUIRED_SECRET_KEYS) {
|
|
230
|
+
// Presence only: the value is compared to null and discarded right here.
|
|
231
|
+
if ((await rail.getOptional(key)) === null)
|
|
232
|
+
missing.push(key);
|
|
186
233
|
}
|
|
187
234
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
235
|
+
catch (error) {
|
|
236
|
+
const kind = error instanceof RailError ? error.kind : 'unrecognized';
|
|
237
|
+
return {
|
|
238
|
+
...base,
|
|
239
|
+
status: 'warn',
|
|
240
|
+
// A non-RailError is not ours to echo (its text could carry anything).
|
|
241
|
+
message: error instanceof RailError
|
|
242
|
+
? error.message
|
|
243
|
+
: 'No se pudo determinar el estado de la bóveda (error interno al leerla).',
|
|
244
|
+
vault: vaultStateForFailure(kind),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const vault = vaultState({ session: true, access: true, missingKeys: missing });
|
|
248
|
+
if (missing.length === 0) {
|
|
249
|
+
return { ...base, status: 'ok', message: OK_MESSAGES.secrets, vault };
|
|
193
250
|
}
|
|
194
|
-
return names;
|
|
195
|
-
}
|
|
196
|
-
/** Build the secrets check (`ok` if all required keys present, else `warn`). */
|
|
197
|
-
function secretsCheck(railContent) {
|
|
198
|
-
const present = readRailKeyNames(railContent);
|
|
199
|
-
const missing = REQUIRED_SECRET_KEYS.filter((key) => !present.has(key));
|
|
200
251
|
return {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`${missing.join(', ')}. Impórtalos desde el card Secrets.`,
|
|
252
|
+
...base,
|
|
253
|
+
status: 'warn',
|
|
254
|
+
message: `Faltan ${missing.length} de ${REQUIRED_SECRET_KEYS.length} claves en ` +
|
|
255
|
+
`\`${VAULT_CONFIG.rail.name}\`: ${missing.join(', ')}.\n` +
|
|
256
|
+
'Pide a un admin de la bóveda que las cargue.',
|
|
257
|
+
vault,
|
|
208
258
|
};
|
|
209
259
|
}
|
|
210
260
|
/**
|
|
@@ -222,6 +272,10 @@ function secretsCheck(railContent) {
|
|
|
222
272
|
* depends on the previous — no auth without gh, no org without auth) but still
|
|
223
273
|
* RETURNS a full ordered report, marking the checks after the failure as
|
|
224
274
|
* `skipped`. The caller decides exit code (0 = all ok, 1 = any fail).
|
|
275
|
+
*
|
|
276
|
+
* Then, always and independently of 1-4, the `secrets` check (5): session → access
|
|
277
|
+
* → key presence in the vault rail. It is `ok` or `warn`, never `fail`, so it never
|
|
278
|
+
* trips the exit code.
|
|
225
279
|
*/
|
|
226
280
|
export async function runLauncherPreflight(deps = {}) {
|
|
227
281
|
const ghInstalled = deps.ghInstalled ?? assertGhInstalled;
|
|
@@ -274,6 +328,6 @@ export async function runLauncherPreflight(deps = {}) {
|
|
|
274
328
|
}
|
|
275
329
|
// Secrets check — independent of the gh/auth/org chain (runs even if those
|
|
276
330
|
// failed) and non-blocking: resolves to `ok` or `warn`, never `fail`/`skipped`.
|
|
277
|
-
results.push(secretsCheck(deps.
|
|
331
|
+
results.push(await secretsCheck(deps.rail ?? createRailResolver({ timeoutMs: LAUNCHER_RAIL_TIMEOUT_MS })));
|
|
278
332
|
return results;
|
|
279
333
|
}
|