Skip to content
144 changes: 144 additions & 0 deletions .github/workflows/publish-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
name: Publish Canary

on:
push:
branches: [master]

jobs:
publish:
name: Publish canary to npm
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # required for OIDC Trusted Publisher

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: latest

- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org

# npm must be upgraded for OIDC Trusted Publisher to work
- name: Upgrade npm
run: npm install -g npm@latest

# Build everything from root — this builds dexie + all addons/libs
- name: Install dependencies
run: pnpm install

- name: Build all packages
run: pnpm run build

# Publish each package if its version is not already on npm
- name: Publish dexie
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Improve npm show error handling to distinguish "not found" from other failures.

The current pattern 2>/dev/null || echo "" treats all errors (network issues, auth problems, registry outages) the same as "version not found". This could cause confusing error messages when pnpm publish then fails for an already-published version.

🔧 Suggested fix using exit code inspection
-          PUBLISHED=$(npm show dexie@$VERSION version 2>/dev/null || echo "")
-          if [ -z "$PUBLISHED" ]; then
+          if npm show dexie@$VERSION version >/dev/null 2>&1; then
+            echo "dexie@$VERSION already published, skipping."
+          else
             echo "Publishing dexie@$VERSION as canary..."
             pnpm publish --tag canary --provenance --no-git-checks
-          else
-            echo "dexie@$VERSION already published, skipping."
           fi

Note: This same pattern applies to all other publish steps.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-canary.yml around lines 74 - 75, The current
PUBLISHED check hides all npm errors by redirecting stderr to /dev/null, so
distinguish a genuine "version not found" from other failures: run the npm show
(npm view) command and capture stderr into a temporary variable, set PUBLISHED
only when the command succeeds (stdout contains a version), but if it fails
inspect the captured stderr for a 404/“not found” indicator to treat as
not-published and otherwise fail-fast (export an error/exit with failure) so
network/auth/registry errors aren’t masked; apply the same pattern wherever
PUBLISHED is computed.

echo "Publishing dexie@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie@$VERSION already published, skipping."
fi

- name: Publish dexie-cloud-common
working-directory: libs/dexie-cloud-common
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie-cloud-common@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing dexie-cloud-common@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie-cloud-common@$VERSION already published, skipping."
fi

- name: Publish dexie-react-hooks
working-directory: libs/dexie-react-hooks
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie-react-hooks@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing dexie-react-hooks@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie-react-hooks@$VERSION already published, skipping."
fi

- name: Publish dexie-svelte-query
working-directory: libs/dexie-svelte-query
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie-svelte-query@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing dexie-svelte-query@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie-svelte-query@$VERSION already published, skipping."
fi

- name: Publish dexie-export-import
working-directory: addons/dexie-export-import
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie-export-import@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing dexie-export-import@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie-export-import@$VERSION already published, skipping."
fi

- name: Publish dexie-observable
working-directory: addons/Dexie.Observable
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie-observable@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing dexie-observable@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie-observable@$VERSION already published, skipping."
fi

- name: Publish dexie-syncable
working-directory: addons/Dexie.Syncable
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie-syncable@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing dexie-syncable@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie-syncable@$VERSION already published, skipping."
fi

- name: Publish y-dexie
working-directory: addons/y-dexie
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show y-dexie@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing y-dexie@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "y-dexie@$VERSION already published, skipping."
fi

- name: Publish dexie-cloud-addon
working-directory: addons/dexie-cloud
run: |
VERSION=$(node -p "require('./package.json').version")
PUBLISHED=$(npm show dexie-cloud-addon@$VERSION version 2>/dev/null || echo "")
if [ -z "$PUBLISHED" ]; then
echo "Publishing dexie-cloud-addon@$VERSION as canary..."
pnpm publish --tag canary --provenance --no-git-checks
else
echo "dexie-cloud-addon@$VERSION already published, skipping."
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Can npm publish the same package version more than once, and if not, what is the supported way to move or update a dist-tag like canary for an already-published version?

💡 Result:

No, npm cannot publish the same package version more than once. The official npm documentation states: "The publish will fail if the package name and version combination already exists in the specified registry. Once a package is published with a given name and version, that specific name and version combination can never be used again, even if it is removed with npm unpublish." The supported way to update or move a dist-tag like canary for an already-published version is to use the npm dist-tag command. Specifically: - To add or move the tag to a specific version: npm dist-tag add @ canary - To remove a tag: npm dist-tag rm canary - To list tags: npm dist-tag ls This allows pointing dist-tags to any existing published version without republishing the version itself.

Citations:


Update the publish logic to account for npm's version immutability.

The skip guard based on exact version will prevent repeated canaries on each master push. Once x.y.z exists on npm, the publish fails (npm forbids republishing the same version). To publish canaries on every push:

  1. Bump to a unique prerelease version per run (e.g., x.y.z-canary.${GITHUB_RUN_NUMBER}) in package.json before publishing, OR
  2. Use npm dist-tag add <pkg>@<version> canary to move the tag to an existing version without republishing.

Option 2 is the standard pattern for dist-tag workflows: publish once per version, then use dist-tag commands to update which version the canary tag points to.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-canary.yml around lines 39 - 144, The current
publish steps (e.g., the jobs named "Publish dexie", "Publish
dexie-cloud-common", "Publish dexie-react-hooks", etc.) check if the exact
version exists and skip, which breaks repeated canary publishing; change each
publish step to either 1) update package.json to a unique prerelease version
(e.g., append -canary.${GITHUB_RUN_NUMBER} to VERSION before publishing) so pnpm
publish always uploads a new version, or preferably 2) keep publishing once and
replace the skip logic with an npm dist-tag command (npm dist-tag add
<package>@$VERSION canary) to move the canary tag to the chosen VERSION when it
already exists; implement this in each publish block (identify by the step names
above) so canary behavior is deterministic.

Loading