@doist/cli-core 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +22 -14
  3. package/dist/auth/index.d.ts +3 -1
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +1 -0
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/auth/keyring/internal.d.ts +25 -0
  8. package/dist/auth/keyring/internal.d.ts.map +1 -0
  9. package/dist/auth/keyring/internal.js +31 -0
  10. package/dist/auth/keyring/internal.js.map +1 -0
  11. package/dist/auth/keyring/migrate.d.ts +19 -17
  12. package/dist/auth/keyring/migrate.d.ts.map +1 -1
  13. package/dist/auth/keyring/migrate.js +111 -49
  14. package/dist/auth/keyring/migrate.js.map +1 -1
  15. package/dist/auth/keyring/record-write.d.ts +70 -16
  16. package/dist/auth/keyring/record-write.d.ts.map +1 -1
  17. package/dist/auth/keyring/record-write.js +139 -30
  18. package/dist/auth/keyring/record-write.js.map +1 -1
  19. package/dist/auth/keyring/slot-naming.d.ts +6 -0
  20. package/dist/auth/keyring/slot-naming.d.ts.map +1 -0
  21. package/dist/auth/keyring/slot-naming.js +8 -0
  22. package/dist/auth/keyring/slot-naming.js.map +1 -0
  23. package/dist/auth/keyring/token-store.d.ts +10 -2
  24. package/dist/auth/keyring/token-store.d.ts.map +1 -1
  25. package/dist/auth/keyring/token-store.js +93 -64
  26. package/dist/auth/keyring/token-store.js.map +1 -1
  27. package/dist/auth/keyring/types.d.ts +20 -0
  28. package/dist/auth/keyring/types.d.ts.map +1 -1
  29. package/dist/auth/persist.d.ts +23 -0
  30. package/dist/auth/persist.d.ts.map +1 -0
  31. package/dist/auth/persist.js +38 -0
  32. package/dist/auth/persist.js.map +1 -0
  33. package/dist/auth/types.d.ts +27 -1
  34. package/dist/auth/types.d.ts.map +1 -1
  35. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.18.0](https://github.com/Doist/cli-core/compare/v0.17.0...v0.18.0) (2026-05-19)
2
+
3
+ ### Features
4
+
5
+ * **auth:** two-phase migrate write for the bundle-shape contract ([#38](https://github.com/Doist/cli-core/issues/38)) ([9cf5e9c](https://github.com/Doist/cli-core/commit/9cf5e9caaed30ec0a989a026bf5b00f6815a2512))
6
+
7
+ ## [0.17.0](https://github.com/Doist/cli-core/compare/v0.16.1...v0.17.0) (2026-05-19)
8
+
9
+ ### Features
10
+
11
+ * **auth:** add TokenBundle storage contract to TokenStore ([#37](https://github.com/Doist/cli-core/issues/37)) ([6513aaa](https://github.com/Doist/cli-core/commit/6513aaa8b6222eb421239f6e41ec63b12cf946e9))
12
+
1
13
  ## [0.16.1](https://github.com/Doist/cli-core/compare/v0.16.0...v0.16.1) (2026-05-16)
2
14
 
3
15
  ### Bug Fixes
package/README.md CHANGED
@@ -12,20 +12,20 @@ npm install @doist/cli-core
12
12
 
13
13
  ## What's in it
14
14
 
15
- | Module | Key exports | Purpose |
16
- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
- | `auth` (subpath) | `attachLoginCommand`, `attachLogoutCommand`, `attachStatusCommand`, `attachTokenViewCommand`, `runOAuthFlow`, `createPkceProvider`, `createSecureStore`, `createKeyringTokenStore`, `migrateLegacyAuth`, PKCE helpers, `AuthProvider` / `TokenStore` / `AccountRef` / `SecureStore` / `UserRecordStore` types, `AttachLogoutRevokeContext` | OAuth runtime plus the Commander attachers for `<cli> [auth] login` / `logout` / `status` / `token`. `attachLogoutCommand` accepts an optional `revokeToken` hook for best-effort server-side token revocation. Ships the standard public-client PKCE flow (`createPkceProvider`), a thin cross-platform OS-keyring wrapper (`createSecureStore`), and a multi-account keyring-backed `TokenStore` (`createKeyringTokenStore`) that stores secrets in the OS credential manager and degrades to plaintext in the consumer's config when the keyring is unavailable (WSL/headless Linux/containers). `AuthProvider` and `TokenStore` remain the escape hatches for DCR or fully bespoke backends. `logout` / `status` / `token` always attach `--user <ref>` and thread the parsed ref to `store.active(ref)` (and `store.clear(ref)` on `logout`). `commander` (when using the attachers), `open` (browser launch), and `@napi-rs/keyring` (when using `createSecureStore` or the keyring `TokenStore`) are optional peer/optional deps. |
18
- | `commands` (subpath) | `registerChangelogCommand`, `registerUpdateCommand` (+ semver helpers) | Commander wiring for cli-core's standard commands (e.g. `<cli> changelog`, `<cli> update`, `<cli> update switch`). **Requires** `commander` as an optional peer-dep. |
19
- | `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig`, `CoreConfig`, `UpdateChannel` | Read / write a per-CLI JSON config file with typed error codes; `CoreConfig` is the shape of fields cli-core itself owns (extend it for per-CLI fields). |
20
- | `empty` | `printEmpty` | Print an empty-state message gated on `--json` / `--ndjson` so machine consumers never see human strings on stdout. |
21
- | `errors` | `CliError` | Typed CLI error class with `code` and exit-code mapping. |
22
- | `global-args` | `parseGlobalArgs`, `stripUserFlag`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled` | Parse well-known global flags (`--json`, `--ndjson`, `--quiet`, `--verbose`, `--accessible`, `--no-spinner`, `--progress-jsonl`, `--user <ref>`) and derive predicates from them. `stripUserFlag` removes `--user` tokens from argv so the cleaned array can be forwarded to Commander when the flag has no root-program attachment. |
23
- | `json` | `formatJson`, `formatNdjson` | Stable JSON / newline-delimited JSON formatting for stdout. |
24
- | `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions` | Lazy-init terminal markdown renderer. **Requires** `marked` and `marked-terminal-renderer` as peer-deps — install only if your CLI uses this subpath. |
25
- | `options` | `ViewOptions` | Type contract for `{ json?, ndjson? }` per-command options that machine-output gates derive from. |
26
- | `spinner` | `createSpinner` | Loading spinner factory wrapping `yocto-spinner` with disable gates. |
27
- | `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY` | TTY / CI detection helpers. |
28
- | `testing` (subpath) | `describeEmptyMachineOutput` | Vitest helpers reusable by consuming CLIs (e.g. parametrised empty-state suite covering `--json` / `--ndjson` / human modes). |
15
+ | Module | Key exports | Purpose |
16
+ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | `auth` (subpath) | `attachLoginCommand`, `attachLogoutCommand`, `attachStatusCommand`, `attachTokenViewCommand`, `runOAuthFlow`, `createPkceProvider`, `createSecureStore`, `createKeyringTokenStore`, `migrateLegacyAuth`, `persistBundle`, PKCE helpers, `AuthProvider` / `TokenStore` / `TokenBundle` / `RefreshInput` / `AccountRef` / `SecureStore` / `UserRecordStore` types, `AttachLogoutRevokeContext` | OAuth runtime plus the Commander attachers for `<cli> [auth] login` / `logout` / `status` / `token`. `attachLogoutCommand` accepts an optional `revokeToken` hook for best-effort server-side token revocation. Ships the standard public-client PKCE flow (`createPkceProvider`), a thin cross-platform OS-keyring wrapper (`createSecureStore`), and a multi-account keyring-backed `TokenStore` (`createKeyringTokenStore`) that stores secrets in the OS credential manager and degrades to plaintext in the consumer's config when the keyring is unavailable (WSL/headless Linux/containers). The store contract supports an optional `setBundle(account, bundle)` write method (required on `KeyringTokenStore`) so consumers that need refresh-token persistence can opt in via `TokenBundle`; `active()` stays narrow (access token + account only) so callers that don't need refresh state don't pay extra keyring IPC. `AuthProvider` and `TokenStore` remain the escape hatches for DCR or fully bespoke backends. `logout` / `status` / `token` always attach `--user <ref>` and thread the parsed ref to `store.active(ref)` (and `store.clear(ref)` on `logout`). `commander` (when using the attachers), `open` (browser launch), and `@napi-rs/keyring` (when using `createSecureStore` or the keyring `TokenStore`) are optional peer/optional deps. |
18
+ | `commands` (subpath) | `registerChangelogCommand`, `registerUpdateCommand` (+ semver helpers) | Commander wiring for cli-core's standard commands (e.g. `<cli> changelog`, `<cli> update`, `<cli> update switch`). **Requires** `commander` as an optional peer-dep. |
19
+ | `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig`, `CoreConfig`, `UpdateChannel` | Read / write a per-CLI JSON config file with typed error codes; `CoreConfig` is the shape of fields cli-core itself owns (extend it for per-CLI fields). |
20
+ | `empty` | `printEmpty` | Print an empty-state message gated on `--json` / `--ndjson` so machine consumers never see human strings on stdout. |
21
+ | `errors` | `CliError` | Typed CLI error class with `code` and exit-code mapping. |
22
+ | `global-args` | `parseGlobalArgs`, `stripUserFlag`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled` | Parse well-known global flags (`--json`, `--ndjson`, `--quiet`, `--verbose`, `--accessible`, `--no-spinner`, `--progress-jsonl`, `--user <ref>`) and derive predicates from them. `stripUserFlag` removes `--user` tokens from argv so the cleaned array can be forwarded to Commander when the flag has no root-program attachment. |
23
+ | `json` | `formatJson`, `formatNdjson` | Stable JSON / newline-delimited JSON formatting for stdout. |
24
+ | `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions` | Lazy-init terminal markdown renderer. **Requires** `marked` and `marked-terminal-renderer` as peer-deps — install only if your CLI uses this subpath. |
25
+ | `options` | `ViewOptions` | Type contract for `{ json?, ndjson? }` per-command options that machine-output gates derive from. |
26
+ | `spinner` | `createSpinner` | Loading spinner factory wrapping `yocto-spinner` with disable gates. |
27
+ | `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY` | TTY / CI detection helpers. |
28
+ | `testing` (subpath) | `describeEmptyMachineOutput` | Vitest helpers reusable by consuming CLIs (e.g. parametrised empty-state suite covering `--json` / `--ndjson` / human modes). |
29
29
 
30
30
  ## Usage
31
31
 
@@ -288,6 +288,14 @@ export const tokenStore: TokenStore<Account> = {
288
288
 
289
289
  For multi-account storage (OS keychain, per-user config slots, …), implement the same five methods against your backend and honour `ref` on `active` / `clear` / `setDefault`. `AccountRef` is an opaque `string` — cli-core does not constrain matching semantics (id-exact, email-case-insensitive, label, …). The store impl owns that.
290
290
 
291
+ ##### Refresh-token storage (`TokenBundle`)
292
+
293
+ Stores that target servers issuing refresh tokens may implement the optional `setBundle(account, bundle, options?)` method. `TokenBundle` carries `{ accessToken, refreshToken?, accessTokenExpiresAt?, refreshTokenExpiresAt? }`. Stores that omit `setBundle` continue to work — cli-core helpers (`persistBundle`) fall back to `set(account, bundle.accessToken)` and silently drop refresh state. `KeyringTokenStore` implements `setBundle` as a required override and routes the refresh token to a sibling keyring slot.
294
+
295
+ `active()` still returns the narrow `{ token, account }` snapshot — refresh-state material is stored but not surfaced on the hot read path so commands that only need the access token don't pay an extra keyring IPC. A bundle-aware read path lands alongside the silent-refresh helper in a follow-up PR.
296
+
297
+ The `persistBundle({ store, account, bundle, promoteDefault? })` helper is the recommended write path for bundle-capable consumers — it prefers `setBundle` when available and falls back to `set` otherwise (the `set` fallback can't honour `promoteDefault`, so multi-account stores wanting silent-refresh-safe selection must implement `setBundle`). cli-core's own `runOAuthFlow` still persists via `store.set()` today; it switches to `persistBundle` when the refresh helper lands.
298
+
291
299
  #### Keyring primitive (`createSecureStore`)
292
300
 
293
301
  When the OS credential manager is the right place for your token, `createSecureStore` is a thin cross-platform wrapper around `@napi-rs/keyring`. It exposes a three-method handle (`getSecret` / `setSecret` / `deleteSecret`) for one slot identified by `serviceName` + `account`:
@@ -11,9 +11,11 @@ export { attachTokenViewCommand } from './token-view.js';
11
11
  export type { AttachTokenViewCommandOptions } from './token-view.js';
12
12
  export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
13
13
  export type { GenerateVerifierOptions } from './pkce.js';
14
+ export { persistBundle } from './persist.js';
15
+ export type { PersistBundleOptions } from './persist.js';
14
16
  export { createPkceProvider } from './providers/pkce.js';
15
17
  export type { PkceLazyString, PkceProviderOptions } from './providers/pkce.js';
16
- export type { AccountRef, AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, TokenStore, ValidateInput, } from './types.js';
18
+ export type { AccountRef, AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, RefreshInput, TokenBundle, TokenStore, ValidateInput, } from './types.js';
17
19
  export { SecureStoreUnavailableError, createKeyringTokenStore, createSecureStore, migrateLegacyAuth, } from './keyring/index.js';
18
20
  export type { CreateKeyringTokenStoreOptions, CreateSecureStoreOptions, KeyringTokenStore, MigrateAuthResult, MigrateLegacyAuthOptions, MigrateSkipReason, SecureStore, TokenStorageLocation, TokenStorageResult, UserRecord, UserRecordStore, } from './keyring/index.js';
19
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,YAAY,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EACR,0BAA0B,EAC1B,mBAAmB,EACnB,yBAAyB,GAC5B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxD,YAAY,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EACR,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,EACb,UAAU,EACV,aAAa,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACR,8BAA8B,EAC9B,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,eAAe,GAClB,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,YAAY,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EACR,0BAA0B,EAC1B,mBAAmB,EACnB,yBAAyB,GAC5B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxD,YAAY,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EACR,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,UAAU,EACV,aAAa,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACR,8BAA8B,EAC9B,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,eAAe,GAClB,MAAM,oBAAoB,CAAA"}
@@ -4,6 +4,7 @@ export { attachLogoutCommand } from './logout.js';
4
4
  export { attachStatusCommand } from './status.js';
5
5
  export { attachTokenViewCommand } from './token-view.js';
6
6
  export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
7
+ export { persistBundle } from './persist.js';
7
8
  export { createPkceProvider } from './providers/pkce.js';
8
9
  export { SecureStoreUnavailableError, createKeyringTokenStore, createSecureStore, migrateLegacyAuth, } from './keyring/index.js';
9
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAMjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAExD,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAexD,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAMjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAExD,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAiBxD,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,25 @@
1
+ import type { AuthAccount } from '../types.js';
2
+ import { type SecureStore } from './secure-store.js';
3
+ import type { UserRecord } from './types.js';
4
+ /**
5
+ * Outcome of resolving the access token for a record. Callers map the
6
+ * structured failure variants to whatever error contract they expose —
7
+ * `KeyringTokenStore.active()` throws `CliError('AUTH_STORE_READ_FAILED', …)`;
8
+ * `migrateLegacyAuth` translates each variant into a `MigrateSkipReason`.
9
+ */
10
+ export type ReadAccessTokenOutcome = {
11
+ ok: true;
12
+ token: string;
13
+ } | {
14
+ ok: false;
15
+ reason: 'slot-empty' | 'slot-unavailable' | 'slot-error';
16
+ detail: string;
17
+ };
18
+ /**
19
+ * `fallbackToken` first (so an offline-keyring write is preferred over a
20
+ * stale slot), then the keyring slot. Single-source for "is this record
21
+ * readable in the current environment" — `KeyringTokenStore.active()` and
22
+ * `migrateLegacyAuth`'s readability probe both call this.
23
+ */
24
+ export declare function readAccessTokenForRecord<TAccount extends AuthAccount>(record: UserRecord<TAccount>, secureStore: SecureStore): Promise<ReadAccessTokenOutcome>;
25
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/internal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,KAAK,WAAW,EAA+B,MAAM,mBAAmB,CAAA;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,YAAY,GAAG,kBAAkB,GAAG,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE7F;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,QAAQ,SAAS,WAAW,EACvE,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,WAAW,EAAE,WAAW,GACzB,OAAO,CAAC,sBAAsB,CAAC,CAmBjC"}
@@ -0,0 +1,31 @@
1
+ import { getErrorMessage } from '../../errors.js';
2
+ import { SecureStoreUnavailableError } from './secure-store.js';
3
+ /**
4
+ * `fallbackToken` first (so an offline-keyring write is preferred over a
5
+ * stale slot), then the keyring slot. Single-source for "is this record
6
+ * readable in the current environment" — `KeyringTokenStore.active()` and
7
+ * `migrateLegacyAuth`'s readability probe both call this.
8
+ */
9
+ export async function readAccessTokenForRecord(record, secureStore) {
10
+ const fallback = record.fallbackToken?.trim();
11
+ if (fallback)
12
+ return { ok: true, token: fallback };
13
+ try {
14
+ const raw = await secureStore.getSecret();
15
+ const trimmed = raw?.trim();
16
+ if (trimmed)
17
+ return { ok: true, token: trimmed };
18
+ return {
19
+ ok: false,
20
+ reason: 'slot-empty',
21
+ detail: 'keyring slot returned no credential',
22
+ };
23
+ }
24
+ catch (error) {
25
+ if (error instanceof SecureStoreUnavailableError) {
26
+ return { ok: false, reason: 'slot-unavailable', detail: error.message };
27
+ }
28
+ return { ok: false, reason: 'slot-error', detail: getErrorMessage(error) };
29
+ }
30
+ }
31
+ //# sourceMappingURL=internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.js","sourceRoot":"","sources":["../../../src/auth/keyring/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,EAAoB,2BAA2B,EAAE,MAAM,mBAAmB,CAAA;AAajF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC1C,MAA4B,EAC5B,WAAwB;IAExB,MAAM,QAAQ,GAAG,MAAM,CAAC,aAAa,EAAE,IAAI,EAAE,CAAA;IAC7C,IAAI,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;IAElD,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,CAAA;QACzC,MAAM,OAAO,GAAG,GAAG,EAAE,IAAI,EAAE,CAAA;QAC3B,IAAI,OAAO;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;QAChD,OAAO;YACH,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,qCAAqC;SAChD,CAAA;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,KAAK,YAAY,2BAA2B,EAAE,CAAC;YAC/C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;QAC3E,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAA;IAC9E,CAAC;AACL,CAAC"}
@@ -49,10 +49,11 @@ export type MigrateLegacyAuthOptions<TAccount extends AuthAccount> = {
49
49
  logPrefix?: string;
50
50
  };
51
51
  /**
52
- * Stable skip reasons. `legacy-keyring-unreachable` is retryable (a later
53
- * run with the keyring online would succeed); the others are diagnostic.
52
+ * Stable skip reasons. `*-keyring-unreachable` variants are retryable (a
53
+ * later run with the keyring online would succeed); the others are
54
+ * diagnostic.
54
55
  */
55
- export type MigrateSkipReason = 'identify-failed' | 'legacy-keyring-unreachable' | 'user-record-write-failed' | 'marker-write-failed';
56
+ export type MigrateSkipReason = 'identify-failed' | 'legacy-keyring-unreachable' | 'user-keyring-unreachable' | 'user-record-write-failed' | 'marker-write-failed';
56
57
  /**
57
58
  * Discriminated by `status`. Narrowing on `status === 'skipped'` exposes
58
59
  * the structured `reason` + free-form `detail`; `migrated` carries the
@@ -72,22 +73,23 @@ export type MigrateAuthResult<TAccount extends AuthAccount = AuthAccount> = {
72
73
  };
73
74
  /**
74
75
  * One-time migration of a v1 single-user auth state into a v2 multi-user
75
- * shape. Best-effort: any failure (offline keyring, network error fetching
76
- * the user, …) leaves the v1 state untouched so the consumer's runtime
77
- * fallback can keep serving the legacy token until the next attempt.
76
+ * shape. Best-effort: any failure leaves v1 untouched so the runtime
77
+ * fallback keeps serving the legacy token until the next attempt.
78
78
  *
79
- * Order of operations is deliberate so the migration is genuinely one-way:
79
+ * Order is deliberate so the migration is one-way AND safe under retry:
80
80
  *
81
- * 1. `hasMigrated()` short-circuits if the durable marker is already set.
82
- * 2. Read the v1 token (legacy keyring slot first, then plaintext).
83
- * 3. `identifyAccount(token)` resolves the v2 `account` shape.
84
- * 4. `writeRecordWithKeyringFallback` writes the v2 record.
85
- * 5. Best-effort `setDefaultId(account.id)` so the new record is active.
86
- * 6. `markMigrated()` persists the marker. **If this fails, we return
87
- * `skipped` even though the v2 record is on disk** — the marker is
88
- * what prevents re-migration on the next run.
89
- * 7. Best-effort legacy keyring delete + `cleanupLegacyConfig()` run
90
- * concurrently. Failures here are harmless because the marker is set.
81
+ * 1. `hasMigrated()` short-circuits when the marker is set.
82
+ * 2. Read the v1 token (legacy keyring first, then plaintext).
83
+ * 3. `identifyAccount(token)` resolves the v2 `account`.
84
+ * 4. **Phase 1** — `ensureV2Record` writes a fallback-bearing record (or
85
+ * no-ops when a v2 record already exists).
86
+ * 5. **Phase 2** when Phase 1 wrote: move the token to the per-user
87
+ * keyring slot and upsert the clean record. When Phase 1 didn't:
88
+ * verify the existing record is readable before retiring legacy.
89
+ * 6. Best-effort `setDefaultId(account.id)`.
90
+ * 7. `markMigrated()` the one-way gate. Failure here surfaces as
91
+ * `skipped(marker-write-failed)` so the caller retries.
92
+ * 8. Best-effort legacy cleanup runs concurrently.
91
93
  */
92
94
  export declare function migrateLegacyAuth<TAccount extends AuthAccount>(options: MigrateLegacyAuthOptions<TAccount>): Promise<MigrateAuthResult<TAccount>>;
93
95
  //# sourceMappingURL=migrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/migrate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAQ9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,MAAM,MAAM,wBAAwB,CAAC,QAAQ,SAAS,WAAW,IAAI;IACjE,WAAW,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAA;IACrB,gEAAgE;IAChE,WAAW,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACtC,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACnC;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC;;;OAGG;IACH,wBAAwB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtD;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACrD;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACvB,iBAAiB,GACjB,4BAA4B,GAC5B,0BAA0B,GAC1B,qBAAqB,CAAA;AAS3B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAClE;IAAE,MAAM,EAAE,kBAAkB,CAAA;CAAE,GAC9B;IAAE,MAAM,EAAE,iBAAiB,CAAA;CAAE,GAC7B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,GACzC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAOtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,SAAS,WAAW,EAChE,OAAO,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAC5C,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAuGtC"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/migrate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAS9C,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,YAAY,CAAA;AAE7D,MAAM,MAAM,wBAAwB,CAAC,QAAQ,SAAS,WAAW,IAAI;IACjE,WAAW,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAA;IACrB,gEAAgE;IAChE,WAAW,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACtC,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACnC;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC;;;OAGG;IACH,wBAAwB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtD;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACrD;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GACvB,iBAAiB,GACjB,4BAA4B,GAC5B,0BAA0B,GAC1B,0BAA0B,GAC1B,qBAAqB,CAAA;AAU3B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAClE;IAAE,MAAM,EAAE,kBAAkB,CAAA;CAAE,GAC9B;IAAE,MAAM,EAAE,iBAAiB,CAAA;CAAE,GAC7B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,GACzC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAOtE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,SAAS,WAAW,EAChE,OAAO,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAC5C,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CA4GtC"}
@@ -1,30 +1,33 @@
1
1
  import { getErrorMessage } from '../../errors.js';
2
- import { writeRecordWithKeyringFallback } from './record-write.js';
2
+ import { readAccessTokenForRecord } from './internal.js';
3
+ import { buildSingleTokenRecord } from './record-write.js';
3
4
  import { createSecureStore, DEFAULT_ACCOUNT_FOR_USER, SecureStoreUnavailableError, } from './secure-store.js';
4
5
  const SKIP_REASON_MESSAGES = {
5
6
  'identify-failed': 'could not identify user',
6
7
  'legacy-keyring-unreachable': 'legacy credential is unreachable (keyring offline)',
8
+ 'user-keyring-unreachable': 'per-user credential slot is unreachable (keyring offline)',
7
9
  'user-record-write-failed': 'failed to update user records',
8
10
  'marker-write-failed': 'failed to persist migration marker',
9
11
  };
10
12
  /**
11
13
  * One-time migration of a v1 single-user auth state into a v2 multi-user
12
- * shape. Best-effort: any failure (offline keyring, network error fetching
13
- * the user, …) leaves the v1 state untouched so the consumer's runtime
14
- * fallback can keep serving the legacy token until the next attempt.
14
+ * shape. Best-effort: any failure leaves v1 untouched so the runtime
15
+ * fallback keeps serving the legacy token until the next attempt.
15
16
  *
16
- * Order of operations is deliberate so the migration is genuinely one-way:
17
+ * Order is deliberate so the migration is one-way AND safe under retry:
17
18
  *
18
- * 1. `hasMigrated()` short-circuits if the durable marker is already set.
19
- * 2. Read the v1 token (legacy keyring slot first, then plaintext).
20
- * 3. `identifyAccount(token)` resolves the v2 `account` shape.
21
- * 4. `writeRecordWithKeyringFallback` writes the v2 record.
22
- * 5. Best-effort `setDefaultId(account.id)` so the new record is active.
23
- * 6. `markMigrated()` persists the marker. **If this fails, we return
24
- * `skipped` even though the v2 record is on disk** — the marker is
25
- * what prevents re-migration on the next run.
26
- * 7. Best-effort legacy keyring delete + `cleanupLegacyConfig()` run
27
- * concurrently. Failures here are harmless because the marker is set.
19
+ * 1. `hasMigrated()` short-circuits when the marker is set.
20
+ * 2. Read the v1 token (legacy keyring first, then plaintext).
21
+ * 3. `identifyAccount(token)` resolves the v2 `account`.
22
+ * 4. **Phase 1** — `ensureV2Record` writes a fallback-bearing record (or
23
+ * no-ops when a v2 record already exists).
24
+ * 5. **Phase 2** when Phase 1 wrote: move the token to the per-user
25
+ * keyring slot and upsert the clean record. When Phase 1 didn't:
26
+ * verify the existing record is readable before retiring legacy.
27
+ * 6. Best-effort `setDefaultId(account.id)`.
28
+ * 7. `markMigrated()` the one-way gate. Failure here surfaces as
29
+ * `skipped(marker-write-failed)` so the caller retries.
30
+ * 8. Best-effort legacy cleanup runs concurrently.
28
31
  */
29
32
  export async function migrateLegacyAuth(options) {
30
33
  const { serviceName, legacyAccount, userRecords, hasMigrated, markMigrated, loadLegacyPlaintextToken, identifyAccount, cleanupLegacyConfig, silent, } = options;
@@ -33,8 +36,6 @@ export async function migrateLegacyAuth(options) {
33
36
  if (await hasMigrated()) {
34
37
  return { status: 'already-migrated' };
35
38
  }
36
- // One legacy-keyring handle covers both the initial read and the
37
- // post-success cleanup delete.
38
39
  const legacyStore = createSecureStore({ serviceName, account: legacyAccount });
39
40
  const legacyToken = await readLegacyToken(legacyStore, loadLegacyPlaintextToken);
40
41
  if (legacyToken.kind === 'none')
@@ -49,28 +50,41 @@ export async function migrateLegacyAuth(options) {
49
50
  catch (error) {
50
51
  return skipped(silent, logPrefix, 'identify-failed', getErrorMessage(error));
51
52
  }
52
- // `writeRecordWithKeyringFallback` swallows `SecureStoreUnavailableError`
53
- // internally (writing to `fallbackToken` instead), so any error here is
54
- // a non-keyring failure — typically a `userRecords.upsert` rejection.
53
+ let phase1;
55
54
  try {
56
- await writeRecordWithKeyringFallback({
57
- secureStore: createSecureStore({
58
- serviceName,
59
- account: accountForUser(account.id),
60
- }),
61
- userRecords,
62
- account,
63
- token: legacyToken.token,
64
- });
55
+ phase1 = await ensureV2Record(userRecords, account, legacyToken.token);
65
56
  }
66
57
  catch (error) {
67
58
  return skipped(silent, logPrefix, 'user-record-write-failed', getErrorMessage(error));
68
59
  }
69
- // Default promotion is best-effort and **only fires when nothing is
70
- // already pinned**. A retry after a previous `markMigrated()` failure
71
- // can land on a store where the user has since logged in to a different
72
- // account and picked it as default — blindly setting the legacy account
73
- // back as default would silently switch the user.
60
+ const userSlot = createSecureStore({
61
+ serviceName,
62
+ account: accountForUser(account.id),
63
+ });
64
+ // Run Phase 2 when EITHER Phase 1 just wrote the fallback record OR
65
+ // the existing record's fallback matches our legacy token — that's a
66
+ // prior-run Phase 1 we owe an upgrade. Other existing records are
67
+ // external state and get a readability check instead.
68
+ const isOurPriorPhase1 = !phase1.written && phase1.existing.fallbackToken?.trim() === legacyToken.token;
69
+ if (phase1.written || isOurPriorPhase1) {
70
+ const phase2Error = await runPhase2(userRecords, userSlot, account, legacyToken.token);
71
+ if (phase2Error) {
72
+ return skipped(silent, logPrefix, phase2Error.reason, phase2Error.detail);
73
+ }
74
+ }
75
+ else {
76
+ // External record — cleaning up legacy is safe only if it can be
77
+ // read in the current environment.
78
+ const outcome = await readAccessTokenForRecord(phase1.existing, userSlot);
79
+ if (!outcome.ok) {
80
+ const reason = outcome.reason === 'slot-unavailable'
81
+ ? 'user-keyring-unreachable'
82
+ : 'user-record-write-failed';
83
+ return skipped(silent, logPrefix, reason, outcome.detail);
84
+ }
85
+ }
86
+ // Only promote when nothing is pinned — a retry must not overwrite a
87
+ // default the user chose between attempts.
74
88
  try {
75
89
  const existingDefault = await userRecords.getDefaultId();
76
90
  if (!existingDefault) {
@@ -80,36 +94,84 @@ export async function migrateLegacyAuth(options) {
80
94
  catch {
81
95
  // best-effort
82
96
  }
83
- // Persist the one-way marker BEFORE legacy cleanup. If this fails, the
84
- // v2 record is already written but the gate is unset — surface as
85
- // `skipped` so the caller retries. Without this ordering, a later
86
- // `logout` could let the next run re-migrate the stale v1 token.
97
+ // Marker BEFORE cleanup: the gate, not cleanup, is what prevents the
98
+ // next run from re-migrating after a later `logout`.
87
99
  try {
88
100
  await markMigrated();
89
101
  }
90
102
  catch (error) {
91
103
  return skipped(silent, logPrefix, 'marker-write-failed', getErrorMessage(error));
92
104
  }
93
- // Best-effort legacy cleanup runs concurrently so we don't pay
94
- // keyring latency followed by config-write latency on every install.
95
- // The marker is already set, so a failure here can't cause
96
- // re-migration on the next run. The `Promise.resolve().then(...)`
97
- // wrappers convert any *synchronous* throw from a consumer-supplied
98
- // `cleanupLegacyConfig` (or an oddly-implemented `SecureStore`) into
99
- // a rejected promise that `Promise.allSettled` can swallow.
105
+ // `Promise.resolve().then(...)` converts any *synchronous* throw from
106
+ // a consumer's `cleanupLegacyConfig` into a rejection that
107
+ // `allSettled` can swallow.
100
108
  await Promise.allSettled([
101
109
  Promise.resolve().then(() => legacyStore.deleteSecret()),
102
110
  Promise.resolve().then(() => cleanupLegacyConfig?.()),
103
111
  ]);
104
112
  if (!silent) {
105
- // No identifier in the log line`account.id` is typed as `string`
106
- // but consumers can legitimately use an email or other PII there.
107
- // Callers that need richer telemetry can compose it from the
108
- // returned `account`.
113
+ // Account id may carry PII (email, etc.) keep it out of logs.
109
114
  console.error(`${logPrefix}: migrated existing token to multi-user store.`);
110
115
  }
111
116
  return { status: 'migrated', account };
112
117
  }
118
+ /**
119
+ * Phase 1. Writes a `fallbackToken`-bearing record so a crash before
120
+ * Phase 2 still leaves a working credential. Returns `{ written: true }`
121
+ * when this call wrote, or `{ written: false, existing }` when a v2
122
+ * record already existed — the existing record is returned so callers
123
+ * decide whether to upgrade it (Phase 2 retry) or treat it as external
124
+ * state, without paying a second `list()`.
125
+ */
126
+ async function ensureV2Record(userRecords, account, legacyToken) {
127
+ const record = buildSingleTokenRecord(account, legacyToken);
128
+ if (userRecords.tryInsert) {
129
+ const wrote = await userRecords.tryInsert(record);
130
+ if (wrote)
131
+ return { written: true };
132
+ const existing = (await userRecords.list()).find((r) => r.account.id === account.id);
133
+ if (!existing) {
134
+ throw new Error('tryInsert returned false but no matching record was listed');
135
+ }
136
+ return { written: false, existing };
137
+ }
138
+ // Non-atomic path. Narrow time-of-check, time-of-use race between
139
+ // `list()` and `upsert()`; acceptable for one-time migration since
140
+ // concurrent runs would write the same shape.
141
+ const all = await userRecords.list();
142
+ const existing = all.find((r) => r.account.id === account.id);
143
+ if (existing)
144
+ return { written: false, existing };
145
+ await userRecords.upsert(record);
146
+ return { written: true };
147
+ }
148
+ /**
149
+ * Phase 2: move the legacy token into the per-user keyring slot and
150
+ * upsert a clean (no `fallbackToken`) record. Inlined rather than
151
+ * delegating to `writeRecordWithKeyringFallback` so the offline-keyring
152
+ * branch doesn't double-upsert the same fallback record Phase 1 just
153
+ * wrote. Returns `null` on success (including the silently-handled
154
+ * SecureStoreUnavailable case); a skip descriptor when a non-keyring
155
+ * failure leaves the marker unset for retry.
156
+ */
157
+ async function runPhase2(userRecords, userSlot, account, legacyToken) {
158
+ try {
159
+ await userSlot.setSecret(legacyToken);
160
+ }
161
+ catch (error) {
162
+ if (error instanceof SecureStoreUnavailableError) {
163
+ return null; // Phase 1 fallback record continues to serve reads.
164
+ }
165
+ return { reason: 'user-record-write-failed', detail: getErrorMessage(error) };
166
+ }
167
+ try {
168
+ await userRecords.upsert(buildSingleTokenRecord(account));
169
+ }
170
+ catch (error) {
171
+ return { reason: 'user-record-write-failed', detail: getErrorMessage(error) };
172
+ }
173
+ return null;
174
+ }
113
175
  async function readLegacyToken(legacyStore, loadLegacyPlaintextToken) {
114
176
  let keyringUnavailable = false;
115
177
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/auth/keyring/migrate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/auth/keyring/migrate.ts"],"names":[],"mappings":"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"}