Skip to content

Commit 2cb7dc0

Browse files
committed
build: add release scripts
1 parent 66a8d63 commit 2cb7dc0

7 files changed

Lines changed: 148 additions & 93 deletions

File tree

.github/workflows/ci.yml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,18 @@ jobs:
4545
with:
4646
fail-on-unsigned: true
4747

48-
verify-artifacts:
49-
runs-on: ubuntu-latest
50-
needs: build-and-test
51-
steps:
52-
- uses: actions/checkout@v4
53-
with:
54-
fetch-depth: 0
55-
56-
- name: Verify dist/index.js attestation
57-
uses: ./
58-
with:
59-
artifact-paths: 'dist/index.js'
60-
fail-on-unattested: false
48+
# TODO: Enable after first signed release (just release X.Y.Z runs auths artifact sign dist/index.js)
49+
# verify-artifacts:
50+
# runs-on: ubuntu-latest
51+
# needs: build-and-test
52+
# steps:
53+
# - uses: actions/checkout@v4
54+
# with:
55+
# fetch-depth: 0
56+
#
57+
# - name: Verify dist/index.js attestation
58+
# uses: ./
59+
# with:
60+
# identity-bundle: ... # provide bundle path or inline JSON
61+
# artifact-paths: 'dist/index.js'
62+
# fail-on-unattested: true

.github/workflows/release.yml

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Triggered by: python scripts/release.py --push
2+
# (tags vX.Y.Z and pushes, which triggers this workflow)
3+
14
name: Release
25

36
on:
@@ -13,6 +16,8 @@ jobs:
1316
runs-on: ubuntu-latest
1417
steps:
1518
- uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
1621

1722
- uses: actions/setup-node@v4
1823
with:
@@ -26,33 +31,78 @@ jobs:
2631
- name: Check dist is up to date
2732
run: git diff --exit-code -- dist/ ':!dist/**/*.d.ts.map'
2833

