Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 14 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,25 @@ permissions:

concurrency:
group: smith-chart-${{ github.ref }}
cancel-in-progress: false
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
verification:
uses: ./.github/workflows/verify.yml
with:
package-artifact: smithkit-package
upload-pages: true

check:
needs: verification
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 2
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium webkit firefox
- run: npm run prepare:release
- uses: actions/upload-pages-artifact@v5
with:
path: dist/demo/
- uses: actions/upload-artifact@v7
with:
name: smithkit-package
path: artifacts/
if-no-files-found: error
- uses: actions/upload-artifact@v7
if: failure()
with:
name: browser-test-results
path: test-results/
retention-days: 7
- name: Require all verification jobs to pass
env:
VERIFICATION_RESULT: ${{ needs.verification.result }}
run: test "$VERIFICATION_RESULT" = success

deploy:
needs: check
Expand Down
30 changes: 5 additions & 25 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,11 @@ jobs:
if: >-
github.repository == 'rosek86/smithkit' &&
(!inputs.release_tag || github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.release_tag && format('refs/tags/{0}', inputs.release_tag) || github.event.release.tag_name || github.ref }}
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- name: Verify release version
if: github.event_name == 'release' || inputs.release_tag != ''
env:
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
run: |
node --input-type=module -e "import fs from 'node:fs'; import assert from 'node:assert/strict'; const p = JSON.parse(fs.readFileSync('package.json')); assert.equal(process.env.RELEASE_TAG, 'v' + p.version); assert.match(p.version, /^\d+\.\d+\.\d+$/);"
- run: npm ci
- run: npx playwright install --with-deps chromium webkit firefox
- run: npm run prepare:release
- uses: actions/upload-artifact@v7
with:
name: npm-package
path: artifacts/
if-no-files-found: error
uses: ./.github/workflows/verify.yml
with:
ref: ${{ inputs.release_tag && format('refs/tags/{0}', inputs.release_tag) || github.event.release.tag_name || github.sha }}
release-tag: ${{ inputs.release_tag || github.event.release.tag_name || '' }}
package-artifact: npm-package

publish:
if: github.event_name == 'release' || inputs.release_tag != ''
Expand Down
186 changes: 186 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
name: Verify Smithkit

