feat(auth): implement pnpm login --scope#11727
Conversation
The online documentation has long listed `pnpm login [--registry <url>] [--scope <scope>]`,
but the CLI only ever accepted `--registry`. `pnpm login --scope foo` errored with
`Unknown option: 'scope'` and there was no way to associate a scope with a registry
through the login command — users had to edit `.npmrc` by hand.
This change wires `--scope` through:
- `cliOptionsTypes` / `rcOptionsTypes` declare the option.
- `help()` documents it.
- After the token is written, `@<scope>:registry=<registry>` is written into the same
auth file (`~/.config/pnpm/auth.ini`), which `config.reader` already accepts as a
source of `@scope:registry=` mappings.
- Scope normalization: `--scope foo` and `--scope @foo` both produce `@foo`. Blank
values (`""`, `" "`, `"@"`) are treated as unset rather than writing a broken
`@:registry=` entry.
Ref: pnpm#11716
📝 WalkthroughWalkthroughThe PR implements the ChangesLogin --scope Flag Implementation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
auth/commands/test/login.test.ts (1)
211-239: ⚡ Quick winAdd explicit tests for invalid scope values handled by normalization.
Please add cases for
scope: ' 'andscope: '@'to assert no@...:registryentry is written, matching the new guard paths innormalizeScope.Proposed test addition
+ it('should not write a scope mapping for blank or invalid scope values', async () => { + for (const scope of [' ', '@']) { + let savedSettings: Record<string, unknown> = {} + const context = createMockContext({ + globalInfo: jest.fn(), + readIniFile: async () => ({}), + writeIniFile: async (_configPath, settings) => { + savedSettings = settings + }, + fetch: async url => { + if (url === 'https://example.com/-/v1/login') { + return createMockResponse({ + ok: true, + status: 200, + json: { + loginUrl: 'https://example.com/auth/login', + doneUrl: 'https://example.com/auth/done', + }, + }) + } + return createMockResponse({ ok: true, status: 200, json: { token: 'tok' } }) + }, + }) + await login({ context, opts: { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://example.com', scope } }) + for (const key of Object.keys(savedSettings)) { + expect(key.startsWith('@')).toBe(false) + } + } + })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@auth/commands/test/login.test.ts` around lines 211 - 239, Add two additional test cases in auth/commands/test/login.test.ts mirroring the existing "should not write a scope mapping when --scope is omitted" test but passing opts.scope = ' ' and opts.scope = '@' respectively; use the same createMockContext stub and savedSettings capture, call login({ context, opts }), and assert that no savedSettings key starts with '@' (i.e., for each Object.keys(savedSettings) expect(key.startsWith('@')).toBe(false)). Ensure the new tests reference the same login invocation and savedSettings assertion so they exercise the normalizeScope guard paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@auth/commands/test/login.test.ts`:
- Around line 211-239: Add two additional test cases in
auth/commands/test/login.test.ts mirroring the existing "should not write a
scope mapping when --scope is omitted" test but passing opts.scope = ' ' and
opts.scope = '@' respectively; use the same createMockContext stub and
savedSettings capture, call login({ context, opts }), and assert that no
savedSettings key starts with '@' (i.e., for each Object.keys(savedSettings)
expect(key.startsWith('@')).toBe(false)). Ensure the new tests reference the
same login invocation and savedSettings assertion so they exercise the
normalizeScope guard paths.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c39ac002-1952-4cf0-b518-488b68a8a8d3
📒 Files selected for processing (3)
.changeset/login-scope-flag.mdauth/commands/src/login.tsauth/commands/test/login.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (javascript)
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Follow Standard Style with trailing commas, prefer functions over classes, declare functions after they are used (rely on hoisting), and use a single options object for functions with more than two or three arguments
Sort imports in three groups: standard libraries, external dependencies (alphabetically), then relative imports
Write code that explains itself through clear naming and types — do not write comments that merely restate what the code already says; use comments only for non-obvious reasons, hidden invariants, or workarounds
Files:
auth/commands/src/login.tsauth/commands/test/login.test.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use util.types.isNativeError() instead of instanceof Error when type-checking errors in Jest tests, as instanceof checks can fail across VM realms
Files:
auth/commands/test/login.test.ts
🧠 Learnings (1)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.
Applied to files:
auth/commands/src/login.tsauth/commands/test/login.test.ts
🔇 Additional comments (2)
auth/commands/src/login.ts (1)
27-30: LGTM!Also applies to: 52-55, 60-60, 75-75, 213-219, 225-233
.changeset/login-scope-flag.md (1)
1-7: LGTM!
What
pnpm.io's
cli/loginpage has long shown the usage asbut in the CLI only
--registrywas actually wired up —pnpm login --scope fooerrored withUnknown option: 'scope'. This PR implements the documented flag.When
--scope <scope>is passed, after the auth token is written, an@<scope>:registry=<registry>mapping is appended to the same pnpm auth file (~/.config/pnpm/auth.ini).config.readeralready accepts@scope:registry=from that file (see Auth file read order), so subsequent installs of@<scope>/*packages route to the chosen registry without any further configuration.Closes #11716.
Behavior summary
auth.inipnpm login --registry https://example.com///example.com/:_authToken=<token>pnpm login --scope my-org --registry https://example.com///example.com/:_authToken=<token>+@my-org:registry=https://example.com/pnpm login --scope @my-org --registry https://example.com/@@)pnpm login --scope " " --registry …/--scope @@:registry=lineFiles touched
auth/commands/src/login.ts— declarescope: allTypes.scopeinrcOptionsTypes, document it inhelp(), normalize the value, write@<scope>:registry=<registry>alongside the token inlogin().auth/commands/test/login.test.ts— three new tests:--scope my-orgnormalizes to@my-organd writes the mapping.--scope @my-orgdoes not double-prefix.--scopewrites no@…:registrykey.Notes
auth.inirather than~/.npmrcbecause that's where pnpm 11 already writes auth-adjacent config (config.readerreads@scope:registry=from it) and it keeps everythingpnpm loginwrites in one file. Happy to switch to~/.npmrcif maintainers prefer the npm-compatible target — let me know and I'll push a follow-up commit.cafilerepro and fix inconfig.readeris up as fix(config/reader): resolve relative cafile path against the .npmrc directory #11726 separately; the two PRs are independent.Test plan
auth.inientries.pnpm logintests (web auth, classic fallback, OTP, etc.) are unchanged and continue to pass through the same code path.@pnpm/auth.commandsandpnpm).Notes
This contribution was prepared with the assistance of Claude Code.
Summary by CodeRabbit
New Features
--scopeflag topnpm logincommand, allowing users to authenticate with specific npm scopes and their dedicated registries. Scope values are automatically normalized with an@prefix if needed.Bug Fixes
pnpm login --scopecommand that previously resulted in an "Unknown option" error.