@levr-one/cli 0.12.4 → 0.12.6
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-LO6SChns.js → addHandler-ybX54_3f.js} +6 -6
- package/dist/{appendHandler-DtxLpPu6.js → appendHandler-CKkl9ymw.js} +7 -7
- package/dist/{artifact-upload-BvBSevIx.js → artifact-upload-Ba801tb6.js} +1 -1
- package/dist/{attachHandler-DXZi2ILT.js → attachHandler-CfNvVAV2.js} +7 -7
- package/dist/cli.js +77 -18
- package/dist/{connect-DVjgyfls.js → connect-yq3fGXkW.js} +9 -8
- package/dist/{find-workspace-Bl4rfr73.js → find-workspace-4Us2vEb7.js} +1 -1
- package/dist/{importHandler-uFdalKtw.js → importHandler-bYBWYC_0.js} +8 -8
- package/dist/{listHandler-BW92JLCl.js → listHandler-Du_YGzre.js} +4 -4
- package/dist/{loginHandler-C9Oi92IY.js → loginHandler-PbKmDAGo.js} +4 -4
- package/dist/{logoutHandler-Cf5aI1wq.js → logoutHandler-DxnzGPYx.js} +1 -1
- package/dist/{pushHandler-CXFZt3SO.js → pushHandler-BulraEwP.js} +15 -13
- package/dist/refsHandler-DtcceTRP.js +1757 -0
- package/dist/{resolve-token-ByZq05Us.js → resolve-token-DKEXT7SA.js} +8 -5
- package/dist/{resolve-workspace-BbK3uYQc.js → resolve-workspace-D8y7twod.js} +37 -5
- package/dist/{runHandler-IyPhhiGs.js → runHandler-B-u-0eRk.js} +25 -13
- package/dist/{sdk-client-B4FMd_Fa.js → sdk-client-BgzKzVIR.js} +1 -1
- package/dist/{selectHandler-D6cvq60S.js → selectHandler-C0gs-Q70.js} +9 -4
- package/dist/{statusHandler-BQXn93-r.js → statusHandler-Dnnh80ZN.js} +7 -5
- package/dist/{token-refresh-CJB7Qlxy.js → token-refresh-BpFuU4Ij.js} +26 -2
- package/package.json +1 -1
- /package/dist/{currentHandler-QhCywr3S.js → currentHandler-PaLZKLGF.js} +0 -0
- /package/dist/{env-CvjMSRd7.js → env-DjeRwmZ7.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { classifyToken, isTokenExpired, refreshToken } from "./token-refresh-BpFuU4Ij.js";
|
|
2
|
+
import { getApiUrl, getPatToken, readCredentials } from "./env-DjeRwmZ7.js";
|
|
3
3
|
|
|
4
4
|
//#region src/auth/resolve-token.ts
|
|
5
5
|
/**
|
|
@@ -16,7 +16,8 @@ var CredentialsMismatchError = class extends Error {
|
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* Resolve auth token in priority order:
|
|
19
|
-
* 1. LEVR_TOKEN env var
|
|
19
|
+
* 1. LEVR_TOKEN env var — a PAT or an OAuth access token, told apart by
|
|
20
|
+
* shape (ENG-6068); never refreshed
|
|
20
21
|
* 2. Stored credentials (JWT) — auto-refresh if expired
|
|
21
22
|
* 3. Error — not authenticated
|
|
22
23
|
*/
|
|
@@ -24,7 +25,8 @@ async function resolveToken() {
|
|
|
24
25
|
const pat = getPatToken();
|
|
25
26
|
if (pat) return {
|
|
26
27
|
token: pat,
|
|
27
|
-
type:
|
|
28
|
+
type: classifyToken(pat),
|
|
29
|
+
source: "env"
|
|
28
30
|
};
|
|
29
31
|
let creds = readCredentials();
|
|
30
32
|
if (creds) {
|
|
@@ -38,7 +40,8 @@ async function resolveToken() {
|
|
|
38
40
|
}
|
|
39
41
|
return {
|
|
40
42
|
token: creds.access_token,
|
|
41
|
-
type: "jwt"
|
|
43
|
+
type: "jwt",
|
|
44
|
+
source: "stored"
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
throw new Error("Not authenticated. Run 'levr auth login' or set LEVR_TOKEN environment variable.");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { authGetSitesV1 } from "./sdk-client-
|
|
1
|
+
import { authGetSitesV1 } from "./sdk-client-BgzKzVIR.js";
|
|
2
2
|
import { clearWorkspace, loadWorkspace, saveWorkspace } from "./workspace-store-Bmw104Qa.js";
|
|
3
3
|
|
|
4
4
|
//#region src/workspace/resolve-workspace.ts
|
|
@@ -40,7 +40,8 @@ async function autoSelectWorkspace() {
|
|
|
40
40
|
};
|
|
41
41
|
return { kind: "none" };
|
|
42
42
|
}
|
|
43
|
-
async function resolveWorkspace(flagValue) {
|
|
43
|
+
async function resolveWorkspace(flagValue, options = {}) {
|
|
44
|
+
const persist = options.persist ?? true;
|
|
44
45
|
let sites = null;
|
|
45
46
|
const getSites = async () => {
|
|
46
47
|
if (!sites) sites = await fetchSites();
|
|
@@ -61,7 +62,7 @@ async function resolveWorkspace(flagValue) {
|
|
|
61
62
|
source: "env"
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
|
-
const cached = loadWorkspace();
|
|
65
|
+
const cached = persist ? loadWorkspace() : null;
|
|
65
66
|
if (cached) {
|
|
66
67
|
if ((await getSites()).some((site) => site.workspace_id === cached)) return {
|
|
67
68
|
workspaceId: cached,
|
|
@@ -73,7 +74,7 @@ async function resolveWorkspace(flagValue) {
|
|
|
73
74
|
if (s.length === 0) throw new Error("No workspaces available.");
|
|
74
75
|
if (s.length === 1) {
|
|
75
76
|
const single = s[0];
|
|
76
|
-
saveWorkspace(single.workspace_id);
|
|
77
|
+
if (persist) saveWorkspace(single.workspace_id);
|
|
77
78
|
return {
|
|
78
79
|
workspaceId: single.workspace_id,
|
|
79
80
|
source: "auto"
|
|
@@ -84,6 +85,37 @@ async function resolveWorkspace(flagValue) {
|
|
|
84
85
|
const overflow = s.length > maxList ? `\n ... and ${s.length - maxList} more` : "";
|
|
85
86
|
throw new Error(`Multiple workspaces. Select one:\n${list}${overflow}\n\nRun: levr workspace select <id>`);
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The workspace a command runs in, for whichever credential it holds —
|
|
90
|
+
* [issue:ENG-6068].
|
|
91
|
+
*
|
|
92
|
+
* A JWT is not bound to a workspace, so one is resolved the usual way: the
|
|
93
|
+
* flag, `LEVR_WORKSPACE_ID`, the cached selection, then auto-select, each
|
|
94
|
+
* validated against the sites the account can see.
|
|
95
|
+
*
|
|
96
|
+
* A personal access token IS bound to one workspace, and the server scopes
|
|
97
|
+
* every request by it. An explicit `--workspace-id` or `LEVR_WORKSPACE_ID` is
|
|
98
|
+
* still sent as `Workspace-Id` — the server refuses one that is not the
|
|
99
|
+
* token's — but nothing is looked up or cached, and with neither set the
|
|
100
|
+
* answer is null: the token's own workspace applies. Until ENG-6068 both were
|
|
101
|
+
* silently dropped for every `LEVR_TOKEN`, including an OAuth access token,
|
|
102
|
+
* which was classified as a PAT by where it came from and has no workspace to
|
|
103
|
+
* fall back to. An OAuth token from LEVR_TOKEN is not the login session, so
|
|
104
|
+
* the saved workspace choice is left alone for it (`persist: false`).
|
|
105
|
+
*/
|
|
106
|
+
async function resolveWorkspaceFor(auth, flagValue) {
|
|
107
|
+
if (auth.type === "jwt") return resolveWorkspace(flagValue, { persist: auth.source !== "env" });
|
|
108
|
+
if (flagValue) return {
|
|
109
|
+
workspaceId: flagValue,
|
|
110
|
+
source: "flag"
|
|
111
|
+
};
|
|
112
|
+
const envWs = process.env["LEVR_WORKSPACE_ID"];
|
|
113
|
+
if (envWs) return {
|
|
114
|
+
workspaceId: envWs,
|
|
115
|
+
source: "env"
|
|
116
|
+
};
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
87
119
|
|
|
88
120
|
//#endregion
|
|
89
|
-
export { autoSelectWorkspace, fetchSites,
|
|
121
|
+
export { autoSelectWorkspace, fetchSites, resolveWorkspaceFor };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { executeInspect } from "./inspect-BbL7kDe-.js";
|
|
2
|
-
import { client, configureClient } from "./sdk-client-
|
|
3
|
-
import "./
|
|
4
|
-
import "./
|
|
5
|
-
import { resolveToken } from "./resolve-token-
|
|
2
|
+
import { client, configureClient } from "./sdk-client-BgzKzVIR.js";
|
|
3
|
+
import "./token-refresh-BpFuU4Ij.js";
|
|
4
|
+
import "./env-DjeRwmZ7.js";
|
|
5
|
+
import { resolveToken } from "./resolve-token-DKEXT7SA.js";
|
|
6
6
|
import "./workspace-store-Bmw104Qa.js";
|
|
7
|
-
import {
|
|
7
|
+
import { resolveWorkspaceFor } from "./resolve-workspace-D8y7twod.js";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { constants as constants$1 } from "node:os";
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
@@ -365,6 +365,15 @@ function delay(ms) {
|
|
|
365
365
|
|
|
366
366
|
//#endregion
|
|
367
367
|
//#region src/commands/gates/runHandler.ts
|
|
368
|
+
/** The server's own words for a failed SDK call, for an error line. */
|
|
369
|
+
function describeSdkError(error) {
|
|
370
|
+
if (typeof error === "string") return error.length > 200 ? `${error.slice(0, 200)}…` : error;
|
|
371
|
+
if (!error || typeof error !== "object") return "request failed";
|
|
372
|
+
const e = error;
|
|
373
|
+
const message = Array.isArray(e.message) ? e.message.map(String).join("; ") : typeof e.message === "string" ? e.message : typeof e.error === "string" ? e.error : "";
|
|
374
|
+
const text = [typeof e.statusCode === "number" ? `HTTP ${e.statusCode}` : "", message].filter(Boolean).join(": ") || "request failed";
|
|
375
|
+
return text.length > 200 ? `${text.slice(0, 200)}…` : text;
|
|
376
|
+
}
|
|
368
377
|
/** ENG-6070 review R5 M3 — dead gates are named, with their remedy. */
|
|
369
378
|
function printDeadGates(out, dead) {
|
|
370
379
|
for (const d of dead ?? []) out(` DEAD ${d.test_name ? `"${d.test_name}"` : d.link_id}${d.required === false ? " (advisory)" : ""}: ${d.reason === "test_deleted" ? "its test was deleted" : "the gate was removed"}${d.remedy ? ` — ${d.remedy}` : ""}`);
|
|
@@ -431,13 +440,15 @@ async function gatesRunHandler(flags, issue) {
|
|
|
431
440
|
};
|
|
432
441
|
if (flags.ci) setCiHeaders(null);
|
|
433
442
|
let workspaceId;
|
|
434
|
-
|
|
435
|
-
const ws = await
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
443
|
+
try {
|
|
444
|
+
const ws = await resolveWorkspaceFor(auth, flags["workspace-id"]);
|
|
445
|
+
if (ws) {
|
|
446
|
+
workspaceId = ws.workspaceId;
|
|
447
|
+
client.setConfig({
|
|
448
|
+
...client.getConfig(),
|
|
449
|
+
workspaceId: ws.workspaceId
|
|
450
|
+
});
|
|
451
|
+
}
|
|
441
452
|
} catch (err) {
|
|
442
453
|
this.logger.error(err instanceof Error ? err.message : "Workspace resolution failed.");
|
|
443
454
|
this.process.exitCode = 1;
|
|
@@ -451,7 +462,8 @@ async function gatesRunHandler(flags, issue) {
|
|
|
451
462
|
} });
|
|
452
463
|
const row = found.data?.data?.[0];
|
|
453
464
|
if (found.error || !row) {
|
|
454
|
-
|
|
465
|
+
const where = workspaceId ? `workspace ${workspaceId}` : "the token's workspace";
|
|
466
|
+
this.logger.error(found.error ? `Could not look up issue ${issue} in ${where} (${describeSdkError(found.error)}).` : `Issue ${issue} not found in ${where}.`);
|
|
455
467
|
this.process.exitCode = 1;
|
|
456
468
|
return;
|
|
457
469
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { authGetSitesV1, configureClient } from "./sdk-client-
|
|
2
|
-
import "./
|
|
3
|
-
import "./
|
|
4
|
-
import { resolveToken } from "./resolve-token-
|
|
1
|
+
import { authGetSitesV1, configureClient } from "./sdk-client-BgzKzVIR.js";
|
|
2
|
+
import "./token-refresh-BpFuU4Ij.js";
|
|
3
|
+
import "./env-DjeRwmZ7.js";
|
|
4
|
+
import { resolveToken } from "./resolve-token-DKEXT7SA.js";
|
|
5
5
|
import { saveWorkspace } from "./workspace-store-Bmw104Qa.js";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/workspace/selectHandler.ts
|
|
@@ -19,6 +19,11 @@ async function selectHandler(_flags, workspaceId) {
|
|
|
19
19
|
this.process.exitCode = 1;
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
+
if (auth.source === "env") {
|
|
23
|
+
this.logger.error("LEVR_TOKEN is set: a workspace selected here would be saved for your login session, not for that token. Pass --workspace-id or set LEVR_WORKSPACE_ID instead.");
|
|
24
|
+
this.process.exitCode = 1;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
22
27
|
configureClient(auth);
|
|
23
28
|
const result = await authGetSitesV1();
|
|
24
29
|
if (result.error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { authGetProfileV1, configureClient } from "./sdk-client-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { authGetProfileV1, configureClient } from "./sdk-client-BgzKzVIR.js";
|
|
2
|
+
import { classifyToken, isTokenExpired } from "./token-refresh-BpFuU4Ij.js";
|
|
3
|
+
import { getApiUrl, getPatToken, readCredentials } from "./env-DjeRwmZ7.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
|
|
6
6
|
//#region src/commands/auth/statusHandler.ts
|
|
@@ -8,8 +8,10 @@ async function statusHandler() {
|
|
|
8
8
|
const apiUrl = getApiUrl();
|
|
9
9
|
const pat = getPatToken();
|
|
10
10
|
if (pat) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const type = classifyToken(pat);
|
|
12
|
+
const kind = type === "pat" ? "personal access token" : "OAuth access token";
|
|
13
|
+
if (await checkApiReachable(pat, type)) {
|
|
14
|
+
this.process.stdout.write(`${chalk.green("ok")} Authenticated via LEVR_TOKEN (${kind})\n`);
|
|
13
15
|
this.process.stdout.write(` API: ${apiUrl} (reachable)\n`);
|
|
14
16
|
} else {
|
|
15
17
|
this.process.stdout.write(`${chalk.yellow("warn")} LEVR_TOKEN is set but API is unreachable\n`);
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-
|
|
1
|
+
import { CLI_CLIENT_ID, deleteCredentials, getApiUrl, writeCredentials } from "./env-DjeRwmZ7.js";
|
|
2
2
|
|
|
3
|
+
//#region src/auth/token-shape.ts
|
|
4
|
+
/**
|
|
5
|
+
* Classify a bearer token by its SHAPE — [issue:ENG-6068].
|
|
6
|
+
*
|
|
7
|
+
* The server decides how to verify a token by looking at the token, not at
|
|
8
|
+
* where it came from (`isPersonalAccessToken` in the backend's auth
|
|
9
|
+
* middleware): a Levr personal access token starts with `tq_`, and anything
|
|
10
|
+
* else is handed to the JWT verifier. `LEVR_TOKEN` carries either kind — a
|
|
11
|
+
* PAT minted in Settings, or an OAuth access token a device-code login
|
|
12
|
+
* printed for CI — so the CLI must apply the same rule. Until ENG-6068 every
|
|
13
|
+
* `LEVR_TOKEN` was called a PAT, and the JWT-only steps (resolving
|
|
14
|
+
* `--workspace-id` / `LEVR_WORKSPACE_ID`, `levr workspace list|select`) were
|
|
15
|
+
* silently skipped for an OAuth token, which has no workspace of its own.
|
|
16
|
+
*
|
|
17
|
+
* Leaf module on purpose: no imports, so `resolve-token`, the handlers and
|
|
18
|
+
* their tests can all read it without pulling in credentials or the SDK.
|
|
19
|
+
*/
|
|
20
|
+
const PAT_PREFIX = "tq_";
|
|
21
|
+
/** `tq_…` is a personal access token; everything else is treated as a JWT. */
|
|
22
|
+
function classifyToken(token) {
|
|
23
|
+
return token.startsWith(PAT_PREFIX) ? "pat" : "jwt";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
3
27
|
//#region src/auth/token-refresh.ts
|
|
4
28
|
const REFRESH_BUFFER_MS = 300 * 1e3;
|
|
5
29
|
/**
|
|
@@ -45,4 +69,4 @@ async function refreshToken(creds) {
|
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
//#endregion
|
|
48
|
-
export { isTokenExpired, refreshToken };
|
|
72
|
+
export { classifyToken, isTokenExpired, refreshToken };
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|