Skip to content

feat(multipublish): add jsr auth token support and token environment key#224

Merged
stephansama merged 7 commits into
mainfrom
stephansama/ste-104-fix-jsr-implementation-for-packages-monorepo
Feb 24, 2026
Merged

feat(multipublish): add jsr auth token support and token environment key#224
stephansama merged 7 commits into
mainfrom
stephansama/ste-104-fix-jsr-implementation-for-packages-monorepo

Conversation

@stephansama
Copy link
Copy Markdown
Owner

Checklist

  • Latest changes from main have been merged
  • Conflicts have been resolved
  • The branch is pointing to main
  • Eslint hasn't reported any issues.
  • All unit tests pass

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 24, 2026

🦋 Changeset detected

Latest commit: c35993e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@stephansama/multipublish Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
packages Ready Ready Preview, Comment Feb 24, 2026 0:57am

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @stephansama, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly extends the multipublish utility by integrating support for publishing packages to the JSR (JavaScript Registry) platform. It introduces a new configuration option for JSR, enabling experimental features like catalog updates and JSR generation, and crucially, allows for secure authentication during the publishing process by reading an authorization token from a specified environment variable. This enhancement broadens the utility's capabilities, allowing developers to seamlessly publish their packages to both npm and JSR.

Highlights

  • JSR Platform Support: Added configuration and publishing logic for the JSR (JavaScript Registry) platform, enabling multipublish to deploy packages to JSR.
  • JSR Authentication Token: Implemented support for authenticating JSR publishes using an authorization token retrieved from a configurable environment variable.
  • Configurable Token Environment Key: Introduced a tokenEnvironmentKey option within the JSR platform configuration, allowing users to specify which environment variable holds their JSR auth token.
  • Improved Scope Validation Handling: Modified the package scope validation for JSR to log a warning and abort the process if the scope does not start with an '@' symbol, instead of throwing a hard error.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .config/.multipublishrc.json
    • Added JSR platform configuration with experimental options and a default exclude pattern.
  • core/multipublish/src/publish.ts
    • Implemented logic to retrieve a JSR authentication token from an environment variable.
    • Modified the JSR publish command to include the authentication token if available.
    • Changed the error handling for missing '@' symbols in package scopes from throwing an error to logging a warning and aborting.
  • core/multipublish/src/schema.ts
    • Added tokenEnvironmentKey to the jsrPlatformOptionsSchema with a default value of "JSR_AUTH_TOKEN".
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/release.yml
Activity
  • The pull request author, stephansama, has provided a checklist in the PR description.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for publishing to JSR with authentication tokens, enhancing the multipublish tool's capabilities. It includes updates to the configuration schema and the publishing logic to handle JSR-specific options and token retrieval. While the feature implementation is generally sound, I've identified a high-severity security concern related to how the authentication token is passed to the jsr publish command, which could lead to command injection. Additionally, a change in error handling from throwing an error to logging and returning might mask critical failures, which is a medium-severity issue.

