Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3384,6 +3384,32 @@ jobs:
- name: Verify type-check coverage
run: pnpm --dir ui run type-check:coverage

# objectui's `lockfile-integrity.yml` (objectui#8326), row 19 of the
# objectstack#25697 workflow roster, ported by objectstack#26717: a pull
# request's `pnpm-lock.yaml` may not give any package a second physical
# copy. The script's header has the mechanism and its boundary.
#
# It lives here because this job already checks out with
# `fetch-depth: 0`, so `origin/<base>` and the merge base are in the
# clone, and because it runs on every change to `pnpm-lock.yaml` (the
# `ui` filter lists it). Before the install on purpose: the gate reads
# two text files with Node builtins only, which Lint UI's pre-install
# import graph check holds. `pull_request` only, because the gate's
# subject is the pull request's delta against its base; a push or queue
# run has none of its own.
#
# ⛔ `continue-on-error` keeps it an ALARM, as objectui ran it: a finding
# prints an `::error::` annotation naming the packages on the pull
# request and does NOT fail this required job. Making it blocking is a
# maintainer decision (`NOT_A_GATE` in `ui/scripts/dependabot-merge-gate.mjs`
# says why), not a step edit.
- name: check:lockfile-integrity — compare this pull request's lockfile with its merge base
if: github.event_name == 'pull_request'
continue-on-error: true
env:
BASE_REF: ${{ github.base_ref }}
run: pnpm --dir ui run check:lockfile-integrity --base-ref "origin/${BASE_REF}"

- name: Install dependencies
run: pnpm install --frozen-lockfile

Expand Down
1 change: 0 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@
"check:merge-queue-head": "node scripts/check-merge-queue-head.mjs --self-test",
"check:required-check-set": "node scripts/check-required-check-set.mjs --self-test",
"check:lockfile-integrity": "node scripts/check-lockfile-integrity.mjs",
"check:lockfile-dedupe": "node scripts/check-lockfile-dedupe.mjs",
"merge-queue-head": "node scripts/check-merge-queue-head.mjs",
"check:pre-install-import-graph": "node scripts/check-pre-install-import-graph.mjs",
"check:test-path-roots": "node scripts/check-test-path-roots.mjs",
Expand Down
600 changes: 0 additions & 600 deletions ui/scripts/__tests__/check-lockfile-dedupe.test.ts

This file was deleted.

65 changes: 56 additions & 9 deletions ui/scripts/__tests__/check-lockfile-integrity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { lockfilePath } from '../workspace-root.mjs';

import { jobBlock, rootCi, rootWorkflow, rootWorkflowFiles, uiRun, withoutComments } from './helpers/root-ci';
import { NOT_A_GATE, OPTIONAL_CONTEXTS, REQUIRED_CONTEXTS } from '../dependabot-merge-gate.mjs';
import {
compareLockfiles,
Expand All @@ -38,7 +39,7 @@ const GATE = path.join(repoRoot, 'scripts/check-lockfile-integrity.mjs');
const LOCKFILE = lockfilePath();
const realLock = fs.readFileSync(LOCKFILE, 'utf8');

/** The check-run name the workflow's job produces. */
/** The check-run name objectui's own job produced, the name the Dependabot merge gate classifies. */
const CHECK_NAME = 'Lockfile Integrity Check';

describe('the gate runs its own self-test', () => {
Expand Down Expand Up @@ -191,15 +192,61 @@ describe('cause 2 — a declared dependency forks with no version moving (object
});
});

describe('the gate is classified the way the dispatch reserved', () => {
// `exists and runs the gate` and `checks out deep enough to have a merge
// base` retired with objectstack#25712. They read objectui's
// `lockfile-integrity.yml`, which runs nowhere under the monorepo: GitHub
// runs the root `.github/workflows/` alone. That workflow is row 19 of the
// objectstack#25697 workflow roster, "kept, follow-up port", re-aimed at the
// one root `pnpm-lock.yaml`; the port brings both pins back against the root
// job that runs the gate.
describe('the gate is wired into the root CI (objectstack#26717)', () => {
// objectui ran it in its own `lockfile-integrity.yml`, which runs nowhere
// under the monorepo: GitHub runs the root `.github/workflows/` alone. Row 19
// of the objectstack#25697 workflow roster ported it as one step of root
// `ci.yml`'s `type-check-ui` job, re-aimed at the one root `pnpm-lock.yaml`.
// The two pins objectui kept on its workflow (`exists and runs the gate`,
// `checks out deep enough to have a merge base`) read that job now.
const JOB = 'type-check-ui';
const STEP_NAME = 'check:lockfile-integrity — ';
const COMMAND = `${uiRun('check:lockfile-integrity')} --base-ref "origin/\${BASE_REF}"`;

/** The step's own lines, from its `- name:` to the next step. Throws when it is gone. */
function step(): string {
const block = withoutComments(jobBlock(rootCi(), JOB));
const start = block.indexOf(`- name: ${STEP_NAME}`);
if (start === -1) throw new Error(`ci.yml job ${JOB} has no step named ${STEP_NAME}…`);
const next = block.indexOf('\n - ', start + 1);
return next === -1 ? block.slice(start) : block.slice(start, next);
}

it('exists and runs the gate against the pull request base', () => {
expect(step()).toContain(`run: ${COMMAND}`);
expect(step()).toContain('BASE_REF: ${{ github.base_ref }}');
expect(step()).toContain("if: github.event_name == 'pull_request'");
});

it('checks out deep enough to have a merge base', () => {
// A shallow clone has no merge base, and the script exits 2 rather than
// guess one: a reading that never measured the change.
const block = withoutComments(jobBlock(rootCi(), JOB));
const checkout = block.indexOf('uses: actions/checkout@');
expect(checkout).toBeGreaterThan(-1);
expect(block.indexOf('fetch-depth: 0', checkout)).toBeGreaterThan(checkout);
expect(block.indexOf('fetch-depth: 0', checkout)).toBeLessThan(block.indexOf(`- name: ${STEP_NAME}`));
});

it('⛔ stays an alarm in a required job: continue-on-error, before the install', () => {
// The step's finding must not fail Type Check UI; enrolling the gate is
// the maintainer's decision (the classification below). Before the
// install, because the script needs nothing installed.
expect(step()).toContain('continue-on-error: true');
const block = withoutComments(jobBlock(rootCi(), JOB));
expect(block.indexOf(`- name: ${STEP_NAME}`)).toBeLessThan(block.indexOf('run: pnpm install --frozen-lockfile'));
});

it('runs in that one place: no other root workflow carries it', () => {
const carriers = rootWorkflowFiles().filter((file) =>
withoutComments(rootWorkflow(file)).includes('check:lockfile-integrity'),
);
expect(carriers).toEqual(['ci.yml']);
expect(withoutComments(rootCi()).split('check:lockfile-integrity').length - 1).toBe(2); // the name and the run
});
});

describe('the gate is classified the way the dispatch reserved', () => {
it('⛔ is classified as NOT a blocking context (the maintainer floor)', () => {
expect(Object.keys(NOT_A_GATE)).toContain(CHECK_NAME);
expect(REQUIRED_CONTEXTS).not.toContain(CHECK_NAME);
Expand Down
Loading
Loading