34+
# --- Artifact signing (mirrors auths/auths release workflow) ---
35+
- name: Install auths CLI
36+
run: |
37+
curl -sL https://github.com/auths-dev/auths/releases/latest/download/auths-linux-x86_64.tar.gz | tar xz -C /usr/local/bin
38+
39+
- name: Sign dist/index.js
40+
env:
41+
AUTHS_PASSPHRASE: ${{ secrets.AUTHS_CI_PASSPHRASE }}
42+
AUTHS_CI_KEYCHAIN_B64: ${{ secrets.AUTHS_CI_KEYCHAIN }}
43+
AUTHS_CI_IDENTITY_BUNDLE_B64: ${{ secrets.AUTHS_CI_IDENTITY_BUNDLE }}
44+
AUTHS_KEYCHAIN_BACKEND: file
45+
AUTHS_KEYCHAIN_FILE: /tmp/auths-ci-keychain
46+
run: |
47+
if [ -z "$AUTHS_PASSPHRASE" ] || [ -z "$AUTHS_CI_KEYCHAIN_B64" ] || [ -z "$AUTHS_CI_IDENTITY_BUNDLE_B64" ]; then
48+
echo "::warning::Skipping artifact signing: AUTHS_CI_PASSPHRASE, AUTHS_CI_KEYCHAIN, and AUTHS_CI_IDENTITY_BUNDLE must all be set"
49+
exit 0
50+
fi
51+
52+
printf '%s' "$AUTHS_CI_KEYCHAIN_B64" | tr -d '[:space:]' | base64 -d > /tmp/auths-ci-keychain
53+
mkdir -p /tmp/auths-identity
54+
printf '%s' "$AUTHS_CI_IDENTITY_BUNDLE_B64" | tr -d '[:space:]' | base64 -d | tar -xz -C /tmp/auths-identity
55+
56+
if ! git -C /tmp/auths-identity rev-parse --git-dir > /dev/null 2>&1; then
57+
echo "::warning::Skipping artifact signing: AUTHS_CI_IDENTITY_BUNDLE does not contain a valid git repository"
58+
exit 0
59+
fi
60+
61+
auths artifact sign dist/index.js \
62+
--device-key ci-release-device \
63+
--note "GitHub Actions release — ${GITHUB_REF_NAME}" \
64+
--repo /tmp/auths-identity
65+
66+
echo "Signed dist/index.js → dist/index.js.auths.json"
67+
68+
- name: Generate SHA256 checksums
69+
run: |
70+
cd dist
71+
sha256sum index.js > index.js.sha256
72+
if [ -f index.js.auths.json ]; then
73+
sha256sum index.js.auths.json >> index.js.sha256
74+
fi
75+
cat index.js.sha256
76+
2977
- name: Create GitHub Release
3078
uses: softprops/action-gh-release@v2
3179
with:
3280
generate_release_notes: true
3381
make_latest: true
82+
files: |
83+
dist/index.js.auths.json
84+
dist/index.js.sha256
3485
body: |
3586
## Auths Verify GitHub Action
3687
37-
Verify commit signatures in your CI pipeline using [Auths](https://github.com/auths-dev/auths) identity keys.
38-
39-
### Features
40-
- Verifies SSH commit signatures against an allowed signers file or identity bundle
41-
- Auto-downloads the `auths` CLI at runtime
42-
- SHA256 checksum verification on downloaded binaries
43-
- Supports `pull_request` and `push` events with automatic commit range detection
44-
- GitHub Step Summary with per-commit verification results
45-
- Optional PR comments with fix instructions for unsigned commits
46-
- Skips merge commits and GPG-signed commits by default
88+
Verify commit signatures and artifact attestations in your CI pipeline using [Auths](https://github.com/auths-dev/auths) identity keys.
4789
4890
### Usage
4991
5092
```yaml
51-
- uses: auths-dev/auths-verify-github-action@v1
93+
- uses: auths-dev/auths-verify-github-action@${{ github.ref_name }}
5294
with:
5395
allowed-signers: '.auths/allowed_signers'
5496
```
5597
98+
**New: Artifact verification**
99+
```yaml
100+
- uses: auths-dev/auths-verify-github-action@${{ github.ref_name }}
101+
with:
102+
identity-bundle: ${{ secrets.AUTHS_IDENTITY_BUNDLE }}
103+
artifact-paths: 'dist/*.tar.gz'
104+
```
105+
56106
See the [README](https://github.com/auths-dev/auths-verify-github-action#readme) for full configuration options.
57107
58108
- name: Update floating major tag

dist/index.js

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71492,33 +71492,35 @@ async function run() {
7149271492
throw new Error('Artifact verification requires an identity bundle (identity-bundle or identity-bundle-json). ' +
7149371493
'The allowed-signers mode is not supported for artifact verification.');
7149471494
}
71495-
const patterns = artifactPathPatterns.join('\n');
71496-
const globber = await glob.create(patterns, { followSymbolicLinks: false });
71497-
let files = await globber.glob();
71498-
// Workspace containment check
71499-
const workspace = path.resolve(process.env.GITHUB_WORKSPACE || process.cwd());
71500-
files = files.filter(f => {
71501-
const resolved = path.resolve(f);
71502-
if (!resolved.startsWith(workspace + path.sep) && resolved !== workspace) {
71503-
core.warning(`Skipping path outside workspace: ${f}`);
71504-
return false;
71505-
}
71506-
return true;
71507-
});
71508-
// Deduplicate
71509-
files = [...new Set(files)];
71510-
if (files.length === 0) {
71511-
core.warning('artifact-paths provided but no files matched');
71512-
}
71513-
for (const file of files) {
71514-
core.info(`Verifying artifact: ${path.basename(file)}`);
71515-
const result = await (0, verifier_1.verifyArtifact)(authsPath, file, resolvedBundlePath, artifactAttestationDir || undefined);
71516-
artifactResults.push(result);
71517-
if (result.valid) {
71518-
core.info(`\u2713 ${path.basename(file)} - verified${result.issuer ? ` (issuer: ${result.issuer})` : ''}`);
71495+
else {
71496+
const patterns = artifactPathPatterns.join('\n');
71497+
const globber = await glob.create(patterns, { followSymbolicLinks: false });
71498+
let files = await globber.glob();
71499+
// Workspace containment check
71500+
const workspace = path.resolve(process.env.GITHUB_WORKSPACE || process.cwd());
71501+
files = files.filter(f => {
71502+
const resolved = path.resolve(f);
71503+
if (!resolved.startsWith(workspace + path.sep) && resolved !== workspace) {
71504+
core.warning(`Skipping path outside workspace: ${f}`);
71505+
return false;
71506+
}
71507+
return true;
71508+
});
71509+
// Deduplicate
71510+
files = [...new Set(files)];
71511+
if (files.length === 0) {
71512+
core.warning('artifact-paths provided but no files matched');
7151971513
}
71520-
else {
71521-
core.warning(`\u2717 ${path.basename(file)} - ${result.error || 'verification failed'}`);
71514+
for (const file of files) {
71515+
core.info(`Verifying artifact: ${path.basename(file)}`);
71516+
const result = await (0, verifier_1.verifyArtifact)(authsPath, file, resolvedBundlePath, artifactAttestationDir || undefined);
71517+
artifactResults.push(result);
71518+
if (result.valid) {
71519+
core.info(`\u2713 ${path.basename(file)} - verified${result.issuer ? ` (issuer: ${result.issuer})` : ''}`);
71520+
}
71521+
else {
71522+
core.warning(`\u2717 ${path.basename(file)} - ${result.error || 'verification failed'}`);
71523+
}
7152271524
}
7152371525
}
7152471526
}

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

justfile

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ check-dist:
1717
# Run the full CI suite locally: test + build + verify dist
1818
ci: test build check-dist
1919

20-
# Sign the dist/index.js artifact (creates dist/index.js.auths.json)
20+
# Sign the dist/index.js artifact locally (creates dist/index.js.auths.json)
2121
sign-dist:
2222
auths artifact sign dist/index.js
2323

24-
# Cut a release: test, build, sign artifact, commit dist, tag, push
24+
# Cut a release: bump version, commit, then tag+push via release script
25+
# The release workflow handles build verification, artifact signing, and GitHub release creation.
2526
# Usage: just release 1.0.3
26-
release VERSION: test build sign-dist
27+
release VERSION: test build
2728
npm version {{VERSION}} --no-git-tag-version
28-
git add package.json dist/ src/ .github/ justfile
29-
git commit -m "Release v{{VERSION}}"
30-
git tag "v{{VERSION}}"
31-
git push && git push origin "v{{VERSION}}"
29+
git add package.json package-lock.json dist/ src/ .github/ justfile
30+
git commit -m "build: bump version to {{VERSION}}"
31+
python scripts/release.py --push

src/__tests__/artifact-integration.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ describe('Artifact verification integration', () => {
249249
expect(artifactFailures).toHaveLength(0);
250250
});
251251

252-
it('requires identity bundle for artifact verification', async () => {
252+
it('fails when no identity bundle provided for artifact verification', async () => {
253253
mockMultilineInputs['artifact-paths'] = ['dist/*.tar.gz'];
254254
// No identity bundle set — only allowed-signers
255255
mockInputs['identity-bundle'] = '';
@@ -258,9 +258,10 @@ describe('Artifact verification integration', () => {
258258

259259
await runMain();
260260

261-
// Should fail with an error about requiring identity bundle
261+
// Should hard-fail — silent skip would give false confidence
262262
const bundleErrors = mockFailed.filter(m => m.includes('identity bundle') || m.includes('Artifact verification requires'));
263263
expect(bundleErrors.length).toBeGreaterThan(0);
264+
expect(mockVerifyArtifact).not.toHaveBeenCalled();
264265
});
265266

266267
it('handles partial success correctly', async () => {

src/main.ts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -167,42 +167,42 @@ async function run(): Promise<void> {
167167
'Artifact verification requires an identity bundle (identity-bundle or identity-bundle-json). ' +
168168
'The allowed-signers mode is not supported for artifact verification.'
169169
);
170-
}
171-
172-
const patterns = artifactPathPatterns.join('\n');
173-
const globber = await glob.create(patterns, { followSymbolicLinks: false });
174-
let files = await globber.glob();
175-
176-
// Workspace containment check
177-
const workspace = path.resolve(process.env.GITHUB_WORKSPACE || process.cwd());
178-
files = files.filter(f => {
179-
const resolved = path.resolve(f);
180-
if (!resolved.startsWith(workspace + path.sep) && resolved !== workspace) {
181-
core.warning(`Skipping path outside workspace: ${f}`);
182-
return false;
183-
}
184-
return true;
185-
});
186-
187-
// Deduplicate
188-
files = [...new Set(files)];
170+
} else {
171+
const patterns = artifactPathPatterns.join('\n');
172+
const globber = await glob.create(patterns, { followSymbolicLinks: false });
173+
let files = await globber.glob();
174+
175+
// Workspace containment check
176+
const workspace = path.resolve(process.env.GITHUB_WORKSPACE || process.cwd());
177+
files = files.filter(f => {
178+
const resolved = path.resolve(f);
179+
if (!resolved.startsWith(workspace + path.sep) && resolved !== workspace) {
180+
core.warning(`Skipping path outside workspace: ${f}`);
181+
return false;
182+
}
183+
return true;
184+
});
189185

190-
if (files.length === 0) {
191-
core.warning('artifact-paths provided but no files matched');
192-
}
186+
// Deduplicate
187+
files = [...new Set(files)];
193188

194-
for (const file of files) {
195-
core.info(`Verifying artifact: ${path.basename(file)}`);
196-
const result = await verifyArtifact(
197-
authsPath, file, resolvedBundlePath,
198-
artifactAttestationDir || undefined
199-
);
200-
artifactResults.push(result);
189+
if (files.length === 0) {
190+
core.warning('artifact-paths provided but no files matched');
191+
}
201192

202-
if (result.valid) {
203-
core.info(`\u2713 ${path.basename(file)} - verified${result.issuer ? ` (issuer: ${result.issuer})` : ''}`);
204-
} else {
205-
core.warning(`\u2717 ${path.basename(file)} - ${result.error || 'verification failed'}`);
193+
for (const file of files) {
194+
core.info(`Verifying artifact: ${path.basename(file)}`);
195+
const result = await verifyArtifact(
196+
authsPath, file, resolvedBundlePath,
197+
artifactAttestationDir || undefined
198+
);
199+
artifactResults.push(result);
200+
201+
if (result.valid) {
202+
core.info(`\u2713 ${path.basename(file)} - verified${result.issuer ? ` (issuer: ${result.issuer})` : ''}`);
203+
} else {
204+
core.warning(`\u2717 ${path.basename(file)} - ${result.error || 'verification failed'}`);
205+
}
206206
}
207207
}
208208
}

0 commit comments

Comments
 (0)