isDryRun && "--dry-run",
config.allowSlowTypes && "--allow-slow-types",
isDryRun && "--dry-run",
authToken && `--token ${authToken}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Directly interpolating authToken into the command string can lead to a command injection vulnerability if the token contains shell metacharacters. It is generally safer to pass sensitive information like API tokens via environment variables to the child process. The jsr publish command supports reading the token from the JSR_API_TOKEN environment variable. Consider passing the token via the env option of cp.execSync instead of as a command-line argument.

Comment on lines +126 to +128
return console.error(
"scope does not start with @ symbol. aborting.",
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Changing from throw new Error to return console.error alters the error handling behavior. Throwing an error provides a clearer indication of a critical failure and propagates the error up the call stack, which can be important for robust error management and debugging. Returning after logging an error might lead to silent failures that are harder to detect and diagnose.

throw new Error("scope must start with `@` symbol");

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
core/multipublish/src/publish.ts 80.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 24, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for publishing to the JavaScript Registry (JSR) platform with authentication token configuration through environment variables.
    • Improved error handling for unscoped packages during JSR publishing operations.
  • Tests

    • Added comprehensive test coverage for JSR publishing workflows including authentication scenarios.
  • Chores

    • Updated CI/CD release workflow and configurations to support JSR publishing.

Walkthrough

Adds JSR platform support and token handling: new .config/.multipublishrc.json jsr entry, schema field tokenEnvironmentKey (default "JSR_AUTH_TOKEN"), release workflow secret, publish logic to read/pass token to the jsr CLI, and corresponding tests and changeset.

Changes

Cohort / File(s) Summary
JSR Configuration
​.config/.multipublishrc.json, core/multipublish/src/schema.ts, core/multipublish/src/schema.test.ts, core/multipublish/src/jsr.test.ts
Adds a new "jsr" platform entry in multipublish config (includes defaultExclude: ["!config"]) and introduces tokenEnvironmentKey (default "JSR_AUTH_TOKEN") to the JSR platform options schema; updates tests to include the new field.
Publish Logic & Tests
core/multipublish/src/publish.ts, core/multipublish/src/publish.test.ts
Reads token from environment (key configurable via schema) and injects --token <token> into the JSR publish command when present; tightens arg filtering to strings; changes invalid npm scope handling from throwing to logging + early return; adds tests for token/no-token scenarios and asserts --token presence.
CI / Release
.github/workflows/release.yml, vitest.config.ts, package.json
Adds JSR_AUTH_TOKEN to the release workflow env; adjusts Vitest junit output path and test script; moves packageManager field position (value unchanged).
Workspace & Changeset
pnpm-workspace.yaml, .changeset/clever-rings-cheer.md
Adds two new catalog entries to workspace; adds a changeset noting JSR auth token feature and patch bump.

Sequence Diagram(s)

sequenceDiagram
  participant GH as "GitHub Actions (release workflow)"
  participant ENV as "Secrets / Environment"
  participant MP as "multipublish (publish.ts)"
  participant JSR as "JSR Registry/CLI"

  GH->>ENV: read JSR_AUTH_TOKEN secret
  GH->>MP: run publish job (env includes token)
  MP->>MP: detect platform == "jsr" and read tokenEnvironmentKey
  MP->>MP: build publish command args (include --token <token> if present)
  MP->>JSR: invoke JSR publish command with args
  JSR-->>MP: respond success/failure
  MP-->>GH: return job exit status
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I found a tiny token, snug beneath the log,

I carried it to publish, through the binary fog.
Env sings softly, flags in tidy rows,
I hop, I push, the registry knows — thump, thump — whoa! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description includes all required checklist items but lacks substantive details about what was changed and why. Add context explaining the purpose of JSR auth token support, which files were modified, and the implementation approach to provide reviewers with meaningful context.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding JSR auth token support and token environment key configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stephansama/ste-104-fix-jsr-implementation-for-packages-monorepo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Line 13: Remove the top-level exposure of JSR_AUTH_TOKEN: since the workflow
already sets id-token: write for OIDC, drop JSR_AUTH_TOKEN from the global env
or move it into the specific release job's env; update the env declaration to
remove JSR_AUTH_TOKEN at the workflow level and add it only to the release job
(where publishing occurs) so test and other jobs no longer receive the secret,
keeping behavior for non-OIDC/local runs intact by scoping the secret to the
release job.

In `@core/multipublish/src/publish.ts`:
- Line 89: The npm branch in core/multipublish/src/publish.ts uses an untyped
filter `.filter((x) => x)` which is inconsistent with the JSR path's type guard
`.filter((x): x is string => !!x)`; change the npm-side filter to a proper type
guard (e.g., `.filter((x): x is string => !!x)`) so downstream code that expects
strings has correct typings—locate the npm path where `.filter((x) => x)` is
used and replace it with the typed predicate.
- Around line 126-129: The current change replaces a thrown error with `return
console.error(...)`, which causes the surrounding async function to resolve
instead of reject and silently swallow failures; restore proper error
propagation by replacing `return console.error("scope does not start with @
symbol. aborting.")` with `throw new Error("scope does not start with @ symbol.
aborting.")` (or, if you intentionally want a non-fatal best-effort publish,
explicitly log the message and return a documented no-op result or an explicit
rejected Promise so callers can distinguish behavior); update the code path in
the publish routine where the scope check occurs to either rethrow the Error or
make the silent continuation explicit and documented.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ed6516a and 8d8b474.

