mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(ci): enforce architecture guards before dependency setup (#8027)
* fix(ci): enforce architecture guards before dependency setup * test(ci): bind cache policy to preceding Node setup
This commit is contained in:
1 parent
a108a1b817
commit
cac6e0613e
4 files changed
+47
-10
No files matched your search
@@ -144,6 +144,9 @@ jobs:
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Validate source architecture before dependency installation
|
||||
if: needs.changes.outputs.code == 'true'
|
||||
run: node scripts/verify/architecture-guards.mjs
|
||||
- name: Cache npm package downloads
|
||||
id: npm-cache
|
||||
uses: ./.github/actions/npm-cache
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
||||
import { compositeKey } from "../../domain/unicodeCodePointOrder.js";
|
||||
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
|
||||
import {
|
||||
addSemanticGraphRelation,
|
||||
@@ -71,10 +72,11 @@ const operatorLiteralIndex = (
|
||||
return node;
|
||||
seen.add(node.node_id);
|
||||
const entry = { node, range, order: order++ };
|
||||
const id = `${literalValueKey(value)}\0${javascriptSourceRangeKey(range)}`;
|
||||
const group = byValue.get(literalValueKey(value)) ?? [];
|
||||
const valueKey = literalValueKey(value);
|
||||
const id = compositeKey([valueKey, javascriptSourceRangeKey(range)]);
|
||||
const group = byValue.get(valueKey) ?? [];
|
||||
group.push(entry);
|
||||
byValue.set(literalValueKey(value), group);
|
||||
byValue.set(valueKey, group);
|
||||
if (!exact.has(id)) exact.set(id, entry);
|
||||
return node;
|
||||
};
|
||||
@@ -92,7 +94,7 @@ const operatorLiteralIndex = (
|
||||
)
|
||||
best = entry;
|
||||
};
|
||||
consider(exact.get(`${valueKey}\0${operandKey}`));
|
||||
consider(exact.get(compositeKey([valueKey, operandKey])));
|
||||
const group = byValue.get(valueKey) ?? [];
|
||||
let low = 0;
|
||||
let high = group.length;
|
||||
|
||||
@@ -361,7 +361,7 @@ printf '%s\n' 'C:\Users\runner with spaces\AppData\Local\npm-cache'
|
||||
);
|
||||
});
|
||||
|
||||
it("replaces every inline setup-node npm cache immediately and preserves conditions", async () => {
|
||||
it("pairs npm caches with preceding Node setup and preserves conditions", async () => {
|
||||
const workflows = await readWorkflows();
|
||||
let npmCacheSteps = 0;
|
||||
|
||||
@@ -377,12 +377,17 @@ printf '%s\n' 'C:\Users\runner with spaces\AppData\Local\npm-cache'
|
||||
const step = steps[index];
|
||||
if (step?.uses !== "./.github/actions/npm-cache") continue;
|
||||
npmCacheSteps++;
|
||||
const setup = steps[index - 1];
|
||||
const setup = steps
|
||||
.slice(0, index)
|
||||
.findLast(
|
||||
(candidate) =>
|
||||
typeof candidate.uses === "string" &&
|
||||
candidate.uses.startsWith("actions/setup-node@"),
|
||||
);
|
||||
expect(
|
||||
typeof setup?.uses === "string" &&
|
||||
setup.uses.startsWith("actions/setup-node@"),
|
||||
`${file}:${jobName} npm cache action must immediately follow setup-node`,
|
||||
).toBe(true);
|
||||
setup,
|
||||
`${file}:${jobName} npm cache action requires preceding setup-node`,
|
||||
).toBeDefined();
|
||||
expect(step.if).toBe(setup?.if);
|
||||
expect(setup?.with).not.toHaveProperty("cache", "npm");
|
||||
const npmCiIndex = steps.findIndex(
|
||||
|
||||
@@ -180,6 +180,33 @@ it("makes the required gate wait for every CI lane, including future additions",
|
||||
);
|
||||
});
|
||||
|
||||
it("runs architecture guards for fast code feedback before installing dependencies", async () => {
|
||||
const steps = z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
run: z.string().optional(),
|
||||
uses: z.string().optional(),
|
||||
if: z.string().optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
)
|
||||
.parse((await workflow()).jobs.static?.steps);
|
||||
const guard = steps.findIndex(
|
||||
(step) => step.run === "node scripts/verify/architecture-guards.mjs",
|
||||
);
|
||||
const setup = steps.findIndex((step) =>
|
||||
step.uses?.startsWith("actions/setup-node@"),
|
||||
);
|
||||
const install = steps.findIndex((step) => step.run === "npm ci");
|
||||
expect(guard).toBeGreaterThanOrEqual(0);
|
||||
expect(setup).toBeGreaterThanOrEqual(0);
|
||||
expect(install).toBeGreaterThan(guard);
|
||||
expect(guard).toBeGreaterThan(setup);
|
||||
expect(steps[guard]?.if).toBe("needs.changes.outputs.code == 'true'");
|
||||
expect(steps[guard]?.["continue-on-error"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("holds native and provider fan-out behind static validation while portable feedback stays independent", async () => {
|
||||
const { jobs } = await workflow();
|
||||
const earlyJobs = new Set([
|
||||
|
||||
Reference in new issue
Block a user