Skip to content

feat: pnpm login (old)#11055

Closed
KSXGitHub wants to merge 47 commits into
mainfrom
claude/implement-pnpm-login-4VvtQ
Closed

feat: pnpm login (old)#11055
KSXGitHub wants to merge 47 commits into
mainfrom
claude/implement-pnpm-login-4VvtQ

Conversation

@KSXGitHub

Copy link
Copy Markdown
Contributor

Summary

This PR implements the pnpm login command for authenticating with npm registries. The implementation supports both modern web-based authentication (with QR code display) and falls back to classic username/password authentication for registries that don't support the web flow.

Key Changes

  • New @pnpm/auth.commands package: Implements the login command with support for:

    • Web-based authentication via /-/v1/login endpoint with QR code generation
    • Automatic fallback to classic authentication (username/password/email) when web login is not supported
    • Token persistence to .npmrc configuration file
    • Interactive terminal requirement validation
  • New @pnpm/network.web-auth package: Extracted shared web authentication utilities:

    • pollForWebAuthToken(): Polls a registry's "done" URL until authentication token is received
    • generateQrCode(): Generates QR codes for authentication URLs
    • WebAuthTimeoutError: Custom error for authentication timeout (5 minute default)
    • Respects Retry-After headers during polling
    • Handles network failures and invalid responses gracefully
  • Refactored OTP authentication: Updated releasing/commands/src/publish/otp.ts to use the new shared @pnpm/network.web-auth utilities, eliminating code duplication

  • Integration: Added login command to pnpm CLI and removed it from the not-implemented commands list

