From 0fa951fa6ec85ad0dd96138225cde3a446fcf794 Mon Sep 17 00:00:00 2001 From: Okiki Ojo Date: Sat, 15 Aug 2026 03:47:46 -0400 Subject: [PATCH] ci: add CI and release workflows for automated testing and publishing Signed-off-by: Okiki Ojo --- .github/workflows/ci.yml | 204 ++++++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 142 +++++++++++++++++++++++ .github/workflows/release.yml | 72 ++++++++++++ 3 files changed, 418 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6269749 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,204 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + commits: + name: Conventional commits + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 0 + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: node + - name: Lint commit messages + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = '0000000000000000000000000000000000000000' ]; then + BASE_SHA="$(git rev-list --max-parents=0 "$HEAD_SHA")" + fi + npx --yes \ + --package=@commitlint/cli@21.2.1 \ + commitlint --default-config --from "$BASE_SHA" --to "$HEAD_SHA" --verbose + + quality: + name: Deno quality and package dry-runs + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno + - run: mise run quality + + deno: + name: Deno ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno + - run: mise run test-deno + + node: + name: Node ${{ matrix.node }} / ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + node: '22' + - os: ubuntu-latest + node: '24' + - os: ubuntu-latest + node: '26' + - os: macos-latest + node: '26' + - os: windows-latest + node: '26' + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + env: + MISE_NODE_VERSION: ${{ matrix.node }} + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno node + cache_key_prefix: mise-node-${{ matrix.node }} + - run: mise run test-node + + bun: + name: Bun / ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno bun + - run: mise run test-bun + + browser: + name: Playwright browser matrix + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno + - run: mise run test-browser + + providers: + name: S3 and Azure provider containers + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno + - run: mise run test-providers + + npm-package: + name: npm tarball consumers + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + fetch-depth: 0 + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno node bun + - run: mise run verify-npm + + bench: + name: Server benchmark smoke + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno node bun + - run: mise run bench + + provider-bench: + name: Provider baseline benchmark smoke + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno bun + - run: mise run bench-providers + + browser-bench: + name: Browser OPFS benchmark smoke + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno + - run: mise run bench-browser diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..a980950 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,142 @@ +name: Publish Registries + +on: + workflow_dispatch: + inputs: + tag: + description: Existing immutable release tag, for example opfs@1.2.3. + required: true + type: string + target: + description: Registry to publish or retry after a partial failure. + required: true + type: choice + default: both + options: [both, jsr, npm] + npm_auth: + description: npm authentication mode. Use token only for the bootstrap publication. + required: true + type: choice + default: auto + options: [auto, trusted, token] + +permissions: + contents: read + id-token: write + +concurrency: + group: publish-${{ inputs.tag || github.run_id }} + cancel-in-progress: false + +jobs: + resolve: + name: Resolve immutable release + runs-on: ubuntu-latest + outputs: + ref: ${{ steps.release.outputs.ref }} + version: ${{ steps.release.outputs.version }} + jsr: ${{ steps.release.outputs.jsr }} + npm: ${{ steps.release.outputs.npm }} + npm_auth: ${{ steps.release.outputs.npm_auth }} + steps: + - id: release + env: + TAG: ${{ inputs.tag }} + TARGET: ${{ inputs.target }} + NPM_AUTH: ${{ inputs.npm_auth }} + run: | + case "$TAG" in + opfs@*) VERSION="${TAG#opfs@}" ;; + *) echo "Expected opfs@, received: $TAG" >&2; exit 1 ;; + esac + if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "Invalid release version: $VERSION" >&2 + exit 1 + fi + echo "ref=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "npm_auth=$NPM_AUTH" >> "$GITHUB_OUTPUT" + [[ "$TARGET" = both || "$TARGET" = jsr ]] && echo 'jsr=true' >> "$GITHUB_OUTPUT" || echo 'jsr=false' >> "$GITHUB_OUTPUT" + [[ "$TARGET" = both || "$TARGET" = npm ]] && echo 'npm=true' >> "$GITHUB_OUTPUT" || echo 'npm=false' >> "$GITHUB_OUTPUT" + + jsr: + name: Publish JSR + needs: resolve + if: needs.resolve.outputs.jsr == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: ${{ needs.resolve.outputs.ref }} + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno + - name: Restore frozen dependencies + run: deno ci + - name: Reverify tagged package + run: deno publish --dry-run --set-version "${{ needs.resolve.outputs.version }}" + - name: Publish JSR package + run: deno publish --set-version "${{ needs.resolve.outputs.version }}" + + npm: + name: Publish npm + needs: resolve + if: needs.resolve.outputs.npm == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: ${{ needs.resolve.outputs.ref }} + fetch-depth: 0 + persist-credentials: false + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: deno node bun + - name: Upgrade npm for trusted publishing + run: npm install --global npm@11 + - name: Build and verify npm tarball + env: + RELEASE_VERSION: ${{ needs.resolve.outputs.version }} + run: mise run verify-npm + - name: Detect first npm publication + id: package + run: | + if npm view @okikio/opfs version > /dev/null 2>&1; then + echo 'exists=true' >> "$GITHUB_OUTPUT" + else + echo 'exists=false' >> "$GITHUB_OUTPUT" + fi + - name: Select npm authentication + id: auth + env: + EXISTS: ${{ steps.package.outputs.exists }} + REQUESTED: ${{ needs.resolve.outputs.npm_auth }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + MODE="$REQUESTED" + if [ "$MODE" = auto ]; then + [[ "$EXISTS" = true ]] && MODE=trusted || MODE=token + fi + if [ "$MODE" = token ] && [ -z "$NPM_TOKEN" ]; then + echo 'NPM_TOKEN is required for the bootstrap npm publication.' >&2 + exit 1 + fi + echo "mode=$MODE" >> "$GITHUB_OUTPUT" + - name: Publish npm package + env: + MODE: ${{ steps.auth.outputs.mode }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ "$MODE" = token ]; then + npm config set //registry.npmjs.org/:_authToken "$NPM_TOKEN" + fi + npm publish .release/npm/*.tgz --access public --provenance diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9bf26c9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,72 @@ +name: Release + +on: + workflow_run: + workflows: [CI] + types: [completed] + branches: [main] + +permissions: + actions: write + contents: write + +concurrency: + group: release-main + cancel-in-progress: false + +jobs: + release: + name: Create semantic release + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: main + fetch-depth: 0 + - name: Verify the CI commit is still main + env: + TESTED_SHA: ${{ github.event.workflow_run.head_sha }} + run: test "$(git rev-parse HEAD)" = "$TESTED_SHA" + - name: Record any existing release tag on this commit + id: before + run: echo "tag=$(git tag --points-at HEAD --list 'opfs@*' | sort -V | tail -n 1)" >> "$GITHUB_OUTPUT" + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: '2026.8.6' + install_args: node + - name: Create Git tag and GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: >- + npx --yes + --package=semantic-release@25.0.8 + --package=conventional-changelog-conventionalcommits@9.3.1 + semantic-release + - name: Resolve newly created release tag + id: release + env: + BEFORE: ${{ steps.before.outputs.tag }} + run: | + AFTER="$(git tag --points-at HEAD --list 'opfs@*' | sort -V | tail -n 1)" + if [ -z "$BEFORE" ] && [ -n "$AFTER" ]; then + echo 'created=true' >> "$GITHUB_OUTPUT" + echo "tag=$AFTER" >> "$GITHUB_OUTPUT" + else + echo 'created=false' >> "$GITHUB_OUTPUT" + echo 'tag=' >> "$GITHUB_OUTPUT" + fi + - name: Dispatch immutable registry publication + if: steps.release.outputs.created == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ steps.release.outputs.tag }} + REPOSITORY: ${{ github.repository }} + run: >- + gh workflow run publish.yml + --repo "$REPOSITORY" + --ref main + -f "tag=$RELEASE_TAG" + -f target=both + -f npm_auth=auto -- 2.51.2