pediatric-ai-scribe-v3/test/release.test.js
Daniel 36cb742ce7
All checks were successful
Forgejo Docker Build / Root app tests (push) Successful in 54s
Forgejo Docker Build / Build Docker image (push) Successful in 7s
ci: fix the failing job, split deploy out, and drop the Android build
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
2026-09-12 23:30:52 +02:00

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);
});