Implementation Details

  • The login command requires an interactive TTY for both stdin and stdout
  • Web login is attempted first; if the registry returns 404 or 405, it falls back to classic login
  • Authentication tokens are stored in the .npmrc file under registry-specific keys (e.g., //registry.npmjs.org/:_authToken)
  • The polling mechanism includes configurable retry logic and respects server-provided retry delays
  • Comprehensive test coverage for both web and classic authentication flows

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S

claude added 24 commits March 22, 2026 01:32
…-auth

Extract generateQrCode() and pollForWebAuthToken() from releasing/commands
into a new shared package so that both `pnpm publish` and the upcoming
`pnpm login` can reuse the web-based authentication flow with QR code
display and doneUrl polling.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Add `pnpm login` (and `pnpm adduser` alias) for authenticating with npm
registries. The command:

- Tries web-based login first (POST /-/v1/login), displaying a QR code
  and polling for the token using @pnpm/network.web-auth
- Falls back to classic username/password/email login (PUT /-/user/
  org.couchdb.user:<username>) when web login is not supported (404/405)
- Saves the received auth token to the user's global rc file

Also fixes a tsgo build issue in releasing/commands where
OtpWebAuthFetchOptions was used as a local type alias but was only
available as a re-exported name.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Switch from globalThis.fetch to fetchWithAgent from @pnpm/network.fetch
so that pnpm login respects proxy settings (httpProxy/httpsProxy/noProxy),
custom SSL certificates (ca/cert/key), strictSsl, and retry configuration.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
- Type LoginContext.fetch using WebAuthFetchOptions/WebAuthFetchResponse
  from @pnpm/network.web-auth, extended with text() and wider method
- Replace regex-based URL construction with new URL() constructor
- Remove redundant LoginFetchInit type

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
- Static DEFAULT_CONTEXT constant instead of createDefaultContext factory
- context = DEFAULT_CONTEXT default parameter instead of context?: Partial
- Destructure context in function signatures for natural calling
- Use plain fetch from @pnpm/network.fetch (like SHARED_CONTEXT in publish)
- Context contains only side-effect functions and modules, not config

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Remove LoginFetchOptions and LoginFetchResponse. Type LoginContext.fetch
as typeof fetch from @pnpm/network.fetch directly, eliminating all casts.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Add readSettings and writeSettings to LoginContext so tests need no
filesystem side effects. Remove @pnpm/prepare devDependency.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Tests now construct their own TEST_CONTEXT with all no-op mocks,
eliminating any reliance on real side-effectful functions.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Each test now uses a different registry and configDir to verify URL
construction, config key generation, and save path are correct for
non-default options.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
All mock functions in TEST_CONTEXT now throw on unexpected calls,
ensuring tests fail loudly if the code makes unanticipated side effects.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Replace custom.registry.io and private.reg.co with example.com and
example.org (RFC 2606 reserved) to prevent domain squatting risks.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Default globalInfo in TEST_CONTEXT now throws. Each test overrides it
to capture messages and asserts the expected output.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Drop explicit `string` annotation so the parameter matches the
`RequestInfo` type expected by the fetch signature.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Use mockResponse helper with `as any` cast to satisfy the Response
type, and String(url) for RequestInfo-to-string conversion.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
…FetchOptions types

Derive the fetch signature from actual call-site usage instead of
coupling to the concrete @pnpm/network.fetch type. This lets test
mocks return plain objects without casts.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
@KSXGitHub KSXGitHub force-pushed the claude/implement-pnpm-login-4VvtQ branch from a34d83b to cd51829 Compare March 22, 2026 01:33
claude added 5 commits March 22, 2026 01:35
These files are created by setup.js during preinstall and should not
be tracked.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Remove Otp-prefixed re-exports (OtpWebAuthFetchOptions,
OtpWebAuthFetchResponse, OtpWebAuthTimeoutError) that only existed as
backwards-compatibility shims. Update the test to import directly from
@pnpm/network.web-auth. Restore the named OtpDate interface that was
unnecessarily inlined.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Add dependency-injected unit tests covering:
- WebAuthTimeoutError: properties, code, hint, message
- generateQrCode: basic output and input differentiation
- pollForWebAuthToken: happy path, fetch argument passing,
  Retry-After handling (valid, non-finite, null, sub-interval,
  capped to remaining timeout, timeout during retry wait),
  error recovery (fetch throws, non-ok response, json parse error,
  missing token, empty token, multiple consecutive errors),
  custom timeout, poll interval timing

All tests use fake Date.now() and setTimeout — no real timers or
side effects.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Replace `.catch((e: WebAuthTimeoutError) => e)` pattern with
`rejects.toMatchObject()` to avoid `string | WebAuthTimeoutError`
union type issue when accessing `.timeout` property.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
… to login

- Create `withOtpHandling<T>()` in `@pnpm/network.web-auth` that wraps
  any operation with EOTP challenge detection, web auth flow, and
  classic OTP prompting.
- Refactor `publishWithOtpHandling` to delegate to the shared function.
- Add OTP handling to `pnpm login`'s classic (CouchDB) login flow:
  detects 401 + `www-authenticate: otp` header and retries with the
  OTP code (or web auth token) in the `npm-otp` header.
- Remove overly strict `this: this` constraints from WebAuthFetchResponse
  interfaces to improve cross-package type compatibility.
- Add 13 unit tests for `withOtpHandling` (classic + webauth flows).
- Add 4 login OTP tests (classic OTP, webauth OTP, non-401, non-otp 401).

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Comment thread auth/commands/test/login.test.ts Fixed
claude and others added 18 commits March 22, 2026 03:06
Replace `m.includes(url)` with a regex that checks the URL is
bounded by whitespace or string boundaries, addressing the CodeQL
"incomplete URL substring sanitization" finding.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Replace manual `.some()` with Jest's `toContainEqual(expect.stringMatching(...))`
for better error messages on failure.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Remove dead re-exports of OtpHandlingPromptOptions and
OtpHandlingPromptResponse from releasing/commands/src/publish/otp.ts.

Add tests for:
- LOGIN_MISSING_CREDENTIALS (empty username in classic login)
- LOGIN_NO_TOKEN (registry returns success without token)
- LOGIN_INVALID_RESPONSE (web login returns incomplete response)
- isWebLoginNotSupported with 405 status code

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
…/writeIniFile

Use the actual function names in the LoginContext interface instead of
abstract names, matching the implementations they wrap.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
OtpNonInteractiveError, OtpSecondChallengeError, and OtpHandlingEnquirer
were re-exported only for the test file, which can import them directly
from @pnpm/network.web-auth.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
All consumers already import SHARED_CONTEXT directly from
./utils/shared-context.js, making this re-export dead code.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Extract named interfaces for the Date and enquirer members of
LoginContext instead of inlining their types.

https://claude.ai/code/session_01YHYqGAAmZ1a9XMWoV7nG4S
Claude Code Web didn't rename them thoroughly, so I had to do it myself
Why did Claude Code Web misaligned?
@KSXGitHub KSXGitHub changed the title feat: pnpm login feat: pnpm login (old) Mar 26, 2026
This was referenced Mar 26, 2026
@KSXGitHub KSXGitHub deleted the claude/implement-pnpm-login-4VvtQ branch March 27, 2026 11:04
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.

3 participants