📒 Files selected for processing (4)
  • .config/.multipublishrc.json
  • .github/workflows/release.yml
  • core/multipublish/src/publish.ts
  • core/multipublish/src/schema.ts

ZX_VERBOSE: true
DO_NOT_TRACK: 1
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
JSR_AUTH_TOKEN: ${{secrets.JSR_AUTH_TOKEN}}
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

JSR_AUTH_TOKEN is exposed to all jobs and is redundant when OIDC is configured.

Two observations:

  1. id-token: write is already set at the workflow level, which is the permission JSR uses for OIDC-based authentication in GitHub Actions. This means the --token argument will never be needed for this specific workflow's CI runs — JSR authenticates automatically via OIDC, making JSR_AUTH_TOKEN unused in practice here (the env var will be undefined unless the secret is explicitly set, and the code handles that gracefully). The secret is useful for non-OIDC environments (e.g., local dev), but that context doesn't apply to this workflow file.

  2. Setting JSR_AUTH_TOKEN at the top-level env block exposes it to the test job, which has no need for it. Scoping it to the release job (matching where publishing actually occurs) would be more aligned with least-privilege, though this is consistent with the existing pattern for NODE_AUTH_TOKEN and other tokens in this file.

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

In @.github/workflows/release.yml at line 13, Remove the top-level exposure of
JSR_AUTH_TOKEN: since the workflow already sets id-token: write for OIDC, drop
JSR_AUTH_TOKEN from the global env or move it into the specific release job's
env; update the env declaration to remove JSR_AUTH_TOKEN at the workflow level
and add it only to the release job (where publishing occurs) so test and other
jobs no longer receive the secret, keeping behavior for non-OIDC/local runs
intact by scoping the secret to the release job.

authToken && `--token ${authToken}`,
]
.filter((x) => x)
.filter((x): x is string => !!x)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Filter type guard inconsistency with the npm path.

The JSR path uses a proper type guard .filter((x): x is string => !!x), while the npm path at Line 160 still uses the untyped .filter((x) => x). Consider applying the same type-safe filter there for consistency.

