@levr-one/cli 0.11.10 → 0.12.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/{addHandler-DR6q5U_R.js → addHandler-BIKNXipz.js} +7 -7
- package/dist/{appendHandler-KVsJMxpX.js → appendHandler-DtxLpPu6.js} +9 -9
- package/dist/{artifact-upload-BzLxue32.js → artifact-upload-BvBSevIx.js} +2 -2
- package/dist/{attachHandler-DQpK_pVa.js → attachHandler-DXZi2ILT.js} +9 -9
- package/dist/cli.js +38 -22
- package/dist/{connect-BgQmrL_0.js → connect-DVjgyfls.js} +3 -3
- package/dist/{currentHandler-u89l068v.js → currentHandler-QhCywr3S.js} +1 -1
- package/dist/{find-workspace-T4xuXwtS.js → find-workspace-Bl4rfr73.js} +1 -1
- package/dist/{importHandler-zw5GKb-S.js → importHandler-uFdalKtw.js} +6 -6
- package/dist/inspect-B8tG9g0q.js +1122 -0
- package/dist/inspect-BzBqSBO3.js +3 -0
- package/dist/{listHandler-YA-0RkwS.js → listHandler-BW92JLCl.js} +5 -5
- package/dist/{loginHandler-DE0L1ehF.js → loginHandler-C9Oi92IY.js} +15 -11
- package/dist/{logoutHandler-S4TFYFwz.js → logoutHandler-Cf5aI1wq.js} +2 -2
- package/dist/{pushHandler-DdNyVQ5t.js → pushHandler-CXFZt3SO.js} +8 -8
- package/dist/{resolve-token-7xb7kG7h.js → resolve-token-ByZq05Us.js} +2 -2
- package/dist/{resolve-workspace-CStHisUb.js → resolve-workspace-BbK3uYQc.js} +2 -2
- package/dist/{runHandler-BFDjtxbt.js → runHandler-itaAVn4U.js} +215 -25
- package/dist/{sdk-client-DYv-0CfA.js → sdk-client-B4FMd_Fa.js} +1 -1
- package/dist/{selectHandler-Cejme5nY.js → selectHandler-D6cvq60S.js} +5 -5
- package/dist/{statusHandler-BNiWlC7e.js → statusHandler-BQXn93-r.js} +3 -3
- package/dist/{token-refresh-DeusLK8H.js → token-refresh-CJB7Qlxy.js} +1 -1
- package/package.json +1 -1
- /package/dist/{env-CdwyPHGV.js → env-CvjMSRd7.js} +0 -0
- /package/dist/{sleep-BpJ39Ypm.js → sleep-CGBHjsj5.js} +0 -0
- /package/dist/{workspace-store-CnrxyYRB.js → workspace-store-Bmw104Qa.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { authGetSitesV1, configureClient } from "./sdk-client-
|
|
2
|
-
import "./env-
|
|
3
|
-
import "./token-refresh-
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
5
|
-
import { loadWorkspace } from "./workspace-store-
|
|
1
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-B4FMd_Fa.js";
|
|
2
|
+
import "./env-CvjMSRd7.js";
|
|
3
|
+
import "./token-refresh-CJB7Qlxy.js";
|
|
4
|
+
import { resolveToken } from "./resolve-token-ByZq05Us.js";
|
|
5
|
+
import { loadWorkspace } from "./workspace-store-Bmw104Qa.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/listHandler.ts
|
|
8
8
|
async function listHandler() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { configureClient } from "./sdk-client-
|
|
2
|
-
import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-
|
|
3
|
-
import { saveWorkspace } from "./workspace-store-
|
|
4
|
-
import { autoSelectWorkspace, fetchSites } from "./resolve-workspace-
|
|
5
|
-
import { sleep } from "./sleep-
|
|
6
|
-
import { findWorkspaceByKeyOrName, looksLikeUrlKey, resolveMcpUrl, scopedMcpUrl } from "./find-workspace-
|
|
1
|
+
import { configureClient } from "./sdk-client-B4FMd_Fa.js";
|
|
2
|
+
import { CLI_CLIENT_ID, getApiUrl, getAuthUrl, setSessionApiUrl, writeCredentials } from "./env-CvjMSRd7.js";
|
|
3
|
+
import { saveWorkspace } from "./workspace-store-Bmw104Qa.js";
|
|
4
|
+
import { autoSelectWorkspace, fetchSites } from "./resolve-workspace-BbK3uYQc.js";
|
|
5
|
+
import { sleep } from "./sleep-CGBHjsj5.js";
|
|
6
|
+
import { findWorkspaceByKeyOrName, looksLikeUrlKey, resolveMcpUrl, scopedMcpUrl } from "./find-workspace-Bl4rfr73.js";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { createHash, randomBytes } from "node:crypto";
|
|
9
9
|
import { createServer } from "node:http";
|
|
@@ -103,6 +103,10 @@ function startCallbackServer(options = {}) {
|
|
|
103
103
|
resolveCode = resolve;
|
|
104
104
|
rejectCode = reject;
|
|
105
105
|
});
|
|
106
|
+
const shutdown = () => {
|
|
107
|
+
server.close();
|
|
108
|
+
server.closeAllConnections();
|
|
109
|
+
};
|
|
106
110
|
const server = createServer((req, res) => {
|
|
107
111
|
const url = new URL$1(req.url ?? "/", `http://127.0.0.1`);
|
|
108
112
|
if (url.pathname === "/callback") {
|
|
@@ -113,7 +117,7 @@ function startCallbackServer(options = {}) {
|
|
|
113
117
|
res.end(renderPage("error", "Authentication failed", "You can close this window.", error));
|
|
114
118
|
clearTimeout(timer);
|
|
115
119
|
rejectCode(/* @__PURE__ */ new Error(`OAuth error: ${error}`));
|
|
116
|
-
|
|
120
|
+
shutdown();
|
|
117
121
|
return;
|
|
118
122
|
}
|
|
119
123
|
if (code) {
|
|
@@ -123,14 +127,14 @@ function startCallbackServer(options = {}) {
|
|
|
123
127
|
res.end(renderPage("error", "Authentication failed", "You can close this window.", "Invalid state parameter."));
|
|
124
128
|
clearTimeout(timer);
|
|
125
129
|
rejectCode(/* @__PURE__ */ new Error("OAuth state mismatch (possible CSRF)"));
|
|
126
|
-
|
|
130
|
+
shutdown();
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
129
133
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
130
134
|
res.end(renderPage("success", "Authentication successful!", "You can close this window and return to the terminal."));
|
|
131
135
|
clearTimeout(timer);
|
|
132
136
|
resolveCode({ code });
|
|
133
|
-
|
|
137
|
+
shutdown();
|
|
134
138
|
return;
|
|
135
139
|
}
|
|
136
140
|
}
|
|
@@ -139,7 +143,7 @@ function startCallbackServer(options = {}) {
|
|
|
139
143
|
});
|
|
140
144
|
const timer = setTimeout(() => {
|
|
141
145
|
rejectCode(/* @__PURE__ */ new Error("Authentication timed out. No callback received."));
|
|
142
|
-
|
|
146
|
+
shutdown();
|
|
143
147
|
}, timeout);
|
|
144
148
|
server.listen(0, "127.0.0.1", () => {
|
|
145
149
|
const addr = server.address();
|
|
@@ -150,7 +154,7 @@ function startCallbackServer(options = {}) {
|
|
|
150
154
|
code: codePromise,
|
|
151
155
|
close: () => {
|
|
152
156
|
clearTimeout(timer);
|
|
153
|
-
|
|
157
|
+
shutdown();
|
|
154
158
|
}
|
|
155
159
|
};
|
|
156
160
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { deleteCredentials, getPatToken } from "./env-
|
|
2
|
-
import { clearWorkspace } from "./workspace-store-
|
|
1
|
+
import { deleteCredentials, getPatToken } from "./env-CvjMSRd7.js";
|
|
2
|
+
import { clearWorkspace } from "./workspace-store-Bmw104Qa.js";
|
|
3
3
|
|
|
4
4
|
//#region src/commands/auth/logoutHandler.ts
|
|
5
5
|
function logoutHandler() {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { client, configureClient, uploadImport } from "./sdk-client-
|
|
2
|
-
import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-
|
|
3
|
-
import "./token-refresh-
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
5
|
-
import "./workspace-store-
|
|
6
|
-
import { resolveWorkspace } from "./resolve-workspace-
|
|
7
|
-
import "./sleep-
|
|
8
|
-
import { resolveArtifactPath, uploadArtifactFile } from "./artifact-upload-
|
|
1
|
+
import { client, configureClient, uploadImport } from "./sdk-client-B4FMd_Fa.js";
|
|
2
|
+
import { getApiUrl, getAutomationSourceIdOverride, getSourceOverride, getTeamId } from "./env-CvjMSRd7.js";
|
|
3
|
+
import "./token-refresh-CJB7Qlxy.js";
|
|
4
|
+
import { resolveToken } from "./resolve-token-ByZq05Us.js";
|
|
5
|
+
import "./workspace-store-Bmw104Qa.js";
|
|
6
|
+
import { resolveWorkspace } from "./resolve-workspace-BbK3uYQc.js";
|
|
7
|
+
import "./sleep-CGBHjsj5.js";
|
|
8
|
+
import { resolveArtifactPath, uploadArtifactFile } from "./artifact-upload-BvBSevIx.js";
|
|
9
9
|
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { basename, dirname, resolve } from "node:path";
|
|
11
11
|
import { execSync } from "node:child_process";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getApiUrl, getPatToken, readCredentials } from "./env-
|
|
2
|
-
import { isTokenExpired, refreshToken } from "./token-refresh-
|
|
1
|
+
import { getApiUrl, getPatToken, readCredentials } from "./env-CvjMSRd7.js";
|
|
2
|
+
import { isTokenExpired, refreshToken } from "./token-refresh-CJB7Qlxy.js";
|
|
3
3
|
|
|
4
4
|
//#region src/auth/resolve-token.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { authGetSitesV1 } from "./sdk-client-
|
|
2
|
-
import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-
|
|
1
|
+
import { authGetSitesV1 } from "./sdk-client-B4FMd_Fa.js";
|
|
2
|
+
import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-Bmw104Qa.js";
|
|
3
3
|
|
|
4
4
|
//#region src/workspace/resolve-workspace.ts
|
|
5
5
|
async function fetchSites() {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./
|
|
3
|
-
import "./
|
|
4
|
-
import
|
|
5
|
-
import "./
|
|
6
|
-
import
|
|
1
|
+
import { executeInspect } from "./inspect-B8tG9g0q.js";
|
|
2
|
+
import { client, configureClient } from "./sdk-client-B4FMd_Fa.js";
|
|
3
|
+
import "./env-CvjMSRd7.js";
|
|
4
|
+
import "./token-refresh-CJB7Qlxy.js";
|
|
5
|
+
import { resolveToken } from "./resolve-token-ByZq05Us.js";
|
|
6
|
+
import "./workspace-store-Bmw104Qa.js";
|
|
7
|
+
import { resolveWorkspace } from "./resolve-workspace-BbK3uYQc.js";
|
|
7
8
|
import { existsSync } from "node:fs";
|
|
8
9
|
import { constants as constants$1 } from "node:os";
|
|
9
10
|
import { spawnSync } from "node:child_process";
|
|
@@ -56,8 +57,7 @@ const issueGateVerificationReportGateResultsV1 = (options) => {
|
|
|
56
57
|
* one the `report_gate_results` MCP tool takes):
|
|
57
58
|
*
|
|
58
59
|
* - each `local_commands[]` entry is run EXACTLY as returned, through
|
|
59
|
-
* `/bin/bash -c` (
|
|
60
|
-
* `chooseShell`), from the current directory — no rewriting and no
|
|
60
|
+
* `/bin/bash -c` (`chooseShell`), from the current directory — no rewriting and no
|
|
61
61
|
* splitting. The server decided what to run; this reports what happened.
|
|
62
62
|
* `levr gates run` prints each command first; there is no local
|
|
63
63
|
* allowlist — the server validates every command it hands out
|
|
@@ -76,13 +76,18 @@ const issueGateVerificationReportGateResultsV1 = (options) => {
|
|
|
76
76
|
* FAIL (0)`) hides the signals the server reads. Spawning the commands
|
|
77
77
|
* from this process means no proxy ever sees them.
|
|
78
78
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
79
|
+
* The qinetic-mcp executor used to split `&&` chains and rewrite paths for
|
|
80
|
+
* the Levr monorepo — the opposite of "exactly as returned" — and was
|
|
81
|
+
* retired (ENG-6175). The CLI reports each command whole.
|
|
82
|
+
*
|
|
83
|
+
* An INSPECT op (ENG-6179) is the one exception to "spawn it": its display
|
|
84
|
+
* command is `npx '@levr-one/cli@<spec>' gates inspect '<op json>'`, and
|
|
85
|
+
* spawning that would run whatever CLI version npm resolves for the pin,
|
|
86
|
+
* which may not have `gates inspect` at all. This CLI has it, so an inspect
|
|
87
|
+
* op runs IN PROCESS (`inspectOpOf` → `executeInspect`, through
|
|
88
|
+
* `@levr/shared`'s `GateOpSchema`, which the bundle inlines). The step
|
|
89
|
+
* still reports the command exactly as issued, with this run's exit code
|
|
90
|
+
* and output, so the server judges it the same way.
|
|
86
91
|
*/
|
|
87
92
|
/** The server's `GATE_STEP_TAIL_LIMIT` (`@levr/shared`), in chars. */
|
|
88
93
|
const GATE_STEP_TAIL_LIMIT = 4e3;
|
|
@@ -196,19 +201,168 @@ function toReportResult(command, outcome) {
|
|
|
196
201
|
};
|
|
197
202
|
}
|
|
198
203
|
/**
|
|
204
|
+
* The display form the server renders for an inspect op:
|
|
205
|
+
* `npx '@levr-one/cli@<spec>' gates inspect '<json>'` (POSIX quoting, where a
|
|
206
|
+
* `'` inside is written `'\''`). Also accepts a bare `levr gates inspect`.
|
|
207
|
+
*/
|
|
208
|
+
const INSPECT_DISPLAY_RE = /^\s*(?:npx(?:\s+--?[A-Za-z][\w-]*)*\s+(?:'@levr-one\/cli(?:@[^']*)?'|@levr-one\/cli(?:@\S*)?)|levr)\s+gates\s+inspect\s+'((?:[^']|'\\'')*)'\s*$/;
|
|
209
|
+
/**
|
|
210
|
+
* The inspect op JSON a local command carries, or `null` when it is not an
|
|
211
|
+
* inspect op. The envelope's `op` is authoritative; the display string is
|
|
212
|
+
* parsed only when the envelope is absent (an older server).
|
|
213
|
+
*/
|
|
214
|
+
function inspectOpOf(command) {
|
|
215
|
+
const op = command.op?.op;
|
|
216
|
+
if (op && typeof op === "object") return op.op === "inspect" ? JSON.stringify(op) : null;
|
|
217
|
+
const match = INSPECT_DISPLAY_RE.exec(command.command);
|
|
218
|
+
if (!match) return null;
|
|
219
|
+
return (match[1] ?? "").replace(/'\\''/g, "'");
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Where a command runs: inspect ops in this process, everything else in the
|
|
223
|
+
* shell `chooseShell` picks. Shown by `--dry-run` so a reader knows which
|
|
224
|
+
* printed `npx … gates inspect` lines never spawn anything.
|
|
225
|
+
*/
|
|
226
|
+
function runsWhere(command) {
|
|
227
|
+
return inspectOpOf(command) === null ? "shell" : "in-process";
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The server's approval for a command (`server_approved`, `confirm`,
|
|
231
|
+
* `denied`) and why, from the op envelope; `undefined` from an older server.
|
|
232
|
+
* Display only: nothing here asks for a confirmation yet (that is D10,
|
|
233
|
+
* ENG-6183), so a `confirm` command still runs.
|
|
234
|
+
*/
|
|
235
|
+
function approvalOf(command) {
|
|
236
|
+
const approval = command.op?.["approval"];
|
|
237
|
+
if (typeof approval !== "string") return void 0;
|
|
238
|
+
const basis = command.op?.["approval_basis"];
|
|
239
|
+
return typeof basis === "string" ? {
|
|
240
|
+
approval,
|
|
241
|
+
approval_basis: basis
|
|
242
|
+
} : { approval };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* True when at least one command must be spawned in a shell (anything that
|
|
246
|
+
* is not an inspect op), so a missing shell matters.
|
|
247
|
+
*/
|
|
248
|
+
function needsShell(commands) {
|
|
249
|
+
return commands.some((command) => inspectOpOf(command) === null);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
199
252
|
* Run every command, in order, and build the `results[]` payload.
|
|
200
253
|
* `onBefore` is called BEFORE each command runs (to print it), `onEach`
|
|
201
254
|
* after, with its result.
|
|
202
255
|
*/
|
|
203
|
-
function runLocalCommands(commands, run = runInShell, onEach, onBefore) {
|
|
256
|
+
function runLocalCommands(commands, run = runInShell, onEach, onBefore, inspect = executeInspect) {
|
|
204
257
|
return commands.map((command, index) => {
|
|
205
258
|
onBefore?.(command, index);
|
|
206
|
-
const
|
|
259
|
+
const opJson = inspectOpOf(command);
|
|
260
|
+
const result = toReportResult(command, opJson !== null ? inspect(opJson) : run(command.command, commandTimeoutMs(command)));
|
|
207
261
|
onEach?.(command, result);
|
|
208
262
|
return result;
|
|
209
263
|
});
|
|
210
264
|
}
|
|
211
265
|
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/commands/gates/ci-oidc.ts
|
|
268
|
+
/**
|
|
269
|
+
* GitHub Actions OIDC for `levr gates run --ci` (ENG-6177).
|
|
270
|
+
*
|
|
271
|
+
* The workflow needs `permissions: id-token: write` (documented with the
|
|
272
|
+
* pin in D13). When the runner env vars are absent, the CLI still declares
|
|
273
|
+
* CI and the server records `unverified` — it never invents a token.
|
|
274
|
+
*
|
|
275
|
+
* `fail_retry`: a report or verify that answers `OIDC_JWKS_UNAVAILABLE`
|
|
276
|
+
* (503) is retried twice with backoff, then the command exits non-zero.
|
|
277
|
+
* Nothing was recorded for those attempts.
|
|
278
|
+
*
|
|
279
|
+
* ENG-5987 review H7: the token is minted for ONE workspace
|
|
280
|
+
* (`levr:<workspace_id>`, matching the server's `ciOidcAudience`), is
|
|
281
|
+
* requested fresh right before each verify and report (an Actions token lives
|
|
282
|
+
* minutes, and a long suite outlived the one fetched at start-up), and the
|
|
283
|
+
* request is bounded by a timeout so a hung token endpoint cannot hang CI.
|
|
284
|
+
* The run's commit (`GITHUB_SHA`) is sent as `commit_sha`, which the server
|
|
285
|
+
* now requires before it will stamp `oidc_verified`.
|
|
286
|
+
*/
|
|
287
|
+
const CI_OIDC_HEADER = "X-Levr-CI-OIDC";
|
|
288
|
+
const CI_DECLARED_HEADER = "X-Levr-CI-Declared";
|
|
289
|
+
const OIDC_JWKS_UNAVAILABLE = "OIDC_JWKS_UNAVAILABLE";
|
|
290
|
+
const RETRY_DELAYS_MS = [250, 750];
|
|
291
|
+
const TOKEN_REQUEST_TIMEOUT_MS = 1e4;
|
|
292
|
+
const COMMIT_SHA = /^[0-9a-f]{40}$/i;
|
|
293
|
+
/** The audience the server expects — `levr:<workspace_id>`. */
|
|
294
|
+
function ciOidcAudience(workspaceId) {
|
|
295
|
+
return `levr:${workspaceId}`;
|
|
296
|
+
}
|
|
297
|
+
/** The commit this Actions run checked out, when the runner says so. */
|
|
298
|
+
function ciCommitSha(env = process.env) {
|
|
299
|
+
const sha = env["GITHUB_SHA"]?.trim();
|
|
300
|
+
return sha && COMMIT_SHA.test(sha) ? sha : void 0;
|
|
301
|
+
}
|
|
302
|
+
function actionsOidcEnvFrom(env = process.env) {
|
|
303
|
+
return {
|
|
304
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: env["ACTIONS_ID_TOKEN_REQUEST_URL"],
|
|
305
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Request an Actions OIDC token for `audience`. Returns null when the runner
|
|
310
|
+
* did not expose the env vars, the request failed, or it did not answer
|
|
311
|
+
* within `timeoutMs` — the caller still sends the declaration header, and
|
|
312
|
+
* the server records `unverified`.
|
|
313
|
+
*/
|
|
314
|
+
async function requestActionsOidcToken(audience, env = actionsOidcEnvFrom(), fetchImpl = globalThis.fetch, timeoutMs = TOKEN_REQUEST_TIMEOUT_MS) {
|
|
315
|
+
const url = env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
316
|
+
const requestToken = env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
317
|
+
if (!url || !requestToken) return null;
|
|
318
|
+
let endpoint;
|
|
319
|
+
try {
|
|
320
|
+
endpoint = new URL(url);
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
endpoint.searchParams.set("audience", audience);
|
|
325
|
+
try {
|
|
326
|
+
const response = await fetchImpl(endpoint, {
|
|
327
|
+
headers: { Authorization: `bearer ${requestToken}` },
|
|
328
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
329
|
+
});
|
|
330
|
+
if (!response.ok) return null;
|
|
331
|
+
const body = await response.json();
|
|
332
|
+
return typeof body.value === "string" && body.value.length > 0 ? body.value : null;
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/** Headers for verify and report. Declaration is `1` whenever `--ci` is set. */
|
|
338
|
+
function ciRequestHeaders(token) {
|
|
339
|
+
const headers = { [CI_DECLARED_HEADER]: "1" };
|
|
340
|
+
if (token) headers[CI_OIDC_HEADER] = token;
|
|
341
|
+
return headers;
|
|
342
|
+
}
|
|
343
|
+
function isOidcJwksUnavailable(result) {
|
|
344
|
+
if (result.response?.status !== 503) return false;
|
|
345
|
+
return JSON.stringify(result.error ?? "").includes(OIDC_JWKS_UNAVAILABLE);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Run `fn` and, on `OIDC_JWKS_UNAVAILABLE`, retry twice. Any other result
|
|
349
|
+
* is returned as-is. `fail_retry(2)`.
|
|
350
|
+
*/
|
|
351
|
+
async function withOidcJwksRetry(fn, sleep = delay) {
|
|
352
|
+
let result = await fn();
|
|
353
|
+
for (const wait of RETRY_DELAYS_MS) {
|
|
354
|
+
if (!isOidcJwksUnavailable(result)) return result;
|
|
355
|
+
await sleep(wait);
|
|
356
|
+
result = await fn();
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
function delay(ms) {
|
|
361
|
+
return new Promise((resolve) => {
|
|
362
|
+
setTimeout(resolve, ms);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
212
366
|
//#endregion
|
|
213
367
|
//#region src/commands/gates/runHandler.ts
|
|
214
368
|
/** ENG-6070 review R5 M3 — dead gates are named, with their remedy. */
|
|
@@ -225,11 +379,13 @@ function exitCodeOf(summary) {
|
|
|
225
379
|
}
|
|
226
380
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
227
381
|
/**
|
|
228
|
-
* Seams for tests: the shell runner (production: `chooseShell`'s bash),
|
|
229
|
-
*
|
|
382
|
+
* Seams for tests: the shell runner (production: `chooseShell`'s bash), the
|
|
383
|
+
* in-process inspect runner (ENG-6180 review C1), and the shell resolution
|
|
384
|
+
* checked BEFORE anything runs (F-020).
|
|
230
385
|
*/
|
|
231
386
|
const gatesRunDeps = {
|
|
232
387
|
run: runInShell,
|
|
388
|
+
inspect: (opJson) => executeInspect(opJson),
|
|
233
389
|
resolveShell: () => chooseShell()
|
|
234
390
|
};
|
|
235
391
|
/**
|
|
@@ -263,8 +419,21 @@ async function gatesRunHandler(flags, issue) {
|
|
|
263
419
|
return;
|
|
264
420
|
}
|
|
265
421
|
configureClient(auth);
|
|
422
|
+
const setCiHeaders = (oidc) => {
|
|
423
|
+
const current = client.getConfig();
|
|
424
|
+
client.setConfig({
|
|
425
|
+
...current,
|
|
426
|
+
headers: {
|
|
427
|
+
...current.headers,
|
|
428
|
+
...ciRequestHeaders(oidc)
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
};
|
|
432
|
+
if (flags.ci) setCiHeaders(null);
|
|
433
|
+
let workspaceId;
|
|
266
434
|
if (auth.type === "jwt") try {
|
|
267
435
|
const ws = await resolveWorkspace(flags["workspace-id"]);
|
|
436
|
+
workspaceId = ws.workspaceId;
|
|
268
437
|
client.setConfig({
|
|
269
438
|
...client.getConfig(),
|
|
270
439
|
workspaceId: ws.workspaceId
|
|
@@ -287,8 +456,23 @@ async function gatesRunHandler(flags, issue) {
|
|
|
287
456
|
return;
|
|
288
457
|
}
|
|
289
458
|
issueId = row.id;
|
|
459
|
+
workspaceId ??= row.workspace_id;
|
|
290
460
|
}
|
|
291
|
-
const
|
|
461
|
+
const commitSha = flags.ci ? ciCommitSha() : void 0;
|
|
462
|
+
const refreshCiToken = async () => {
|
|
463
|
+
if (!flags.ci) return;
|
|
464
|
+
if (!workspaceId) workspaceId = (await issueFindAllV1({ query: {
|
|
465
|
+
"filter.id": [`$eq:${issueId}`],
|
|
466
|
+
limit: 1
|
|
467
|
+
} })).data?.data?.[0]?.workspace_id;
|
|
468
|
+
setCiHeaders(workspaceId ? await requestActionsOidcToken(ciOidcAudience(workspaceId)) : null);
|
|
469
|
+
};
|
|
470
|
+
const ciBody = commitSha ? { commit_sha: commitSha } : {};
|
|
471
|
+
await refreshCiToken();
|
|
472
|
+
const verified = await withOidcJwksRetry(() => issueGateVerificationVerifyGatesV1({
|
|
473
|
+
path: { id: issueId },
|
|
474
|
+
...commitSha ? { body: ciBody } : {}
|
|
475
|
+
}));
|
|
292
476
|
if (verified.error) {
|
|
293
477
|
this.logger.error(`verify-gates failed (${String(verified.response?.status ?? "unknown")}): ${JSON.stringify(verified.error)}`);
|
|
294
478
|
this.process.exitCode = 1;
|
|
@@ -316,6 +500,8 @@ async function gatesRunHandler(flags, issue) {
|
|
|
316
500
|
gate_type: c.gate_type,
|
|
317
501
|
link_id: c.link_id,
|
|
318
502
|
command: c.command,
|
|
503
|
+
runs: runsWhere(c),
|
|
504
|
+
...approvalOf(c),
|
|
319
505
|
...c.expected !== void 0 ? { expected: c.expected } : {},
|
|
320
506
|
...c.step_id ? { step_id: c.step_id } : {},
|
|
321
507
|
...c.rule_hash ? { rule_hash: c.rule_hash } : {}
|
|
@@ -338,7 +524,7 @@ async function gatesRunHandler(flags, issue) {
|
|
|
338
524
|
if (!flags.json) printManualTasks(out, tasks);
|
|
339
525
|
return;
|
|
340
526
|
}
|
|
341
|
-
try {
|
|
527
|
+
if (needsShell(commands)) try {
|
|
342
528
|
gatesRunDeps.resolveShell();
|
|
343
529
|
} catch (error) {
|
|
344
530
|
if (!(error instanceof NoPosixShellError)) throw error;
|
|
@@ -352,11 +538,15 @@ async function gatesRunHandler(flags, issue) {
|
|
|
352
538
|
out(` [exit ${result.steps[0].exit_code}]`);
|
|
353
539
|
}, (command, index) => {
|
|
354
540
|
announce(`[${index + 1}/${commands.length}] ${command.test_name ?? command.gate_type}: ${command.command}`);
|
|
355
|
-
});
|
|
356
|
-
|
|
541
|
+
}, gatesRunDeps.inspect);
|
|
542
|
+
await refreshCiToken();
|
|
543
|
+
const reported = await withOidcJwksRetry(() => issueGateVerificationReportGateResultsV1({
|
|
357
544
|
path: { id: issueId },
|
|
358
|
-
body: {
|
|
359
|
-
|
|
545
|
+
body: {
|
|
546
|
+
results,
|
|
547
|
+
...ciBody
|
|
548
|
+
}
|
|
549
|
+
}));
|
|
360
550
|
if (reported.error) {
|
|
361
551
|
this.logger.error(`gate-results failed (${String(reported.response?.status ?? "unknown")}): ${JSON.stringify(reported.error)}`);
|
|
362
552
|
this.process.exitCode = 1;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { authGetSitesV1, configureClient } from "./sdk-client-
|
|
2
|
-
import "./env-
|
|
3
|
-
import "./token-refresh-
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
5
|
-
import { saveWorkspace } from "./workspace-store-
|
|
1
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-B4FMd_Fa.js";
|
|
2
|
+
import "./env-CvjMSRd7.js";
|
|
3
|
+
import "./token-refresh-CJB7Qlxy.js";
|
|
4
|
+
import { resolveToken } from "./resolve-token-ByZq05Us.js";
|
|
5
|
+
import { saveWorkspace } from "./workspace-store-Bmw104Qa.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/selectHandler.ts
|
|
8
8
|
async function selectHandler(_flags, workspaceId) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { authGetProfileV1, configureClient } from "./sdk-client-
|
|
2
|
-
import { getApiUrl, getPatToken, readCredentials } from "./env-
|
|
3
|
-
import { isTokenExpired } from "./token-refresh-
|
|
1
|
+
import { authGetProfileV1, configureClient } from "./sdk-client-B4FMd_Fa.js";
|
|
2
|
+
import { getApiUrl, getPatToken, readCredentials } from "./env-CvjMSRd7.js";
|
|
3
|
+
import { isTokenExpired } from "./token-refresh-CJB7Qlxy.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
|
|
6
6
|
//#region src/commands/auth/statusHandler.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-
|
|
1
|
+
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-CvjMSRd7.js";
|
|
2
2
|
|
|
3
3
|
//#region src/auth/token-refresh.ts
|
|
4
4
|
const REFRESH_BUFFER_MS = 300 * 1e3;
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|