From 9c95e4b1f619612dc1c82baa5a12b5cce6d93252 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 7 Dec 2025 18:03:26 -0700 Subject: [PATCH] Add CI scripts and workflow for forgejo --- .forgejo/workflows/draft-release.yaml | 89 +++++++++++++++++++++++++++ .github/workflows/draft-release.yaml | 51 --------------- scripts/createForgejoRelease.mjs | 54 ++++++++++++++++ scripts/prepareManifest.mjs | 21 +++++-- scripts/tagExists.mjs | 38 ++++++++++++ scripts/uploadToS3.mjs | 65 +++++++++++++++++++ 6 files changed, 262 insertions(+), 56 deletions(-) create mode 100644 .forgejo/workflows/draft-release.yaml delete mode 100644 .github/workflows/draft-release.yaml create mode 100644 scripts/createForgejoRelease.mjs create mode 100644 scripts/tagExists.mjs create mode 100644 scripts/uploadToS3.mjs diff --git a/.forgejo/workflows/draft-release.yaml b/.forgejo/workflows/draft-release.yaml new file mode 100644 index 0000000..c34a896 --- /dev/null +++ b/.forgejo/workflows/draft-release.yaml @@ -0,0 +1,89 @@ +on: [ workflow_dispatch ] +jobs: + create-artifacts: + name: "Create artifacts" + runs-on: act + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: npm clean-install + + - id: version + run: cat module.json | echo version=`jq -r ".version"` >> "$FORGEJO_OUTPUT" + + - name: Assert that the tag doesn't exist + run: node scripts/tagExists.mjs + env: + TAG_NAME: "v${{steps.version.outputs.version}}" + + - name: Compress files + run: zip -r release.zip langs module styles templates README.md assets + + - name: Upload artifacts + uses: https://data.forgejo.org/forgejo/upload-artifact@v4 + with: + path: | + module.json + release.zip + scripts/*.mjs + package-lock.json + package.json + retention-days: 7 + if-no-files-found: error + + + forgejo-release: + name: "Create Forgejo release" + runs-on: act + needs: + - create-artifacts + steps: + - name: Download artifacts + uses: https://data.forgejo.org/forgejo/download-artifact@v4 + with: + merge-multiple: true + + - name: Install dependencies + run: npm i + + - id: version + run: cat module.json | echo version=`jq -r ".version"` >> "$FORGEJO_OUTPUT" + + - name: Update manifest + run: node scripts/prepareManifest.mjs + env: + DOWNLOAD_URL: "${{forgejo.server_url}}/${{forgejo.repository}}/releases/download/v${{steps.version.outputs.version}}/release.zip" + LATEST_URL: "${{forgejo.server_url}}/${{forgejo.repository}}/releases/download/latest/module.json" + + - name: Add manifest into release archive + run: zip release.zip --update module.json + + - name: Upload archive to s3 + run: node scripts/uploadToS3.mjs + env: + TAG: "v${{steps.version.outputs.version}}" + FILE: "release.zip" + S3_BUCKET: "${{vars.S3_BUCKET}}" + S3_REGION: "${{vars.S3_REGION}}" + S3_KEY: "${{secrets.S3_KEY}}" + S3_SECRET: "${{secrets.S3_SECRET}}" + S3_ENDPOINT: "${{vars.S3_ENDPOINT}}" + + - name: Upload manifest to s3 + run: node scripts/uploadToS3.mjs + env: + TAG: "v${{steps.version.outputs.version}}" + FILE: "module.json" + S3_BUCKET: "${{vars.S3_BUCKET}}" + S3_REGION: "${{vars.S3_REGION}}" + S3_KEY: "${{secrets.S3_KEY}}" + S3_SECRET: "${{secrets.S3_SECRET}}" + S3_ENDPOINT: "${{vars.S3_ENDPOINT}}" + + - name: Create draft release + run: node scripts/createForgejoRelease.mjs + env: + TAG: "v${{steps.version.outputs.version}}" + CDN_URL: "${{vars.CDN_URL}}" diff --git a/.github/workflows/draft-release.yaml b/.github/workflows/draft-release.yaml deleted file mode 100644 index a30ab21..0000000 --- a/.github/workflows/draft-release.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: Create Draft Release -on: [workflow_dispatch] -jobs: - everything: - runs-on: ubuntu-latest - steps: - # Checkout the repository - - uses: actions/checkout@v4 - - # Install node and NPM - - uses: actions/setup-node@v4 - with: - node-version: "19" - - # Install required packages - - run: npm install - - - name: Reading the module.json for the version - id: "version" - run: cat module.json | echo version=`jq -r ".version"` >> "$GITHUB_OUTPUT" - - # Check that tag doesn't exist - - uses: mukunku/tag-exists-action@v1.5.0 - id: check-tag - with: - tag: "v${{ steps.version.outputs.version }}" - - - name: "Ensure that the tag doesn't exist" - if: ${{ steps.check-tag.outputs.exists == 'true' }} - run: exit 1 - - - name: Update the manifest with the relevant properties - id: manifest-update - uses: microsoft/variable-substitution@v1 - with: - files: "module.json" - env: - download: "https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/release.zip" - - - name: Create the zip - run: zip -r release.zip module.json packs module langs assets templates README.md - - - name: Create the draft release - uses: ncipollo/release-action@v1 - with: - tag: "v${{ steps.version.outputs.version }}" - commit: ${{ github.ref }} - draft: true - body: - generateReleaseNotes: true - artifacts: "release.zip,module.json" diff --git a/scripts/createForgejoRelease.mjs b/scripts/createForgejoRelease.mjs new file mode 100644 index 0000000..67a8629 --- /dev/null +++ b/scripts/createForgejoRelease.mjs @@ -0,0 +1,54 @@ +import axios from "axios"; + +const { + TAG, + FORGEJO_SERVER_URL: WEB_URL, + FORGEJO_API_URL: API, + FORGEJO_REPOSITORY: REPO, + FORGEJO_TOKEN: TOKEN, + CDN_URL, +} = process.env; + +async function addReleaseAsset(releaseID, name) { + return axios.post( + `${API}/repos/${REPO}/releases/${releaseID}/assets`, + { external_url: `${CDN_URL}/${REPO}/${TAG}/${name}`, }, + { + headers: { + Authorization: `token ${TOKEN}`, + "Content-Type": `multipart/form-data`, + }, + params: { name }, + } + ); +}; + +async function main() { + + // Initial Release Data + const release = await axios.post( + `${API}/repos/${REPO}/releases`, + { + name: TAG, + tag_name: TAG, + draft: true, + hide_archive_links: true, + body: ``, + }, + { + headers: { Authorization: `token ${TOKEN}` }, + } + ); + + try { + await addReleaseAsset(release.data.id, `release.zip`); + await addReleaseAsset(release.data.id, `system.json`); + } catch (e) { + console.error(`Failed to add assets to the release`); + process.exit(1); + }; + + console.log(`Release created`); +}; + +main(); diff --git a/scripts/prepareManifest.mjs b/scripts/prepareManifest.mjs index a2bc22e..2d1db41 100644 --- a/scripts/prepareManifest.mjs +++ b/scripts/prepareManifest.mjs @@ -1,35 +1,46 @@ /* -The intent of this script is to do all of the modifications of the manifest file -that we need to do in order to release the package. This can include removing -dev-only fields/attributes that end users will never, and should never, care -about nor need. +The intent of this script is to do all of the modifications of the +manifest file that we need to do in order to release the system. +This can include removing dev-only fields/attributes that end +users will never, and should never, care about nor need. */ import { readFile, writeFile } from "fs/promises"; const MANIFEST_PATH = `module.json`; +const { + DOWNLOAD_URL, + LATEST_URL, +} = process.env; + let manifest; try { manifest = JSON.parse(await readFile(MANIFEST_PATH, `utf-8`)); + console.log(`Manifest loaded from disk`); } catch { console.error(`Failed to parse manifest file.`); process.exit(1); }; +console.log(`Updating download/manifest URLs`); +manifest.download = DOWNLOAD_URL; +manifest.manifest = LATEST_URL; // Filter out dev-only resources if (manifest.esmodules) { + console.log(`Removing dev-only esmodules`); manifest.esmodules = manifest.esmodules.filter( filepath => !filepath.startsWith(`dev/`) ); }; // Remove dev flags +console.log(`Cleaning up flags`); delete manifest.flags?.hotReload; delete manifest.flags?.inDev; - if (Object.keys(manifest.flags).length === 0) { delete manifest.flags; }; await writeFile(MANIFEST_PATH, JSON.stringify(manifest, undefined, `\t`)); +console.log(`Manifest written back to disk`); diff --git a/scripts/tagExists.mjs b/scripts/tagExists.mjs new file mode 100644 index 0000000..2ddcdbd --- /dev/null +++ b/scripts/tagExists.mjs @@ -0,0 +1,38 @@ +import axios from "axios"; + +const { + TAG_NAME, + FORGEJO_API_URL: API_URL, + FORGEJO_REPOSITORY: REPO, + FORGEJO_TOKEN: TOKEN, +} = process.env; + + +async function main() { + + if (!TAG_NAME) { + console.log(`Tag name must not be blank`); + process.exit(1); + }; + + const requestURL = `${API_URL}/repos/${REPO}/tags/${TAG_NAME}`; + + const response = await axios.get( + requestURL, + { + headers: { Authorization: `token ${TOKEN}` }, + validateStatus: () => true, + }, + ); + + // We actually *want* an error when the tag exists, instead of when + // it doesn't + if (response.status === 200) { + console.log(`Tag with name "${TAG_NAME}" already exists`); + process.exit(1); + }; + + console.log(`Tag with name "${TAG_NAME}" not found, proceeding`); +}; + +main(); diff --git a/scripts/uploadToS3.mjs b/scripts/uploadToS3.mjs new file mode 100644 index 0000000..dacd2e8 --- /dev/null +++ b/scripts/uploadToS3.mjs @@ -0,0 +1,65 @@ +import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { createReadStream } from "fs"; + +const requiredEnvVariables = [ + `TAG`, `FILE`, + `FORGEJO_REPOSITORY`, + `S3_BUCKET`, `S3_REGION`, `S3_KEY`, `S3_SECRET`, `S3_ENDPOINT`, +]; + +async function main() { + + // Assert all of the required env variables are present + const missing = []; + for (const envVar of requiredEnvVariables) { + if (!(envVar in process.env)) { + missing.push(envVar); + }; + }; + if (missing.length > 0) { + console.error(`Missing the following required environment variables: ${missing.join(`, `)}`); + process.exit(1); + }; + + const { + TAG, + S3_ENDPOINT, + S3_REGION, + S3_KEY, + S3_SECRET, + S3_BUCKET, + FILE, + FORGEJO_REPOSITORY: REPO, + } = process.env; + + const s3Client = new S3Client({ + endpoint: S3_ENDPOINT, + forcePathStyle: false, + region: S3_REGION, + credentials: { + accessKeyId: S3_KEY, + secretAccessKey: S3_SECRET + }, + }); + + const name = FILE.split(`/`).at(-1); + + const params = { + Bucket: S3_BUCKET, + Key: `${REPO}/${TAG}/${name}`, + Body: createReadStream(FILE), + ACL: "public-read", + METADATA: { + "x-repo-version": TAG, + }, + }; + + try { + const response = await s3Client.send(new PutObjectCommand(params)); + console.log("Upload successful"); + } catch (err) { + console.error("Upload to s3 failed"); + }; +}; + +main();