♻️ Proposed fix
// Line 160 (npm path)
-					.filter((x) => x)
+					.filter((x): x is string => !!x)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/multipublish/src/publish.ts` at line 89, The npm branch in
core/multipublish/src/publish.ts uses an untyped filter `.filter((x) => x)`
which is inconsistent with the JSR path's type guard `.filter((x): x is string
=> !!x)`; change the npm-side filter to a proper type guard (e.g., `.filter((x):
x is string => !!x)`) so downstream code that expects strings has correct
typings—locate the npm path where `.filter((x) => x)` is used and replace it
with the typed predicate.

Comment on lines +126 to 129
return console.error(
"scope does not start with @ symbol. aborting.",
);
}
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 | 🔴 Critical

return console.error(...) silently swallows the failure — callers receive a resolved Promise<void> instead of a rejection.

console.error returns undefined. So return console.error(...) is equivalent to calling console.error(...) and then return;, which resolves the enclosing async function's promise successfully. The pre-existing throw new Error(...) would have rejected the promise and surfaced the failure to callers. With this change, if a package name has no @ scope, the publish is silently skipped with no observable error propagation — callers have no way to distinguish this from a successful publish.

🐛 Proposed fix
-					return console.error(
-						"scope does not start with @ symbol. aborting.",
-					);
+					console.error("scope does not start with @ symbol. aborting.");
+					throw new Error("scope does not start with @ symbol. aborting.");

Or, if silent continuation is intentional (best-effort multi-package publish), document this explicitly and ensure the caller accounts for no-op returns.

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

In `@core/multipublish/src/publish.ts` around lines 126 - 129, The current change
replaces a thrown error with `return console.error(...)`, which causes the
surrounding async function to resolve instead of reject and silently swallow
failures; restore proper error propagation by replacing `return
console.error("scope does not start with @ symbol. aborting.")` with `throw new
Error("scope does not start with @ symbol. aborting.")` (or, if you
intentionally want a non-fatal best-effort publish, explicitly log the message
and return a documented no-op result or an explicit rejected Promise so callers
can distinguish behavior); update the code path in the publish routine where the
scope check occurs to either rethrow the Error or make the silent continuation
explicit and documented.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/multipublish/src/schema.test.ts (1)

13-39: 🧹 Nitpick | 🔵 Trivial

Consider adding a test for the custom tokenEnvironmentKey override path.

The existing test only exercises the default value path. Adding one case that explicitly provides tokenEnvironmentKey for JSR would confirm the field is overridable and guard against accidental schema constraints being introduced in the future.

✅ Suggested additional test case
  it("should accept platform tuples with options", () => {
    // ... existing test ...
  });

+ it("should accept a custom tokenEnvironmentKey for jsr", () => {
+   const input = [["jsr", { tokenEnvironmentKey: "MY_JSR_TOKEN" }]];
+   const result = platformsSchema.parse(input);
+   expect(result).toEqual([
+     [
+       "jsr",
+       {
+         allowSlowTypes: false,
+         experimentalGenerateJSR: false,
+         experimentalUpdateCatalogs: false,
+         tokenEnvironmentKey: "MY_JSR_TOKEN",
+       },
+     ],
+   ]);
+ });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/multipublish/src/schema.test.ts` around lines 13 - 39, Add a test case
to the "should accept platform tuples with options" spec that includes an
explicit tokenEnvironmentKey override (e.g., for "jsr") to ensure
platformsSchema accepts user-provided tokenEnvironmentKey values; update the
input tuple to include a third option object for "jsr" with tokenEnvironmentKey
set to a custom string and assert the parsed result contains that custom
tokenEnvironmentKey rather than the default in the expected array, referencing
platformsSchema.parse and the existing test block to place the new case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/clever-rings-cheer.md:
- Line 5: Reword the changeset summary to clearly state that the package publish
step for unscoped packages on the NPM publish path logs an error but returns
without throwing (causing CI/workflows to continue as successful); reference
that this silent-failure occurs where the NPM publish handler uses
console.error() and returns (publish.ts NPM publish handler), and explicitly
call out the affected platform as "npm publishing" (or "npm registry publish")
and that an auth token was added to jsr.

---

Outside diff comments:
In `@core/multipublish/src/schema.test.ts`:
- Around line 13-39: Add a test case to the "should accept platform tuples with
options" spec that includes an explicit tokenEnvironmentKey override (e.g., for
"jsr") to ensure platformsSchema accepts user-provided tokenEnvironmentKey
values; update the input tuple to include a third option object for "jsr" with
tokenEnvironmentKey set to a custom string and assert the parsed result contains
that custom tokenEnvironmentKey rather than the default in the expected array,
referencing platformsSchema.parse and the existing test block to place the new
case.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8d8b474 and cf485ac.

📒 Files selected for processing (2)
  • .changeset/clever-rings-cheer.md
  • core/multipublish/src/schema.test.ts

"@stephansama/multipublish": patch
---

Updated jsr to have auth token. fail without error for unscoped packages
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for JSR publish-related code and unscoped package handling
rg -n "unscoped|jsr.*publish|publish.*jsr|tokenEnvironmentKey|JSR_AUTH_TOKEN" --type ts --type js -C 3

Repository: stephansama/packages

Length of output: 4830


🏁 Script executed:

#!/bin/bash
# Read the publish.ts file to see the full JSR publish logic
head -120 core/multipublish/src/publish.ts | tail -60

Repository: stephansama/packages

Length of output: 1711


🏁 Script executed:

#!/bin/bash
# Read the JSR-specific logic to see if there's validation for unscoped packages
sed -n '50,80p' core/multipublish/src/publish.ts

Repository: stephansama/packages

Length of output: 1019


🏁 Script executed:

#!/bin/bash
# Look for JSR module and check for scope/unscoped package handling
find . -name "*jsr*" -type f | head -20

Repository: stephansama/packages

Length of output: 130


🏁 Script executed:

#!/bin/bash
# Read the JSR module to check for unscoped package handling and warnings
cat -n core/multipublish/src/jsr.ts

Repository: stephansama/packages

