Skip to content

Commit a4422a7

Browse files
authored
🤖 Fix npm publish workflow to skip existing versions (#365)
## Problem The npm publish workflow on `main` has been failing with: ``` npm error 404 '@coder/[email protected]' is not in this registry. ``` This error is misleading. The actual issue is: - The workflow runs on **every push to main** - It tries to publish version `0.3.0` every time - npm doesn't allow republishing the same version - The 404 error is npm's confusing way of saying "this version already exists" ## Solution Use `git describe` to generate **unique pre-release versions** for each commit to main. This allows every push to be publishable without version conflicts. ### Version Format **On main branch (pre-releases):** ``` 0.3.0-next.14.g6c0e10d └─┬─┘ └─┬─┘ └┬┘ └──┬──┘ │ │ │ └─ short git hash │ │ └─────── commits since last tag │ └──────────── pre-release identifier └────────────────── base version from package.json ``` Published to npm with `next` tag: `npm install @coder/cmux@next` **On version tags (stable releases):** ``` 0.3.0 ``` Published to npm with `latest` tag: `npm install @coder/cmux` ## How It Works 1. **Generate unique version**: Calculate commits since last tag + git hash 2. **Update package.json**: Modify version before building 3. **Build**: Run `make build` with the updated version 4. **Check if version exists**: See if this exact version is already on npm 5. **Publish or promote**: - New version → Publish normally - Existing version + tag push → Update dist-tag to `latest` (promotion) - Existing version + main push → Skip (already published) ### Handling Version Promotion If a tagged release (e.g., `v0.3.0`) is pushed but `0.3.0` already exists on npm (maybe published as a pre-release or from a previous run), the workflow will **update the dist-tag** to `latest` instead of failing. This allows promoting versions without re-uploading the tarball. Every commit to main now gets a unique, installable pre-release version! 🎉 _Generated with `cmux`_
1 parent 3a4a09a commit a4422a7

File tree

1 file changed

+65
-8
lines changed

1 file changed

+65
-8
lines changed

.github/workflows/publish-npm.yml

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,79 @@ jobs:
2929
with:
3030
registry-url: 'https://registry.npmjs.org'
3131

32+
- name: Generate unique version from git
33+
id: version
34+
run: |
35+
# Get base version from package.json
36+
BASE_VERSION=$(node -p "require('./package.json').version")
37+
38+
# Generate git describe version
39+
GIT_DESCRIBE=$(git describe --tags --always --dirty 2>/dev/null || echo "unknown")
40+
41+
if [[ $GITHUB_REF == refs/tags/* ]]; then
42+
# For tags, use the base version as-is (stable release)
43+
NPM_VERSION="${BASE_VERSION}"
44+
NPM_TAG="latest"
45+
echo "Publishing stable release: ${NPM_VERSION}"
46+
else
47+
# For main branch, create a pre-release version using git describe
48+
# Format: 0.3.0-next.5.g1a2b3c4 (base-next.commits.hash)
49+
GIT_COMMIT=$(git rev-parse --short HEAD)
50+
COMMITS_SINCE_TAG=$(git rev-list --count HEAD ^$(git describe --tags --abbrev=0 2>/dev/null || echo HEAD) 2>/dev/null || echo "0")
51+
NPM_VERSION="${BASE_VERSION}-next.${COMMITS_SINCE_TAG}.g${GIT_COMMIT}"
52+
NPM_TAG="next"
53+
echo "Publishing pre-release: ${NPM_VERSION}"
54+
fi
55+
56+
echo "version=${NPM_VERSION}" >> $GITHUB_OUTPUT
57+
echo "tag=${NPM_TAG}" >> $GITHUB_OUTPUT
58+
59+
# Update package.json with the new version
60+
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); pkg.version = '${NPM_VERSION}'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');"
61+
62+
echo "Updated package.json to version ${NPM_VERSION}"
63+
3264
- name: Generate version file
3365
run: ./scripts/generate-version.sh
3466

3567
- name: Build application
3668
run: make build
3769

38-
- name: Determine NPM tag
39-
id: npm-tag
70+
- name: Check if version exists
71+
id: check-exists
4072
run: |
41-
if [[ $GITHUB_REF == refs/tags/* ]]; then
42-
echo "tag=latest" >> $GITHUB_OUTPUT
43-
echo "Publishing as 'latest' tag (stable release)"
73+
PACKAGE_NAME=$(node -p "require('./package.json').name")
74+
VERSION="${{ steps.version.outputs.version }}"
75+
76+
if npm view "${PACKAGE_NAME}@${VERSION}" version &>/dev/null; then
77+
echo "exists=true" >> $GITHUB_OUTPUT
78+
echo "Version ${VERSION} already exists on npm"
4479
else
45-
echo "tag=next" >> $GITHUB_OUTPUT
46-
echo "Publishing as 'next' tag (pre-release from main)"
80+
echo "exists=false" >> $GITHUB_OUTPUT
81+
echo "Version ${VERSION} does not exist, will publish"
4782
fi
83+
env:
84+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
4885

4986
- name: Publish to NPM
50-
run: npm publish --tag ${{ steps.npm-tag.outputs.tag }}
87+
if: steps.check-exists.outputs.exists == 'false'
88+
run: npm publish --tag ${{ steps.version.outputs.tag }}
89+
env:
90+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
91+
92+
- name: Update dist-tag (version already exists)
93+
if: steps.check-exists.outputs.exists == 'true' && github.ref_type == 'tag'
94+
run: |
95+
PACKAGE_NAME=$(node -p "require('./package.json').name")
96+
VERSION="${{ steps.version.outputs.version }}"
97+
TAG="${{ steps.version.outputs.tag }}"
98+
99+
echo "Version ${VERSION} already published, updating dist-tag to ${TAG}"
100+
npm dist-tag add "${PACKAGE_NAME}@${VERSION}" "${TAG}"
101+
env:
102+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
103+
104+
- name: Skip (pre-release already exists)
105+
if: steps.check-exists.outputs.exists == 'true' && github.ref_type != 'tag'
106+
run: |
107+
echo "⏭️ Pre-release version already exists, skipping"

0 commit comments

Comments
 (0)