mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
8e695cc267
The ruleset required only `lint` (shared's eslint and prettier) and `check-target`. Types, lint, coverage, end-to-end and the S3 contract were advisory, and the three health PRs merged with red checks. Nothing on a PR into dev built the Dockerfile or rendered the Helm chart either, which is how #2610 shipped a Dockerfile no build could parse. ci-ok needs every gate, test, image and chart job and is meant to be the one check the ruleset requires. It runs with always(), fails when a job it needs failed or was cancelled, and passes when they passed or were skipped. The Sonar scan stays outside: its quality gate reports on the PR by itself, and an outage there should not block merges. A required check cannot sit behind a workflow-level path filter: on a docs-only PR the workflow never starts and the check never reports. The filter moves into a `changes` job. It diffs the PR's merge commit against its base (or a push against the previous head) and scripts/ci/ changed-areas.mjs maps the files to code, image and deploy areas. It fails closed: no usable diff, an empty one or a path it cannot read cleanly runs everything. Its tests run in the same job. docker-image builds the Dockerfile with buildx (layer cache written by pushes only, read by PRs), starts it with the same hardening as docker-compose.yml, waits up to three minutes for /api/health/ready, checks the VERSION file and that / serves the client, and prints the container log. deploy-manifests lints the chart, renders it with the default values and with each secret, persistence and ingress mode, validates the output with kubeconform, and runs `docker compose config` on every compose file. helm and kubeconform are pinned by version and sha256. While the file is open: every job runs on ubuntu-24.04 instead of the moving ubuntu-latest, reads its Node version from .nvmrc (the plugin-sdk jobs ran 22 under a comment saying it matched the server, which runs 24), download-artifact moves from v6, still on Node 20, to v7, and the Sonar action is pinned by commit.
101 lines
4.2 KiB
JavaScript
101 lines
4.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { after, describe, it } from 'node:test';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { AREAS, classify } from './changed-areas.mjs';
|
|
|
|
const script = fileURLToPath(new URL('./changed-areas.mjs', import.meta.url));
|
|
const scratch = mkdtempSync(join(tmpdir(), 'changed-areas-'));
|
|
after(() => rmSync(scratch, { recursive: true, force: true }));
|
|
|
|
const none = Object.fromEntries(Object.keys(AREAS).map((area) => [area, false]));
|
|
const every = Object.fromEntries(Object.keys(AREAS).map((area) => [area, true]));
|
|
|
|
function run(args, env = {}) {
|
|
return spawnSync(process.execPath, [script, ...args], {
|
|
encoding: 'utf8',
|
|
env: { ...process.env, GITHUB_OUTPUT: '', ...env },
|
|
});
|
|
}
|
|
|
|
describe('classify', () => {
|
|
it('runs nothing for a docs-only change', () => {
|
|
assert.deepEqual(classify(['wiki/Home.md', 'README.md', 'server/CLAUDE.md', 'docs/logo-trek-dark.svg']), none);
|
|
});
|
|
|
|
it('runs the tests and the image for a server change', () => {
|
|
assert.deepEqual(classify(['server/src/nest/weather/weather.service.ts']), { code: true, image: true, deploy: false });
|
|
});
|
|
|
|
it('runs the tests but not the image for a plugin-sdk change', () => {
|
|
assert.deepEqual(classify(['plugin-sdk/src/index.ts']), { code: true, image: false, deploy: false });
|
|
});
|
|
|
|
it('runs the image but not the tests for a .dockerignore change', () => {
|
|
assert.deepEqual(classify(['.dockerignore']), { code: false, image: true, deploy: false });
|
|
});
|
|
|
|
it('runs the chart and compose checks for a chart template or a compose file', () => {
|
|
assert.deepEqual(classify(['charts/trek/templates/deployment.yaml']), { code: false, image: false, deploy: true });
|
|
assert.deepEqual(classify(['docker-compose.minio-test.yml']), { code: false, image: false, deploy: true });
|
|
assert.equal(classify(['charts/README.md']).deploy, false);
|
|
assert.equal(classify(['server/docker-compose.yml']).deploy, false);
|
|
});
|
|
|
|
it('runs everything when the workflow or the classifier itself changes', () => {
|
|
assert.deepEqual(classify(['.github/workflows/test.yml']), every);
|
|
assert.deepEqual(classify(['scripts/ci/changed-areas.mjs']), every);
|
|
});
|
|
|
|
it('matches whole file names, not prefixes of them', () => {
|
|
assert.deepEqual(classify(['package.json.bak', 'Dockerfile.old', 'serverless/x.ts']), none);
|
|
});
|
|
|
|
it('runs everything when there is no list, an empty one or a quoted path', () => {
|
|
assert.deepEqual(classify(null), every);
|
|
assert.deepEqual(classify(['', ' ']), every);
|
|
assert.deepEqual(classify(['"wiki/\\303\\244.md"']), every);
|
|
});
|
|
|
|
it('reads CRLF lists the same as LF ones', () => {
|
|
assert.deepEqual(classify(['wiki/Home.md\r', 'server/src/index.ts\r']), { code: true, image: true, deploy: false });
|
|
});
|
|
});
|
|
|
|
describe('the command line', () => {
|
|
it('writes the areas to stdout and to GITHUB_OUTPUT', () => {
|
|
const list = join(scratch, 'list.txt');
|
|
const output = join(scratch, 'output.txt');
|
|
writeFileSync(list, 'charts/trek/values.yaml\n');
|
|
writeFileSync(output, '');
|
|
const result = run(['--files', list], { GITHUB_OUTPUT: output });
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(result.stdout, 'code=false\nimage=false\ndeploy=true\n');
|
|
assert.equal(readFileSync(output, 'utf8'), 'code=false\nimage=false\ndeploy=true\n');
|
|
});
|
|
|
|
it('turns everything on with --all', () => {
|
|
const result = run(['--all']);
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.equal(result.stdout, 'code=true\nimage=true\ndeploy=true\n');
|
|
});
|
|
|
|
it('fails on a list it cannot read', () => {
|
|
const result = run(['--files', join(scratch, 'missing.txt')]);
|
|
assert.equal(result.status, 1);
|
|
assert.match(result.stderr, /ENOENT/);
|
|
assert.equal(result.stdout, '');
|
|
});
|
|
|
|
it('fails on arguments it does not know', () => {
|
|
for (const args of [[], ['--files'], ['--all', '--files', 'x'], ['list.txt']]) {
|
|
const result = run(args);
|
|
assert.equal(result.status, 1, `args ${JSON.stringify(args)}`);
|
|
assert.match(result.stderr, /usage/);
|
|
}
|
|
});
|
|
});
|