build: release on GitHub, crudylabs/pgmorbac and pgmorbac.crudy.fr
The signed distribution is published as a GitHub release by the release workflow on v* tags, replacing the Gitea release and its Drone pipeline. The release body and the dist README take the homepage from META.json.
This commit is contained in:
-34
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: release
|
|
||||||
|
|
||||||
# Cuts the signed PGXN distribution and publishes it as a Gitea release.
|
|
||||||
# Triggered by version tags (v<major>.<minor>.<patch>).
|
|
||||||
#
|
|
||||||
# Repo must be Trusted in Drone. Required secrets:
|
|
||||||
# pgmorbac_release_key Ed25519 private key PEM (content, not a path)
|
|
||||||
# gitea_token Gitea API token with write access to crudy/pgmorbac
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
ref:
|
|
||||||
- refs/tags/v*
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build-and-release
|
|
||||||
image: node:24-alpine
|
|
||||||
environment:
|
|
||||||
PGMORBAC_RELEASE_KEY_PEM:
|
|
||||||
from_secret: pgmorbac_release_key
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: gitea_token
|
|
||||||
GITEA_URL: https://git.villains.fr
|
|
||||||
GITEA_REPO: crudy/pgmorbac
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache zip bash
|
|
||||||
- npm install --no-audit --no-fund
|
|
||||||
- umask 077 && printf '%s\n' "$PGMORBAC_RELEASE_KEY_PEM" > /tmp/release-key.pem
|
|
||||||
- PGMORBAC_RELEASE_KEY=/tmp/release-key.pem node scripts/build-dist.mjs
|
|
||||||
- node scripts/gitea-release.mjs
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Cuts the signed PGXN distribution and publishes it as a GitHub release on
|
||||||
|
# version tags (v<major>.<minor>.<patch>). Secret: PGMORBAC_RELEASE_KEY, the
|
||||||
|
# Ed25519 private key PEM (content, not a path).
|
||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
- run: npm install --no-audit --no-fund
|
||||||
|
- name: Build and sign
|
||||||
|
env:
|
||||||
|
PGMORBAC_RELEASE_KEY_PEM: ${{ secrets.PGMORBAC_RELEASE_KEY }}
|
||||||
|
run: |
|
||||||
|
umask 077
|
||||||
|
printf '%s\n' "$PGMORBAC_RELEASE_KEY_PEM" > "$RUNNER_TEMP/release-key.pem"
|
||||||
|
PGMORBAC_RELEASE_KEY="$RUNNER_TEMP/release-key.pem" node scripts/build-dist.mjs
|
||||||
|
- name: Publish
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: node scripts/github-release.mjs
|
||||||
+1
-1
@@ -55,4 +55,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
`rls_check(activity, view, row_org_id[, row_user_id])` for row-scoped tables
|
`rls_check(activity, view, row_org_id[, row_user_id])` for row-scoped tables
|
||||||
where a NULL `row_org_id` means the record is unattributed
|
where a NULL `row_org_id` means the record is unattributed
|
||||||
|
|
||||||
[1.0.0]: https://git.villains.fr/crudy/pgmorbac/releases/tag/v1.0.0
|
[1.0.0]: https://github.com/crudylabs/pgmorbac/releases/tag/v1.0.0
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
"homepage": "https://pgmorbac.villains.fr",
|
"homepage": "https://pgmorbac.crudy.fr",
|
||||||
"bugtracker": {
|
"bugtracker": {
|
||||||
"web": "https://github.com/crudylabs/pgmorbac/issues"
|
"web": "https://github.com/crudylabs/pgmorbac/issues"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ git push --tags
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- Read the [documentation](https://pgmorbac.villains.fr)
|
- Read the [documentation](https://pgmorbac.crudy.fr)
|
||||||
- Report bugs via [GitHub Issues](https://github.com/crudylabs/pgmorbac/issues)
|
- Report bugs via [GitHub Issues](https://github.com/crudylabs/pgmorbac/issues)
|
||||||
- Security issues: see [SECURITY.md](SECURITY.md)
|
- Security issues: see [SECURITY.md](SECURITY.md)
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -67,12 +67,13 @@ git tag vX.Y.Z
|
|||||||
git push origin master --tags
|
git push origin master --tags
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Signed SQL distribution, attached to the Gitea release (Drone does this on the
|
5. Signed SQL distribution, attached to the GitHub release. The `release`
|
||||||
tag when the repository is activated there):
|
workflow does this on the tag, signing with the `PGMORBAC_RELEASE_KEY`
|
||||||
|
repository secret. By hand:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PGMORBAC_RELEASE_KEY=<path to the Ed25519 key> GITEA_URL=https://git.villains.fr \
|
PGMORBAC_RELEASE_KEY=<path to the Ed25519 key> GITHUB_REPOSITORY=crudylabs/pgmorbac \
|
||||||
GITEA_REPO=crudy/pgmorbac GITEA_TOKEN=<token> npm run release
|
GITHUB_TOKEN=<token> npm run release
|
||||||
```
|
```
|
||||||
|
|
||||||
6. The npm package `@crudy/pgmorbac` is released from the `pgmorbac-node`
|
6. The npm package `@crudy/pgmorbac` is released from the `pgmorbac-node`
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"description": "Release tooling for the pgmorbac PostgreSQL extension (build + sign + publish the PGXN distribution).",
|
"description": "Release tooling for the pgmorbac PostgreSQL extension (build + sign + publish the PGXN distribution).",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dist": "node scripts/build-dist.mjs",
|
"dist": "node scripts/build-dist.mjs",
|
||||||
"release": "node scripts/build-dist.mjs && node scripts/gitea-release.mjs"
|
"release": "node scripts/build-dist.mjs && node scripts/github-release.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jose": "^5"
|
"jose": "^5"
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ include $(PGXS)
|
|||||||
|
|
||||||
writeFileSync(join(stageDir, 'README-INSTALL.md'), `# Installing pgmorbac ${version}
|
writeFileSync(join(stageDir, 'README-INSTALL.md'), `# Installing pgmorbac ${version}
|
||||||
|
|
||||||
Full documentation: https://pgmorbac.villains.fr
|
Full documentation: ${meta.resources.homepage}
|
||||||
|
|
||||||
## As a PostgreSQL extension (recommended)
|
## As a PostgreSQL extension (recommended)
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// Creates (or reuses) release v<version> on Gitea and attaches the dist assets.
|
|
||||||
// Run after build-dist.mjs. Requires GITEA_URL, GITEA_REPO, GITEA_TOKEN.
|
|
||||||
|
|
||||||
import { readFileSync, existsSync } from 'fs';
|
|
||||||
import { dirname, join } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
||||||
const version = readFileSync(join(root, 'pgmorbac.control'), 'utf8').match(/default_version\s*=\s*'([^']+)'/)?.[1];
|
|
||||||
if (!version) {
|
|
||||||
console.error('[gitea-release] could not read default_version from pgmorbac.control');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = process.env.GITEA_URL;
|
|
||||||
const repo = process.env.GITEA_REPO;
|
|
||||||
const token = process.env.GITEA_TOKEN;
|
|
||||||
if (!base || !repo || !token) {
|
|
||||||
console.error('[gitea-release] GITEA_URL, GITEA_REPO and GITEA_TOKEN are required');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const distDir = join(root, 'dist');
|
|
||||||
const files = [
|
|
||||||
`pgmorbac-${version}.zip`,
|
|
||||||
`pgmorbac-${version}.zip.sha256`,
|
|
||||||
`pgmorbac-${version}.manifest.jwt`,
|
|
||||||
`pgmorbac-${version}.pub.pem`,
|
|
||||||
];
|
|
||||||
for (const f of files) {
|
|
||||||
if (!existsSync(join(distDir, f))) {
|
|
||||||
console.error(`[gitea-release] Missing artifact ${f} - run build-dist.mjs first`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = (path) => `${base}/api/v1/repos/${repo}${path}`;
|
|
||||||
const headers = { Authorization: `token ${token}` };
|
|
||||||
|
|
||||||
async function ensureRelease() {
|
|
||||||
const create = await fetch(api('/releases'), {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
tag_name: `v${version}`,
|
|
||||||
name: `pgmorbac ${version}`,
|
|
||||||
body: `PGXN-compatible SQL distribution. Verify with sha256sum -c and the signed manifest (public key attached; see https://pgmorbac.villains.fr/download).`,
|
|
||||||
draft: false,
|
|
||||||
prerelease: false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (create.ok) return (await create.json()).id;
|
|
||||||
if (create.status !== 409) {
|
|
||||||
console.error(`[gitea-release] Release creation failed: ${create.status} ${await create.text()}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const existing = await fetch(api(`/releases/tags/v${version}`), { headers });
|
|
||||||
if (!existing.ok) {
|
|
||||||
console.error(`[gitea-release] Release exists but lookup failed: ${existing.status}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
return (await existing.json()).id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const releaseId = await ensureRelease();
|
|
||||||
for (const f of files) {
|
|
||||||
const form = new FormData();
|
|
||||||
form.set('attachment', new Blob([readFileSync(join(distDir, f))]), f);
|
|
||||||
const res = await fetch(api(`/releases/${releaseId}/assets?name=${encodeURIComponent(f)}`), {
|
|
||||||
method: 'POST', headers, body: form,
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text();
|
|
||||||
if (res.status === 409 || text.includes('already exist')) {
|
|
||||||
console.log(`[gitea-release] ${f} already attached - skipping`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
console.error(`[gitea-release] Upload of ${f} failed: ${res.status} ${text}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
console.log(`[gitea-release] Attached ${f}`);
|
|
||||||
}
|
|
||||||
console.log(`[gitea-release] Release v${version} ready on ${repo}`);
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Creates (or reuses) release v<version> on GitHub and attaches the dist assets.
|
||||||
|
// Run after build-dist.mjs. Requires GITHUB_REPOSITORY (owner/name) and GITHUB_TOKEN.
|
||||||
|
|
||||||
|
import { readFileSync, existsSync } from 'fs';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
const version = readFileSync(join(root, 'pgmorbac.control'), 'utf8').match(/default_version\s*=\s*'([^']+)'/)?.[1];
|
||||||
|
if (!version) {
|
||||||
|
console.error('[github-release] could not read default_version from pgmorbac.control');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const homepage = JSON.parse(readFileSync(join(root, 'META.json'), 'utf8')).resources.homepage;
|
||||||
|
|
||||||
|
const repo = process.env.GITHUB_REPOSITORY;
|
||||||
|
const token = process.env.GITHUB_TOKEN;
|
||||||
|
if (!repo || !token) {
|
||||||
|
console.error('[github-release] GITHUB_REPOSITORY and GITHUB_TOKEN are required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const distDir = join(root, 'dist');
|
||||||
|
const files = [
|
||||||
|
`pgmorbac-${version}.zip`,
|
||||||
|
`pgmorbac-${version}.zip.sha256`,
|
||||||
|
`pgmorbac-${version}.manifest.jwt`,
|
||||||
|
`pgmorbac-${version}.pub.pem`,
|
||||||
|
];
|
||||||
|
for (const f of files) {
|
||||||
|
if (!existsSync(join(distDir, f))) {
|
||||||
|
console.error(`[github-release] Missing artifact ${f} - run build-dist.mjs first`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28' };
|
||||||
|
const api = (path) => `https://api.github.com/repos/${repo}${path}`;
|
||||||
|
|
||||||
|
async function ensureRelease() {
|
||||||
|
const existing = await fetch(api(`/releases/tags/v${version}`), { headers });
|
||||||
|
if (existing.ok) return existing.json();
|
||||||
|
if (existing.status !== 404) {
|
||||||
|
console.error(`[github-release] Release lookup failed: ${existing.status}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const create = await fetch(api('/releases'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
tag_name: `v${version}`,
|
||||||
|
name: `pgmorbac ${version}`,
|
||||||
|
body: `PGXN-compatible SQL distribution. Verify with sha256sum -c and the signed manifest (public key attached; see ${homepage}/download).`,
|
||||||
|
draft: false,
|
||||||
|
prerelease: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!create.ok) {
|
||||||
|
console.error(`[github-release] Release creation failed: ${create.status} ${await create.text()}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return create.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = await ensureRelease();
|
||||||
|
const attached = new Set((release.assets ?? []).map((a) => a.name));
|
||||||
|
for (const f of files) {
|
||||||
|
if (attached.has(f)) {
|
||||||
|
console.log(`[github-release] ${f} already attached - skipping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const res = await fetch(`https://uploads.github.com/repos/${repo}/releases/${release.id}/assets?name=${encodeURIComponent(f)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { ...headers, 'Content-Type': 'application/octet-stream' },
|
||||||
|
body: readFileSync(join(distDir, f)),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error(`[github-release] Upload of ${f} failed: ${res.status} ${await res.text()}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`[github-release] Attached ${f}`);
|
||||||
|
}
|
||||||
|
console.log(`[github-release] Release v${version} ready on ${repo}`);
|
||||||
Reference in New Issue
Block a user