Length of output: 3811


🏁 Script executed:

#!/bin/bash
# Check the JSR publish test to understand expected behavior
cat -n core/multipublish/src/publish.test.ts | sed -n '153,200p'

Repository: stephansama/packages

Length of output: 1029


🏁 Script executed:

#!/bin/bash
# Check if there are any tests or logic for unscoped packages
rg -n "scope" core/multipublish/src/jsr.ts core/multipublish/src/publish.ts -C 2

Repository: stephansama/packages

Length of output: 867


🏁 Script executed:

#!/bin/bash
# Get more context around the npm scope validation and check if JSR has similar logic
sed -n '40,90p' core/multipublish/src/publish.ts

Repository: stephansama/packages

Length of output: 1613


🏁 Script executed:

#!/bin/bash
# Check if there's any validation for unscoped packages in JSR publish, and compare with NPM
sed -n '44,95p' core/multipublish/src/publish.ts

Repository: stephansama/packages

Length of output: 1521


🏁 Script executed:

#!/bin/bash
# Get the complete NPM scope validation and error handling
sed -n '110,135p' core/multipublish/src/publish.ts

Repository: stephansama/packages

Length of output: 726


Clarify the changelog description; silent-failure behavior for unscoped packages is confirmed in the NPM publish path.

The phrase "fail without error for unscoped packages" is grammatically awkward and ambiguous. More importantly, this silent-failure behavior is confirmed in the codebase at the NPM publish handler (lines 125–129 of publish.ts): when a package is unscoped, console.error() logs a diagnostic but the function returns without throwing, allowing the CI/workflow to continue appearing successful.

Reword for clarity and specify which platform(s) this applies to:

-Updated jsr to have auth token. fail without error for unscoped packages
+Updated NPM and JSR publish to support auth tokens via configurable environment keys. Note: NPM publishing fails silently for unscoped packages (logs error but does not throw).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Updated jsr to have auth token. fail without error for unscoped packages
---
`@stephansama/multipublish`: patch
---
Updated NPM and JSR publish to support auth tokens via configurable environment keys. Note: NPM publishing fails silently for unscoped packages (logs error but does not throw).
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

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

In @.changeset/clever-rings-cheer.md at line 5, Reword the changeset summary to
clearly state that the package publish step for unscoped packages on the NPM
publish path logs an error but returns without throwing (causing CI/workflows to
continue as successful); reference that this silent-failure occurs where the NPM
publish handler uses console.error() and returns (publish.ts NPM publish
handler), and explicitly call out the affected platform as "npm publishing" (or
"npm registry publish") and that an auth token was added to jsr.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Feb 24, 2026

More templates

@stephansama/ai-commit-msg

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/ai-commit-msg@224

@stephansama/alfred-kaomoji

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/alfred-kaomoji@224

@stephansama/astro-iconify-svgmap

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/astro-iconify-svgmap@224

@stephansama/auto-readme

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/auto-readme@224

@stephansama/catppuccin-opml

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/catppuccin-opml@224

@stephansama/catppuccin-rss

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/catppuccin-rss@224

@stephansama/catppuccin-typedoc

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/catppuccin-typedoc@224

@stephansama/catppuccin-xsl

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/catppuccin-xsl@224

create-stephansama-example

pnpm add https://pkg.pr.new/stephansama/packages/create-stephansama-example@224

@stephansama/find-makefile-targets

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/find-makefile-targets@224

@stephansama/github-env

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/github-env@224

@stephansama/multipublish

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/multipublish@224

@stephansama/prettier-plugin-handlebars

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/prettier-plugin-handlebars@224

@stephansama/remark-asciinema

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/remark-asciinema@224

@stephansama/svelte-social-share-links

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/svelte-social-share-links@224

@stephansama/typed-env

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/typed-env@224

@stephansama/typed-events

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/typed-events@224

@stephansama/typed-nocodb-api

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/typed-nocodb-api@224

@stephansama/typed-templates

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/typed-templates@224

@stephansama/types-lhci

pnpm add https://pkg.pr.new/stephansama/packages/@stephansama/types-lhci@224

commit: c35993e

