diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 65f71bf..94d9f64 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,22 +1,28 @@ name: Publish -# A tag push publishes. A manual run does everything except publish, so the -# release can be rehearsed from any branch. +# A push to main publishes package.json's version to registry.npmjs.org when +# that version is not on npm yet; bumping the version is the release intent. +# A manual run does everything except publish, so the release can be +# rehearsed from any branch. on: push: - tags: ['v*'] + branches: [main] workflow_dispatch: permissions: contents: read +concurrency: + group: npm-publish + cancel-in-progress: false + jobs: publish: runs-on: ubuntu-latest - permissions: - contents: read - # Required for npm provenance; without it `npm publish --provenance` fails. - id-token: write + # The environment isolates NPM_TOKEN from every other workflow in this repo. + environment: + name: npm-publish + url: https://www.npmjs.com/package/@evomap/dsh-evolver steps: # Pinned to a commit: a tag can be moved, and this job holds the npm token. - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 @@ -24,35 +30,51 @@ jobs: - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 - registry-url: https://registry.npmjs.org - run: npm ci --ignore-scripts --no-audit --no-fund - run: npm test - - name: Check the tag matches the manifest version - if: startsWith(github.ref, 'refs/tags/v') - run: | - tag="${GITHUB_REF_NAME#v}" - manifest="$(node -p "require('./package.json').version")" - if [ "$tag" != "$manifest" ]; then - echo "tag v$tag does not match package.json $manifest" >&2 - exit 1 - fi - - - name: Check this version is not already on npm + - name: Decide whether this version still needs publishing + id: release run: | name="$(node -p "require('./package.json').name")" version="$(node -p "require('./package.json').version")" - if npm view "$name@$version" version >/dev/null 2>&1; then - echo "$name@$version is already published; bump the version" >&2 - exit 1 + if npm view "$name@$version" version --registry https://registry.npmjs.org >/dev/null 2>&1; then + echo "$name@$version is already on npm; nothing to publish" + echo "publish=false" >> "$GITHUB_OUTPUT" + else + echo "publish=true" >> "$GITHUB_OUTPUT" fi + echo "version=$version" >> "$GITHUB_OUTPUT" - run: npm pack --dry-run - - name: Publish - if: startsWith(github.ref, 'refs/tags/v') - run: npm publish --provenance --access public + # Provenance is off because npm rejects provenance from a private source repository. + - name: Publish to npm + if: github.event_name == 'push' && steps.release.outputs.publish == 'true' + run: | + : "${NPM_TOKEN:?NPM_TOKEN is not set on the npm-publish environment}" + npm_config="$(mktemp)" + trap 'rm -f "$npm_config"' EXIT + chmod 600 "$npm_config" + printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$npm_config" + NPM_CONFIG_USERCONFIG="$npm_config" NPM_CONFIG_PROVENANCE=false \ + npm publish --access public --registry https://registry.npmjs.org + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Verify the package resolves from npm + if: github.event_name == 'push' && steps.release.outputs.publish == 'true' + run: | + name="$(node -p "require('./package.json').name")" + for attempt in 1 2 3 4 5 6; do + if npm view "$name@$VERSION" version --registry https://registry.npmjs.org; then + exit 0 + fi + sleep 10 + done + echo "$name@$VERSION did not appear on npm after publishing" >&2 + exit 1 env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + VERSION: ${{ steps.release.outputs.version }} diff --git a/README.md b/README.md index b976627..7cc9b6b 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Powered by the [Genome Evolution Protocol](https://evomap.ai) and [`@evomap/evolver`](https://github.com/EvoMap/evolver). > **Status:** pre-release `0.1.0`. Local memory, commands, Skill, and the current -> Proxy-backed asset loop are implemented. The npm package is published only when the -> first matching release tag is created. +> Proxy-backed asset loop are implemented. The npm package is published when a +> `package.json` version that is not yet on npm lands on `main`. ## What it does @@ -174,7 +174,8 @@ npm pack --dry-run ``` CI validates Node 22 and 24 plus the declared DSH compatibility lines. The release workflow -publishes only a tag whose version matches `package.json`; this task does not create that tag. +publishes to npm on every push to `main` whose `package.json` version is not on npm yet, using +the `NPM_TOKEN` secret of the `npm-publish` environment; a manual run rehearses without publishing. ## License