@tested/cli 0.1.6 → 0.1.7
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 +2 -2
- package/dist/td.js +192 -26
- package/dist/tested.js +192 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ CI uses the composite Action (`tested-hq/cli/action@main`). It installs `@tested
|
|
|
25
25
|
```yaml
|
|
26
26
|
- uses: tested-hq/cli/action@main
|
|
27
27
|
with:
|
|
28
|
-
version: 0.1.
|
|
28
|
+
version: 0.1.7
|
|
29
29
|
token: ${{ secrets.TESTED_TOKEN }}
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -135,7 +135,7 @@ If `thresholds` is missing from `.tested.yaml`, `tested check` prints a notice o
|
|
|
135
135
|
```yaml
|
|
136
136
|
- uses: tested-hq/cli/action@main
|
|
137
137
|
with:
|
|
138
|
-
version: 0.1.
|
|
138
|
+
version: 0.1.7
|
|
139
139
|
push: true
|
|
140
140
|
pr-number: ${{ github.event.pull_request.number }}
|
|
141
141
|
token: ${{ secrets.TESTED_TOKEN }}
|
package/dist/td.js
CHANGED
|
@@ -631,6 +631,25 @@ import { resolve as resolve3 } from "path";
|
|
|
631
631
|
|
|
632
632
|
// src/git.ts
|
|
633
633
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
634
|
+
|
|
635
|
+
// src/git-ref.ts
|
|
636
|
+
var SAFE_GIT_REF_RE = /^[A-Za-z0-9_./@~^-]{1,256}$/;
|
|
637
|
+
function assertSafeGitRef(ref) {
|
|
638
|
+
if (!ref) {
|
|
639
|
+
throw new Error("git ref must not be empty");
|
|
640
|
+
}
|
|
641
|
+
if (ref.startsWith("-")) {
|
|
642
|
+
throw new Error(`git ref must not start with '-': ${ref}`);
|
|
643
|
+
}
|
|
644
|
+
if (!SAFE_GIT_REF_RE.test(ref)) {
|
|
645
|
+
throw new Error(
|
|
646
|
+
`git ref contains invalid characters or is too long (max 256): ${ref}`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
return ref;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/git.ts
|
|
634
653
|
async function openRepo(cwd) {
|
|
635
654
|
const git = simpleGit2({ baseDir: cwd });
|
|
636
655
|
const repoRoot = (await git.revparse(["--show-toplevel"])).trim();
|
|
@@ -669,6 +688,28 @@ async function resolveEffectiveBase(ctx, requested) {
|
|
|
669
688
|
async function headSha(ctx) {
|
|
670
689
|
return (await ctx.git.revparse(["HEAD"])).trim();
|
|
671
690
|
}
|
|
691
|
+
async function fetchOriginRef(ctx, ref) {
|
|
692
|
+
const spec = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
|
|
693
|
+
try {
|
|
694
|
+
assertSafeGitRef(spec);
|
|
695
|
+
} catch {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
await ctx.git.raw(["fetch", "--depth=1", "origin", spec]);
|
|
700
|
+
return true;
|
|
701
|
+
} catch {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
async function resolveAfterFetch(ctx, ref) {
|
|
706
|
+
if (await tryRevparse(ctx, ref)) return ref;
|
|
707
|
+
if (!ref.startsWith("origin/")) {
|
|
708
|
+
const originRef = `origin/${ref}`;
|
|
709
|
+
if (await tryRevparse(ctx, originRef)) return originRef;
|
|
710
|
+
}
|
|
711
|
+
return tryRevparse(ctx, "FETCH_HEAD");
|
|
712
|
+
}
|
|
672
713
|
async function unifiedDiff(ctx, base) {
|
|
673
714
|
try {
|
|
674
715
|
const mergeBase = (await ctx.git.raw(["merge-base", base, "HEAD"])).trim();
|
|
@@ -699,23 +740,6 @@ async function gitUserName(ctx) {
|
|
|
699
740
|
}
|
|
700
741
|
}
|
|
701
742
|
|
|
702
|
-
// src/git-ref.ts
|
|
703
|
-
var SAFE_GIT_REF_RE = /^[A-Za-z0-9_./@~^-]{1,256}$/;
|
|
704
|
-
function assertSafeGitRef(ref) {
|
|
705
|
-
if (!ref) {
|
|
706
|
-
throw new Error("git ref must not be empty");
|
|
707
|
-
}
|
|
708
|
-
if (ref.startsWith("-")) {
|
|
709
|
-
throw new Error(`git ref must not start with '-': ${ref}`);
|
|
710
|
-
}
|
|
711
|
-
if (!SAFE_GIT_REF_RE.test(ref)) {
|
|
712
|
-
throw new Error(
|
|
713
|
-
`git ref contains invalid characters or is too long (max 256): ${ref}`
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
return ref;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
743
|
// src/core/istanbul.ts
|
|
720
744
|
import { readFile as readFile2 } from "fs/promises";
|
|
721
745
|
import { isAbsolute, relative, resolve } from "path";
|
|
@@ -1170,6 +1194,125 @@ function sanitizeAuthor(name) {
|
|
|
1170
1194
|
function toBranchName(ref) {
|
|
1171
1195
|
return ref.replace(/^refs\/heads\//, "").replace(/^refs\/remotes\//, "").replace(/^origin\//, "");
|
|
1172
1196
|
}
|
|
1197
|
+
var GITHUB_COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
1198
|
+
function githubApiToken(env) {
|
|
1199
|
+
const raw = env.GITHUB_TOKEN ?? env.GH_TOKEN;
|
|
1200
|
+
if (raw === void 0 || raw === "") return null;
|
|
1201
|
+
return raw;
|
|
1202
|
+
}
|
|
1203
|
+
function parseGitHubPullBase(parsed) {
|
|
1204
|
+
if (!parsed || typeof parsed !== "object" || !("base" in parsed)) return null;
|
|
1205
|
+
const base = parsed.base;
|
|
1206
|
+
if (!base || typeof base !== "object") return null;
|
|
1207
|
+
const ref = "ref" in base ? base.ref : void 0;
|
|
1208
|
+
const sha = "sha" in base ? base.sha : void 0;
|
|
1209
|
+
if (typeof ref !== "string" || typeof sha !== "string") return null;
|
|
1210
|
+
if (!GITHUB_COMMIT_SHA_RE.test(sha)) return null;
|
|
1211
|
+
try {
|
|
1212
|
+
return { ref: assertSafeGitRef(ref), sha: assertSafeGitRef(sha) };
|
|
1213
|
+
} catch {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
async function fetchGitHubPullBase(opts) {
|
|
1218
|
+
if (!parseGitHubRepository(`${opts.owner}/${opts.name}`)) return null;
|
|
1219
|
+
if (!Number.isInteger(opts.prNumber) || opts.prNumber <= 0) return null;
|
|
1220
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
1221
|
+
const url = `https://api.github.com/repos/${opts.owner}/${opts.name}/pulls/${opts.prNumber}`;
|
|
1222
|
+
const headers = {
|
|
1223
|
+
Accept: "application/vnd.github+json",
|
|
1224
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1225
|
+
"User-Agent": "tested-cli"
|
|
1226
|
+
};
|
|
1227
|
+
const token = githubApiToken(opts.env ?? process.env);
|
|
1228
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1229
|
+
let res;
|
|
1230
|
+
try {
|
|
1231
|
+
res = await fetchFn(url, { method: "GET", redirect: "manual", headers });
|
|
1232
|
+
} catch {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
if (res.status !== 200) return null;
|
|
1236
|
+
try {
|
|
1237
|
+
return parseGitHubPullBase(JSON.parse(await res.text()));
|
|
1238
|
+
} catch {
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
async function resolvePrPushBase(opts) {
|
|
1243
|
+
let requested;
|
|
1244
|
+
try {
|
|
1245
|
+
requested = assertSafeGitRef(opts.requested);
|
|
1246
|
+
} catch {
|
|
1247
|
+
return void 0;
|
|
1248
|
+
}
|
|
1249
|
+
if (await tryRevparse(opts.ctx, requested)) return requested;
|
|
1250
|
+
const branch = toBranchName(requested) || "main";
|
|
1251
|
+
const originRef = `origin/${branch}`;
|
|
1252
|
+
if (originRef !== requested && await tryRevparse(opts.ctx, originRef)) {
|
|
1253
|
+
return originRef;
|
|
1254
|
+
}
|
|
1255
|
+
let prBase = null;
|
|
1256
|
+
if (opts.owner && opts.name) {
|
|
1257
|
+
prBase = await fetchGitHubPullBase({
|
|
1258
|
+
owner: opts.owner,
|
|
1259
|
+
name: opts.name,
|
|
1260
|
+
prNumber: opts.prNumber,
|
|
1261
|
+
...opts.fetchFn ? { fetchFn: opts.fetchFn } : {},
|
|
1262
|
+
...opts.env ? { env: opts.env } : {}
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
if (prBase && await tryRevparse(opts.ctx, prBase.sha)) return prBase.sha;
|
|
1266
|
+
const fetchTargets = [];
|
|
1267
|
+
if (prBase) {
|
|
1268
|
+
fetchTargets.push(prBase.sha, prBase.ref);
|
|
1269
|
+
}
|
|
1270
|
+
fetchTargets.push(branch);
|
|
1271
|
+
let announced = false;
|
|
1272
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1273
|
+
for (const target of fetchTargets) {
|
|
1274
|
+
if (seen.has(target)) continue;
|
|
1275
|
+
seen.add(target);
|
|
1276
|
+
let spec;
|
|
1277
|
+
try {
|
|
1278
|
+
spec = assertSafeGitRef(target);
|
|
1279
|
+
} catch {
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (!announced) {
|
|
1283
|
+
announced = true;
|
|
1284
|
+
opts.onProgress?.("fetching base\u2026");
|
|
1285
|
+
}
|
|
1286
|
+
if (!await fetchOriginRef(opts.ctx, spec)) continue;
|
|
1287
|
+
const resolved = await resolveAfterFetch(opts.ctx, spec);
|
|
1288
|
+
if (resolved) return resolved;
|
|
1289
|
+
if (prBase && await tryRevparse(opts.ctx, prBase.sha)) return prBase.sha;
|
|
1290
|
+
}
|
|
1291
|
+
return void 0;
|
|
1292
|
+
}
|
|
1293
|
+
async function peekRepoIdentity(opts) {
|
|
1294
|
+
let owner = opts.owner;
|
|
1295
|
+
let name = opts.name;
|
|
1296
|
+
if (!owner || !name) {
|
|
1297
|
+
const fromActions = parseGitHubRepository(opts.env.GITHUB_REPOSITORY);
|
|
1298
|
+
if (fromActions) {
|
|
1299
|
+
owner = owner ?? fromActions.owner;
|
|
1300
|
+
name = name ?? fromActions.name;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (!owner || !name) {
|
|
1304
|
+
try {
|
|
1305
|
+
const origin = await remoteUrl(opts.ctx, "origin");
|
|
1306
|
+
const parsed = parseGitHubRemote(origin);
|
|
1307
|
+
if (parsed) {
|
|
1308
|
+
owner = owner ?? parsed.owner;
|
|
1309
|
+
name = name ?? parsed.name;
|
|
1310
|
+
}
|
|
1311
|
+
} catch {
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return { owner: owner ?? null, name: name ?? null };
|
|
1315
|
+
}
|
|
1173
1316
|
function buildIngestBody(input) {
|
|
1174
1317
|
const baseRefName = toBranchName(input.baseRef);
|
|
1175
1318
|
return {
|
|
@@ -1362,16 +1505,16 @@ function formatMissingTokenError(opts) {
|
|
|
1362
1505
|
"or pass --token <token> (avoid on shared hosts: visible in ps)"
|
|
1363
1506
|
]);
|
|
1364
1507
|
}
|
|
1365
|
-
function formatPushError(status, message, code) {
|
|
1508
|
+
function formatPushError(status, message, code, identity) {
|
|
1366
1509
|
if (status === 0) {
|
|
1367
1510
|
return errorBlock(message);
|
|
1368
1511
|
}
|
|
1369
1512
|
const normalized = (code ?? message).toLowerCase();
|
|
1370
|
-
if (normalized.includes("token_required") || normalized.includes("invalid token") || normalized.includes("unauthorized") || status === 401) {
|
|
1513
|
+
if (normalized.includes("token_required") || normalized.includes("invalid token") || normalized.includes("unauthorized") || normalized.includes("invalid credentials") || status === 401) {
|
|
1371
1514
|
return errorBlock("ingest auth failed", [
|
|
1372
1515
|
message,
|
|
1373
1516
|
"",
|
|
1374
|
-
...tokenMintGuidance(),
|
|
1517
|
+
...tokenMintGuidance(identity),
|
|
1375
1518
|
"or --token"
|
|
1376
1519
|
]);
|
|
1377
1520
|
}
|
|
@@ -1460,21 +1603,41 @@ async function executePush(cli, deps) {
|
|
|
1460
1603
|
}
|
|
1461
1604
|
const config = await loadConfigFn({ cwd: deps.cwd });
|
|
1462
1605
|
const ctx = await openRepoFn(deps.cwd);
|
|
1463
|
-
|
|
1606
|
+
const peeked = await peekRepoIdentity({
|
|
1607
|
+
...cli.owner !== void 0 ? { owner: cli.owner } : {},
|
|
1608
|
+
...cli.name !== void 0 ? { name: cli.name } : {},
|
|
1609
|
+
env,
|
|
1610
|
+
ctx
|
|
1611
|
+
});
|
|
1464
1612
|
let diff;
|
|
1465
1613
|
try {
|
|
1614
|
+
let baseOverride = cli.base;
|
|
1615
|
+
if (baseOverride === void 0 && prNumber !== null) {
|
|
1616
|
+
const resolved = await resolvePrPushBase({
|
|
1617
|
+
ctx,
|
|
1618
|
+
requested: config.base,
|
|
1619
|
+
prNumber,
|
|
1620
|
+
...peeked.owner != null ? { owner: peeked.owner } : {},
|
|
1621
|
+
...peeked.name != null ? { name: peeked.name } : {},
|
|
1622
|
+
fetchFn,
|
|
1623
|
+
env,
|
|
1624
|
+
onProgress
|
|
1625
|
+
});
|
|
1626
|
+
if (resolved !== void 0) baseOverride = resolved;
|
|
1627
|
+
}
|
|
1628
|
+
onProgress("computing diff\u2026");
|
|
1466
1629
|
diff = await computeDiffFn({
|
|
1467
1630
|
cwd: deps.cwd,
|
|
1468
1631
|
config,
|
|
1469
|
-
...
|
|
1632
|
+
...baseOverride !== void 0 ? { baseRef: baseOverride } : {},
|
|
1470
1633
|
ctx
|
|
1471
1634
|
});
|
|
1472
1635
|
} catch (err) {
|
|
1473
1636
|
const message = err instanceof Error ? err.message : String(err);
|
|
1474
1637
|
return { exitCode: 1, stdout: "", stderr: errorBlock(message) };
|
|
1475
1638
|
}
|
|
1476
|
-
let owner = cli.owner;
|
|
1477
|
-
let name = cli.name;
|
|
1639
|
+
let owner = cli.owner ?? peeked.owner ?? void 0;
|
|
1640
|
+
let name = cli.name ?? peeked.name ?? void 0;
|
|
1478
1641
|
if (!owner || !name) {
|
|
1479
1642
|
const fromActions = parseGitHubRepository(env.GITHUB_REPOSITORY);
|
|
1480
1643
|
if (fromActions) {
|
|
@@ -1562,7 +1725,10 @@ async function executePush(cli, deps) {
|
|
|
1562
1725
|
return {
|
|
1563
1726
|
exitCode: 1,
|
|
1564
1727
|
stdout: "",
|
|
1565
|
-
stderr: formatPushError(result.status, result.message, result.code
|
|
1728
|
+
stderr: formatPushError(result.status, result.message, result.code, {
|
|
1729
|
+
owner,
|
|
1730
|
+
name
|
|
1731
|
+
})
|
|
1566
1732
|
};
|
|
1567
1733
|
}
|
|
1568
1734
|
const formatted = formatPushSuccess(result.data, cli.json);
|
|
@@ -1961,7 +2127,7 @@ import pc3 from "picocolors";
|
|
|
1961
2127
|
// package.json
|
|
1962
2128
|
var package_default = {
|
|
1963
2129
|
name: "@tested/cli",
|
|
1964
|
-
version: "0.1.
|
|
2130
|
+
version: "0.1.7",
|
|
1965
2131
|
description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
|
|
1966
2132
|
license: "MIT",
|
|
1967
2133
|
homepage: "https://tested.dev",
|
package/dist/tested.js
CHANGED
|
@@ -631,6 +631,25 @@ import { resolve as resolve3 } from "path";
|
|
|
631
631
|
|
|
632
632
|
// src/git.ts
|
|
633
633
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
634
|
+
|
|
635
|
+
// src/git-ref.ts
|
|
636
|
+
var SAFE_GIT_REF_RE = /^[A-Za-z0-9_./@~^-]{1,256}$/;
|
|
637
|
+
function assertSafeGitRef(ref) {
|
|
638
|
+
if (!ref) {
|
|
639
|
+
throw new Error("git ref must not be empty");
|
|
640
|
+
}
|
|
641
|
+
if (ref.startsWith("-")) {
|
|
642
|
+
throw new Error(`git ref must not start with '-': ${ref}`);
|
|
643
|
+
}
|
|
644
|
+
if (!SAFE_GIT_REF_RE.test(ref)) {
|
|
645
|
+
throw new Error(
|
|
646
|
+
`git ref contains invalid characters or is too long (max 256): ${ref}`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
return ref;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/git.ts
|
|
634
653
|
async function openRepo(cwd) {
|
|
635
654
|
const git = simpleGit2({ baseDir: cwd });
|
|
636
655
|
const repoRoot = (await git.revparse(["--show-toplevel"])).trim();
|
|
@@ -669,6 +688,28 @@ async function resolveEffectiveBase(ctx, requested) {
|
|
|
669
688
|
async function headSha(ctx) {
|
|
670
689
|
return (await ctx.git.revparse(["HEAD"])).trim();
|
|
671
690
|
}
|
|
691
|
+
async function fetchOriginRef(ctx, ref) {
|
|
692
|
+
const spec = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
|
|
693
|
+
try {
|
|
694
|
+
assertSafeGitRef(spec);
|
|
695
|
+
} catch {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
await ctx.git.raw(["fetch", "--depth=1", "origin", spec]);
|
|
700
|
+
return true;
|
|
701
|
+
} catch {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
async function resolveAfterFetch(ctx, ref) {
|
|
706
|
+
if (await tryRevparse(ctx, ref)) return ref;
|
|
707
|
+
if (!ref.startsWith("origin/")) {
|
|
708
|
+
const originRef = `origin/${ref}`;
|
|
709
|
+
if (await tryRevparse(ctx, originRef)) return originRef;
|
|
710
|
+
}
|
|
711
|
+
return tryRevparse(ctx, "FETCH_HEAD");
|
|
712
|
+
}
|
|
672
713
|
async function unifiedDiff(ctx, base) {
|
|
673
714
|
try {
|
|
674
715
|
const mergeBase = (await ctx.git.raw(["merge-base", base, "HEAD"])).trim();
|
|
@@ -699,23 +740,6 @@ async function gitUserName(ctx) {
|
|
|
699
740
|
}
|
|
700
741
|
}
|
|
701
742
|
|
|
702
|
-
// src/git-ref.ts
|
|
703
|
-
var SAFE_GIT_REF_RE = /^[A-Za-z0-9_./@~^-]{1,256}$/;
|
|
704
|
-
function assertSafeGitRef(ref) {
|
|
705
|
-
if (!ref) {
|
|
706
|
-
throw new Error("git ref must not be empty");
|
|
707
|
-
}
|
|
708
|
-
if (ref.startsWith("-")) {
|
|
709
|
-
throw new Error(`git ref must not start with '-': ${ref}`);
|
|
710
|
-
}
|
|
711
|
-
if (!SAFE_GIT_REF_RE.test(ref)) {
|
|
712
|
-
throw new Error(
|
|
713
|
-
`git ref contains invalid characters or is too long (max 256): ${ref}`
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
return ref;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
743
|
// src/core/istanbul.ts
|
|
720
744
|
import { readFile as readFile2 } from "fs/promises";
|
|
721
745
|
import { isAbsolute, relative, resolve } from "path";
|
|
@@ -1170,6 +1194,125 @@ function sanitizeAuthor(name) {
|
|
|
1170
1194
|
function toBranchName(ref) {
|
|
1171
1195
|
return ref.replace(/^refs\/heads\//, "").replace(/^refs\/remotes\//, "").replace(/^origin\//, "");
|
|
1172
1196
|
}
|
|
1197
|
+
var GITHUB_COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
1198
|
+
function githubApiToken(env) {
|
|
1199
|
+
const raw = env.GITHUB_TOKEN ?? env.GH_TOKEN;
|
|
1200
|
+
if (raw === void 0 || raw === "") return null;
|
|
1201
|
+
return raw;
|
|
1202
|
+
}
|
|
1203
|
+
function parseGitHubPullBase(parsed) {
|
|
1204
|
+
if (!parsed || typeof parsed !== "object" || !("base" in parsed)) return null;
|
|
1205
|
+
const base = parsed.base;
|
|
1206
|
+
if (!base || typeof base !== "object") return null;
|
|
1207
|
+
const ref = "ref" in base ? base.ref : void 0;
|
|
1208
|
+
const sha = "sha" in base ? base.sha : void 0;
|
|
1209
|
+
if (typeof ref !== "string" || typeof sha !== "string") return null;
|
|
1210
|
+
if (!GITHUB_COMMIT_SHA_RE.test(sha)) return null;
|
|
1211
|
+
try {
|
|
1212
|
+
return { ref: assertSafeGitRef(ref), sha: assertSafeGitRef(sha) };
|
|
1213
|
+
} catch {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
async function fetchGitHubPullBase(opts) {
|
|
1218
|
+
if (!parseGitHubRepository(`${opts.owner}/${opts.name}`)) return null;
|
|
1219
|
+
if (!Number.isInteger(opts.prNumber) || opts.prNumber <= 0) return null;
|
|
1220
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
1221
|
+
const url = `https://api.github.com/repos/${opts.owner}/${opts.name}/pulls/${opts.prNumber}`;
|
|
1222
|
+
const headers = {
|
|
1223
|
+
Accept: "application/vnd.github+json",
|
|
1224
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1225
|
+
"User-Agent": "tested-cli"
|
|
1226
|
+
};
|
|
1227
|
+
const token = githubApiToken(opts.env ?? process.env);
|
|
1228
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1229
|
+
let res;
|
|
1230
|
+
try {
|
|
1231
|
+
res = await fetchFn(url, { method: "GET", redirect: "manual", headers });
|
|
1232
|
+
} catch {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
if (res.status !== 200) return null;
|
|
1236
|
+
try {
|
|
1237
|
+
return parseGitHubPullBase(JSON.parse(await res.text()));
|
|
1238
|
+
} catch {
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
async function resolvePrPushBase(opts) {
|
|
1243
|
+
let requested;
|
|
1244
|
+
try {
|
|
1245
|
+
requested = assertSafeGitRef(opts.requested);
|
|
1246
|
+
} catch {
|
|
1247
|
+
return void 0;
|
|
1248
|
+
}
|
|
1249
|
+
if (await tryRevparse(opts.ctx, requested)) return requested;
|
|
1250
|
+
const branch = toBranchName(requested) || "main";
|
|
1251
|
+
const originRef = `origin/${branch}`;
|
|
1252
|
+
if (originRef !== requested && await tryRevparse(opts.ctx, originRef)) {
|
|
1253
|
+
return originRef;
|
|
1254
|
+
}
|
|
1255
|
+
let prBase = null;
|
|
1256
|
+
if (opts.owner && opts.name) {
|
|
1257
|
+
prBase = await fetchGitHubPullBase({
|
|
1258
|
+
owner: opts.owner,
|
|
1259
|
+
name: opts.name,
|
|
1260
|
+
prNumber: opts.prNumber,
|
|
1261
|
+
...opts.fetchFn ? { fetchFn: opts.fetchFn } : {},
|
|
1262
|
+
...opts.env ? { env: opts.env } : {}
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
if (prBase && await tryRevparse(opts.ctx, prBase.sha)) return prBase.sha;
|
|
1266
|
+
const fetchTargets = [];
|
|
1267
|
+
if (prBase) {
|
|
1268
|
+
fetchTargets.push(prBase.sha, prBase.ref);
|
|
1269
|
+
}
|
|
1270
|
+
fetchTargets.push(branch);
|
|
1271
|
+
let announced = false;
|
|
1272
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1273
|
+
for (const target of fetchTargets) {
|
|
1274
|
+
if (seen.has(target)) continue;
|
|
1275
|
+
seen.add(target);
|
|
1276
|
+
let spec;
|
|
1277
|
+
try {
|
|
1278
|
+
spec = assertSafeGitRef(target);
|
|
1279
|
+
} catch {
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (!announced) {
|
|
1283
|
+
announced = true;
|
|
1284
|
+
opts.onProgress?.("fetching base\u2026");
|
|
1285
|
+
}
|
|
1286
|
+
if (!await fetchOriginRef(opts.ctx, spec)) continue;
|
|
1287
|
+
const resolved = await resolveAfterFetch(opts.ctx, spec);
|
|
1288
|
+
if (resolved) return resolved;
|
|
1289
|
+
if (prBase && await tryRevparse(opts.ctx, prBase.sha)) return prBase.sha;
|
|
1290
|
+
}
|
|
1291
|
+
return void 0;
|
|
1292
|
+
}
|
|
1293
|
+
async function peekRepoIdentity(opts) {
|
|
1294
|
+
let owner = opts.owner;
|
|
1295
|
+
let name = opts.name;
|
|
1296
|
+
if (!owner || !name) {
|
|
1297
|
+
const fromActions = parseGitHubRepository(opts.env.GITHUB_REPOSITORY);
|
|
1298
|
+
if (fromActions) {
|
|
1299
|
+
owner = owner ?? fromActions.owner;
|
|
1300
|
+
name = name ?? fromActions.name;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (!owner || !name) {
|
|
1304
|
+
try {
|
|
1305
|
+
const origin = await remoteUrl(opts.ctx, "origin");
|
|
1306
|
+
const parsed = parseGitHubRemote(origin);
|
|
1307
|
+
if (parsed) {
|
|
1308
|
+
owner = owner ?? parsed.owner;
|
|
1309
|
+
name = name ?? parsed.name;
|
|
1310
|
+
}
|
|
1311
|
+
} catch {
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return { owner: owner ?? null, name: name ?? null };
|
|
1315
|
+
}
|
|
1173
1316
|
function buildIngestBody(input) {
|
|
1174
1317
|
const baseRefName = toBranchName(input.baseRef);
|
|
1175
1318
|
return {
|
|
@@ -1362,16 +1505,16 @@ function formatMissingTokenError(opts) {
|
|
|
1362
1505
|
"or pass --token <token> (avoid on shared hosts: visible in ps)"
|
|
1363
1506
|
]);
|
|
1364
1507
|
}
|
|
1365
|
-
function formatPushError(status, message, code) {
|
|
1508
|
+
function formatPushError(status, message, code, identity) {
|
|
1366
1509
|
if (status === 0) {
|
|
1367
1510
|
return errorBlock(message);
|
|
1368
1511
|
}
|
|
1369
1512
|
const normalized = (code ?? message).toLowerCase();
|
|
1370
|
-
if (normalized.includes("token_required") || normalized.includes("invalid token") || normalized.includes("unauthorized") || status === 401) {
|
|
1513
|
+
if (normalized.includes("token_required") || normalized.includes("invalid token") || normalized.includes("unauthorized") || normalized.includes("invalid credentials") || status === 401) {
|
|
1371
1514
|
return errorBlock("ingest auth failed", [
|
|
1372
1515
|
message,
|
|
1373
1516
|
"",
|
|
1374
|
-
...tokenMintGuidance(),
|
|
1517
|
+
...tokenMintGuidance(identity),
|
|
1375
1518
|
"or --token"
|
|
1376
1519
|
]);
|
|
1377
1520
|
}
|
|
@@ -1460,21 +1603,41 @@ async function executePush(cli, deps) {
|
|
|
1460
1603
|
}
|
|
1461
1604
|
const config = await loadConfigFn({ cwd: deps.cwd });
|
|
1462
1605
|
const ctx = await openRepoFn(deps.cwd);
|
|
1463
|
-
|
|
1606
|
+
const peeked = await peekRepoIdentity({
|
|
1607
|
+
...cli.owner !== void 0 ? { owner: cli.owner } : {},
|
|
1608
|
+
...cli.name !== void 0 ? { name: cli.name } : {},
|
|
1609
|
+
env,
|
|
1610
|
+
ctx
|
|
1611
|
+
});
|
|
1464
1612
|
let diff;
|
|
1465
1613
|
try {
|
|
1614
|
+
let baseOverride = cli.base;
|
|
1615
|
+
if (baseOverride === void 0 && prNumber !== null) {
|
|
1616
|
+
const resolved = await resolvePrPushBase({
|
|
1617
|
+
ctx,
|
|
1618
|
+
requested: config.base,
|
|
1619
|
+
prNumber,
|
|
1620
|
+
...peeked.owner != null ? { owner: peeked.owner } : {},
|
|
1621
|
+
...peeked.name != null ? { name: peeked.name } : {},
|
|
1622
|
+
fetchFn,
|
|
1623
|
+
env,
|
|
1624
|
+
onProgress
|
|
1625
|
+
});
|
|
1626
|
+
if (resolved !== void 0) baseOverride = resolved;
|
|
1627
|
+
}
|
|
1628
|
+
onProgress("computing diff\u2026");
|
|
1466
1629
|
diff = await computeDiffFn({
|
|
1467
1630
|
cwd: deps.cwd,
|
|
1468
1631
|
config,
|
|
1469
|
-
...
|
|
1632
|
+
...baseOverride !== void 0 ? { baseRef: baseOverride } : {},
|
|
1470
1633
|
ctx
|
|
1471
1634
|
});
|
|
1472
1635
|
} catch (err) {
|
|
1473
1636
|
const message = err instanceof Error ? err.message : String(err);
|
|
1474
1637
|
return { exitCode: 1, stdout: "", stderr: errorBlock(message) };
|
|
1475
1638
|
}
|
|
1476
|
-
let owner = cli.owner;
|
|
1477
|
-
let name = cli.name;
|
|
1639
|
+
let owner = cli.owner ?? peeked.owner ?? void 0;
|
|
1640
|
+
let name = cli.name ?? peeked.name ?? void 0;
|
|
1478
1641
|
if (!owner || !name) {
|
|
1479
1642
|
const fromActions = parseGitHubRepository(env.GITHUB_REPOSITORY);
|
|
1480
1643
|
if (fromActions) {
|
|
@@ -1562,7 +1725,10 @@ async function executePush(cli, deps) {
|
|
|
1562
1725
|
return {
|
|
1563
1726
|
exitCode: 1,
|
|
1564
1727
|
stdout: "",
|
|
1565
|
-
stderr: formatPushError(result.status, result.message, result.code
|
|
1728
|
+
stderr: formatPushError(result.status, result.message, result.code, {
|
|
1729
|
+
owner,
|
|
1730
|
+
name
|
|
1731
|
+
})
|
|
1566
1732
|
};
|
|
1567
1733
|
}
|
|
1568
1734
|
const formatted = formatPushSuccess(result.data, cli.json);
|
|
@@ -1961,7 +2127,7 @@ import pc3 from "picocolors";
|
|
|
1961
2127
|
// package.json
|
|
1962
2128
|
var package_default = {
|
|
1963
2129
|
name: "@tested/cli",
|
|
1964
|
-
version: "0.1.
|
|
2130
|
+
version: "0.1.7",
|
|
1965
2131
|
description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
|
|
1966
2132
|
license: "MIT",
|
|
1967
2133
|
homepage: "https://tested.dev",
|
package/package.json
CHANGED