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
32 changes: 24 additions & 8 deletions .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
name: CI + Auto Tag
name: CI + Auto Tag & Publish

on:
push:
branches: [main]

jobs:
ci-and-tag:
name: Test, Build & Tag
name: Test, Build, Tag & Publish
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
permissions:
contents: write
id-token: write # npm provenance
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -34,20 +35,35 @@ jobs:
- name: Build
run: npm run build

# Version source of truth is package.json, NOT the latest git tag.
# The old logic read the newest v* tag (v0.0.x) and bumped from there,
# so tags diverged from the package version forever (issue #26).
- name: Tag HEAD with next patch version
id: version
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Determine next version from latest git tag (fallback to package.json)
LATEST=$(git tag -l 'v*' --sort=-version:refname | head -1)
if [ -z "$LATEST" ]; then
LATEST="v$(node -p "require('./package.json').version")"
fi
CURRENT="${LATEST#v}"
CURRENT="$(node -p "require('./package.json').version")"
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"

git tag "v$NEW_VERSION"
git push origin "v$NEW_VERSION"
echo "tagged=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "Tagged HEAD as v$NEW_VERSION"

# Publish in the SAME run. A tag pushed with GITHUB_TOKEN cannot trigger
# publish.yml (GitHub blocks workflow re-entry from token-created events),
# so a separate tag-triggered workflow would never fire (issue #26).
- name: Publish to npm
if: steps.version.outputs.tagged != ''
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "::warning::NPM_TOKEN secret not configured — tag created but npm publish skipped"
exit 0
fi
npm version "${{ steps.version.outputs.tagged }}" --no-git-tag-version
npm publish --provenance --access public
7 changes: 6 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ jobs:

- name: Set version from tag
run: |
# Use tag version if triggered by tag push, otherwise use package.json as-is
# Use tag version if triggered by tag push (keep the two in sync),
# otherwise use package.json as-is for manual dispatch.
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
PKG_VERSION="$(node -p "require('./package.json').version")"
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "warning: tag v$TAG_VERSION != package.json $PKG_VERSION; publishing as $TAG_VERSION" >&2
fi
npm version "$TAG_VERSION" --no-git-tag-version
fi

Expand Down
123 changes: 123 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,20 @@
"devDependencies": {
"@types/node": "^26.1.2",
"@vitest/coverage-v8": "^4.1.6",
"eslint": "^10.8.0",
"globals": "^17.6.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.3",
"vitest": "^4.1.6",
"eslint": "^10.8.0"
"vitest": "^4.1.6"
},
"publishConfig": {
"access": "public",
"provenance": true
},
"engines": {
"node": ">=20.19.0"
},
"dependencies": {
"fast-xml-parser": "^5.10.1"
}
}
5 changes: 5 additions & 0 deletions src/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ describe('parseArgs', () => {
it('throws when --fail-on is given without a value', () => {
expect(() => parseArgs(['old.json', 'new.json', '--fail-on'])).toThrow(/Invalid --fail-on/);
});

it('parses --runtime-only as true (default false)', () => {
expect(parseArgs(['old.json', 'new.json']).runtimeOnly).toBe(false);
expect(parseArgs(['old.json', 'new.json', '--runtime-only']).runtimeOnly).toBe(true);
});
});

describe('gateFailures', () => {
Expand Down
Loading