@levr-one/cli 0.12.8 → 0.12.10
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-ybX54_3f.js → addHandler-CD1pda0z.js} +7 -7
- package/dist/{appendHandler-CKkl9ymw.js → appendHandler-BYOoCNXJ.js} +9 -9
- package/dist/{artifact-upload-Ba801tb6.js → artifact-upload-t6kL20b5.js} +2 -2
- package/dist/{attachHandler-CfNvVAV2.js → attachHandler-Ojk7mc2E.js} +9 -9
- package/dist/bindingsHandler-Dxx2LIci.js +2781 -0
- package/dist/change-scope-DBmyRWZV.js +379 -0
- package/dist/changedCoverageHandler-B8Zh5WIK.js +200 -0
- package/dist/cli.js +300 -15
- package/dist/{connect-yq3fGXkW.js → connect-A5VSJUv5.js} +3 -3
- package/dist/coverage-defaults-Ct3KNnGl.js +24 -0
- package/dist/{currentHandler-PaLZKLGF.js → currentHandler-DmffKHyZ.js} +1 -1
- package/dist/dependencyAuditHandler-CdpW-IvY.js +291 -0
- package/dist/dist-DU3htaZ_.js +325 -0
- package/dist/{find-workspace-4Us2vEb7.js → find-workspace-IWolSyrf.js} +1 -1
- package/dist/gate-op-BINrIT2l.js +278 -0
- package/dist/{importHandler-bYBWYC_0.js → importHandler-86cjc2Zs.js} +6 -6
- package/dist/inspect-CAxNgTwj.js +4 -0
- package/dist/{inspect-BbL7kDe-.js → inspect-CinRBSv6.js} +2 -277
- package/dist/{listHandler-Du_YGzre.js → listHandler-BoCzGUDK.js} +5 -5
- package/dist/{loginHandler-PbKmDAGo.js → loginHandler-CXmNViCo.js} +6 -6
- package/dist/{logoutHandler-DxnzGPYx.js → logoutHandler-DwTURB_A.js} +2 -2
- package/dist/{pushHandler-BulraEwP.js → pushHandler-BkCmzQz7.js} +9 -330
- package/dist/{resolve-token-DKEXT7SA.js → resolve-token-D1zteEfm.js} +2 -2
- package/dist/{resolve-workspace-D8y7twod.js → resolve-workspace-CJN1plpd.js} +2 -2
- package/dist/{runHandler-B-u-0eRk.js → runHandler-BLkPzImG.js} +8 -7
- package/dist/{sdk-client-BgzKzVIR.js → sdk-client-CSy2LOEh.js} +1 -1
- package/dist/{selectHandler-C0gs-Q70.js → selectHandler-Dp74pefc.js} +5 -5
- package/dist/{statusHandler-Dnnh80ZN.js → statusHandler-ByTUMdKK.js} +3 -3
- package/dist/{token-refresh-BpFuU4Ij.js → token-refresh-DEmYbJ8V.js} +1 -1
- package/package.json +1 -1
- package/dist/inspect-EGW-cgVD.js +0 -3
- /package/dist/{env-DjeRwmZ7.js → env-Cv-anxJA.js} +0 -0
- /package/dist/{refsHandler--ySvLjmX.js → refsHandler-BPlcdJvR.js} +0 -0
- /package/dist/{sleep-CGBHjsj5.js → sleep-DNLj0FMk.js} +0 -0
- /package/dist/{workspace-store-Bmw104Qa.js → workspace-store-BJyB_WEG.js} +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import "./dist-DU3htaZ_.js";
|
|
2
|
+
import { GitError, NoMergeBaseError, computeChangeScope, readAtBase, runGit } from "./change-scope-DBmyRWZV.js";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path$1 from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/gates/dependencyAuditHandler.ts
|
|
8
|
+
/**
|
|
9
|
+
* `levr gates dependency-audit` (ENG-7618 D4.3): vulnerabilities and
|
|
10
|
+
* disallowed licenses in only the packages the change ADDED or UPGRADED. The
|
|
11
|
+
* `dependency_audit` gate runs it; whole-repo scanning stays in CI.
|
|
12
|
+
*
|
|
13
|
+
* Each lockfile in scope is read at the merge base (`git show`) and in the
|
|
14
|
+
* working tree; the (name, version) pairs new in the working tree are the
|
|
15
|
+
* change's packages. trivy scans the checkout against its LOCAL database
|
|
16
|
+
* only, and its findings are kept when they name one of those packages. A
|
|
17
|
+
* vulnerable dependency the change did not touch stays green.
|
|
18
|
+
*
|
|
19
|
+
* Exit 0 no findings; 1 findings (package@version, CVE or license); 2 for a
|
|
20
|
+
* usage error, no merge base, trivy missing, a trivy failure (an empty or
|
|
21
|
+
* missing database included), or a crash. Never a pass on a failure.
|
|
22
|
+
*/
|
|
23
|
+
const LOCKFILES = [
|
|
24
|
+
"yarn.lock",
|
|
25
|
+
"package-lock.json",
|
|
26
|
+
"pnpm-lock.yaml",
|
|
27
|
+
"Cargo.lock"
|
|
28
|
+
];
|
|
29
|
+
const SEVERITIES = [
|
|
30
|
+
"UNKNOWN",
|
|
31
|
+
"LOW",
|
|
32
|
+
"MEDIUM",
|
|
33
|
+
"HIGH",
|
|
34
|
+
"CRITICAL"
|
|
35
|
+
];
|
|
36
|
+
/** trivy's argv. Reads its existing database and never writes a file. */
|
|
37
|
+
const TRIVY_ARGS = [
|
|
38
|
+
"fs",
|
|
39
|
+
"--scanners",
|
|
40
|
+
"vuln,license",
|
|
41
|
+
"--skip-db-update",
|
|
42
|
+
"--offline-scan",
|
|
43
|
+
"--format",
|
|
44
|
+
"json",
|
|
45
|
+
"--quiet",
|
|
46
|
+
"."
|
|
47
|
+
];
|
|
48
|
+
const runTrivy = (args, cwd) => {
|
|
49
|
+
const r = spawnSync("trivy", [...args], {
|
|
50
|
+
cwd,
|
|
51
|
+
encoding: "utf8",
|
|
52
|
+
maxBuffer: 512 * 1024 * 1024
|
|
53
|
+
});
|
|
54
|
+
const missing = r.error?.code === "ENOENT";
|
|
55
|
+
return {
|
|
56
|
+
code: r.status ?? 1,
|
|
57
|
+
stdout: r.stdout ?? "",
|
|
58
|
+
stderr: r.stderr ?? (r.error ? r.error.message : ""),
|
|
59
|
+
missing
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
function add(set, name, version) {
|
|
63
|
+
if (!name || !version) return;
|
|
64
|
+
const versions = set.get(name) ?? /* @__PURE__ */ new Set();
|
|
65
|
+
versions.add(version);
|
|
66
|
+
set.set(name, versions);
|
|
67
|
+
}
|
|
68
|
+
/** The package name of a yarn descriptor: `@scope/pkg@npm:^1` → `@scope/pkg`. */
|
|
69
|
+
function descriptorName(descriptor) {
|
|
70
|
+
const d = descriptor.trim().replace(/^"|"$/g, "");
|
|
71
|
+
const at = d.indexOf("@", 1);
|
|
72
|
+
return at === -1 ? d : d.slice(0, at);
|
|
73
|
+
}
|
|
74
|
+
/** yarn.lock, classic (`version "1.2.3"`) and berry (`version: 1.2.3`). */
|
|
75
|
+
function parseYarnLock(text) {
|
|
76
|
+
const set = /* @__PURE__ */ new Map();
|
|
77
|
+
let name = null;
|
|
78
|
+
for (const line of text.split(/\r?\n/)) {
|
|
79
|
+
if (!line || line.startsWith("#")) continue;
|
|
80
|
+
if (!line.startsWith(" ") && line.endsWith(":")) {
|
|
81
|
+
const first = line.slice(0, -1).split(",")[0] ?? "";
|
|
82
|
+
name = first.trim() === "__metadata" ? null : descriptorName(first);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const m = /^\s+version:?\s+"?([^"\s]+)"?\s*$/.exec(line);
|
|
86
|
+
if (m?.[1] && name) {
|
|
87
|
+
add(set, name, m[1]);
|
|
88
|
+
name = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return set;
|
|
92
|
+
}
|
|
93
|
+
/** package-lock.json v2/v3 (`packages`) with a v1 (`dependencies`) fallback. */
|
|
94
|
+
function parsePackageLock(text) {
|
|
95
|
+
const set = /* @__PURE__ */ new Map();
|
|
96
|
+
const json = JSON.parse(text);
|
|
97
|
+
if (json.packages) {
|
|
98
|
+
for (const [key, entry] of Object.entries(json.packages)) {
|
|
99
|
+
if (!key || entry.link || !entry.version) continue;
|
|
100
|
+
const idx = key.lastIndexOf("node_modules/");
|
|
101
|
+
add(set, idx === -1 ? entry.name ?? key : key.slice(idx + 13), entry.version);
|
|
102
|
+
}
|
|
103
|
+
return set;
|
|
104
|
+
}
|
|
105
|
+
const walk = (deps) => {
|
|
106
|
+
for (const [name, entry] of Object.entries(deps ?? {})) {
|
|
107
|
+
if (entry.version) add(set, name, entry.version);
|
|
108
|
+
walk(entry.dependencies);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
walk(json.dependencies);
|
|
112
|
+
return set;
|
|
113
|
+
}
|
|
114
|
+
/** pnpm-lock.yaml `packages:` keys: `/name@1.2.3:`, `name@1.2.3(peer):`, `/name/1.2.3:`. */
|
|
115
|
+
function parsePnpmLock(text) {
|
|
116
|
+
const set = /* @__PURE__ */ new Map();
|
|
117
|
+
let inPackages = false;
|
|
118
|
+
for (const line of text.split(/\r?\n/)) {
|
|
119
|
+
if (/^\S/.test(line)) {
|
|
120
|
+
inPackages = line.startsWith("packages:");
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (!inPackages) continue;
|
|
124
|
+
const m = /^ {2}['"]?\/?([^'"\s]+?)['"]?:\s*$/.exec(line);
|
|
125
|
+
if (!m?.[1]) continue;
|
|
126
|
+
const key = m[1].replace(/\(.*$/, "");
|
|
127
|
+
const at = key.lastIndexOf("@");
|
|
128
|
+
if (at > 0) add(set, key.slice(0, at), key.slice(at + 1));
|
|
129
|
+
else {
|
|
130
|
+
const slash = key.lastIndexOf("/");
|
|
131
|
+
if (slash > 0) add(set, key.slice(0, slash), key.slice(slash + 1));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return set;
|
|
135
|
+
}
|
|
136
|
+
/** Cargo.lock `[[package]]` tables. */
|
|
137
|
+
function parseCargoLock(text) {
|
|
138
|
+
const set = /* @__PURE__ */ new Map();
|
|
139
|
+
for (const block of text.split("[[package]]").slice(1)) {
|
|
140
|
+
const name = /^name\s*=\s*"([^"]+)"/m.exec(block)?.[1];
|
|
141
|
+
const version = /^version\s*=\s*"([^"]+)"/m.exec(block)?.[1];
|
|
142
|
+
if (name && version) add(set, name, version);
|
|
143
|
+
}
|
|
144
|
+
return set;
|
|
145
|
+
}
|
|
146
|
+
function parseLockfile(file, text) {
|
|
147
|
+
const base = path$1.basename(file);
|
|
148
|
+
if (base === "yarn.lock") return parseYarnLock(text);
|
|
149
|
+
if (base === "package-lock.json") return parsePackageLock(text);
|
|
150
|
+
if (base === "pnpm-lock.yaml") return parsePnpmLock(text);
|
|
151
|
+
if (base === "Cargo.lock") return parseCargoLock(text);
|
|
152
|
+
return /* @__PURE__ */ new Map();
|
|
153
|
+
}
|
|
154
|
+
/** (name, version) pairs in `after` that `before` lacks: added or upgraded. */
|
|
155
|
+
function diffPackages(before, after) {
|
|
156
|
+
const changed = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const [name, versions] of after) {
|
|
158
|
+
const old = before.get(name);
|
|
159
|
+
for (const v of versions) if (!old?.has(v)) add(changed, name, v);
|
|
160
|
+
}
|
|
161
|
+
return changed;
|
|
162
|
+
}
|
|
163
|
+
/** Findings that name a changed package, at or above the severity floor. */
|
|
164
|
+
function filterFindings(report, changed, minSeverity, allowedLicenses) {
|
|
165
|
+
const floor = SEVERITIES.indexOf(minSeverity);
|
|
166
|
+
const findings = [];
|
|
167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
168
|
+
const push = (f) => {
|
|
169
|
+
const key = `${f.kind}|${f.pkg}|${f.version ?? ""}|${f.id}`;
|
|
170
|
+
if (seen.has(key)) return;
|
|
171
|
+
seen.add(key);
|
|
172
|
+
findings.push(f);
|
|
173
|
+
};
|
|
174
|
+
for (const result of report.Results ?? []) {
|
|
175
|
+
for (const v of result.Vulnerabilities ?? []) {
|
|
176
|
+
const name = v.PkgName ?? "";
|
|
177
|
+
const version = v.InstalledVersion ?? "";
|
|
178
|
+
if (!changed.get(name)?.has(version)) continue;
|
|
179
|
+
const sev = (v.Severity ?? "UNKNOWN").toUpperCase();
|
|
180
|
+
const rank = SEVERITIES.indexOf(sev);
|
|
181
|
+
if ((rank === -1 ? 0 : rank) < floor) continue;
|
|
182
|
+
push({
|
|
183
|
+
kind: "vulnerability",
|
|
184
|
+
pkg: name,
|
|
185
|
+
version,
|
|
186
|
+
id: v.VulnerabilityID ?? "?",
|
|
187
|
+
severity: sev
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (!allowedLicenses) continue;
|
|
191
|
+
for (const l of result.Licenses ?? []) {
|
|
192
|
+
const name = l.PkgName ?? "";
|
|
193
|
+
const license = l.Name ?? "";
|
|
194
|
+
if (!changed.has(name) || !license) continue;
|
|
195
|
+
if (allowedLicenses.has(license.toUpperCase())) continue;
|
|
196
|
+
push({
|
|
197
|
+
kind: "license",
|
|
198
|
+
pkg: name,
|
|
199
|
+
id: license,
|
|
200
|
+
severity: l.Severity
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return findings;
|
|
205
|
+
}
|
|
206
|
+
function fail2(message) {
|
|
207
|
+
return {
|
|
208
|
+
exitCode: 2,
|
|
209
|
+
stdout: "",
|
|
210
|
+
stderr: `gates dependency-audit: ${message}\n`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function formatFinding(f) {
|
|
214
|
+
return f.kind === "vulnerability" ? ` ${f.pkg}@${f.version} ${f.id} (${f.severity})` : ` ${f.pkg} license ${f.id} is not in --allow-licenses`;
|
|
215
|
+
}
|
|
216
|
+
/** The command body; `execute…` below maps any throw to exit 2. */
|
|
217
|
+
function runDependencyAudit(flags, cwd = process.cwd(), deps = {}) {
|
|
218
|
+
const severity = flags.severity.trim().toUpperCase();
|
|
219
|
+
if (!SEVERITIES.includes(severity)) return fail2(`--severity "${flags.severity}" must be one of: ${SEVERITIES.join(", ")}`);
|
|
220
|
+
const allowed = flags["allow-licenses"]?.trim() ? new Set(flags["allow-licenses"].split(",").map((l) => l.trim().toUpperCase()).filter(Boolean)) : null;
|
|
221
|
+
const git = deps.git ?? deps.scopeOptions?.git ?? runGit;
|
|
222
|
+
try {
|
|
223
|
+
const scope = computeChangeScope({
|
|
224
|
+
cwd,
|
|
225
|
+
base: flags.base,
|
|
226
|
+
scope: flags.scope,
|
|
227
|
+
git,
|
|
228
|
+
...deps.scopeOptions
|
|
229
|
+
});
|
|
230
|
+
const lockfiles = scope.files.filter((f) => f.status !== "deleted" && LOCKFILES.includes(path$1.basename(f.path)));
|
|
231
|
+
const changed = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const file of lockfiles) {
|
|
233
|
+
const atBase = readAtBase(scope, file, git);
|
|
234
|
+
const current = fs.readFileSync(path$1.join(scope.root, file.path), "utf8");
|
|
235
|
+
const delta = diffPackages(atBase === null ? /* @__PURE__ */ new Map() : parseLockfile(file.path, atBase), parseLockfile(file.path, current));
|
|
236
|
+
for (const [name, versions] of delta) for (const v of versions) add(changed, name, v);
|
|
237
|
+
}
|
|
238
|
+
const where = `against ${scope.baseRef} (merge base ${scope.mergeBase.slice(0, 12)})`;
|
|
239
|
+
const count = [...changed.values()].reduce((n, s) => n + s.size, 0);
|
|
240
|
+
if (count === 0) return {
|
|
241
|
+
exitCode: 0,
|
|
242
|
+
stdout: `PASS no added or upgraded packages in ${lockfiles.length} changed lockfile(s) ${where}\n`,
|
|
243
|
+
stderr: ""
|
|
244
|
+
};
|
|
245
|
+
const trivy = (deps.trivy ?? runTrivy)(TRIVY_ARGS, scope.root);
|
|
246
|
+
if (trivy.missing) return fail2("trivy is not installed (https://trivy.dev); see `levr gates dependency-audit --help`");
|
|
247
|
+
if (trivy.code !== 0) return fail2(`trivy exited ${trivy.code}; an empty or missing vulnerability database is an error here, never a pass (warm the cache first, see --help): ` + trivy.stderr.trim().split("\n").slice(-5).join(" | "));
|
|
248
|
+
let report;
|
|
249
|
+
try {
|
|
250
|
+
report = JSON.parse(trivy.stdout);
|
|
251
|
+
} catch {
|
|
252
|
+
return fail2("trivy printed no JSON report");
|
|
253
|
+
}
|
|
254
|
+
const findings = filterFindings(report, changed, severity, allowed);
|
|
255
|
+
const summary = `${count} added or upgraded package version(s) ${where}`;
|
|
256
|
+
if (findings.length === 0) return {
|
|
257
|
+
exitCode: 0,
|
|
258
|
+
stdout: `PASS no ${severity}+ findings${allowed ? " or disallowed licenses" : ""} in ${summary}\n`,
|
|
259
|
+
stderr: ""
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
exitCode: 1,
|
|
263
|
+
stdout: `FAIL ${findings.length} finding(s) in ${summary}:\n` + findings.map(formatFinding).join("\n") + "\n",
|
|
264
|
+
stderr: ""
|
|
265
|
+
};
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error instanceof NoMergeBaseError || error instanceof GitError) return fail2(error.message);
|
|
268
|
+
return fail2(`crashed: ${error instanceof Error ? error.message : String(error)}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The whole command minus process I/O, so tests can drive it. Any throw —
|
|
273
|
+
* from validation, path resolution or anything below — is exit 2, never an
|
|
274
|
+
* uncaught error: stricli would exit 1 for it, which reads as a verdict.
|
|
275
|
+
*/
|
|
276
|
+
function executeDependencyAudit(flags, cwd, deps = {}) {
|
|
277
|
+
try {
|
|
278
|
+
return runDependencyAudit(flags, cwd ?? process.cwd(), deps);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return fail2(`crashed: ${error instanceof Error ? error.message : String(error)}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function gatesDependencyAuditHandler(flags) {
|
|
284
|
+
const result = executeDependencyAudit(flags);
|
|
285
|
+
if (result.stdout.length > 0) this.process.stdout.write(result.stdout);
|
|
286
|
+
if (result.stderr.length > 0) this.process.stderr.write(result.stderr);
|
|
287
|
+
this.process.exitCode = result.exitCode;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//#endregion
|
|
291
|
+
export { gatesDependencyAuditHandler };
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
//#region ../ci-env/dist/providers/github.js
|
|
5
|
+
const MAX_EVENT_PAYLOAD_BYTES = 5 * 1024 * 1024;
|
|
6
|
+
const PR_EVENT_NAMES = new Set(["pull_request", "pull_request_target"]);
|
|
7
|
+
function readPullRequestEvent(eventPath) {
|
|
8
|
+
if (!eventPath) return void 0;
|
|
9
|
+
try {
|
|
10
|
+
if (statSync(eventPath).size > MAX_EVENT_PAYLOAD_BYTES) return void 0;
|
|
11
|
+
const raw = readFileSync(eventPath, "utf8");
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
} catch {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const github = {
|
|
18
|
+
name: "github_actions",
|
|
19
|
+
detect(env) {
|
|
20
|
+
return env.GITHUB_ACTIONS === "true";
|
|
21
|
+
},
|
|
22
|
+
extract(env) {
|
|
23
|
+
const isPr = !!env.GITHUB_EVENT_NAME && PR_EVENT_NAMES.has(env.GITHUB_EVENT_NAME);
|
|
24
|
+
const ref = env.GITHUB_REF ?? "";
|
|
25
|
+
const prPayload = isPr ? readPullRequestEvent(env.GITHUB_EVENT_PATH) : void 0;
|
|
26
|
+
const refMatch = isPr ? /^refs\/pull\/(\d+)\/merge$/.exec(ref) : null;
|
|
27
|
+
const prNumber = prPayload?.pull_request?.number?.toString() ?? refMatch?.[1];
|
|
28
|
+
const serverUrl = env.GITHUB_SERVER_URL ?? "https://github.com";
|
|
29
|
+
const repo = env.GITHUB_REPOSITORY ?? "";
|
|
30
|
+
const commitSha = env.GITHUB_PULL_REQUEST_HEAD_SHA ?? prPayload?.pull_request?.head?.sha ?? env.GITHUB_SHA;
|
|
31
|
+
return {
|
|
32
|
+
ci_provider: "github_actions",
|
|
33
|
+
ci_build_id: env.GITHUB_RUN_ID,
|
|
34
|
+
ci_build_number: env.GITHUB_RUN_NUMBER,
|
|
35
|
+
ci_build_url: repo ? `${serverUrl}/${repo}/actions/runs/${env.GITHUB_RUN_ID}` : void 0,
|
|
36
|
+
ci_job_name: env.GITHUB_JOB,
|
|
37
|
+
ci_job_url: repo ? `${serverUrl}/${repo}/actions/runs/${env.GITHUB_RUN_ID}` : void 0,
|
|
38
|
+
commit_sha: commitSha,
|
|
39
|
+
commit_author: env.GITHUB_ACTOR,
|
|
40
|
+
branch: isPr ? env.GITHUB_HEAD_REF : ref.startsWith("refs/heads/") ? ref.replace(/^refs\/heads\//, "") : void 0,
|
|
41
|
+
tag: ref.startsWith("refs/tags/") ? ref.replace(/^refs\/tags\//, "") : void 0,
|
|
42
|
+
is_pr: isPr,
|
|
43
|
+
pr_number: prNumber,
|
|
44
|
+
pr_branch: isPr ? env.GITHUB_HEAD_REF : void 0,
|
|
45
|
+
pr_target_branch: isPr ? env.GITHUB_BASE_REF : void 0,
|
|
46
|
+
repository_url: repo ? `${serverUrl}/${repo}` : void 0,
|
|
47
|
+
repository_slug: repo || void 0,
|
|
48
|
+
runner_os: env.RUNNER_OS?.toLowerCase(),
|
|
49
|
+
runner_arch: env.RUNNER_ARCH?.toLowerCase(),
|
|
50
|
+
runner_name: env.RUNNER_NAME
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region ../ci-env/dist/providers/gitlab.js
|
|
57
|
+
const gitlab = {
|
|
58
|
+
name: "gitlab_ci",
|
|
59
|
+
detect(env) {
|
|
60
|
+
return env.GITLAB_CI === "true";
|
|
61
|
+
},
|
|
62
|
+
extract(env) {
|
|
63
|
+
const isPr = !!env.CI_MERGE_REQUEST_IID;
|
|
64
|
+
return {
|
|
65
|
+
ci_provider: "gitlab_ci",
|
|
66
|
+
ci_build_id: env.CI_PIPELINE_ID,
|
|
67
|
+
ci_build_number: env.CI_PIPELINE_IID,
|
|
68
|
+
ci_build_url: env.CI_PIPELINE_URL,
|
|
69
|
+
ci_job_name: env.CI_JOB_NAME,
|
|
70
|
+
ci_job_url: env.CI_JOB_URL,
|
|
71
|
+
commit_sha: env.CI_COMMIT_SHA,
|
|
72
|
+
commit_message: env.CI_COMMIT_MESSAGE,
|
|
73
|
+
commit_author: env.CI_COMMIT_AUTHOR,
|
|
74
|
+
branch: env.CI_COMMIT_BRANCH ?? env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
|
|
75
|
+
tag: env.CI_COMMIT_TAG,
|
|
76
|
+
is_pr: isPr,
|
|
77
|
+
pr_number: env.CI_MERGE_REQUEST_IID,
|
|
78
|
+
pr_branch: env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
|
|
79
|
+
pr_target_branch: env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME,
|
|
80
|
+
repository_url: env.CI_PROJECT_URL,
|
|
81
|
+
repository_slug: env.CI_PROJECT_PATH,
|
|
82
|
+
runner_name: env.CI_RUNNER_DESCRIPTION
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region ../ci-env/dist/providers/jenkins.js
|
|
89
|
+
const jenkins = {
|
|
90
|
+
name: "jenkins",
|
|
91
|
+
detect(env) {
|
|
92
|
+
return !!env.JENKINS_URL;
|
|
93
|
+
},
|
|
94
|
+
extract(env) {
|
|
95
|
+
const isPr = !!env.CHANGE_ID;
|
|
96
|
+
return {
|
|
97
|
+
ci_provider: "jenkins",
|
|
98
|
+
ci_build_id: env.BUILD_ID,
|
|
99
|
+
ci_build_number: env.BUILD_NUMBER,
|
|
100
|
+
ci_build_url: env.BUILD_URL,
|
|
101
|
+
ci_job_name: env.JOB_NAME,
|
|
102
|
+
ci_job_url: env.JOB_URL,
|
|
103
|
+
commit_sha: env.GIT_COMMIT,
|
|
104
|
+
branch: env.GIT_BRANCH ?? env.BRANCH_NAME,
|
|
105
|
+
is_pr: isPr,
|
|
106
|
+
pr_number: env.CHANGE_ID,
|
|
107
|
+
pr_branch: env.CHANGE_BRANCH,
|
|
108
|
+
pr_target_branch: env.CHANGE_TARGET,
|
|
109
|
+
repository_url: env.GIT_URL
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region ../ci-env/dist/providers/circleci.js
|
|
116
|
+
const circleci = {
|
|
117
|
+
name: "circleci",
|
|
118
|
+
detect(env) {
|
|
119
|
+
return env.CIRCLECI === "true";
|
|
120
|
+
},
|
|
121
|
+
extract(env) {
|
|
122
|
+
const isPr = !!env.CIRCLE_PULL_REQUEST;
|
|
123
|
+
const prNumber = (env.CIRCLE_PULL_REQUEST ?? "").split("/").pop();
|
|
124
|
+
return {
|
|
125
|
+
ci_provider: "circleci",
|
|
126
|
+
ci_build_id: env.CIRCLE_WORKFLOW_ID,
|
|
127
|
+
ci_build_number: env.CIRCLE_BUILD_NUM,
|
|
128
|
+
ci_build_url: env.CIRCLE_BUILD_URL,
|
|
129
|
+
ci_job_name: env.CIRCLE_JOB,
|
|
130
|
+
commit_sha: env.CIRCLE_SHA1,
|
|
131
|
+
branch: env.CIRCLE_BRANCH,
|
|
132
|
+
tag: env.CIRCLE_TAG,
|
|
133
|
+
is_pr: isPr,
|
|
134
|
+
pr_number: isPr ? prNumber : void 0,
|
|
135
|
+
repository_url: env.CIRCLE_REPOSITORY_URL,
|
|
136
|
+
repository_slug: env.CIRCLE_PROJECT_USERNAME && env.CIRCLE_PROJECT_REPONAME ? `${env.CIRCLE_PROJECT_USERNAME}/${env.CIRCLE_PROJECT_REPONAME}` : void 0
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region ../ci-env/dist/providers/azure.js
|
|
143
|
+
const azure = {
|
|
144
|
+
name: "azure_devops",
|
|
145
|
+
detect(env) {
|
|
146
|
+
return env.TF_BUILD === "True";
|
|
147
|
+
},
|
|
148
|
+
extract(env) {
|
|
149
|
+
const isPr = env.BUILD_REASON === "PullRequest" || !!env.SYSTEM_PULLREQUEST_PULLREQUESTID;
|
|
150
|
+
const orgUrl = env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI ?? "";
|
|
151
|
+
const project = env.SYSTEM_TEAMPROJECT ?? "";
|
|
152
|
+
const buildId = env.BUILD_BUILDID ?? "";
|
|
153
|
+
return {
|
|
154
|
+
ci_provider: "azure_devops",
|
|
155
|
+
ci_build_id: buildId,
|
|
156
|
+
ci_build_number: env.BUILD_BUILDNUMBER,
|
|
157
|
+
ci_build_url: orgUrl && project && buildId ? `${orgUrl}${project}/_build/results?buildId=${buildId}` : void 0,
|
|
158
|
+
ci_job_name: env.AGENT_JOBNAME,
|
|
159
|
+
commit_sha: env.BUILD_SOURCEVERSION,
|
|
160
|
+
commit_message: env.BUILD_SOURCEVERSIONMESSAGE,
|
|
161
|
+
branch: env.BUILD_SOURCEBRANCH?.replace(/^refs\/heads\//, ""),
|
|
162
|
+
is_pr: isPr,
|
|
163
|
+
pr_number: env.SYSTEM_PULLREQUEST_PULLREQUESTID,
|
|
164
|
+
pr_branch: env.SYSTEM_PULLREQUEST_SOURCEBRANCH?.replace(/^refs\/heads\//, ""),
|
|
165
|
+
pr_target_branch: env.SYSTEM_PULLREQUEST_TARGETBRANCH?.replace(/^refs\/heads\//, ""),
|
|
166
|
+
repository_url: env.BUILD_REPOSITORY_URI,
|
|
167
|
+
repository_slug: env.BUILD_REPOSITORY_NAME,
|
|
168
|
+
runner_os: env.AGENT_OS?.toLowerCase(),
|
|
169
|
+
runner_name: env.AGENT_NAME
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region ../ci-env/dist/providers/buildkite.js
|
|
176
|
+
const buildkite = {
|
|
177
|
+
name: "buildkite",
|
|
178
|
+
detect(env) {
|
|
179
|
+
return env.BUILDKITE === "true";
|
|
180
|
+
},
|
|
181
|
+
extract(env) {
|
|
182
|
+
const isPr = env.BUILDKITE_PULL_REQUEST !== "false" && !!env.BUILDKITE_PULL_REQUEST;
|
|
183
|
+
return {
|
|
184
|
+
ci_provider: "buildkite",
|
|
185
|
+
ci_build_id: env.BUILDKITE_BUILD_ID,
|
|
186
|
+
ci_build_number: env.BUILDKITE_BUILD_NUMBER,
|
|
187
|
+
ci_build_url: env.BUILDKITE_BUILD_URL,
|
|
188
|
+
ci_job_name: env.BUILDKITE_LABEL ?? env.BUILDKITE_STEP_KEY,
|
|
189
|
+
commit_sha: env.BUILDKITE_COMMIT,
|
|
190
|
+
commit_message: env.BUILDKITE_MESSAGE,
|
|
191
|
+
commit_author: env.BUILDKITE_BUILD_AUTHOR,
|
|
192
|
+
branch: env.BUILDKITE_BRANCH,
|
|
193
|
+
tag: env.BUILDKITE_TAG,
|
|
194
|
+
is_pr: isPr,
|
|
195
|
+
pr_number: isPr ? env.BUILDKITE_PULL_REQUEST : void 0,
|
|
196
|
+
pr_branch: isPr ? env.BUILDKITE_BRANCH : void 0,
|
|
197
|
+
pr_target_branch: isPr ? env.BUILDKITE_PULL_REQUEST_BASE_BRANCH : void 0,
|
|
198
|
+
repository_url: env.BUILDKITE_REPO,
|
|
199
|
+
repository_slug: env.BUILDKITE_PIPELINE_SLUG ? `${env.BUILDKITE_ORGANIZATION_SLUG}/${env.BUILDKITE_PIPELINE_SLUG}` : void 0,
|
|
200
|
+
runner_name: env.BUILDKITE_AGENT_NAME
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region ../ci-env/dist/providers/bitbucket.js
|
|
207
|
+
const bitbucket = {
|
|
208
|
+
name: "bitbucket",
|
|
209
|
+
detect(env) {
|
|
210
|
+
return !!env.BITBUCKET_BUILD_NUMBER;
|
|
211
|
+
},
|
|
212
|
+
extract(env) {
|
|
213
|
+
const isPr = !!env.BITBUCKET_PR_ID;
|
|
214
|
+
const workspace = env.BITBUCKET_WORKSPACE ?? "";
|
|
215
|
+
const repo = env.BITBUCKET_REPO_SLUG ?? "";
|
|
216
|
+
return {
|
|
217
|
+
ci_provider: "bitbucket",
|
|
218
|
+
ci_build_id: env.BITBUCKET_PIPELINE_UUID,
|
|
219
|
+
ci_build_number: env.BITBUCKET_BUILD_NUMBER,
|
|
220
|
+
ci_build_url: workspace && repo && env.BITBUCKET_BUILD_NUMBER ? `https://bitbucket.org/${workspace}/${repo}/pipelines/results/${env.BITBUCKET_BUILD_NUMBER}` : void 0,
|
|
221
|
+
ci_job_name: env.BITBUCKET_STEP_UUID,
|
|
222
|
+
commit_sha: env.BITBUCKET_COMMIT,
|
|
223
|
+
branch: env.BITBUCKET_BRANCH,
|
|
224
|
+
tag: env.BITBUCKET_TAG,
|
|
225
|
+
is_pr: isPr,
|
|
226
|
+
pr_number: env.BITBUCKET_PR_ID,
|
|
227
|
+
pr_branch: isPr ? env.BITBUCKET_BRANCH : void 0,
|
|
228
|
+
pr_target_branch: env.BITBUCKET_PR_DESTINATION_BRANCH,
|
|
229
|
+
repository_url: workspace && repo ? `https://bitbucket.org/${workspace}/${repo}` : void 0,
|
|
230
|
+
repository_slug: workspace && repo ? `${workspace}/${repo}` : void 0
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region ../ci-env/dist/providers/generic.js
|
|
237
|
+
/**
|
|
238
|
+
* Generic CI fallback — detects basic CI environment using common env vars.
|
|
239
|
+
* Used when no specific provider is matched.
|
|
240
|
+
*/
|
|
241
|
+
const generic = {
|
|
242
|
+
name: "generic_ci",
|
|
243
|
+
detect(env) {
|
|
244
|
+
return env.CI === "true" || env.CI === "1" || env.CONTINUOUS_INTEGRATION === "true";
|
|
245
|
+
},
|
|
246
|
+
extract(env) {
|
|
247
|
+
return {
|
|
248
|
+
ci_provider: "generic_ci",
|
|
249
|
+
ci_build_id: env.BUILD_ID ?? env.BUILD_NUMBER,
|
|
250
|
+
ci_build_number: env.BUILD_NUMBER,
|
|
251
|
+
ci_build_url: env.BUILD_URL,
|
|
252
|
+
commit_sha: env.GIT_COMMIT ?? env.COMMIT_SHA,
|
|
253
|
+
commit_author: env.GIT_AUTHOR_NAME,
|
|
254
|
+
branch: env.GIT_BRANCH ?? env.BRANCH_NAME ?? env.BRANCH,
|
|
255
|
+
tag: env.GIT_TAG ?? env.TAG_NAME,
|
|
256
|
+
repository_url: env.GIT_URL ?? env.REPOSITORY_URL
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region ../ci-env/dist/workspace-path.js
|
|
263
|
+
/**
|
|
264
|
+
* Detect the workspace path relative to the git repository root.
|
|
265
|
+
* Useful for monorepos to distinguish which sub-project ran tests.
|
|
266
|
+
*
|
|
267
|
+
* Returns undefined if not in a git repository or git is unavailable.
|
|
268
|
+
*/
|
|
269
|
+
function detectWorkspacePath() {
|
|
270
|
+
try {
|
|
271
|
+
return execSync("git rev-parse --show-prefix", {
|
|
272
|
+
encoding: "utf-8",
|
|
273
|
+
timeout: 5e3,
|
|
274
|
+
stdio: [
|
|
275
|
+
"pipe",
|
|
276
|
+
"pipe",
|
|
277
|
+
"pipe"
|
|
278
|
+
]
|
|
279
|
+
}).trim().replace(/\/$/, "") || void 0;
|
|
280
|
+
} catch {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region ../ci-env/dist/detect.js
|
|
287
|
+
/**
|
|
288
|
+
* Ordered list of CI providers to check.
|
|
289
|
+
* More specific providers first, generic fallback last.
|
|
290
|
+
*/
|
|
291
|
+
const PROVIDERS = [
|
|
292
|
+
github,
|
|
293
|
+
gitlab,
|
|
294
|
+
jenkins,
|
|
295
|
+
circleci,
|
|
296
|
+
azure,
|
|
297
|
+
buildkite,
|
|
298
|
+
bitbucket,
|
|
299
|
+
generic
|
|
300
|
+
];
|
|
301
|
+
let cachedResult;
|
|
302
|
+
/**
|
|
303
|
+
* Detect the CI environment from environment variables.
|
|
304
|
+
*
|
|
305
|
+
* @param env - Environment variables to inspect. Defaults to `process.env`.
|
|
306
|
+
* @returns Normalized CI environment metadata, or `null` if not running in CI.
|
|
307
|
+
*/
|
|
308
|
+
function detectCiEnvironment(env) {
|
|
309
|
+
if (env === void 0 && cachedResult !== void 0) return cachedResult;
|
|
310
|
+
const source = env ?? process.env;
|
|
311
|
+
for (const provider of PROVIDERS) if (provider.detect(source)) {
|
|
312
|
+
const result = provider.extract(source);
|
|
313
|
+
if (!result.workspace_path) result.workspace_path = detectWorkspacePath();
|
|
314
|
+
if (!result.runner_os) result.runner_os = process.platform;
|
|
315
|
+
if (!result.runner_arch) result.runner_arch = process.arch;
|
|
316
|
+
const cleaned = Object.fromEntries(Object.entries(result).filter(([, v]) => v !== void 0));
|
|
317
|
+
if (env === void 0) cachedResult = cleaned;
|
|
318
|
+
return cleaned;
|
|
319
|
+
}
|
|
320
|
+
if (env === void 0) cachedResult = null;
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
//#endregion
|
|
325
|
+
export { detectCiEnvironment };
|