fix(KAN-58): avoid admin API in macOS release workflow
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 58s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 3m22s
CI/CD / Publish immutable image (pull_request) Canceled after 0s
CI/CD / test_and_check_app (pull_request) Canceled after 4m18s
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 58s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 3m22s
CI/CD / Publish immutable image (pull_request) Canceled after 0s
CI/CD / test_and_check_app (pull_request) Canceled after 4m18s
This commit is contained in:
@@ -2,6 +2,8 @@ name: macOS release to Hetzner
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
# Gitea enforces the configured macos-v* protection when tags are pushed.
|
||||||
|
# Reading /tag_protections requires admin rights unavailable to job tokens.
|
||||||
tags:
|
tags:
|
||||||
- "macos-v*"
|
- "macos-v*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -69,24 +71,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
- name: Require protected macOS release tags
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
set -Eeuo pipefail
|
|
||||||
curl --fail --silent --show-error \
|
|
||||||
--header "Authorization: Bearer $GITEA_TOKEN" \
|
|
||||||
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/tag_protections" \
|
|
||||||
--output "$RUNNER_TEMP/macos-tag-protections.json"
|
|
||||||
node --input-type=module - <<'JS'
|
|
||||||
import { readFileSync } from 'node:fs';
|
|
||||||
const rules = JSON.parse(readFileSync(`${process.env.RUNNER_TEMP}/macos-tag-protections.json`, 'utf8'));
|
|
||||||
if (!rules.some((rule) => rule.name_pattern === 'macos-v*')) {
|
|
||||||
throw new Error('Configure a protected macos-v* tag rule in Gitea before publishing.');
|
|
||||||
}
|
|
||||||
JS
|
|
||||||
|
|
||||||
- name: Validate app versions and Hetzner configuration
|
- name: Validate app versions and Hetzner configuration
|
||||||
env:
|
env:
|
||||||
RELEASE_TAG: ${{ steps.ref.outputs.tag }}
|
RELEASE_TAG: ${{ steps.ref.outputs.tag }}
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import {
|
||||||
|
mkdirSync,
|
||||||
|
mkdtempSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
const workflow = readFileSync(
|
||||||
|
new URL('../../.github/workflows/macos-release.yml', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
const selectRef = workflow
|
||||||
|
.split(
|
||||||
|
' - name: Select an existing version tag contained in main\n',
|
||||||
|
)[1]
|
||||||
|
.split('\n - name:')[0]
|
||||||
|
.split(' run: |\n')[1]
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.replace(/^ {10}/, ''))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
function fixture(t) {
|
||||||
|
const directory = mkdtempSync(join(tmpdir(), 'kanbangit-release-ref-'));
|
||||||
|
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
||||||
|
const remote = join(directory, 'remote');
|
||||||
|
const checkout = join(directory, 'checkout');
|
||||||
|
mkdirSync(remote);
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
GIT_AUTHOR_NAME: 'Release test',
|
||||||
|
GIT_COMMITTER_NAME: 'Release test',
|
||||||
|
GIT_AUTHOR_EMAIL: 'release@example.invalid',
|
||||||
|
GIT_COMMITTER_EMAIL: 'release@example.invalid',
|
||||||
|
GIT_CONFIG_NOSYSTEM: '1',
|
||||||
|
GIT_CONFIG_GLOBAL: '/dev/null',
|
||||||
|
};
|
||||||
|
const git = (cwd, ...args) => {
|
||||||
|
const result = spawnSync('git', args, { cwd, env, encoding: 'utf8' });
|
||||||
|
assert.equal(result.status, 0, result.stderr);
|
||||||
|
return result.stdout.trim();
|
||||||
|
};
|
||||||
|
git(remote, 'init', '-b', 'main');
|
||||||
|
writeFileSync(join(remote, 'app.txt'), 'release');
|
||||||
|
git(remote, 'add', 'app.txt');
|
||||||
|
git(remote, '-c', 'commit.gpgsign=false', 'commit', '-m', 'Release');
|
||||||
|
const commit = git(remote, 'rev-parse', 'HEAD');
|
||||||
|
git(remote, 'tag', 'macos-v1.2.3');
|
||||||
|
git(directory, 'clone', remote, checkout);
|
||||||
|
const output = join(directory, 'outputs');
|
||||||
|
const run = (overrides = {}) => {
|
||||||
|
writeFileSync(output, '');
|
||||||
|
const result = spawnSync(
|
||||||
|
'bash',
|
||||||
|
['--noprofile', '--norc', '-c', selectRef],
|
||||||
|
{
|
||||||
|
cwd: checkout,
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: {
|
||||||
|
...env,
|
||||||
|
GITHUB_EVENT_NAME: 'push',
|
||||||
|
GITHUB_REF: 'refs/tags/macos-v1.2.3',
|
||||||
|
REQUESTED_TAG: '',
|
||||||
|
MACOS_RUNNER: 'macos-local',
|
||||||
|
GITHUB_OUTPUT: output,
|
||||||
|
...overrides,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return { ...result, output: readFileSync(output, 'utf8') };
|
||||||
|
};
|
||||||
|
return { run, git, checkout, commit };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('release selection succeeds using Git alone without an administrative API token', (t) => {
|
||||||
|
const f = fixture(t);
|
||||||
|
const result = f.run({
|
||||||
|
GITEA_TOKEN: '',
|
||||||
|
GITHUB_TOKEN: '',
|
||||||
|
GITHUB_API_URL: 'http://127.0.0.1:1',
|
||||||
|
});
|
||||||
|
assert.equal(result.status, 0, result.stderr);
|
||||||
|
assert.equal(
|
||||||
|
result.output,
|
||||||
|
`commit=${f.commit}\ntag=macos-v1.2.3\nrunner=macos-local\n`,
|
||||||
|
);
|
||||||
|
assert.equal(f.git(f.checkout, 'rev-parse', 'HEAD'), f.commit);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('manual release on main accepts an existing tag without moving it', (t) => {
|
||||||
|
const f = fixture(t);
|
||||||
|
const result = f.run({
|
||||||
|
GITHUB_EVENT_NAME: 'workflow_dispatch',
|
||||||
|
GITHUB_REF: 'refs/heads/main',
|
||||||
|
REQUESTED_TAG: 'macos-v1.2.3',
|
||||||
|
});
|
||||||
|
assert.equal(result.status, 0, result.stderr);
|
||||||
|
assert.equal(f.git(f.checkout, 'rev-parse', 'macos-v1.2.3'), f.commit);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [name, overrides] of [
|
||||||
|
['web release tag', { GITHUB_REF: 'refs/tags/v1.2.3' }],
|
||||||
|
['missing tag', { GITHUB_REF: 'refs/tags/macos-v9.9.9' }],
|
||||||
|
[
|
||||||
|
'shell characters in tag',
|
||||||
|
{ GITHUB_REF: 'refs/tags/macos-v1.2.3;exit 0' },
|
||||||
|
],
|
||||||
|
['missing runner label', { MACOS_RUNNER: '' }],
|
||||||
|
[
|
||||||
|
'output injection in runner label',
|
||||||
|
{ MACOS_RUNNER: 'macos-local\ncommit=other' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'manual release outside main',
|
||||||
|
{
|
||||||
|
GITHUB_EVENT_NAME: 'workflow_dispatch',
|
||||||
|
GITHUB_REF: 'refs/heads/feature',
|
||||||
|
REQUESTED_TAG: 'macos-v1.2.3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]) {
|
||||||
|
test(`release selection rejects ${name} before producing job outputs`, (t) => {
|
||||||
|
const result = fixture(t).run(overrides);
|
||||||
|
assert.notEqual(result.status, 0);
|
||||||
|
assert.equal(result.output, '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('release selection rejects a tag whose commit is outside main', (t) => {
|
||||||
|
const f = fixture(t);
|
||||||
|
f.git(f.checkout, 'switch', '-c', 'unmerged');
|
||||||
|
f.git(
|
||||||
|
f.checkout,
|
||||||
|
'-c',
|
||||||
|
'commit.gpgsign=false',
|
||||||
|
'commit',
|
||||||
|
'--allow-empty',
|
||||||
|
'-m',
|
||||||
|
'Unmerged',
|
||||||
|
);
|
||||||
|
f.git(f.checkout, 'tag', 'macos-v1.2.4');
|
||||||
|
const result = f.run({ GITHUB_REF: 'refs/tags/macos-v1.2.4' });
|
||||||
|
assert.notEqual(result.status, 0);
|
||||||
|
assert.match(result.stderr, /Release tag must belong to main/);
|
||||||
|
assert.equal(result.output, '');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user