@abloh/repository 1.0.2-staging.34846809255 → 1.0.2-staging.34856316728
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/index.d.ts +3 -3
- package/dist/index.js +91 -91
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1527,9 +1527,9 @@ type PackageManagerName = "npm" | "pnpm" | "yarn" | "bun";
|
|
|
1527
1527
|
declare const CI_RECIPE_REFUSALS: {
|
|
1528
1528
|
readonly browser: "browser-driven suites are not supported in the sealed run - keep them in your own CI";
|
|
1529
1529
|
readonly noWorkflows: "no .github/workflows directory";
|
|
1530
|
-
readonly noPullRequestWorkflow: "no workflow runs on pull_request, so your CI declares no build
|
|
1531
|
-
readonly noTestJob: "no pull-request job runs a test command
|
|
1532
|
-
readonly noTestJobAnywhere: "no job in your CI runs a test command
|
|
1530
|
+
readonly noPullRequestWorkflow: "no workflow runs on pull_request, so your CI declares no build abloh can read";
|
|
1531
|
+
readonly noTestJob: "no pull-request job runs a test command abloh recognizes";
|
|
1532
|
+
readonly noTestJobAnywhere: "no job in your CI runs a test command abloh recognizes";
|
|
1533
1533
|
};
|
|
1534
1534
|
/**
|
|
1535
1535
|
* Is this refusal worth a customer's attention, or is it just "there was nothing here to read"?
|
package/dist/index.js
CHANGED
|
@@ -559,12 +559,12 @@ function readAptStep(command) {
|
|
|
559
559
|
if (APT_SEGMENT_UPDATE.test(segment)) continue;
|
|
560
560
|
const install = APT_SEGMENT_INSTALL.exec(segment);
|
|
561
561
|
if (install === null) {
|
|
562
|
-
return { ok: false, why: `an apt step also runs something
|
|
562
|
+
return { ok: false, why: `an apt step also runs something abloh cannot read: ${clip(command)}` };
|
|
563
563
|
}
|
|
564
564
|
for (const token of `${install[1]} ${install[2]}`.split(/\s+/u).filter((value) => value !== "")) {
|
|
565
565
|
if (token.startsWith("-")) {
|
|
566
566
|
if (!APT_FLAGS.has(token)) {
|
|
567
|
-
return { ok: false, why: `an apt step carries the option ${token}, which
|
|
567
|
+
return { ok: false, why: `an apt step carries the option ${token}, which abloh will not pass through: ${clip(command)}` };
|
|
568
568
|
}
|
|
569
569
|
continue;
|
|
570
570
|
}
|
|
@@ -990,7 +990,7 @@ function readStep(step, manifest, reader) {
|
|
|
990
990
|
if (step.uses.startsWith("./")) return { kind: "refuse", why: `unresolved local action ${step.uses}` };
|
|
991
991
|
for (const key of ["command", "run", "script", "args"]) {
|
|
992
992
|
if (typeof step.with?.[key] === "string") {
|
|
993
|
-
return { kind: "refuse", why: `the action ${id} is handed a ${key}
|
|
993
|
+
return { kind: "refuse", why: `the action ${id} is handed a ${key} abloh cannot read` };
|
|
994
994
|
}
|
|
995
995
|
}
|
|
996
996
|
return { kind: "ignore" };
|
|
@@ -1068,7 +1068,7 @@ function readStep(step, manifest, reader) {
|
|
|
1068
1068
|
}
|
|
1069
1069
|
if (TEST_BINARIES.has(name)) return { kind: "test", command };
|
|
1070
1070
|
if (BUILD_BINARIES.has(name)) {
|
|
1071
|
-
return /\b(build|compile|dist|bundle)\b/u.test(tail) || tail.trim() === "" ? { kind: "build", command, env } : { kind: "refuse", why: `a build tool runs a
|
|
1071
|
+
return /\b(build|compile|dist|bundle)\b/u.test(tail) || tail.trim() === "" ? { kind: "build", command, env } : { kind: "refuse", why: `a build tool runs a task abloh cannot classify: ${clip(command)}` };
|
|
1072
1072
|
}
|
|
1073
1073
|
if (name === "exec" || name === "dlx" || name === "npx") {
|
|
1074
1074
|
if (declaredToolRun(command, manifest)) return { kind: "build", command, env };
|
|
@@ -1079,7 +1079,7 @@ function readStep(step, manifest, reader) {
|
|
|
1079
1079
|
}
|
|
1080
1080
|
if (invokesRunnerDirectly(command)) return { kind: "test", command };
|
|
1081
1081
|
if (declaredToolRun(command, manifest)) return { kind: "build", command, env };
|
|
1082
|
-
return { kind: "refuse", why: `a step runs a command
|
|
1082
|
+
return { kind: "refuse", why: `a step runs a command abloh does not recognize: ${clip(command)}` };
|
|
1083
1083
|
}
|
|
1084
1084
|
function declaredToolRun(command, manifest) {
|
|
1085
1085
|
const plan = planCommand(command);
|
|
@@ -1104,7 +1104,7 @@ function classifyRepoDefinedBuild(command, reader) {
|
|
|
1104
1104
|
}
|
|
1105
1105
|
for (const token of tokens.slice(interpreted ? 2 : 1)) {
|
|
1106
1106
|
if (!LITERAL_ARGV_TOKEN.test(token)) {
|
|
1107
|
-
return { ok: false, why: `a repository build command carries an argument
|
|
1107
|
+
return { ok: false, why: `a repository build command carries an argument abloh will not pass through: ${clip(command)}` };
|
|
1108
1108
|
}
|
|
1109
1109
|
}
|
|
1110
1110
|
if (interpreted) {
|
|
@@ -1120,7 +1120,7 @@ function classifyRepoDefinedBuild(command, reader) {
|
|
|
1120
1120
|
}
|
|
1121
1121
|
const target = tokens.slice(1).find((token) => !token.startsWith("-"));
|
|
1122
1122
|
if (target === void 0) {
|
|
1123
|
-
return { ok: false, why: `a repository build command names no
|
|
1123
|
+
return { ok: false, why: `a repository build command names no task abloh can classify: ${clip(command)}` };
|
|
1124
1124
|
}
|
|
1125
1125
|
if (!BUILD_NAMES.test(target)) return null;
|
|
1126
1126
|
return { ok: true };
|
|
@@ -1335,9 +1335,9 @@ function narrowedByPathFilter(trigger) {
|
|
|
1335
1335
|
return trigger.pathsIgnore.some((entry) => !nothingToMeasurePathFilter(entry));
|
|
1336
1336
|
}
|
|
1337
1337
|
var NO_WORKFLOWS = "no .github/workflows directory";
|
|
1338
|
-
var NO_PR_WORKFLOW = "no workflow runs on pull_request, so your CI declares no build
|
|
1339
|
-
var NO_TEST_JOB = "no pull-request job runs a test command
|
|
1340
|
-
var NO_TEST_JOB_ANYWHERE = "no job in your CI runs a test command
|
|
1338
|
+
var NO_PR_WORKFLOW = "no workflow runs on pull_request, so your CI declares no build abloh can read";
|
|
1339
|
+
var NO_TEST_JOB = "no pull-request job runs a test command abloh recognizes";
|
|
1340
|
+
var NO_TEST_JOB_ANYWHERE = "no job in your CI runs a test command abloh recognizes";
|
|
1341
1341
|
var BROWSER_REFUSAL = "browser-driven suites are not supported in the sealed run - keep them in your own CI";
|
|
1342
1342
|
function toolchainRefusal(toolchains) {
|
|
1343
1343
|
return `your CI test job needs ${[...new Set(toolchains)].join(" and ")}, which the Node proof image does not carry`;
|
|
@@ -1480,7 +1480,7 @@ function githubCandidates(reader, manifest, bounds = [], calls = []) {
|
|
|
1480
1480
|
});
|
|
1481
1481
|
const scope = workflowScope(reader, (found) => {
|
|
1482
1482
|
bounds.push(
|
|
1483
|
-
`your .github/workflows directory holds ${found} workflow files and
|
|
1483
|
+
`your .github/workflows directory holds ${found} workflow files and abloh reads the first ${MAX_WORKFLOW_FILES} in name order, so the rest were not read. Anything derived below comes from the files that were.`
|
|
1484
1484
|
);
|
|
1485
1485
|
});
|
|
1486
1486
|
if (scope === null) return none(NO_WORKFLOWS);
|
|
@@ -1507,18 +1507,18 @@ function unreadableWorkflowCall(uses, documents) {
|
|
|
1507
1507
|
const called = uses.trim();
|
|
1508
1508
|
if (called === "") return null;
|
|
1509
1509
|
if (!called.startsWith(IN_REPO_WORKFLOW)) {
|
|
1510
|
-
return `calls the reusable workflow \`${called}\`, which lives in another repository, so
|
|
1510
|
+
return `calls the reusable workflow \`${called}\`, which lives in another repository, so abloh could not read the steps it runs`;
|
|
1511
1511
|
}
|
|
1512
1512
|
const target = called.slice(IN_REPO_WORKFLOW.length).split("@")[0];
|
|
1513
1513
|
if (documents.has(target)) return null;
|
|
1514
|
-
return `calls \`${called}\`, and
|
|
1514
|
+
return `calls \`${called}\`, and abloh found no such workflow file in this repository to read`;
|
|
1515
1515
|
}
|
|
1516
1516
|
function noteUnreadableCalls(entries, bounds) {
|
|
1517
1517
|
if (entries.length === 0) return;
|
|
1518
1518
|
const calls = entries.map((entry) => `\`${entry.job}\` ${entry.reason}`);
|
|
1519
1519
|
const named = calls.slice(0, MAX_NAMED_CALLS);
|
|
1520
1520
|
const rest = calls.length - named.length;
|
|
1521
|
-
const line = `${named.join("; ")}${rest > 0 ? `, and ${rest} further job${rest === 1 ? "" : "s"} like them` : ""}. If one of those runs the suite you want measured,
|
|
1521
|
+
const line = `${named.join("; ")}${rest > 0 ? `, and ${rest} further job${rest === 1 ? "" : "s"} like them` : ""}. If one of those runs the suite you want measured, abloh did not read it, and nothing derived below comes from it.`;
|
|
1522
1522
|
if (!bounds.includes(line)) bounds.push(line);
|
|
1523
1523
|
}
|
|
1524
1524
|
function jobsInScope(reader, manifest, documents, reachable, noTestJob = NO_TEST_JOB, bounds = [], calls = []) {
|
|
@@ -1591,7 +1591,7 @@ function assemble(input) {
|
|
|
1591
1591
|
const fallbackTrigger = input.fallbackTriggers.get(chosen.file);
|
|
1592
1592
|
if (fallbackTrigger !== void 0) {
|
|
1593
1593
|
notices.push(
|
|
1594
|
-
`no pull_request workflow in this repository declares a suite, so
|
|
1594
|
+
`no pull_request workflow in this repository declares a suite, so abloh read ${chosen.file}::${chosen.jobId}, which runs on ${fallbackTrigger}. Everything derived from your CI below comes from that job.`
|
|
1595
1595
|
);
|
|
1596
1596
|
}
|
|
1597
1597
|
const reading = readBrowserLane(repoDir);
|
|
@@ -1611,16 +1611,16 @@ function assemble(input) {
|
|
|
1611
1611
|
const read = laneReading.evidence.map((entry) => `${entry.file} (${entry.fact})`).join(", ");
|
|
1612
1612
|
if (laneReading.measurable) {
|
|
1613
1613
|
notices.push(
|
|
1614
|
-
`your ${laneReading.kind} suite drives a real browser, and
|
|
1614
|
+
`your ${laneReading.kind} suite drives a real browser, and abloh measures it: ${read}. abloh wrote environment.browser: chromium, which puts a browser in the proof image and puts the run on the browser seal - still --network none, so the suite reaches 127.0.0.1 and nothing else.` + (install === null ? " Add `npx playwright install chromium` to your setup script: abloh found no step in your CI that places a browser, and the sealed run has no network to fetch one." : installIsLiteral ? ` Your own \`${clip(install)}\` was kept as a setup step, so the browser your lockfile pins is the one measured.` : ` Copy your own \`${clip(install)}\` into your setup script - abloh could not write it as one command, and without it the run has no browser your lockfile pinned.`)
|
|
1615
1615
|
);
|
|
1616
1616
|
} else {
|
|
1617
1617
|
notices.push(
|
|
1618
|
-
`your CI job ${chosen.file}::${chosen.jobId} runs a ${laneReading.kind} browser lane and abloh does not measure this browser lane yet (${read}). ${BROWSER_REFUSAL}. Nothing was derived from that step
|
|
1618
|
+
`your CI job ${chosen.file}::${chosen.jobId} runs a ${laneReading.kind} browser lane and abloh does not measure this browser lane yet (${read}). ${BROWSER_REFUSAL}. Nothing was derived from that step. The rest of the job was read normally.`
|
|
1619
1619
|
);
|
|
1620
1620
|
}
|
|
1621
1621
|
} else if (chosen.browser !== null && chosen.browserBy === "evidence") {
|
|
1622
1622
|
notices.push(
|
|
1623
|
-
`your CI job ${chosen.file}::${chosen.jobId} also runs a browser lane (${clip(chosen.browser)}), which
|
|
1623
|
+
`your CI job ${chosen.file}::${chosen.jobId} also runs a browser lane (${clip(chosen.browser)}), which abloh does not measure - ${BROWSER_REFUSAL}. Nothing was derived from that step. The rest of the job was read normally.`
|
|
1624
1624
|
);
|
|
1625
1625
|
} else if (chosen.browser !== null) {
|
|
1626
1626
|
}
|
|
@@ -1670,17 +1670,17 @@ function assemble(input) {
|
|
|
1670
1670
|
const producers = producerJobs(chosen, byFile.get(chosen.file));
|
|
1671
1671
|
if (producers.length === 0) {
|
|
1672
1672
|
notices.push(
|
|
1673
|
-
`your CI test job downloads a build produced by another job and
|
|
1673
|
+
`your CI test job downloads a build produced by another job and abloh could not find which one. Declare the build under environment.setupCommands if the suite needs it`
|
|
1674
1674
|
);
|
|
1675
1675
|
} else if (producers.length > 1) {
|
|
1676
1676
|
notices.push(
|
|
1677
|
-
`your CI test job downloads a build produced by more than one job (${producers.map((producer) => producer.jobId).join(", ")})
|
|
1677
|
+
`your CI test job downloads a build produced by more than one job (${producers.map((producer) => producer.jobId).join(", ")}). Declare the build under environment.setupCommands if the suite needs it`
|
|
1678
1678
|
);
|
|
1679
1679
|
} else {
|
|
1680
1680
|
const producer = producers[0];
|
|
1681
1681
|
if (preparationBuilds(producer).length === 0) {
|
|
1682
1682
|
notices.push(
|
|
1683
|
-
`your CI builds in ${producer.file}::${producer.jobId} and
|
|
1683
|
+
`your CI builds in ${producer.file}::${producer.jobId} and abloh could not read that job's build step. Declare it under environment.setupCommands if the suite needs it`
|
|
1684
1684
|
);
|
|
1685
1685
|
}
|
|
1686
1686
|
for (const build of preparationBuilds(producer)) {
|
|
@@ -1692,19 +1692,19 @@ function assemble(input) {
|
|
|
1692
1692
|
const afterSuite = chosen.steps.filter((step) => step.kind === "build" && step.afterTests);
|
|
1693
1693
|
if (afterSuite.length > 0) {
|
|
1694
1694
|
notices.push(
|
|
1695
|
-
`your CI job ${chosen.file}::${chosen.jobId} builds after it has run its suite (${afterSuite.map((step) => step.command).join("; ")}), so
|
|
1695
|
+
`your CI job ${chosen.file}::${chosen.jobId} builds after it has run its suite (${afterSuite.map((step) => step.command).join("; ")}), so abloh read that as a check on the build rather than as preparation and did not run it first - declare it under environment.setupCommands if your suite needs it`
|
|
1696
1696
|
);
|
|
1697
1697
|
}
|
|
1698
1698
|
if (drafted.length > MAX_SETUP_COMMANDS) {
|
|
1699
1699
|
return empty(
|
|
1700
|
-
`your CI runs ${drafted.length} build steps and a recipe takes at most ${MAX_SETUP_COMMANDS}
|
|
1700
|
+
`your CI runs ${drafted.length} build steps and a recipe takes at most ${MAX_SETUP_COMMANDS}. Declare the ones the suite needs under environment.setupCommands`,
|
|
1701
1701
|
notices
|
|
1702
1702
|
);
|
|
1703
1703
|
}
|
|
1704
1704
|
const scoped = drafted.filter((entry) => /--workspace[= ]|--filter[= ]|yarn workspace /u.test(entry.command));
|
|
1705
1705
|
if (scoped.length > 0) {
|
|
1706
1706
|
notices.push(
|
|
1707
|
-
`the build
|
|
1707
|
+
`the build abloh read is scoped to one workspace package (${scoped[0].command}). Your CI keeps one job per package, so check it covers what your testCommand measures`
|
|
1708
1708
|
);
|
|
1709
1709
|
}
|
|
1710
1710
|
const jobRef = `${chosen.file}::${chosen.jobId}`;
|
|
@@ -1731,14 +1731,14 @@ function assemble(input) {
|
|
|
1731
1731
|
systemPackages.sort((left, right) => left.name.localeCompare(right.name));
|
|
1732
1732
|
if (systemPackages.length > MAX_SYSTEM_PACKAGES) {
|
|
1733
1733
|
notices.push(
|
|
1734
|
-
`your CI installs ${systemPackages.length} OS packages and environment.systemPackages takes at most ${MAX_SYSTEM_PACKAGES}
|
|
1734
|
+
`your CI installs ${systemPackages.length} OS packages and environment.systemPackages takes at most ${MAX_SYSTEM_PACKAGES}. Pin environment.runtimeImage to an image that already carries them instead`
|
|
1735
1735
|
);
|
|
1736
1736
|
systemPackages.length = 0;
|
|
1737
1737
|
}
|
|
1738
1738
|
const derivedTest = chosen.testChoice.command !== null && !schemaWouldRefuse(chosen.testChoice.command, "test") ? { command: chosen.testChoice.command, source: jobRef } : null;
|
|
1739
1739
|
if (derivedTest === null && chosen.testChoice.command === null) {
|
|
1740
1740
|
notices.push(
|
|
1741
|
-
`
|
|
1741
|
+
`abloh could not tell which command your CI job ${jobRef} treats as the suite - ${chosen.testChoice.why}. Declare environment.testCommand if the derived one is wrong`
|
|
1742
1742
|
);
|
|
1743
1743
|
}
|
|
1744
1744
|
const environmentValues = /* @__PURE__ */ new Map();
|
|
@@ -1795,7 +1795,7 @@ function assemble(input) {
|
|
|
1795
1795
|
const namePreparationGaps = (unresolved, command) => {
|
|
1796
1796
|
if (unresolved.length === 0) return;
|
|
1797
1797
|
notices.push(
|
|
1798
|
-
`your CI sets ${unresolved.join(", ")} for ${command} and computes the value in the workflow runner, so
|
|
1798
|
+
`your CI sets ${unresolved.join(", ")} for ${command} and computes the value in the workflow runner, so abloh wrote none - add the line to your setup script if that command needs it`
|
|
1799
1799
|
);
|
|
1800
1800
|
};
|
|
1801
1801
|
const installSteps = [chosen, ...inlinedProducers].flatMap((candidate) => candidate.steps.filter((step) => step.kind === "install"));
|
|
@@ -1818,13 +1818,13 @@ function assemble(input) {
|
|
|
1818
1818
|
const computed = bySkipClass("computed");
|
|
1819
1819
|
if (computed.length > 0) {
|
|
1820
1820
|
notices.push(
|
|
1821
|
-
`your CI sets ${listed(computed)}
|
|
1821
|
+
`your CI sets ${listed(computed)}. abloh cannot evaluate a workflow expression, so a sealed run gets no value for those. Write the literal your suite needs under environment.environmentValues - one \`NAME: value\` line each - or, where the value is a secret rather than a setting, name it under environment.requiredVariables and supply it at run time`
|
|
1822
1822
|
);
|
|
1823
1823
|
}
|
|
1824
1824
|
const unwritable = bySkipClass("unwritable");
|
|
1825
1825
|
if (unwritable.length > 0) {
|
|
1826
1826
|
notices.push(
|
|
1827
|
-
`your CI sets ${listed(unwritable)}
|
|
1827
|
+
`your CI sets ${listed(unwritable)}. abloh wrote no value for those - declare what the suite needs under environment.environmentValues or environment.requiredVariables`
|
|
1828
1828
|
);
|
|
1829
1829
|
}
|
|
1830
1830
|
const services = [...chosen.services];
|
|
@@ -2547,7 +2547,7 @@ function readJob(reader, manifest, file, jobId, job, workflowEnv, bounds = []) {
|
|
|
2547
2547
|
let suiteClosed = false;
|
|
2548
2548
|
for (const step of expandSteps(reader, job.steps, 0, () => {
|
|
2549
2549
|
bounds.push(
|
|
2550
|
-
`the job ${file}::${jobId} declares more than ${MAX_STEPS_PER_JOB} steps and
|
|
2550
|
+
`the job ${file}::${jobId} declares more than ${MAX_STEPS_PER_JOB} steps and abloh reads the first ${MAX_STEPS_PER_JOB}, so any step after that was not read.`
|
|
2551
2551
|
);
|
|
2552
2552
|
})) {
|
|
2553
2553
|
const stepEnvironment = layerEnvironment([
|
|
@@ -2934,16 +2934,16 @@ function classifyNxTestTarget(projectJson, directory) {
|
|
|
2934
2934
|
if (target === void 0) {
|
|
2935
2935
|
return {
|
|
2936
2936
|
state: "unmeasurable",
|
|
2937
|
-
reason: `the Nx project at ${directory} declares no test
|
|
2938
|
-
remedy: `give ${directory} a test
|
|
2937
|
+
reason: `the Nx project at ${directory} declares no \`targets.test\` entry`,
|
|
2938
|
+
remedy: `give ${directory} a \`targets.test\` entry in its project.json, or a "test" script in its package.json`
|
|
2939
2939
|
};
|
|
2940
2940
|
}
|
|
2941
2941
|
const executor = typeof target.executor === "string" ? target.executor : typeof target.builder === "string" ? target.builder : null;
|
|
2942
2942
|
if (executor === null) {
|
|
2943
2943
|
return {
|
|
2944
2944
|
state: "unmeasurable",
|
|
2945
|
-
reason: `the Nx project at ${directory} declares a test
|
|
2946
|
-
remedy: `name an executor on ${directory}'s test
|
|
2945
|
+
reason: `the Nx project at ${directory} declares a \`targets.test\` entry with no executor`,
|
|
2946
|
+
remedy: `name an executor on ${directory}'s \`targets.test\` entry, or give the package a "test" script`
|
|
2947
2947
|
};
|
|
2948
2948
|
}
|
|
2949
2949
|
const rootConfig = ROOT_CONFIG_EXECUTORS.get(executor);
|
|
@@ -2953,7 +2953,7 @@ function classifyNxTestTarget(projectJson, directory) {
|
|
|
2953
2953
|
return {
|
|
2954
2954
|
state: "unmeasurable",
|
|
2955
2955
|
reason: `the Nx project at ${directory} runs ${rootConfig.runner} through ${executor} but names no ${rootConfig.configOption}, so there is no config to run it against from the workspace root`,
|
|
2956
|
-
remedy: `state ${rootConfig.configOption} on ${directory}'s test
|
|
2956
|
+
remedy: `state ${rootConfig.configOption} on ${directory}'s \`targets.test\` entry, or give the package a "test" script`
|
|
2957
2957
|
};
|
|
2958
2958
|
}
|
|
2959
2959
|
const configFile = normalizeWorkspacePath(declaredConfig);
|
|
@@ -2986,23 +2986,23 @@ function classifyNxTestTarget(projectJson, directory) {
|
|
|
2986
2986
|
if (commands !== void 0 && commands.length !== 1) {
|
|
2987
2987
|
return {
|
|
2988
2988
|
state: "unmeasurable",
|
|
2989
|
-
reason: `the Nx project at ${directory} runs ${commands.length} commands for its test
|
|
2990
|
-
remedy: `split ${directory}'s test
|
|
2989
|
+
reason: `the Nx project at ${directory} runs ${commands.length} commands for its \`targets.test\` entry, which is not one suite`,
|
|
2990
|
+
remedy: `split ${directory}'s \`targets.test\` entry so one command runs one suite, or give the package a "test" script`
|
|
2991
2991
|
};
|
|
2992
2992
|
}
|
|
2993
2993
|
const only = single ?? (typeof commands?.[0] === "string" ? commands[0] : void 0);
|
|
2994
2994
|
if (only === void 0) {
|
|
2995
2995
|
return {
|
|
2996
2996
|
state: "unmeasurable",
|
|
2997
|
-
reason: `the Nx project at ${directory} declares a run-commands test
|
|
2998
|
-
remedy: `state one literal command on ${directory}'s test
|
|
2997
|
+
reason: `the Nx project at ${directory} declares a run-commands \`targets.test\` entry with no literal command`,
|
|
2998
|
+
remedy: `state one literal command on ${directory}'s \`targets.test\` entry, or give the package a "test" script`
|
|
2999
2999
|
};
|
|
3000
3000
|
}
|
|
3001
3001
|
if (/[;&|<>`\n]|\$\(/u.test(only)) {
|
|
3002
3002
|
return {
|
|
3003
3003
|
state: "unmeasurable",
|
|
3004
|
-
reason: `the Nx project at ${directory} composes shell commands in its test
|
|
3005
|
-
remedy: `reduce ${directory}'s test
|
|
3004
|
+
reason: `the Nx project at ${directory} composes shell commands in its \`targets.test\` entry rather than running one suite`,
|
|
3005
|
+
remedy: `reduce ${directory}'s \`targets.test\` entry to one runner invocation, or give the package a "test" script`
|
|
3006
3006
|
};
|
|
3007
3007
|
}
|
|
3008
3008
|
const cwd = typeof options["cwd"] === "string" ? options["cwd"] : null;
|
|
@@ -3010,7 +3010,7 @@ function classifyNxTestTarget(projectJson, directory) {
|
|
|
3010
3010
|
return {
|
|
3011
3011
|
state: "unmeasurable",
|
|
3012
3012
|
reason: `the Nx project at ${directory} runs its test command from ` + (cwd === null ? "the workspace root" : `${cwd}`) + ", and this run measures a package in its own directory",
|
|
3013
|
-
remedy: `set cwd to "{projectRoot}" on ${directory}'s test
|
|
3013
|
+
remedy: `set cwd to "{projectRoot}" on ${directory}'s \`targets.test\` entry, or give the package a "test" script`
|
|
3014
3014
|
};
|
|
3015
3015
|
}
|
|
3016
3016
|
const runner = commandRunner(only);
|
|
@@ -3018,7 +3018,7 @@ function classifyNxTestTarget(projectJson, directory) {
|
|
|
3018
3018
|
return {
|
|
3019
3019
|
state: "unmeasurable",
|
|
3020
3020
|
reason: `the Nx project at ${directory} names no single supported test runner in its test command`,
|
|
3021
|
-
remedy: `run one of ${[...SCRIPT_NAMED_RUNNERS2].sort().join(", ")} or node --test from ${directory}'s test
|
|
3021
|
+
remedy: `run one of ${[...SCRIPT_NAMED_RUNNERS2].sort().join(", ")} or node --test from ${directory}'s \`targets.test\` entry`
|
|
3022
3022
|
};
|
|
3023
3023
|
}
|
|
3024
3024
|
return { state: "runner", runner, command: only };
|
|
@@ -3424,7 +3424,7 @@ function expandOnDisk(ctx, repoDir, relative7, segments, out, refusals) {
|
|
|
3424
3424
|
expandOnDisk(ctx, repoDir, prefix, rest, out, refusals);
|
|
3425
3425
|
if (depth >= MAX_DOUBLE_STAR_DISK_DEPTH) {
|
|
3426
3426
|
refusals?.push(
|
|
3427
|
-
`a \`**\` in this workspace's package globs is
|
|
3427
|
+
`a \`**\` in this workspace's package globs is read ${MAX_DOUBLE_STAR_DISK_DEPTH} directories deep, so anything below ${prefix.join("/") || "the repository root"} was not reached`
|
|
3428
3428
|
);
|
|
3429
3429
|
return;
|
|
3430
3430
|
}
|
|
@@ -3480,7 +3480,7 @@ function enumerateWorkspacePackages(repoDir, ctx = diskRepoAccess(repoDir)) {
|
|
|
3480
3480
|
const lists = parsePatternLists(patterns);
|
|
3481
3481
|
if ("unsupported" in lists) {
|
|
3482
3482
|
refusals.push(
|
|
3483
|
-
`this workspace declares a package glob
|
|
3483
|
+
`this workspace declares a package glob abloh has no rule for (${lists.unsupported}), so its packages could not be enumerated`
|
|
3484
3484
|
);
|
|
3485
3485
|
return done([]);
|
|
3486
3486
|
}
|
|
@@ -3489,7 +3489,7 @@ function enumerateWorkspacePackages(repoDir, ctx = diskRepoAccess(repoDir)) {
|
|
|
3489
3489
|
const matched = [...out].filter((dir) => !lists.negatives.some((pattern) => matchesPattern(dir.split("/"), pattern))).sort();
|
|
3490
3490
|
if (matched.length > MAX_ENUMERATED_PACKAGES) {
|
|
3491
3491
|
refusals.push(
|
|
3492
|
-
`this workspace declares ${matched.length} packages and
|
|
3492
|
+
`this workspace declares ${matched.length} packages and abloh enumerates the first ${MAX_ENUMERATED_PACKAGES} in path order, so the rest were not considered`
|
|
3493
3493
|
);
|
|
3494
3494
|
}
|
|
3495
3495
|
return done(matched.slice(0, MAX_ENUMERATED_PACKAGES));
|
|
@@ -3555,26 +3555,26 @@ function selectUndeclaredNestedOwner(input) {
|
|
|
3555
3555
|
};
|
|
3556
3556
|
if (outsideSource.length > 0) {
|
|
3557
3557
|
return skipped(
|
|
3558
|
-
`changed source spans ${owners.join(", ")} and the repository root (${shown(outsideSource)})
|
|
3558
|
+
`changed source spans ${owners.join(", ")} and the repository root (${shown(outsideSource)}) - ${rootBehavior}`
|
|
3559
3559
|
);
|
|
3560
3560
|
}
|
|
3561
3561
|
if (owners.length > 1) {
|
|
3562
|
-
return skipped(`changed source spans undeclared nested packages: ${owners.join(", ")}
|
|
3562
|
+
return skipped(`changed source spans undeclared nested packages: ${owners.join(", ")} - ${rootBehavior}`);
|
|
3563
3563
|
}
|
|
3564
3564
|
const candidate = owners[0];
|
|
3565
|
-
const named = (why, remedy) => skipped(`${candidate} owns every changed source file but ${why}
|
|
3565
|
+
const named = (why, remedy) => skipped(`${candidate} owns every changed source file but ${why} - ${rootBehavior}. ${remedy}`);
|
|
3566
3566
|
const segments = candidate.split("/");
|
|
3567
3567
|
for (let depth = 0; depth <= segments.length; depth += 1) {
|
|
3568
3568
|
const path = depth === 0 ? "package.json" : `${segments.slice(0, depth).join("/")}/package.json`;
|
|
3569
3569
|
const trusted = parseManifest(blobText(repoDir, trustedRev, path), path);
|
|
3570
3570
|
const measured = parseManifest(measuredText(path), path);
|
|
3571
3571
|
if (trusted === void 0 || measured === void 0) {
|
|
3572
|
-
return named(`${path} is unreadable`, `make ${path} valid JSON on both sides of the
|
|
3572
|
+
return named(`${path} is unreadable`, `make ${path} valid JSON on both sides of the change.`);
|
|
3573
3573
|
}
|
|
3574
3574
|
if (testRelevantManifest(trusted) !== testRelevantManifest(measured)) {
|
|
3575
3575
|
return named(
|
|
3576
|
-
`${path} changes its scripts or dependencies in the measured
|
|
3577
|
-
`land the manifest change separately from the code change, or
|
|
3576
|
+
`${path} changes its scripts or dependencies in the change measured`,
|
|
3577
|
+
`land the manifest change separately from the code change, or name the package with --subdir ${candidate}.`
|
|
3578
3578
|
);
|
|
3579
3579
|
}
|
|
3580
3580
|
}
|
|
@@ -3588,10 +3588,10 @@ function selectUndeclaredNestedOwner(input) {
|
|
|
3588
3588
|
if (runner === null || !DIRECT_RUNNERS.has(runner)) {
|
|
3589
3589
|
return classified.aggregatorTaskRefusal != null ? named(
|
|
3590
3590
|
classified.aggregatorTaskRefusal,
|
|
3591
|
-
`give ${candidate} a "test" script naming the one suite this run should measure, or
|
|
3591
|
+
`give ${candidate} a "test" script naming the one suite this run should measure, or name the package with --subdir ${candidate}.`
|
|
3592
3592
|
) : named(
|
|
3593
3593
|
"it declares no direct supported test runner of its own",
|
|
3594
|
-
`give ${candidate} a "test" script that runs one of ${[...DIRECT_RUNNERS].sort().join(", ")} directly, or
|
|
3594
|
+
`give ${candidate} a "test" script that runs one of ${[...DIRECT_RUNNERS].sort().join(", ")} directly, or name the package with --subdir ${candidate}.`
|
|
3595
3595
|
);
|
|
3596
3596
|
}
|
|
3597
3597
|
const dependencies = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
@@ -3636,7 +3636,7 @@ function selectAutoTarget(input) {
|
|
|
3636
3636
|
const rootManifest = parseManifest(rootManifestText);
|
|
3637
3637
|
if (rootManifest === void 0) return skipped("root package.json is unreadable at the merge base");
|
|
3638
3638
|
const rootDeclaresRunner = rootManifest !== null && classifyWithAliasHop(rootManifest, aliasHopFamily(rootManifest)).runner !== null;
|
|
3639
|
-
const rootRunnerSkip = "repository root already declares a direct test runner
|
|
3639
|
+
const rootRunnerSkip = "repository root already declares a direct test runner - measuring the root as usual";
|
|
3640
3640
|
const pnpmWorkspaceText = blobText(repoDir, trustedRev, "pnpm-workspace.yaml");
|
|
3641
3641
|
let patterns;
|
|
3642
3642
|
let definitionPath;
|
|
@@ -3708,7 +3708,7 @@ function selectAutoTarget(input) {
|
|
|
3708
3708
|
}
|
|
3709
3709
|
for (const path of /* @__PURE__ */ new Set([definitionPath, "package.json"])) {
|
|
3710
3710
|
const trusted = blobText(repoDir, trustedRev, path);
|
|
3711
|
-
if (trusted !== measuredText(path)) return skipped(`${path} changed in the measured
|
|
3711
|
+
if (trusted !== measuredText(path)) return skipped(`${path} changed in the change measured`);
|
|
3712
3712
|
}
|
|
3713
3713
|
const entries = changedEntries(repoDir, base, head, uncommitted, trustedRev);
|
|
3714
3714
|
const filesByDir = /* @__PURE__ */ new Map();
|
|
@@ -3747,14 +3747,14 @@ function selectAutoTarget(input) {
|
|
|
3747
3747
|
for (let depth = 1; depth <= segments.length; depth += 1) {
|
|
3748
3748
|
const path = `${segments.slice(0, depth).join("/")}/package.json`;
|
|
3749
3749
|
if (blobText(repoDir, trustedRev, path) !== measuredText(path)) {
|
|
3750
|
-
return { ok: false, reason: `package manifest changed in the measured
|
|
3750
|
+
return { ok: false, reason: `package manifest changed in the change measured: ${path}`, environmental: false };
|
|
3751
3751
|
}
|
|
3752
3752
|
}
|
|
3753
3753
|
const nxProject = nxProjects.get(candidate);
|
|
3754
3754
|
if (nxProject !== void 0) {
|
|
3755
3755
|
const path = `${candidate}/project.json`;
|
|
3756
3756
|
if (blobText(repoDir, trustedRev, path) !== measuredText(path)) {
|
|
3757
|
-
return { ok: false, reason: `Nx project definition changed in the measured
|
|
3757
|
+
return { ok: false, reason: `Nx project definition changed in the change measured: ${path}`, environmental: false };
|
|
3758
3758
|
}
|
|
3759
3759
|
}
|
|
3760
3760
|
const manifest = parseManifest(blobText(repoDir, trustedRev, `${candidate}/package.json`), `${candidate}/package.json`);
|
|
@@ -3768,13 +3768,13 @@ function selectAutoTarget(input) {
|
|
|
3768
3768
|
const path = nxTest.configFile;
|
|
3769
3769
|
const trustedConfig = blobText(repoDir, trustedRev, path);
|
|
3770
3770
|
if (trustedConfig !== measuredText(path)) {
|
|
3771
|
-
return { ok: false, reason: `workspace-root suite config changed in the measured
|
|
3771
|
+
return { ok: false, reason: `workspace-root suite config changed in the change measured: ${path}`, environmental: false };
|
|
3772
3772
|
}
|
|
3773
3773
|
if (trustedConfig === null) {
|
|
3774
3774
|
return {
|
|
3775
3775
|
ok: false,
|
|
3776
3776
|
reason: `the Nx project at ${candidate} names a test config that is not in the tree: ${path}`,
|
|
3777
|
-
remedy: `add ${path}, or point ${candidate}'s test
|
|
3777
|
+
remedy: `add ${path}, or point ${candidate}'s \`targets.test\` entry at a config file that exists`,
|
|
3778
3778
|
environmental: true
|
|
3779
3779
|
};
|
|
3780
3780
|
}
|
|
@@ -3835,7 +3835,7 @@ function selectAutoTarget(input) {
|
|
|
3835
3835
|
if (entryFiles.length > 0 && !entryFiles.some((entry) => existsSync(join7(repoDir, depDir, entry)))) {
|
|
3836
3836
|
return {
|
|
3837
3837
|
ok: false,
|
|
3838
|
-
reason: `workspace dependency ${name} is unbuilt (missing ${entryFiles[0]})
|
|
3838
|
+
reason: `workspace dependency ${name} is unbuilt (missing ${entryFiles[0]}) - build workspace packages before measurement`,
|
|
3839
3839
|
remedy: `build ${name} before abloh runs, so ${entryFiles[0]} exists on disk`,
|
|
3840
3840
|
environmental: true
|
|
3841
3841
|
};
|
|
@@ -3863,7 +3863,7 @@ function selectAutoTarget(input) {
|
|
|
3863
3863
|
for (const a of targetDirs) {
|
|
3864
3864
|
for (const b of targetDirs) {
|
|
3865
3865
|
if (a !== b && b.startsWith(`${a}/`)) {
|
|
3866
|
-
return skipped(`workspace
|
|
3866
|
+
return skipped(`workspace packages nest (${a} contains ${b}), so a diff inside the nested one keeps root behavior`);
|
|
3867
3867
|
}
|
|
3868
3868
|
}
|
|
3869
3869
|
}
|
|
@@ -3883,7 +3883,7 @@ function selectAutoTarget(input) {
|
|
|
3883
3883
|
});
|
|
3884
3884
|
continue;
|
|
3885
3885
|
}
|
|
3886
|
-
return skipped(`multi-package selection refused
|
|
3886
|
+
return skipped(`multi-package selection refused - ${verdict.reason}`);
|
|
3887
3887
|
}
|
|
3888
3888
|
runners.set(dir, verdict.runner);
|
|
3889
3889
|
}
|
|
@@ -5062,7 +5062,7 @@ function suiteFanoutPlan(probe, manager) {
|
|
|
5062
5062
|
const aggregated = probe.aggregator === null ? null : { tool: probe.aggregator, tasks: [...probe.aggregatorTasks] };
|
|
5063
5063
|
if (aggregated !== null) {
|
|
5064
5064
|
return {
|
|
5065
|
-
coverageExclusion: `this package's test script delegates to ${aggregated.tool}, which runs ${aggregatorTaskPhrase(aggregated)} rather than one suite, so no single coverage report describes it
|
|
5065
|
+
coverageExclusion: `this package's test script delegates to ${aggregated.tool}, which runs ${aggregatorTaskPhrase(aggregated)} rather than one suite, so no single coverage report describes it. abloh measures such a repository per package instead`
|
|
5066
5066
|
};
|
|
5067
5067
|
}
|
|
5068
5068
|
const resolved = wrapperFanout(probe, manager) ?? concurrentlyFanout(probe, manager) ?? chainFanout(probe, manager);
|
|
@@ -5256,7 +5256,7 @@ function classicPerTestRunner(runner) {
|
|
|
5256
5256
|
return runner !== "command" && classicPerTestRefusal(runner) === null;
|
|
5257
5257
|
}
|
|
5258
5258
|
function classicPerTestRefusal(runner) {
|
|
5259
|
-
return runner === "deno" ? "deno's mutation runs the whole suite per mutant (Stryker ships no deno runner), so the classic per-test layer has no per-test report to read
|
|
5259
|
+
return runner === "deno" ? "deno's mutation runs the whole suite per mutant (Stryker ships no deno runner), so the classic per-test layer has no per-test report to read. The sealed v2 engine drives deno one test at a time and is where a proposed test for this repository comes from" : null;
|
|
5260
5260
|
}
|
|
5261
5261
|
function declaresWorkspace(ctx, repoDir) {
|
|
5262
5262
|
for (const name of ["pnpm-workspace.yaml", "pnpm-workspace.yml", "nx.json", "lerna.json"]) {
|
|
@@ -5323,7 +5323,7 @@ function detectTarget(repoDir, subdir, resolution = {}, ctx = diskRepoAccess(rep
|
|
|
5323
5323
|
if (subdir && explicit === null) {
|
|
5324
5324
|
throw new TargetNotFoundError(
|
|
5325
5325
|
"configured-directory-has-no-package",
|
|
5326
|
-
`the configured target
|
|
5326
|
+
`the configured \`target.directory\` ${JSON.stringify(subdir)} has no package.json, so there is no package there to measure. point --subdir (or abloh.yml target.directory) at the directory holding the package's package.json, or remove it to measure the repository root`,
|
|
5327
5327
|
"point --subdir (or abloh.yml target.directory) at the directory holding the package's package.json, or remove it to measure the repository root"
|
|
5328
5328
|
);
|
|
5329
5329
|
}
|
|
@@ -5334,7 +5334,7 @@ function detectTarget(repoDir, subdir, resolution = {}, ctx = diskRepoAccess(rep
|
|
|
5334
5334
|
if (!probe) {
|
|
5335
5335
|
throw new TargetNotFoundError(
|
|
5336
5336
|
"no-testable-package",
|
|
5337
|
-
`no package.json with a test runner or test script found under ${repoDir}` + (subdir ? ` or in the configured target
|
|
5337
|
+
`no package.json with a test runner or test script found under ${repoDir}` + (subdir ? ` or in the configured \`target.directory\` ${JSON.stringify(subdir)}` : "") + candidateSentence(repoDir),
|
|
5338
5338
|
candidateRemedy(repoDir)
|
|
5339
5339
|
);
|
|
5340
5340
|
}
|
|
@@ -5348,7 +5348,7 @@ function detectTarget(repoDir, subdir, resolution = {}, ctx = diskRepoAccess(rep
|
|
|
5348
5348
|
if (probe.declaredCommand === null && probe.testScript === null && direct !== null && probe.dir === repoDir && declaresWorkspace(ctx, repoDir)) {
|
|
5349
5349
|
throw new TargetNotFoundError(
|
|
5350
5350
|
"workspace-root-declares-no-test-script",
|
|
5351
|
-
`${repoDir} is a workspace root that declares no test script, so there is no suite here to measure and
|
|
5351
|
+
`${repoDir} is a workspace root that declares no test script, so there is no suite here to measure and abloh will not guess one from an installed runner: point --subdir (or abloh.yml target.directory) at the package you want measured, or declare environment.testCommand if one command runs the suite from the root` + candidateSentence(repoDir),
|
|
5352
5352
|
"point --subdir (or abloh.yml target.directory) at the package you want measured, or declare environment.testCommand if one command runs the suite from the root" + candidateSentence(repoDir)
|
|
5353
5353
|
);
|
|
5354
5354
|
}
|
|
@@ -5770,7 +5770,7 @@ function renderEnvironmentBlock(input) {
|
|
|
5770
5770
|
` image: ${yamlScalar(service.image, quote)}`
|
|
5771
5771
|
);
|
|
5772
5772
|
if (service.env.length > 0) {
|
|
5773
|
-
lines.push(" env: # your workflow's own values
|
|
5773
|
+
lines.push(" env: # your workflow's own values, and the suite connects with these");
|
|
5774
5774
|
for (const entry of service.env) lines.push(` ${entry.name}: ${yamlScalar(entry.value, quote)}`);
|
|
5775
5775
|
}
|
|
5776
5776
|
if (service.healthCommand !== null) {
|
|
@@ -5789,7 +5789,7 @@ function renderEnvironmentBlock(input) {
|
|
|
5789
5789
|
for (const path of input.identityFiles) lines.push(` - ${yamlScalar(path, quote)}`);
|
|
5790
5790
|
}
|
|
5791
5791
|
if (input.requiredVariables.length > 0) {
|
|
5792
|
-
lines.push(" requiredVariables: # names only
|
|
5792
|
+
lines.push(" requiredVariables: # names only. Values come from your environment and are masked in every log");
|
|
5793
5793
|
for (const variable of input.requiredVariables) {
|
|
5794
5794
|
lines.push(...commentLines(variable.evidence, " "), ` - ${yamlScalar(variable.name, quote)}`);
|
|
5795
5795
|
}
|
|
@@ -5799,7 +5799,7 @@ function renderEnvironmentBlock(input) {
|
|
|
5799
5799
|
const shape = matchesCredentialShape2(variable.value);
|
|
5800
5800
|
if (shape !== null) throw initRefusal("credential-in-environment-value", credentialShapeRefusal(variable.name, shape));
|
|
5801
5801
|
}
|
|
5802
|
-
lines.push(" environmentValues: # literal values your CI sets
|
|
5802
|
+
lines.push(" environmentValues: # literal values your CI sets. A secret goes in requiredVariables, by name");
|
|
5803
5803
|
for (const variable of input.environmentValues) {
|
|
5804
5804
|
lines.push(
|
|
5805
5805
|
...commentLines(variable.evidence, " "),
|
|
@@ -5808,13 +5808,13 @@ function renderEnvironmentBlock(input) {
|
|
|
5808
5808
|
}
|
|
5809
5809
|
}
|
|
5810
5810
|
if (input.generatedFiles.length > 0) {
|
|
5811
|
-
lines.push(" generatedFiles: # written by your tooling during a run
|
|
5811
|
+
lines.push(" generatedFiles: # written by your tooling during a run. An undeclared change aborts it");
|
|
5812
5812
|
for (const generated of input.generatedFiles) {
|
|
5813
5813
|
lines.push(...commentLines(generated.evidence, " "), ` - ${yamlScalar(generated.path, quote)}`);
|
|
5814
5814
|
}
|
|
5815
5815
|
}
|
|
5816
5816
|
if (input.rewrittenFiles.length > 0) {
|
|
5817
|
-
lines.push(" rewrittenFiles: # committed files your own install rewrites
|
|
5817
|
+
lines.push(" rewrittenFiles: # committed files your own install rewrites, measured as your install left them");
|
|
5818
5818
|
for (const rewritten of input.rewrittenFiles) {
|
|
5819
5819
|
lines.push(...commentLines(rewritten.evidence, " "), ` - ${yamlScalar(rewritten.path, quote)}`);
|
|
5820
5820
|
}
|
|
@@ -6078,7 +6078,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6078
6078
|
"environment.installCommand": {
|
|
6079
6079
|
key: "environment.installCommand",
|
|
6080
6080
|
ask: "How does this repository install its dependencies?",
|
|
6081
|
-
why: "
|
|
6081
|
+
why: "abloh read your CI without running it, so it saw the command your workflow declares and not what your install actually writes. On the borrow road your own job installs and abloh measures inside it, so this is only needed when abloh builds for itself.",
|
|
6082
6082
|
candidates: [],
|
|
6083
6083
|
required: false,
|
|
6084
6084
|
unreadable: true,
|
|
@@ -6093,7 +6093,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6093
6093
|
"environment.setupCommands": {
|
|
6094
6094
|
key: "environment.setupCommands",
|
|
6095
6095
|
ask: "What does this repository build between installing and testing?",
|
|
6096
|
-
why: "
|
|
6096
|
+
why: "abloh read the build steps out of the job it rides. A step it could not read is one the proof container never runs, and a suite that needs it then fails inside abloh rather than in your CI.",
|
|
6097
6097
|
candidates: [],
|
|
6098
6098
|
required: true,
|
|
6099
6099
|
/* ONE STEP PER ` && `, WHICH IS THE ONE KEY THAT IS NOT COMMA-SEPARATED and the CLI's prompt says
|
|
@@ -6115,8 +6115,8 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6115
6115
|
},
|
|
6116
6116
|
"environment.runtimeImage": {
|
|
6117
6117
|
key: "environment.runtimeImage",
|
|
6118
|
-
ask: "Which image should
|
|
6119
|
-
why: "
|
|
6118
|
+
ask: "Which image should abloh build its proof container from?",
|
|
6119
|
+
why: "abloh pins one by pulling it and reading its digest, which needs a machine. Left unanswered, a run uses the reviewed default for the Node your repository declares.",
|
|
6120
6120
|
candidates: [],
|
|
6121
6121
|
required: false,
|
|
6122
6122
|
unreadable: true,
|
|
@@ -6169,7 +6169,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6169
6169
|
/* THE VALUES THE CONTRACT ACCEPTS, not a prose label: the card writes the chosen candidate into
|
|
6170
6170
|
`abloh.yml`, so a candidate that read well and parsed badly would be a form that writes an
|
|
6171
6171
|
invalid file. What each one MEANS is the ask above it. */
|
|
6172
|
-
why: "quarantine sets them aside and measures the rest. strict measures nothing until they pass.",
|
|
6172
|
+
why: "`quarantine` sets them aside and measures the rest. `strict` measures nothing until they pass.",
|
|
6173
6173
|
candidates: ["quarantine", "strict"],
|
|
6174
6174
|
required: true,
|
|
6175
6175
|
shape: { kind: "scalar" },
|
|
@@ -6181,7 +6181,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6181
6181
|
"environment.testCommand": {
|
|
6182
6182
|
key: "environment.testCommand",
|
|
6183
6183
|
ask: "Which command runs your tests?",
|
|
6184
|
-
why: "
|
|
6184
|
+
why: "abloh read your CI without running it, and the command it found did not run a suite it could observe.",
|
|
6185
6185
|
candidates: [],
|
|
6186
6186
|
required: true,
|
|
6187
6187
|
shape: { kind: "scalar" },
|
|
@@ -6201,7 +6201,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6201
6201
|
"environment.rebuildCommand": {
|
|
6202
6202
|
key: "environment.rebuildCommand",
|
|
6203
6203
|
ask: "Which command builds the output your tests import?",
|
|
6204
|
-
why: "Your tests read compiled output rather than the source abloh plants in, so without the build every planted bug is reported as missed.
|
|
6204
|
+
why: "Your tests read compiled output rather than the source abloh plants in, so without the build every planted bug is reported as missed. abloh cannot resolve which of your build scripts writes it from your files alone.",
|
|
6205
6205
|
candidates: [],
|
|
6206
6206
|
required: true,
|
|
6207
6207
|
shape: { kind: "scalar" },
|
|
@@ -6305,7 +6305,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6305
6305
|
"environment.identityFiles": {
|
|
6306
6306
|
key: "environment.identityFiles",
|
|
6307
6307
|
ask: "Which files should a change to reinstall rather than reuse the container?",
|
|
6308
|
-
why: "
|
|
6308
|
+
why: "abloh reuses a prepared container between runs unless one of these moved.",
|
|
6309
6309
|
candidates: [],
|
|
6310
6310
|
required: true,
|
|
6311
6311
|
shape: { kind: "list" },
|
|
@@ -6354,7 +6354,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6354
6354
|
"environment.services": {
|
|
6355
6355
|
key: "environment.services",
|
|
6356
6356
|
ask: "Which backing services does your suite need, and where is each one ready?",
|
|
6357
|
-
why: "A proof runs in the same network namespace as its services, so your suite reaches them on localhost exactly as in your CI.
|
|
6357
|
+
why: "A proof runs in the same network namespace as its services, so your suite reaches them on localhost exactly as in your CI. abloh pins the image by digest because a mutable tag cannot be part of what a run records, and each service needs something to wait on before the suite starts.",
|
|
6358
6358
|
candidates: [],
|
|
6359
6359
|
required: true,
|
|
6360
6360
|
shape: { kind: "services" },
|
|
@@ -6389,7 +6389,7 @@ var SETUP_QUESTION_DESCRIPTORS = {
|
|
|
6389
6389
|
"environment.runner": {
|
|
6390
6390
|
key: "environment.runner",
|
|
6391
6391
|
ask: "Which test runner does this repository use?",
|
|
6392
|
-
why: "
|
|
6392
|
+
why: "abloh reads it from your own command, and a declaration outranks that reading.",
|
|
6393
6393
|
candidates: [],
|
|
6394
6394
|
required: false,
|
|
6395
6395
|
shape: { kind: "scalar" },
|
|
@@ -6760,7 +6760,7 @@ function testStepLabel(step) {
|
|
|
6760
6760
|
}
|
|
6761
6761
|
var MAX_LABELLED_NAME_LENGTH = 60;
|
|
6762
6762
|
function unreadableConditionLine(step) {
|
|
6763
|
-
return `test step ${testStepLabel(step)} runs on a condition
|
|
6763
|
+
return `test step ${testStepLabel(step)} runs on a condition abloh cannot read from the file`;
|
|
6764
6764
|
}
|
|
6765
6765
|
function setupStepAnchor(job, leg) {
|
|
6766
6766
|
const steps = job.testSteps;
|
|
@@ -7150,14 +7150,14 @@ function unaskableFanOut(job, defaultBranch) {
|
|
|
7150
7150
|
if (excluded !== null) return excluded;
|
|
7151
7151
|
const ridable2 = ridableMatrixCombinations(job);
|
|
7152
7152
|
if (ridable2 !== null && ridable2.length === 0 && (job.matrixCombinations ?? []).length > 0) {
|
|
7153
|
-
return "every leg its matrix runs is on a runner
|
|
7153
|
+
return "every leg its matrix runs is on a runner abloh cannot work on, so no leg of it could carry the step";
|
|
7154
7154
|
}
|
|
7155
7155
|
const legs = ridableLegs(job);
|
|
7156
7156
|
const dead = job.matrixDimensions.find(
|
|
7157
7157
|
(dimension) => (job.matrixLegs?.[dimension] ?? []).length > 0 && (legs[dimension] ?? []).length === 0
|
|
7158
7158
|
);
|
|
7159
7159
|
if (dead === void 0) return null;
|
|
7160
|
-
return `every value its \`${dead}\` matrix lists is a runner
|
|
7160
|
+
return `every value its \`${dead}\` matrix lists is a runner abloh cannot work on, so no leg of it could carry the step`;
|
|
7161
7161
|
}
|
|
7162
7162
|
function declarationRefusal(job, defaultBranch) {
|
|
7163
7163
|
if (!job.installs) {
|
|
@@ -7605,21 +7605,21 @@ function isAblohAttestationJob(job) {
|
|
|
7605
7605
|
function attestationJobLines(input) {
|
|
7606
7606
|
const inner = `${input.keyIndent}${input.unit}`;
|
|
7607
7607
|
const source = input.stepIn === null ? [
|
|
7608
|
-
`${input.jobIndent}# it takes the report the
|
|
7608
|
+
`${input.jobIndent}# it takes the report the abloh step above left on this run and files it.`
|
|
7609
7609
|
] : [
|
|
7610
|
-
`${input.jobIndent}# it takes the report the
|
|
7610
|
+
`${input.jobIndent}# it takes the report the abloh step in ${input.stepIn} left on this`,
|
|
7611
7611
|
`${input.jobIndent}# run and files it. That step is there and this job is here because GitHub caps a`,
|
|
7612
7612
|
`${input.jobIndent}# called workflow's jobs at the permissions its caller grants, and this file is the`,
|
|
7613
7613
|
`${input.jobIndent}# caller.`
|
|
7614
7614
|
];
|
|
7615
7615
|
return [
|
|
7616
|
-
`${input.jobIndent}#
|
|
7616
|
+
`${input.jobIndent}# abloh's publish job. It runs no code of yours, builds nothing and measures nothing:`,
|
|
7617
7617
|
...source,
|
|
7618
7618
|
`${input.jobIndent}#`,
|
|
7619
7619
|
`${input.jobIndent}# It is a separate job because GitHub grants ${IDENTITY_SCOPE}: ${IDENTITY_VALUE} to a whole JOB and`,
|
|
7620
7620
|
`${input.jobIndent}# not to one step: putting it on the job that measures would hand the same identity to`,
|
|
7621
|
-
`${input.jobIndent}# every step that runs there,
|
|
7622
|
-
`${input.jobIndent}# step and nothing else. It authorizes one thing, filing this report with
|
|
7621
|
+
`${input.jobIndent}# every step that runs there, abloh's and everybody else's. Here it reaches abloh's`,
|
|
7622
|
+
`${input.jobIndent}# step and nothing else. It authorizes one thing, filing this report with abloh, and`,
|
|
7623
7623
|
`${input.jobIndent}# carries no write access to your repository. Declaring the block below takes no`,
|
|
7624
7624
|
`${input.jobIndent}# grant away from anything, because this job is new and had none.`,
|
|
7625
7625
|
`${input.jobIndent}${ATTESTATION_JOB_ID}:`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloh/repository",
|
|
3
|
-
"version": "1.0.2-staging.
|
|
3
|
+
"version": "1.0.2-staging.34856316728",
|
|
4
4
|
"description": "Reads a repository and decides what abloh may measure in it: the CI recipe, the target, the package manager, the workspace packages, the job abloh's step rides and the measurement plan. Shared by the CLI, which reads a checkout, and by the control plane, which reads the same repository over GitHub's contents API. \"name\" will publish under the @abloh scope.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"semver": "7.8.5",
|
|
25
25
|
"yaml": "^2.6.1",
|
|
26
|
-
"@abloh/core": "1.0.2-staging.
|
|
27
|
-
"@abloh/measure": "1.0.2-staging.
|
|
26
|
+
"@abloh/core": "1.0.2-staging.34856316728",
|
|
27
|
+
"@abloh/measure": "1.0.2-staging.34856316728"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/semver": "7.7.1",
|