Three things, one subject: making CI say the truth about this repo.
## The red on every run was ours, not the runners'
Every docker-build run came back success, success, failure — the same
shape for weeks. The failing job was `deploy`, and it was failing to
*not run*:
if: ${{ github.event.inputs.deploy == 'true' }}
On a push there is no github.event.inputs at all. This Forgejo does not
treat that as false and skip; it dispatches the job, the runner cannot
resolve it, and the task ends in "Early termination". The runners were
never at fault, and nothing about them needed changing.
The `'runs-on' key not defined` line is a red herring: the `build` job
prints it too and succeeds. It names the job's *needs* target, not the
job, and the old android-apk workflow used `needs:` happily for months.
Deploy is now its own workflow with only workflow_dispatch — no
condition to evaluate, so nothing can be dispatched by mistake. No job
in either file now carries a job-level `if`. The one conditional left is
a *step* (push to registry), and step conditions are evaluated by the
runner once the job is already running, which is why that one has always
worked.
## dev and main
docker-build now runs on `dev` as well. Both branches prove the same two
things — tests pass, image builds — and only `main` publishes the image,
so nothing on `dev` can be mistaken for something deployable. Deploying
stays a person pressing a button after looking at the change.
CONTRIBUTING.md documents the flow.
## Android
Removed: the mobile/ Capacitor project, docs/mobile-build.md, and the
Android bits of scripts/release.sh. All of it is in git history — 4613a278
is the last commit that had it — for when it is rebuilt.
src/utils/platform.js stays. isMobileClient only decides token lifetime,
it is twelve lines, and it is the contract a future app would come back
to; deleting it would be a change to auth for no gain.
.github/workflows/ went too — all five. There is no GitHub remote on
this repository, so none of them has ever run, and two of them wrote
into mobile/ paths that no longer exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
58 lines
3.2 KiB
JavaScript
58 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { execFileSync } = require('node:child_process');
|
|
|
|
test('release commits manifest and atomic lock metadata together without dependency resolution or push', t => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ped-release-'));
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
const run = (command, args) => execFileSync(command, args, {
|
|
cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
env: { ...process.env, npm_config_offline: 'true' },
|
|
}).trim();
|
|
const write = (file, data) => fs.writeFileSync(path.join(root, file), JSON.stringify(data, null, 2) + '\n');
|
|
const read = file => JSON.parse(fs.readFileSync(path.join(root, file), 'utf8'));
|
|
fs.mkdirSync(path.join(root, 'scripts'));
|
|
fs.copyFileSync(path.join(__dirname, '../scripts/release.sh'), path.join(root, 'scripts/release.sh'));
|
|
const manifest = { nested: { version: 'leave-alone' }, version: '1.0.0', dependencies: { example: '^1.0.0' } };
|
|
const lock = { name: 'test', version: '0.9.0', lockfileVersion: 3, packages: {
|
|
'': { name: 'test', version: '0.9.0', dependencies: manifest.dependencies },
|
|
'node_modules/example': { version: '1.2.3', resolved: 'https://example.invalid/never-fetch.tgz', integrity: 'unchanged' },
|
|
} };
|
|
write('package.json', manifest);
|
|
write('package-lock.json', lock);
|
|
run('git', ['init']);
|
|
run('git', ['config', 'user.name', 'Release Test']);
|
|
run('git', ['config', 'user.email', 'release@example.invalid']);
|
|
run('git', ['config', 'commit.gpgsign', 'false']);
|
|
run('git', ['config', 'tag.gpgsign', 'false']);
|
|
run('git', ['add', '.']);
|
|
run('git', ['commit', '-m', 'fixture']);
|
|
// No remote exists: a successful run cannot have pushed anything.
|
|
run('bash', ['scripts/release.sh', '2.3.4']);
|
|
const expected = { ...manifest, version: '2.3.4' };
|
|
assert.deepEqual(read('package.json'), expected);
|
|
lock.version = lock.packages[''].version = '2.3.4';
|
|
assert.deepEqual(read('package-lock.json'), lock);
|
|
assert.equal(run('git', ['status', '--porcelain']), '');
|
|
// The Capacitor project was removed, so a release touches these two files and
|
|
// nothing else. If Android comes back, its manifests come back here too.
|
|
assert.deepEqual(run('git', ['show', '--format=', '--name-only', 'HEAD']).split('\n').sort(),
|
|
['package-lock.json', 'package.json']);
|
|
assert.equal(run('git', ['rev-parse', 'v2.3.4^{commit}']), run('git', ['rev-parse', 'HEAD']));
|
|
assert.equal(fs.existsSync(path.join(root, 'node_modules')), false);
|
|
|
|
// An invalid lock fails before either manifest changes or a release commit is made.
|
|
fs.writeFileSync(path.join(root, 'package-lock.json'), '{invalid');
|
|
run('git', ['add', 'package-lock.json']);
|
|
run('git', ['commit', '-m', 'invalid lock fixture']);
|
|
const head = run('git', ['rev-parse', 'HEAD']);
|
|
assert.throws(() => run('bash', ['scripts/release.sh', '2.3.5']));
|
|
assert.deepEqual(read('package.json'), expected);
|
|
assert.equal(run('git', ['status', '--porcelain']), '');
|
|
assert.equal(run('git', ['rev-parse', 'HEAD']), head);
|
|
});
|