on:
workflow_call:
inputs:
ref:
type: string
default: ''
release-tag:
type: string
default: ''
package-artifact:
type: string
required: true
upload-pages:
type: boolean
default: false

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
commit: ${{ steps.source.outputs.commit }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- name: Record the exact source commit
id: source
run: |
test -z "$(git status --porcelain)"
echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Verify release version
if: inputs.release-tag != ''
env:
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
node --input-type=module -e "import fs from 'node:fs'; import assert from 'node:assert/strict'; const p = JSON.parse(fs.readFileSync('package.json')); assert.equal(process.env.RELEASE_TAG, 'v' + p.version); assert.match(p.version, /^\d+\.\d+\.\d+$/);"
- run: npm ci
- run: npm run check
- name: Pack the candidate once
run: |
mkdir -p artifacts
npm pack --ignore-scripts --pack-destination artifacts
test -z "$(git status --porcelain)"
- uses: actions/upload-artifact@v7
with:
name: verification-build
path: |
dist/
artifacts/*.tgz
if-no-files-found: error
retention-days: 1

browsers:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
browser: [chromium, webkit, firefox]
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.build.outputs.commit }}
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- uses: actions/download-artifact@v8
with:
name: verification-build
- run: npx playwright install --with-deps ${{ matrix.browser }}
- name: Test the built demo
run: npx playwright test --browser=${{ matrix.browser }} --workers=2
- uses: actions/upload-artifact@v7
if: failure()
with:
name: browser-test-results-${{ matrix.browser }}
path: test-results/
retention-days: 7

consumers:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.build.outputs.commit }}
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- uses: actions/download-artifact@v8
with:
name: verification-build
- run: npx playwright install --with-deps chromium webkit firefox
- name: Check the packed library
run: npm run check:package -- artifacts/*.tgz
- name: Check standalone consumers in all three browsers
run: npm run check:example -- artifacts/*.tgz
- name: Require unchanged source
run: test -z "$(git status --porcelain)"

complete:
needs: [build, browsers, consumers]
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Require all verification jobs to pass
env:
BUILD_RESULT: ${{ needs.build.result }}
BROWSER_RESULT: ${{ needs.browsers.result }}
CONSUMER_RESULT: ${{ needs.consumers.result }}
run: |
test "$BUILD_RESULT" = success
test "$BROWSER_RESULT" = success
test "$CONSUMER_RESULT" = success
- uses: actions/checkout@v7
with:
ref: ${{ needs.build.outputs.commit }}
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
- uses: actions/download-artifact@v8
with:
name: verification-build
# Keep this self-contained so a dispatch can still verify older release tags.
- name: Record the verified archive
env:
VERIFIED_COMMIT: ${{ needs.build.outputs.commit }}
run: |
node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
const manifest = JSON.parse(readFileSync('package.json', 'utf8'));
assert.equal(manifest.name, 'smithkit');
assert.match(manifest.version, /^\d+\.\d+\.\d+$/);
const archives = readdirSync('artifacts').filter(name => name.endsWith('.tgz'));
assert.deepEqual(archives, [`smithkit-${manifest.version}.tgz`]);
const archive = `./artifacts/${archives[0]}`;
const [packed] = JSON.parse(execFileSync('npm', ['pack', archive, '--dry-run', '--ignore-scripts', '--json'], { encoding: 'utf8' }));
assert.equal(packed.name, manifest.name);
assert.equal(packed.version, manifest.version);
const bytes = readFileSync(archive);
const integrity = `sha512-${createHash('sha512').update(bytes).digest('base64')}`;
assert.equal(packed.integrity, integrity);
const commit = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
assert.equal(commit, process.env.VERIFIED_COMMIT);
assert.equal(execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8' }).trim(), '');
const report = {
name: packed.name, version: packed.version, commit, archive: archives[0],
sha256: createHash('sha256').update(bytes).digest('hex'), integrity,
size: bytes.length, unpackedSize: packed.unpackedSize,
verified: ['source', 'chromium', 'webkit', 'firefox', 'package-consumer', 'standalone-example'],
};
writeFileSync('artifacts/release-manifest.json', JSON.stringify(report, null, 2) + '\n');
JS
- uses: actions/upload-artifact@v7
with:
name: ${{ inputs.package-artifact }}
path: artifacts/
if-no-files-found: error
- uses: actions/upload-pages-artifact@v5
if: inputs.upload-pages
with:
path: dist/demo/
15 changes: 13 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,24 @@ archive path after `--`, so release checks can reuse exactly the archive being s

From a clean committed checkout, run `npm run prepare:release` for all source checks,
all three demo browser suites, package-consumer checks, and the standalone example. It
writes `artifacts/smithkit-0.5.0.tgz` and `artifacts/release-manifest.json`, containing
writes `artifacts/smithkit-<version>.tgz` and `artifacts/release-manifest.json`, containing
the commit, SHA-256, npm integrity, and archive sizes. Nothing is published.
Install all three engines first with `npx playwright install chromium webkit firefox`.
`PLAYWRIGHT_CHANNEL=chrome` can select installed Chrome for local checks; the script
clears that override when running WebKit and Firefox.

CI and the release workflow use this same command. The release workflow also has
CI and the release workflow use `.github/workflows/verify.yml` to run the same
checks in parallel: build and pack once, then run a three-browser matrix alongside
package and standalone-consumer verification. Each job uses the recorded source
commit and downloads the same build artifact. Only after all jobs succeed does the
workflow write the release manifest and upload the verified package (and Pages
artifact for CI). The top-level `check` status remains the merge/deployment gate.

A new push automatically cancels older runs of the same PR. Runs on `main` and npm
release workflows are not cancelled by newer runs. The local `prepare:release`
command retains its sequential execution and remains a complete verification path.

The release workflow also has
a manual dispatch: leave `release_tag` empty to verify only, or set an existing
release tag on the default branch to retry its publication with the current workflow.

Expand Down
19 changes: 12 additions & 7 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ GitHub Pages serves the built demo from `dist/demo/`, not the source files on `m

A push to `main` runs `.github/workflows/ci.yml`:

1. Install dependencies and Chromium/Firefox/WebKit.
2. Run `npm run prepare:release` to check the source, build the library and demo,
test all three browsers, and verify the packed library and standalone consumer.
3. Upload `dist/demo/` as the `github-pages` artifact.
4. Deploy that exact artifact with `actions/deploy-pages` after checks pass.

Pull requests run the same checks and upload artifacts without deploying.
1. Call `.github/workflows/verify.yml` to run source checks, build the library and
demo, and pack one candidate archive.
2. Download that same build in parallel jobs: Chromium, WebKit, Firefox, and
package/standalone-consumer verification. All jobs check out the recorded commit.
3. Require every verification job to succeed, then create the release manifest
and upload `dist/demo/` as the `github-pages` artifact.
4. Require the top-level `check` gate, then deploy that exact artifact with
`actions/deploy-pages`.

Pull requests run the same checks and upload artifacts without deploying. A new
push cancels older runs of that PR so obsolete checks do not hold up the latest
revision. Runs on `main` and release publication are not automatically cancelled.
You can also select **Actions → Check and publish Smith chart → Run workflow**
on `main` to retry a deployment. Runs on other branches cannot deploy.
Preparing a release archive does not publish the npm package.
Expand Down
Loading