@stephansama stephansama merged commit 45af1b2 into main Feb 24, 2026
10 of 11 checks passed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/multipublish/src/jsr.test.ts (1)

70-77: 🧹 Nitpick | 🔵 Trivial

Trailing comma missing on line 76.

Line 53 uses a trailing comma after tokenEnvironmentKey: "test",; line 76 does not. Minor inconsistency worth aligning.

🔧 Suggested fix
-			tokenEnvironmentKey: "test"
+			tokenEnvironmentKey: "test",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/multipublish/src/jsr.test.ts` around lines 70 - 77, Update the object
literal assigned to appConfig (type JsrPlatformOptionsSchema) so that the final
property tokenEnvironmentKey: "test" includes a trailing comma to match the rest
of the file's style; modify the appConfig declaration in jsr.test.ts (the object
with allowSlowTypes, defaultExclude, defaultInclude, experimentalGenerateJSR,
experimentalUpdateCatalogs, tokenEnvironmentKey) to add the trailing comma after
tokenEnvironmentKey.
core/multipublish/src/publish.test.ts (1)

153-186: 🧹 Nitpick | 🔵 Trivial

Extract shared JSR test setup to eliminate duplication.

Both test cases share identical loadConfig mock and pkg definitions (lines 155–171 vs. 192–210). Extract them into a shared setup to reduce noise.

♻️ Suggested refactor
+	describe("publishPlatform jsr", () => {
+		const pkg = {
+			dir: "/path/to/pkg",
+			packageJson: { name: "@scope/pkg", version: "1.0.0" },
+			relativeDir: "./pkg",
+		};
+
+		beforeEach(() => {
+			vi.mocked(mocks.loadConfig).mockResolvedValue({
+				config: { exports: "index.ts", name: "@scope/pkg", version: "1.0.0" },
+				filename: "/path/to/pkg/jsr.json",
+			});
+		});

-		it("should publish to jsr without token", async () => {
-			vi.stubEnv("JSR_AUTH_TOKEN", "");
-			vi.mocked(mocks.loadConfig).mockResolvedValue({ ... });
-			const pkg = { ... };
-			const platform = "jsr";
-			await publishPlatform(pkg, platform);
+		it("should publish to jsr without token", async () => {
+			vi.stubEnv("JSR_AUTH_TOKEN", "");
+			await publishPlatform(pkg, "jsr");
 			expect(mocks.writeFile).toHaveBeenCalledWith("/path/to/pkg/jsr.json", expect.any(String));
 			expect(mocks.execSync).toHaveBeenCalledWith(
 				"pnpm dlx jsr publish --allow-dirty --allow-slow-types",
 				{ stdio: "inherit" },
 			);
 		});

-		it("should publish to jsr with token", async () => {
-			const token = "test-token";
-			vi.stubEnv("JSR_AUTH_TOKEN", token);
-			vi.mocked(mocks.loadConfig).mockResolvedValue({ ... });
-			const pkg = { ... };
-			const platform = "jsr";
-			await publishPlatform(pkg, platform);
+		it("should publish to jsr with token", async () => {
+			const token = "test-token";
+			vi.stubEnv("JSR_AUTH_TOKEN", token);
+			await publishPlatform(pkg, "jsr");
 			expect(mocks.writeFile).toHaveBeenCalledWith("/path/to/pkg/jsr.json", expect.any(String));
 			expect(mocks.execSync).toHaveBeenCalledWith(
 				`pnpm dlx jsr publish --allow-dirty --allow-slow-types --token ${token}`,
 				{ stdio: "inherit" },
 			);
 		});
+	});

Also applies to: 188-223

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

In `@core/multipublish/src/publish.test.ts` around lines 153 - 186, Extract the
duplicated test setup for the JSR tests by moving the repeated
mocks.loadConfig.mockResolvedValue payload and the pkg object into a shared
helper or a beforeEach so both "should publish to jsr without token" and the
other JSR test reuse it; keep per-test vi.stubEnv calls local, and update each
test to reference the shared pkg and not redeclare loadConfig or pkg; ensure
publishPlatform calls and assertions against mocks.writeFile and mocks.execSync
remain unchanged and still reference the same expected values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/multipublish/src/publish.test.ts`:
- Around line 188-223: The test shows publish.ts interpolates JSR_AUTH_TOKEN
directly into an execSync shell string (in publishPlatform), which risks shell
injection; change publishPlatform to call child_process.execFileSync("pnpm",
args, { stdio: "inherit" }) instead of execSync by building an argv array like
["dlx","jsr","publish","--allow-dirty","--allow-slow-types", "--token", token]
only when token exists, and update the test to expect mocks.execFileSync with
the same args/stdio call; ensure the unique symbol publishPlatform (in
publish.ts) is updated and any mocks use execFileSync instead of execSync.

