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
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: ci

# Runs the test suite on every pull request, and on main after a merge.
on:
pull_request:
push:
branches: [main]

permissions: {}

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test (node ${{ matrix.node }})
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
node: [18, 20, 22]

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test
32 changes: 32 additions & 0 deletions .github/workflows/pr-notify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: pr-notify

# Posts to Slack when a PR lands on main. Opening a PR notifies nobody —
# this channel reports what actually merged.
on:
pull_request:
branches: [main]
types: [closed]

permissions: {}

jobs:
notify:
name: Notify Slack
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.merged }}

env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}

steps:
- name: Notify Slack (merged to main)
if: ${{ env.SLACK_WEBHOOK_URL != '' }}
run: |
TEXT=$(jq -n \
--arg t "$PR_TITLE" --arg u "$PR_URL" --arg n "$PR_NUMBER" --arg a "$PR_AUTHOR" \
'{text: ("*plain-forge*: <" + $u + "|#" + $n + " " + $t + "> merged to `main` :white_check_mark: — by *" + $a + "*.")}')
curl -s -X POST "$SLACK_WEBHOOK_URL" -H 'Content-type: application/json' --data "$TEXT"
126 changes: 126 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: publish

# Manual release. Bump the version in package.json first, merge it, then run
# this workflow from the Actions tab against main.
#
# Auth is OIDC (npm trusted publishing) — no NPM_TOKEN secret. The trusted
# publisher must be configured once on npmjs.com for this repo and this exact
# workflow filename (publish.yml).
on:
workflow_dispatch:

# Least privilege by default: jobs get no GITHUB_TOKEN scopes unless they
# opt in below.
permissions: {}

concurrency:
group: publish
cancel-in-progress: false

jobs:
test:
name: Test
runs-on: ubuntu-latest
permissions:
contents: read

env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test

- name: Notify Slack (tests failed)
if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }}
run: |
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
--data "{\"text\":\"*plain-forge*: release halted — tests FAILED :x:. Nothing was published to npm. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}"

publish:
name: Publish to npm
needs: test
runs-on: ubuntu-latest
permissions:
contents: write # push the version tag, create the release
id-token: write # OIDC token for trusted publishing

env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

steps:
- name: Refuse to publish from anywhere but main
if: github.ref != 'refs/heads/main'
run: |
echo "::error::releases are cut from main, not ${{ github.ref_name }}"
exit 1

- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22 # trusted publishing needs >= 22.14
cache: npm

- name: Use an npm that supports trusted publishing
run: |
npm install -g npm@latest
npm --version

- run: npm ci

- name: Read version
id: pkg
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"

- name: Refuse if this version is already on npm
run: |
if npm view "plain-forge@${{ steps.pkg.outputs.version }}" version >/dev/null 2>&1; then
echo "::error::plain-forge@${{ steps.pkg.outputs.version }} is already published — bump the version in package.json first"
exit 1
fi

- name: Notify Slack (start)
if: ${{ env.SLACK_WEBHOOK_URL != '' }}
run: |
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
--data "{\"text\":\"*plain-forge*: release of \`v${{ steps.pkg.outputs.version }}\` started by *${{ github.actor }}* — publishing to npm...\"}"

# No NODE_AUTH_TOKEN: npm exchanges the GitHub OIDC token for a
# short-lived npm token. Provenance is attached automatically.
- name: Publish
run: npm publish --access public

- name: Tag the release
env:
VERSION: ${{ steps.pkg.outputs.version }}
run: |
git tag "v$VERSION"
git push origin "v$VERSION"

- name: Create the GitHub release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.pkg.outputs.version }}
run: gh release create "v$VERSION" --title "v$VERSION" --generate-notes

- name: Notify Slack (success)
if: ${{ success() && env.SLACK_WEBHOOK_URL != '' }}
run: |
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
--data "{\"text\":\"*plain-forge*: \`v${{ steps.pkg.outputs.version }}\` published to npm :white_check_mark:. Install with \`npx plain-forge@latest update\`.\"}"

- name: Notify Slack (failure)
if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }}
run: |
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
--data "{\"text\":\"*plain-forge*: publishing \`v${{ steps.pkg.outputs.version }}\` to npm FAILED :x:. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}"
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ So a change here is almost always one of: (a) editing the installer CLI, or (b)
## Commands

```bash
npm test # full suite: node --test "test/**/*.test.mjs"
node --test --test-name-pattern="<regex>" "test/**/*.test.mjs" # run a single test by name
npm test # full suite: node --test test/*.test.mjs
node --test --test-name-pattern="<regex>" test/*.test.mjs # run a single test by name
node --test test/cli.test.mjs # run a single test file
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"scripts": {
"build": "tsx bin/forge-build.ts",
"clean": "tsx bin/forge-build.ts --clean",
"test": "node --test \"test/**/*.test.mjs\""
"test": "node --test test/*.test.mjs"
},
"devDependencies": {
"@types/node": "^22.10.0",
Expand Down
Loading