diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..2d5d354 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,184 @@ +name: Build FAPI specifications + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build spec HTML + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # build-all.py runs ./pandoc-3.1.9/bin/pandoc for the FAPI 1.0 documents; + # fetch the pinned release it expects (as the old Bitbucket pipeline did). + - name: Install pandoc + run: | + curl -fsSL https://github.com/jgm/pandoc/releases/download/3.1.9/pandoc-3.1.9-linux-amd64.tar.gz \ + | tar -xz pandoc-3.1.9/bin/pandoc + + # Builds every spec (markdown2rfc via docker for the mmark ones, pandoc + # for FAPI 1.0) plus index.html and manifest.json, all at the repo root. + - name: Build specifications + run: ./build-all.py + + - name: Assemble site + run: | + mkdir -p _site + cp *.html manifest.json _site/ + + - name: Upload site artifact + uses: actions/upload-artifact@v4 + with: + name: site + path: _site + + # The xml2rfc XML (still carrying its -NN draft-version suffix), kept as + # an artifact for draft submissions; not published to the site. + - name: Upload XML artifact + uses: actions/upload-artifact@v4 + with: + name: xml + path: '*.xml' + if-no-files-found: error + + # The site is served from the gh-pages branch (Pages source: "Deploy from a + # branch"): master's drafts live at the root, and per-PR previews live under + # PREVIEW-DO-NOT-USE/pr-/ (removed again by preview-cleanup.yml). Same + # model as the openid/dchp repo. + publish-to-pages: + name: Publish drafts to GitHub Pages + if: github.ref == 'refs/heads/master' + needs: build + runs-on: ubuntu-latest + # Serialise gh-pages pushes so two quick merges to master can't race. + concurrency: + group: "pages" + cancel-in-progress: false + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - name: Download site artifact + uses: actions/download-artifact@v4 + with: + name: site + path: ${{ runner.temp }}/site + - name: Publish to gh-pages branch + # Run a copy from outside the work tree: the script switches the + # checkout to gh-pages, which has no tools/. + run: | + cp tools/publish-gh-pages.sh "$RUNNER_TEMP/" + bash "$RUNNER_TEMP/publish-gh-pages.sh" root "$RUNNER_TEMP/site" \ + "Publish FAPI drafts for ${GITHUB_SHA}" + + # HTML preview of the PR's drafts, linked from a sticky PR comment. Fork PRs + # get a read-only GITHUB_TOKEN, so previews only run for branches in this + # repo. + deploy-preview: + name: Deploy PR preview to GitHub Pages + if: > + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository + needs: build + runs-on: ubuntu-latest + concurrency: + group: preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Download site artifact + uses: actions/download-artifact@v4 + with: + name: site + path: ${{ runner.temp }}/site + - name: Deploy preview to gh-pages branch + run: | + cp tools/publish-gh-pages.sh "$RUNNER_TEMP/" + bash "$RUNNER_TEMP/publish-gh-pages.sh" subdir \ + "PREVIEW-DO-NOT-USE/pr-${{ github.event.pull_request.number }}" \ + "$RUNNER_TEMP/site" \ + "Preview for PR #${{ github.event.pull_request.number }}" + - name: Comment with preview links + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const prNum = context.issue.number; + const sha = context.payload.pull_request.head.sha; + // Assumes the default .github.io/ Pages host (no CNAME). + const base = `https://${context.repo.owner}.github.io/${context.repo.repo}/PREVIEW-DO-NOT-USE/pr-${prNum}/`; + + // Source .md -> generated .html, written by build-all.py and + // shipped inside the site artifact. + const manifest = JSON.parse( + fs.readFileSync(`${process.env.RUNNER_TEMP}/site/manifest.json`, 'utf8')); + + // Link directly to the drafts whose sources this PR touches. A PR + // touching no spec sources (or only unbuilt ones) gets just the + // index link. + const changedFiles = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNum, + per_page: 100, + }); + const changed = new Set(changedFiles.map(f => f.filename)); + const changedDrafts = Object.entries(manifest) + .filter(([src]) => changed.has(src)) + .map(([, html]) => html) + .sort(); + + // Invisible marker identifying our comment, so the visible heading + // can be reworded without breaking the update-in-place matching. + const marker = ''; + const lines = ['📄 **HTML preview**', '']; + if (changedDrafts.length > 0) { + lines.push('Drafts changed in this PR:'); + for (const html of changedDrafts) { + lines.push(`- [${html}](${base}${html})`); + } + lines.push(''); + } + lines.push(`[All drafts](${base}index.html)`); + lines.push(''); + lines.push( + `_Preview of ${sha}; updated on every push. For review only — ` + + 'the official specifications are published at https://openid.net/specs/_'); + lines.push(marker); + const body = lines.join('\n'); + + // Update the existing preview comment rather than adding a new one. + let existing = null; + for await (const response of github.paginate.iterator( + github.rest.issues.listComments, + { owner: context.repo.owner, repo: context.repo.repo, issue_number: prNum, per_page: 100 } + )) { + const found = response.data.find(c => c.body.includes(marker)); + if (found) { existing = found; break; } + } + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNum, + body, + }); + } diff --git a/.github/workflows/preview-cleanup.yml b/.github/workflows/preview-cleanup.yml new file mode 100644 index 0000000..883f015 --- /dev/null +++ b/.github/workflows/preview-cleanup.yml @@ -0,0 +1,34 @@ +name: Clean up PR preview + +# Removes the PR's HTML preview (deployed to the gh-pages branch by the +# deploy-preview job in build.yml) once the PR is closed or merged. +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + cleanup-preview: + name: Remove PR preview from GitHub Pages + # Fork PRs never get a preview (see deploy-preview in build.yml). + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + # Same group as deploy-preview, so a still-running deployment for this PR + # finishes before we delete its output. + concurrency: + group: preview-${{ github.event.pull_request.number }} + cancel-in-progress: false + steps: + # Check out the default branch for tools/: the PR's merge ref can be + # gone by the time a closed PR's workflow runs. + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + - name: Remove preview + run: | + cp tools/publish-gh-pages.sh "$RUNNER_TEMP/" + bash "$RUNNER_TEMP/publish-gh-pages.sh" delete \ + "PREVIEW-DO-NOT-USE/pr-${{ github.event.pull_request.number }}" \ + "Remove preview for PR #${{ github.event.pull_request.number }}" diff --git a/.gitignore b/.gitignore index 1f1025f..0e4ba37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ .idea -.DS_Store \ No newline at end of file +.DS_Store +*~ + +# build outputs (root-anchored: FAPI_1.0/ has tracked .html/.xml artifacts) +/*.html +/*.xml +/manifest.json +/_site/ +/pandoc-3.1.9/ diff --git a/README.md b/README.md index 16ebe98..280e354 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This is the official repository for OpenID Foundation Financial-grade API (FAPI) Working Group. +The latest drafts are built automatically from this repository and published at . These reflect in-progress edits; the official specifications are published at . + ### What is this repository for? ### * FAPI 1.0 - This is a profile of OpenID Connect. @@ -26,7 +28,7 @@ You can find the overview and rationale of the working group at [the WG page](ht * You MUST execute the Contributor Agreement either by * [Electronic Signature](http://openid.net/intellectual-property/) and notify openid-specs-fapi-owner@lists.openid.net; OR * print this [PDF](http://openid.net/wordpress-content/uploads/2010/01/paper-contribution-agreement-20100122.pdf) and fill, sign, scan, and send it to openid-specs-fapi-owner@lists.openid.net. -* You MUST file an issue to [the issue tracker](https://bitbucket.org/openid/fapi/issues?status=new&status=open) before contributing a code. +* You MUST file an issue to [the issue tracker](https://github.com/openid/fapi/issues) before contributing a code. * You MUST test that the contributed code compiles without error. ### Who do I talk to? ### diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml deleted file mode 100644 index a8748e8..0000000 --- a/bitbucket-pipelines.yml +++ /dev/null @@ -1,43 +0,0 @@ -image: atlassian/default-image:2 - -pipelines: - default: - - step: - name: build - services: - - docker - script: - - wget https://github.com/jgm/pandoc/releases/download/3.1.9/pandoc-3.1.9-linux-amd64.tar.gz - - tar -xzvf pandoc-3.1.9-linux-amd64.tar.gz pandoc-3.1.9/bin/pandoc - - ./build-all.py - artifacts: - - '*.html' - - '*.xml' - branches: - master: - - step: - name: build - services: - - docker - script: - - wget https://github.com/jgm/pandoc/releases/download/3.1.9/pandoc-3.1.9-linux-amd64.tar.gz - - tar -xzvf pandoc-3.1.9-linux-amd64.tar.gz pandoc-3.1.9/bin/pandoc - - ./build-all.py - artifacts: - - '*.html' - - '*.xml' - - step: - name: publish - clone: - enabled: false - script: - - git config --global user.email "pipeline@bitbucket.org" - - git config --global user.name "Bitbucket FAPI Pipeline" - - git config --global push.default simple - - git clone git@bitbucket.org:openid/openid.bitbucket.io.git - - mkdir -p openid.bitbucket.io/fapi/ - - cp *.html openid.bitbucket.io/fapi/ - - cd openid.bitbucket.io/fapi - - git add * - - git diff-index --quiet HEAD || git commit -m "Automatic update of FAPI drafts by pipeline" - - git push origin master diff --git a/build-all.py b/build-all.py index 2562df6..03def43 100755 --- a/build-all.py +++ b/build-all.py @@ -6,6 +6,7 @@ # exits with appropriate success / failure return code import glob +import json import os import re import sys @@ -47,10 +48,19 @@ './FAPI_1.0/openid-financial-api-part-2-1_0.md' ] +# Pinned by digest so the rendering is reproducible: an untagged/:latest image +# could silently change the output between identical commits. Regenerate the +# digest with `docker buildx imagetools inspect danielfett/markdown2rfc:latest`. +MD2RFC_IMAGE = 'danielfett/markdown2rfc@sha256:7b4412559d6ba5db45a14174a28da5b240512e7c2a886a5e4adb44e5e67f34ca' + failed = [] files_generated = [] +# source .md -> generated .html, written to manifest.json for CI to link the +# drafts changed in a pull request from the preview comment +manifest = {} + def get_output_filename(fname): # get the output filename, i.e. do what https://github.com/oauthstuff/markdown2rfc/blob/master/make.sh#L18 does # and find the line like: value = "fapi-2_0-baseline-01" @@ -88,12 +98,13 @@ def execute_command(cmd, fname, outputfname): os.rename(outputfname, newoutputfname) outputfname = newoutputfname print("Renamed output to "+outputfname) - files_generated.append(newoutputfname) + files_generated.append(outputfname) + manifest[fname[2:] if fname.startswith('./') else fname] = outputfname print() def process_spec(fname): currentdir = os.getcwd() - cmd = [ 'docker', 'run', '-v', currentdir+':/data', 'danielfett/markdown2rfc', fname ] + cmd = [ 'docker', 'run', '-v', currentdir+':/data', MD2RFC_IMAGE, fname ] print("Running: " + ' '.join(cmd)) outputfname = get_output_filename(fname) outputfname += ".html" @@ -133,9 +144,16 @@ def generate_index(): OpenID Foundation FAPI Working Group Drafts -