In `@vitest.config.ts`:
- Line 10: The junit report path was changed in the vitest config (see
outputFile: { junit: "./test-report.junit.xml" }) but you must also 1) add the
new root-level filename test-report.junit.xml to .gitignore so it isn't
committed, and 2) update the codecov/test-results-action invocation in
.github/workflows/release.yml to point to "./test-report.junit.xml" instead of
"./coverage/test-report.junit.xml"; make both edits so the produced report is
ignored locally and CI uploads the correct file.

---

Outside diff comments:
In `@core/multipublish/src/jsr.test.ts`:
- Around line 70-77: Update the object literal assigned to appConfig (type
JsrPlatformOptionsSchema) so that the final property tokenEnvironmentKey: "test"
includes a trailing comma to match the rest of the file's style; modify the
appConfig declaration in jsr.test.ts (the object with allowSlowTypes,
defaultExclude, defaultInclude, experimentalGenerateJSR,
experimentalUpdateCatalogs, tokenEnvironmentKey) to add the trailing comma after
tokenEnvironmentKey.

In `@core/multipublish/src/publish.test.ts`:
- Around line 153-186: Extract the duplicated test setup for the JSR tests by
moving the repeated mocks.loadConfig.mockResolvedValue payload and the pkg
object into a shared helper or a beforeEach so both "should publish to jsr
without token" and the other JSR test reuse it; keep per-test vi.stubEnv calls
local, and update each test to reference the shared pkg and not redeclare
loadConfig or pkg; ensure publishPlatform calls and assertions against
mocks.writeFile and mocks.execSync remain unchanged and still reference the same
expected values.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cf485ac and c35993e.

📒 Files selected for processing (5)
  • core/multipublish/src/jsr.test.ts
  • core/multipublish/src/publish.test.ts
  • package.json
  • pnpm-workspace.yaml
  • vitest.config.ts

Comment on lines +188 to +223
it("should publish to jsr with token", async () => {
const token = "test-token";
vi.stubEnv("JSR_AUTH_TOKEN", token);

vi.mocked(mocks.loadConfig).mockResolvedValue({
config: {
exports: "index.ts",
name: "@scope/pkg",
version: "1.0.0",
},
filename: "/path/to/pkg/jsr.json",
});

const pkg = {
dir: "/path/to/pkg",
packageJson: {
name: "@scope/pkg",
version: "1.0.0",
},
relativeDir: "./pkg",
};

const platform = "jsr";

await publishPlatform(pkg, platform);

expect(mocks.writeFile).toHaveBeenCalledWith(
"/path/to/pkg/jsr.json",
expect.any(String),
);

expect(mocks.execSync).toHaveBeenCalledWith(
`pnpm dlx jsr publish --allow-dirty --allow-slow-types --token ${token}`,
{ stdio: "inherit" },
);
});
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

Token interpolated directly into shell command in publish.ts — potential shell injection.

The test at line 220 confirms publish.ts builds the execSync string by raw interpolation:

pnpm dlx jsr publish --allow-dirty --allow-slow-types --token ${token}

execSync spawns a subshell; if JSR_AUTH_TOKEN ever contains shell metacharacters (spaces, ;, &&, $(...), etc.), it will misbehave or inject commands. While CI-provisioned tokens are typically alphanumeric, the implementation in publish.ts provides no sanitisation or quoting.

Consider switching to execFileSync with an explicit argv array in publish.ts, which bypasses the shell entirely:

