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
39 changes: 33 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ jobs:
- run: pnpm typecheck
# Before the tests, so the one that compares the built schema with what the generator derives in process has a file to compare.
- run: pnpm build
# The explorer in site/ is built from the library's source, so a change to the library that breaks it fails here, before the Pages workflow would deploy it.
- run: pnpm site:build
- run: pnpm test
- run: pnpm exec publint
- run: pnpm exec attw --pack --profile esm-only
Expand Down Expand Up @@ -281,8 +279,10 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The site is built from git: its footer names the release or commit it came from, and the version a commit will release as is read from the history since the last release tag, so the build needs every commit and tag.
fetch-depth: 0
persist-credentials: false
# The files the site, its tests, the library it is built from and this workflow live in. A dispatch always runs. The base is the pull request's base or the commit a push replaces; one that cannot be fetched (a force-pushed branch, a first push) runs the tests, since not knowing is no reason to skip them. The diff is of two trees, so one commit of history each is enough.
# The files the site, its tests, the library it is built from, the release rules its version label is predicted from and this workflow live in. This job is also what builds the site on a pull request, so a change to any of them that breaks the build fails here, before the Pages workflow would deploy it. A dispatch always runs. The base is the pull request's base or the commit a push replaces; one that cannot be fetched (a force-pushed branch, a first push) runs the tests, since not knowing is no reason to skip them. The fetch takes the commit whole and never a depth of one: a shallow boundary would make the history incomplete again, and the build reads it.
- name: Decide whether the browser tests are needed
id: changes
env:
Expand All @@ -295,14 +295,14 @@ jobs:
echo "The browser tests run: there is no base to compare with."
exit 0
fi
if ! git fetch --no-tags --depth=1 "https://github.com/${REPOSITORY}.git" "$BASE_SHA"; then
if ! git fetch --no-tags "https://github.com/${REPOSITORY}.git" "$BASE_SHA"; then
echo "relevant=true" >> "$GITHUB_OUTPUT"
echo "The browser tests run: the base commit could not be fetched."
exit 0
fi
if git diff --name-only "$BASE_SHA" HEAD | grep -Eq '^(site/|src/|e2e/|playwright\.config\.ts$|package\.json$|pnpm-lock\.yaml$|\.github/workflows/ci\.yml$)'; then
if git diff --name-only "$BASE_SHA" HEAD | grep -Eq '^(site/|src/|e2e/|playwright\.config\.ts$|package\.json$|pnpm-lock\.yaml$|release\.config\.ts$|\.github/workflows/ci\.yml$)'; then
echo "relevant=true" >> "$GITHUB_OUTPUT"
echo "The browser tests run: the site, the library, the tests or the dependencies changed."
echo "The browser tests run: the site, the library, the release rules, the tests or the dependencies changed."
else
echo "relevant=false" >> "$GITHUB_OUTPUT"
echo "The browser tests are skipped: nothing they depend on changed."
Expand Down Expand Up @@ -385,6 +385,8 @@ jobs:
issues: write # to comment on released issues
pull-requests: write # to comment on released pull requests
id-token: write # OIDC identity for npm trusted publishing and provenance (no NPM_TOKEN)
outputs:
released: ${{ steps.released.outputs.released }}
steps:
# The default GITHUB_TOKEN cannot bypass a repository ruleset (GitHub does not support naming the github-actions[bot] identity as a bypass actor), so semantic-release's release commit and tag push needs its own bypass identity. A deploy key with write access, listed as a DeployKey bypass actor on the main ruleset, is the lightest one: repository-scoped, with no GitHub App installation and no personal or organisation-admin credential. Because the key can push straight to main, it exists on the runner only for the last two steps: checkout does not receive it, so it is not in git config or on disk while the setup actions, the install and the npm upgrade run. That is the whole gain. Those earlier steps can still change PATH, the environment or installed code (node_modules, global npm) that later runs with the key present, so this narrows exposure to code that merely reads the secret when it runs and does not isolate the key from a compromised earlier step. Full isolation needs the install separated from the credential.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -427,6 +429,31 @@ jobs:
# Blanked, not omitted: an inherited NPM_TOKEN or NODE_AUTH_TOKEN would otherwise be used in preference to the OIDC exchange.
NPM_TOKEN: ''
NODE_AUTH_TOKEN: ''
# semantic-release commits the release and tags that commit in this checkout, so a tag on HEAD afterwards means a release was cut, and none means the pushed commit released nothing.
- name: Record whether a release was cut
id: released
run: |
if [ -n "$(git tag --points-at HEAD)" ]; then
echo "released=true" >> "$GITHUB_OUTPUT"
else
echo "released=false" >> "$GITHUB_OUTPUT"
fi
- name: Remove the deploy key
if: always()
run: rm -rf "$RUNNER_TEMP/release-ssh"

# The site's footer links to the release a build came from only when a release tag is on the commit it is built from. A deploy of the commit that triggers a release runs before the tag exists and shows the commit, so a release starts the deploy again, from the tagged commit. A commit that releases nothing is deployed by the Pages workflow's own push trigger and needs no second deploy.
redeploy-site:
name: Redeploy site
needs: [determine-runner, release]
if: needs.release.outputs.released == 'true'
runs-on: ${{ fromJson(needs.determine-runner.outputs.selected-runner) }}
timeout-minutes: 5
permissions:
actions: write # to start the Pages workflow
steps:
- name: Start the Pages workflow on main
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: gh workflow run pages.yml --ref main
4 changes: 3 additions & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Pages

# Deploys the explorer in site/ to GitHub Pages as the repository's project site, https://exadev.github.io/combinatrix/. The repository's Pages source is "GitHub Actions". The site imports the library from src/, so a change to either rebuilds it; dependency changes arrive through package.json.
# Deploys the explorer in site/ to GitHub Pages as the repository's project site, https://exadev.github.io/combinatrix/. The repository's Pages source is "GitHub Actions". The site imports the library from src/, so a change to either rebuilds it; dependency changes arrive through package.json. A release starts it again from the tagged commit (the redeploy-site job in ci.yml), since the release commit itself is not built.

on:
push:
Expand All @@ -27,6 +27,8 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The footer names the release or commit the site is built from, and the version a commit will release as is read from the history since the last release tag, so the build needs every commit and tag.
fetch-depth: 0
persist-credentials: false
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Constraints are evaluated three-valued on partial assignments, answering valid,

## Explorer

An [explorer](https://exadev.github.io/combinatrix/) runs the library in the browser: build a model with a form (dimensions and their values, and rules as nested conditions, with a control for every kind of node in trilean's schema) or edit it as JSON in the Advanced view, where each is the same model as the other, then count its combinations, list them up to a limit, find dead values, explain why a combination is excluded, build a covering array and check it, and move between a combination and its index. The engine runs in a Web Worker, so the page stays responsive and Cancel stops a long operation. Copy link puts the model, compressed, in the link's fragment, which a browser never sends to a server, so a link is self-contained, and the address bar holds that same link as the model or the selected index changes (a model too large for an address goes by gist instead); `?gist=<id>` loads the first `.json` file of a public gist for a model too large for a link. Models can be saved in the browser and imported or exported as `.json` files. Its source is in `site/` and is not part of the published package.
An [explorer](https://exadev.github.io/combinatrix/) runs the library in the browser: build a model with a form (dimensions and their values, and rules as nested conditions, with a control for every kind of node in trilean's schema) or edit it as JSON in the Advanced view, where each is the same model as the other, then count its combinations, list them up to a limit, find dead values, explain why a combination is excluded, build a covering array and check it, and move between a combination and its index. The engine runs in a Web Worker, so the page stays responsive and Cancel stops a long operation. Copy link puts the model, compressed, in the link's fragment, which a browser never sends to a server, so a link is self-contained, and the address bar holds that same link as the model or the selected index changes (a model too large for an address goes by gist instead); `?gist=<id>` loads the first `.json` file of a public gist for a model too large for a link. Models can be saved in the browser and imported or exported as `.json` files. Its source is in `site/` and is not part of the published package. The explorer is an installable app that works offline once it has loaded: it is cached by a service worker, which offers a newer version in a prompt instead of replacing the running one, and a footer link names the release, or the commit when the build is not a release, that the page was built from.

## Examples

Expand Down
Loading
Loading