Skip to content
Open
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
5 changes: 5 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"permissions": {
"allow": ["Bash(npx vitest:*)", "Bash(npx tsc:*)", "Bash(npx oxfmt:*)", "Bash(npx oxlint:*)", "Bash(npm test:*)", "Bash(npm run:*)"]
}
}
62 changes: 62 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/sh
# Blocks a commit when any staged file is unformatted or fails lint.
#
# oxfmt is the formatter (.oxfmtrc.json); oxlint is the linter (.oxlintrc.json). Wired up by
# the "prepare" script in package.json (git config core.hooksPath .githooks) on npm install.
# Emergency bypass: git commit --no-verify
#
# This file is shared verbatim with jetstreamapp/sf-formula-parser - keep the two copies in
# sync when changing it.

OXFMT="./node_modules/.bin/oxfmt"
OXLINT="./node_modules/.bin/oxlint"

for bin in "$OXFMT" "$OXLINT"; do
if [ ! -x "$bin" ]; then
echo "pre-commit: $bin not found - run 'npm install' first." >&2
exit 1
fi
done

# Added, copied, modified and renamed paths only - deleted paths are not there to check.
staged=$(git diff --cached --name-only --diff-filter=ACMR)

if [ -z "$staged" ]; then
exit 0
fi

# Both tools skip what they do not handle, so the whole staged list goes to each rather than
# being filtered by extension here, which would drift as the tools gain file types.
# --no-error-on-unmatched-pattern: a staged list with nothing for that tool is not an error.
# The list is newline delimited, so convert to NUL to keep paths containing spaces intact.
check() {
printf '%s\n' "$staged" | tr '\n' '\0' | xargs -0 "$@" --no-error-on-unmatched-pattern
}

# Both run even when the first fails, so one commit attempt reports everything to fix.
status=0

if ! check "$OXFMT" --check; then
cat >&2 <<'MSG'

Commit blocked: the staged files listed above are not formatted.

npm run format # format the repo
git add -u # re-stage the result
MSG
status=1
fi

if ! check "$OXLINT"; then
cat >&2 <<'MSG'

Commit blocked: the staged files listed above have lint errors.

npm run lint # review the full report
npm run lint:fix # apply the fixes oxlint can make automatically
git add -u # re-stage the result
MSG
status=1
fi

exit $status
69 changes: 69 additions & 0 deletions .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Changelog

# A pull request that changes published code has to record it in CHANGELOG.md, because the
# [Unreleased] section is what decides the next version (scripts/derive-increment.mjs). Without
# this check the release is the first place anyone notices a missing or unclassifiable entry,
# and by then the version has already been derived. Label a pull request `skip-changelog` to
# opt out.
#
# This file is shared verbatim with jetstreamapp/sf-formula-parser - keep the two copies in
# sync when changing it.

on:
pull_request:
# labeled/unlabeled so adding `skip-changelog` re-runs the check rather than leaving a
# stale failure behind.
types: [opened, synchronize, reopened, labeled, unlabeled]

permissions:
contents: read

jobs:
changelog:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The whole history, so the pull request's base commit is available to diff against.
fetch-depth: 0

- name: Require a changelog entry for published code
env:
BASE: ${{ github.event.pull_request.base.sha }}
LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
run: |
# The label is checked here rather than in a job-level `if` so the check always
# reports a result. A skipped job is awkward to require in branch protection.
if printf '%s' "$LABELS" | grep -q '"skip-changelog"'; then
echo "skip-changelog label present - not requiring a changelog entry."
exit 0
fi

# Three dots: everything on this branch since it diverged from the base.
changed=$(git diff --name-only "$BASE"...HEAD)

# Only published code counts. Tooling and docs changes do not force an entry;
# cli/ and bin/ do not exist in every repo sharing this file, which is harmless.
if ! printf '%s\n' "$changed" | grep -qE '^(src|cli|bin)/'; then
echo "No published code changed - no changelog entry required."
exit 0
fi

# -x so docs/CHANGELOG.md does not satisfy the root one.
if ! printf '%s\n' "$changed" | grep -qx 'CHANGELOG.md'; then
echo "This pull request changes published code but does not touch CHANGELOG.md." >&2
echo "" >&2
echo " Add an entry under '## [Unreleased]' using one of the seven headings:" >&2
echo " Breaking Changes, Added, Deprecated, Changed, Removed, Fixed, Security." >&2
echo "" >&2
echo " That section decides the next version, so an unrecorded change ships" >&2
echo " unversioned. If this genuinely needs no entry, add the 'skip-changelog' label." >&2
exit 1
fi

# Touching the file is not enough - prove the section classifies to a bump, which is
# exactly what the release will do. Uses only Node builtins, so no install needed.
echo "CHANGELOG.md was updated. Checking that [Unreleased] derives a version:"
node scripts/derive-increment.mjs --explain
69 changes: 57 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,69 @@
name: Build and test
on: [push]
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
build:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
name: Checkout [main]
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Use Node.js 24
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
fetch-depth: 0
node-version: 24
cache: npm

- run: npm ci
# The pre-commit hook runs these too, but it can be bypassed with --no-verify
- run: npm run format:check
- run: npm run lint
- run: npm run typecheck
# build before test: the CLI suite in soql-parser-js runs the built dist/cli,
# and this workflow is shared verbatim between both repositories.
- run: npm run build
- run: npm test

- name: Install dependencies
run: npm install
docs-build:
runs-on: ubuntu-latest
# Only run when docs-related files change
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3.0.4
id: changes
with:
filters: |
docs:
- 'docs/**'

- name: Use Node.js 24
if: steps.changes.outputs.docs == 'true'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: npm
cache-dependency-path: |
package-lock.json
docs/package-lock.json

- name: Build application
run: npm run build
- name: Build library
if: steps.changes.outputs.docs == 'true'
run: |
npm ci
npm run build

- name: Run Tests
run: npm run test
- name: Build docs
if: steps.changes.outputs.docs == 'true'
run: |
npm ci
npm run build
working-directory: docs
95 changes: 69 additions & 26 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,51 +1,94 @@
name: Create Release
name: Release

# Actions are pinned to a commit SHA with the version in a trailing comment: a tag is mutable
# and can be repointed at new code, a SHA cannot.
#
# This file is shared verbatim with jetstreamapp/sf-formula-parser - keep the two copies in
# sync when changing it.

on:
workflow_dispatch:
inputs:
increment:
type: choice
description: Increment
required: true
options:
- patch
- minor
- major
version:
# Normally dispatched by `npm run release`, which derives this from the [Unreleased]
# section of CHANGELOG.md. `auto` derives it here instead, and a bump keyword or an
# explicit version both work when running the workflow by hand from the GitHub UI.
description: '`auto`, a bump keyword (major, minor, patch) or an explicit version (1.2.3)'
required: false
default: auto
type: string

permissions:
id-token: write
contents: write
id-token: write

jobs:
release:
runs-on: ubuntu-latest

steps:
- name: Generate Jetstream Bot token
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout [main]
uses: actions/checkout@v7

- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# Persisted for the release commit, tag and push that release-it makes later.
token: ${{ steps.app-token.outputs.token }}
- name: Init npm cache
uses: actions/setup-node@v7

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
cache: 'npm'
node-version: 24
cache: npm
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false
- name: install dependencies

- name: Resolve version
id: resolve
run: |
if [ -z "$INPUT" ] || [ "$INPUT" = "auto" ]; then
VALUE=$(node scripts/derive-increment.mjs)
echo "Derived '$VALUE' from the [Unreleased] section of CHANGELOG.md:"
node scripts/derive-increment.mjs --explain
else
VALUE="$INPUT"
echo "Using the version supplied to the workflow: $VALUE"
fi
echo "value=$VALUE" >> "$GITHUB_OUTPUT"
env:
INPUT: ${{ inputs.version }}

- name: Install dependencies
run: npm ci
- name: git config

- name: Get GitHub App user ID
id: app-user
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
run: |
git config --global user.name "Release Workflow"
git config --global user.email "support@getjetstream.app"
- name: Run Release
run: npm run release -- "$INCREMENT" --ci
USER_ID=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
echo "user-id=${USER_ID}" >> "$GITHUB_OUTPUT"

- name: Configure git
env:
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
USER_ID: ${{ steps.app-user.outputs.user-id }}
run: |
# `<id>+<slug>[bot]@users.noreply.github.com` is the address GitHub matches back to the
# App account, so release commits are attributed to the bot with its avatar rather than
# to an unrecognized author. Both halves come from the token, so this identity follows
# whichever App the credential belongs to instead of being hardcoded per repository.
git config user.name "${APP_SLUG}[bot]"
git config user.email "${USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com"

- name: Release
run: npm run release:ci -- "$VERSION" --ci
env:
INCREMENT: ${{ github.event.inputs.increment }}
VERSION: ${{ steps.resolve.outputs.value }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
23 changes: 0 additions & 23 deletions .npmignore

This file was deleted.

4 changes: 3 additions & 1 deletion .prettierrc → .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 140,
"semi": true,
"tabWidth": 2,
Expand All @@ -7,5 +8,6 @@
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid",
"insertPragma": false
"sortPackageJson": false,
"ignorePatterns": []
}
Loading
Loading