// In publish.ts (not in the test file)
const args = ["dlx", "jsr", "publish", "--allow-dirty", "--allow-slow-types"];
if (token) args.push("--token", token);
cp.execFileSync("pnpm", args, { stdio: "inherit" });

The test assertion would then change to match execFileSync instead of execSync.

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

In `@core/multipublish/src/publish.test.ts` around lines 188 - 223, The test shows
publish.ts interpolates JSR_AUTH_TOKEN directly into an execSync shell string
(in publishPlatform), which risks shell injection; change publishPlatform to
call child_process.execFileSync("pnpm", args, { stdio: "inherit" }) instead of
execSync by building an argv array like
["dlx","jsr","publish","--allow-dirty","--allow-slow-types", "--token", token]
only when token exists, and update the test to expect mocks.execFileSync with
the same args/stdio call; ensure the unique symbol publishPlatform (in
publish.ts) is updated and any mocks use execFileSync instead of execSync.

Comment thread vitest.config.ts
reporter: ["html", "json", "text"],
},
outputFile: { junit: "./coverage/test-report.junit.xml" },
outputFile: { junit: "./test-report.junit.xml" },
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Check if test-report.junit.xml at root is covered by .gitignore
echo "=== Checking .gitignore for test-report patterns ==="
if [ -f .gitignore ]; then
  rg -n "test-report" .gitignore || echo "No test-report entry found in .gitignore"
else
  echo ".gitignore not found"
fi

echo ""
echo "=== Checking for broad patterns that might cover root-level test files ==="
rg -n "^\*\.\(xml\|junit\)" .gitignore || echo "No broad XML/junit patterns found"

echo ""
echo "=== Full .gitignore content for context ==="
cat .gitignore

echo ""
echo "=== Checking CI workflow files for junit path references ==="
if [ -d .github/workflows ]; then
  rg -n "test-report\|junit\|coverage" .github/workflows/ || echo "No junit/coverage references found in CI workflows"
else
  echo ".github/workflows directory not found"
fi

Repository: stephansama/packages

Length of output: 850


🏁 Script executed:

#!/bin/bash
# Check for other CI system config files
echo "=== Searching for CI configuration files ==="
fd -type f -name ".gitlab-ci.yml" -o -name "*.circleci*" -o -name "bitbucket-pipelines.yml" -o -name ".travis.yml" 2>/dev/null || echo "No other CI configs found via fd"

echo ""
echo "=== Searching for any 'test' or 'junit' references in .github/ recursively ==="
if [ -d .github ]; then
  find .github -type f | head -20
  echo ""
  rg -i "test|junit|artifact" .github/ || echo "No artifact/test references in .github"
else
  echo ".github directory not found"
fi

echo ""
echo "=== Checking if .github/workflows exists and listing files ==="
if [ -d .github/workflows ]; then
  ls -la .github/workflows/
else
  echo ".github/workflows not found"
fi

echo ""
echo "=== Broader search for 'test-report' in entire repo (excluding node_modules) ==="
rg -i "test-report\|junit\.xml" --glob='!node_modules' . 2>/dev/null | head -20

Repository: stephansama/packages

Length of output: 1309


Update .gitignore and CI workflow for new junit report location.

The junit report path change from ./coverage/test-report.junit.xml to ./test-report.junit.xml requires two fixes:

  1. Add test-report.junit.xml to .gitignore — the new root-level file is not currently ignored (only **/coverage/ is covered).
  2. Update .github/workflows/release.yml — the codecov/test-results-action step still references the old path ("./coverage/test-report.junit.xml") and must be changed to "./test-report.junit.xml" to match the new output location.

Without these changes, the junit report will be committed to the repo and the CI test upload will fail.

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

In `@vitest.config.ts` at line 10, The junit report path was changed in the vitest
config (see outputFile: { junit: "./test-report.junit.xml" }) but you must also
1) add the new root-level filename test-report.junit.xml to .gitignore so it
isn't committed, and 2) update the codecov/test-results-action invocation in
.github/workflows/release.yml to point to "./test-report.junit.xml" instead of
"./coverage/test-report.junit.xml"; make both edits so the produced report is
ignored locally and CI uploads the correct file.

@coderabbitai coderabbitai Bot mentioned this pull request Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant