@doist/cli-core 0.12.0 → 0.13.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.
- package/CHANGELOG.md +6 -0
- package/README.md +41 -14
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keyring/index.d.ts +3 -0
- package/dist/auth/keyring/index.d.ts.map +1 -0
- package/dist/auth/keyring/index.js +2 -0
- package/dist/auth/keyring/index.js.map +1 -0
- package/dist/auth/keyring/secure-store.d.ts +39 -0
- package/dist/auth/keyring/secure-store.d.ts.map +1 -0
- package/dist/auth/keyring/secure-store.js +71 -0
- package/dist/auth/keyring/secure-store.js.map +1 -0
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.13.0](https://github.com/Doist/cli-core/compare/v0.12.0...v0.13.0) (2026-05-16)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **auth:** add createSecureStore keyring primitive (1/4) ([#25](https://github.com/Doist/cli-core/issues/25)) ([30bcbd7](https://github.com/Doist/cli-core/commit/30bcbd7e49aede98d0777424169324af03f56eaf))
|
|
6
|
+
|
|
1
7
|
## [0.12.0](https://github.com/Doist/cli-core/compare/v0.11.0...v0.12.0) (2026-05-14)
|
|
2
8
|
|
|
3
9
|
### Features
|
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
|
|
16
|
-
| -------------------- |
|
|
17
|
-
| `auth` (subpath) | `attachLoginCommand`, `attachLogoutCommand`, `attachStatusCommand`, `attachTokenViewCommand`, `runOAuthFlow`, `createPkceProvider`, PKCE helpers, `AuthProvider` / `TokenStore` / `AccountRef` 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`)
|
|
18
|
-
| `commands` (subpath) | `registerChangelogCommand`, `registerUpdateCommand` (+ semver helpers)
|
|
19
|
-
| `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig`, `CoreConfig`, `UpdateChannel`
|
|
20
|
-
| `empty` | `printEmpty`
|
|
21
|
-
| `errors` | `CliError`
|
|
22
|
-
| `global-args` | `parseGlobalArgs`, `stripUserFlag`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled`
|
|
23
|
-
| `json` | `formatJson`, `formatNdjson`
|
|
24
|
-
| `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions`
|
|
25
|
-
| `options` | `ViewOptions`
|
|
26
|
-
| `spinner` | `createSpinner`
|
|
27
|
-
| `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY`
|
|
28
|
-
| `testing` (subpath) | `describeEmptyMachineOutput`
|
|
15
|
+
| Module | Key exports | Purpose |
|
|
16
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `auth` (subpath) | `attachLoginCommand`, `attachLogoutCommand`, `attachStatusCommand`, `attachTokenViewCommand`, `runOAuthFlow`, `createPkceProvider`, `createSecureStore`, PKCE helpers, `AuthProvider` / `TokenStore` / `AccountRef` / `SecureStore` 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`) and a thin cross-platform OS-keyring wrapper (`createSecureStore`) backed by `@napi-rs/keyring`. `AuthProvider` and `TokenStore` are the escape hatches for DCR, OS-keychain, multi-account, etc. — consumers implement `TokenStore` directly (single-user store implements `list` / `setDefault` trivially against the one account). `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`) 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,33 @@ 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
|
+
#### Keyring primitive (`createSecureStore`)
|
|
292
|
+
|
|
293
|
+
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`:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { createSecureStore, SecureStoreUnavailableError } from '@doist/cli-core/auth'
|
|
297
|
+
|
|
298
|
+
const slot = createSecureStore({ serviceName: 'todoist-cli', account: 'api-token' })
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
await slot.setSecret(token)
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error instanceof SecureStoreUnavailableError) {
|
|
304
|
+
// Keyring unreachable (WSL without D-Bus, missing native binary on an
|
|
305
|
+
// exotic arch, Keychain locked, …). Fall back to wherever your CLI
|
|
306
|
+
// stores fallback state — config file with a clear plaintext warning,
|
|
307
|
+
// an env-var prompt, etc.
|
|
308
|
+
} else {
|
|
309
|
+
throw error
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Every failure mode — `@napi-rs/keyring` failing to load on an arch without a prebuilt binary, libsecret not running on a headless Linux box, the Keychain prompting and the user denying — is normalised into `SecureStoreUnavailableError`. The `@napi-rs/keyring` module is dynamic-imported so a missing native binary doesn't crash module load before the error can surface.
|
|
315
|
+
|
|
316
|
+
`@napi-rs/keyring` is declared in cli-core's own `optionalDependencies`, so npm pulls it in transitively when you install `@doist/cli-core` — your consumer CLI does not need to add it explicitly. The library ships pre-built native binaries for Windows (Credential Manager), macOS (Keychain), and Linux glibc + musl (libsecret / Secret Service).
|
|
317
|
+
|
|
291
318
|
#### `--user <ref>` and multi-user wiring
|
|
292
319
|
|
|
293
320
|
The three account-touching attachers (`attachLogoutCommand` / `attachStatusCommand` / `attachTokenViewCommand`) always attach `--user <ref>` on their subcommand. `attachLogoutCommand` threads the parsed ref to both `store.active(ref)` and `store.clear(ref)`; `attachStatusCommand` and `attachTokenViewCommand` only call `store.active(ref)`. When `--user` is supplied but `store.active(ref)` returns `null`, each attacher throws `CliError('ACCOUNT_NOT_FOUND', …)` so the user sees a typed miss rather than `NOT_AUTHENTICATED` or a silent `✓ Logged out`. Single-user stores returning `null` for a non-matching ref is the supported way to feed this guard.
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -14,4 +14,6 @@ export type { GenerateVerifierOptions } from './pkce.js';
|
|
|
14
14
|
export { createPkceProvider } from './providers/pkce.js';
|
|
15
15
|
export type { PkceLazyString, PkceProviderOptions } from './providers/pkce.js';
|
|
16
16
|
export type { AccountRef, AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, TokenStore, ValidateInput, } from './types.js';
|
|
17
|
+
export { SecureStoreUnavailableError, createSecureStore } from './keyring/index.js';
|
|
18
|
+
export type { CreateSecureStoreOptions, SecureStore } from './keyring/index.js';
|
|
17
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -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"}
|
|
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,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACnF,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/auth/index.js
CHANGED
|
@@ -5,4 +5,5 @@ 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
7
|
export { createPkceProvider } from './providers/pkce.js';
|
|
8
|
+
export { SecureStoreUnavailableError, createSecureStore } from './keyring/index.js';
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
package/dist/auth/index.js.map
CHANGED
|
@@ -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"}
|
|
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,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAClF,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/keyring/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when the OS credential manager cannot be reached — missing native
|
|
3
|
+
* binary for the current architecture, libsecret/D-Bus unavailable
|
|
4
|
+
* (common in WSL / headless Linux / containers / CI), Keychain locked, or
|
|
5
|
+
* any other error returned by the underlying keyring backend.
|
|
6
|
+
*
|
|
7
|
+
* The original throwable is preserved on `cause` so platform-specific
|
|
8
|
+
* diagnostics (stack, native error code) aren't lost — callers that want
|
|
9
|
+
* to degrade to a plaintext fallback should catch this specific class
|
|
10
|
+
* rather than swallowing every `Error`.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SecureStoreUnavailableError extends Error {
|
|
13
|
+
constructor(message?: string, options?: {
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export type SecureStore = {
|
|
18
|
+
getSecret(): Promise<string | null>;
|
|
19
|
+
setSecret(secret: string): Promise<void>;
|
|
20
|
+
deleteSecret(): Promise<boolean>;
|
|
21
|
+
};
|
|
22
|
+
export type CreateSecureStoreOptions = {
|
|
23
|
+
/** Stable per-application identifier (Keychain "service", Credential Manager "target prefix", libsecret "service"). */
|
|
24
|
+
serviceName: string;
|
|
25
|
+
/** Per-credential identifier within the service. For multi-account CLIs, typically `user-${id}`. */
|
|
26
|
+
account: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Thin wrapper around `@napi-rs/keyring` that normalizes every failure mode
|
|
30
|
+
* into `SecureStoreUnavailableError`. `serviceName` + `account` together
|
|
31
|
+
* identify one credential slot in the OS keyring.
|
|
32
|
+
*
|
|
33
|
+
* The dynamic import + `AsyncEntry` construction is memoised per-store so
|
|
34
|
+
* repeated reads/writes share one entry and a missing native binary
|
|
35
|
+
* fast-fails on subsequent calls instead of retrying the import every time
|
|
36
|
+
* (the rejected promise replays its rejection on each `await`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function createSecureStore(options: CreateSecureStoreOptions): SecureStore;
|
|
39
|
+
//# sourceMappingURL=secure-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-store.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/secure-store.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBAE9C,OAAO,SAA6C,EACpD,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAKpC;AAED,MAAM,MAAM,WAAW,GAAG;IACtB,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACnC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACnC,uHAAuH;IACvH,WAAW,EAAE,MAAM,CAAA;IACnB,oGAAoG;IACpG,OAAO,EAAE,MAAM,CAAA;CAClB,CAAA;AAID;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,WAAW,CAoChF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getErrorMessage } from '../../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the OS credential manager cannot be reached — missing native
|
|
4
|
+
* binary for the current architecture, libsecret/D-Bus unavailable
|
|
5
|
+
* (common in WSL / headless Linux / containers / CI), Keychain locked, or
|
|
6
|
+
* any other error returned by the underlying keyring backend.
|
|
7
|
+
*
|
|
8
|
+
* The original throwable is preserved on `cause` so platform-specific
|
|
9
|
+
* diagnostics (stack, native error code) aren't lost — callers that want
|
|
10
|
+
* to degrade to a plaintext fallback should catch this specific class
|
|
11
|
+
* rather than swallowing every `Error`.
|
|
12
|
+
*/
|
|
13
|
+
export class SecureStoreUnavailableError extends Error {
|
|
14
|
+
constructor(message = 'System credential storage is unavailable', options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.name = 'SecureStoreUnavailableError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Thin wrapper around `@napi-rs/keyring` that normalizes every failure mode
|
|
21
|
+
* into `SecureStoreUnavailableError`. `serviceName` + `account` together
|
|
22
|
+
* identify one credential slot in the OS keyring.
|
|
23
|
+
*
|
|
24
|
+
* The dynamic import + `AsyncEntry` construction is memoised per-store so
|
|
25
|
+
* repeated reads/writes share one entry and a missing native binary
|
|
26
|
+
* fast-fails on subsequent calls instead of retrying the import every time
|
|
27
|
+
* (the rejected promise replays its rejection on each `await`).
|
|
28
|
+
*/
|
|
29
|
+
export function createSecureStore(options) {
|
|
30
|
+
const { serviceName, account } = options;
|
|
31
|
+
let entryPromise;
|
|
32
|
+
async function withEntry(fn) {
|
|
33
|
+
if (!entryPromise) {
|
|
34
|
+
// Dynamic import: `@napi-rs/keyring` is an optional dependency.
|
|
35
|
+
// On unsupported architectures the native binary is absent and
|
|
36
|
+
// a static import would crash module load before we can surface
|
|
37
|
+
// `SecureStoreUnavailableError`.
|
|
38
|
+
entryPromise = (async () => {
|
|
39
|
+
const { AsyncEntry } = await import('@napi-rs/keyring');
|
|
40
|
+
return new AsyncEntry(serviceName, account);
|
|
41
|
+
})();
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const entry = await entryPromise;
|
|
45
|
+
return await fn(entry);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw toUnavailableError(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
async getSecret() {
|
|
53
|
+
return withEntry(async (entry) => (await entry.getPassword()) ?? null);
|
|
54
|
+
},
|
|
55
|
+
async setSecret(secret) {
|
|
56
|
+
return withEntry(async (entry) => {
|
|
57
|
+
await entry.setPassword(secret);
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
async deleteSecret() {
|
|
61
|
+
return withEntry((entry) => entry.deleteCredential());
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function toUnavailableError(error) {
|
|
66
|
+
if (error instanceof SecureStoreUnavailableError) {
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
return new SecureStoreUnavailableError(getErrorMessage(error), { cause: error });
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=secure-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-store.js","sourceRoot":"","sources":["../../../src/auth/keyring/secure-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD;;;;;;;;;;GAUG;AACH,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAClD,YACI,OAAO,GAAG,0CAA0C,EACpD,OAA6B;QAE7B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAA;IAC7C,CAAC;CACJ;AAiBD;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAiC;IAC/D,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IACxC,IAAI,YAA6C,CAAA;IAEjD,KAAK,UAAU,SAAS,CAAI,EAAqC;QAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,gEAAgE;YAChE,+DAA+D;YAC/D,gEAAgE;YAChE,iCAAiC;YACjC,YAAY,GAAG,CAAC,KAAK,IAAI,EAAE;gBACvB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;gBACvD,OAAO,IAAI,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;YAC/C,CAAC,CAAC,EAAE,CAAA;QACR,CAAC;QACD,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,YAAY,CAAA;YAChC,OAAO,MAAM,EAAE,CAAC,KAAK,CAAC,CAAA;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAA;QACnC,CAAC;IACL,CAAC;IAED,OAAO;QACH,KAAK,CAAC,SAAS;YACX,OAAO,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,IAAI,CAAC,CAAA;QAC1E,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,MAAM;YAClB,OAAO,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;gBAC7B,MAAM,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YACnC,CAAC,CAAC,CAAA;QACN,CAAC;QACD,KAAK,CAAC,YAAY;YACd,OAAO,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAA;QACzD,CAAC;KACJ,CAAA;AACL,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACtC,IAAI,KAAK,YAAY,2BAA2B,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAA;IAChB,CAAC;IACD,OAAO,IAAI,2BAA2B,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;AACpF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Shared core utilities for Doist CLI projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -83,6 +83,9 @@
|
|
|
83
83
|
"chalk": "5.6.2",
|
|
84
84
|
"yocto-spinner": "1.2.0"
|
|
85
85
|
},
|
|
86
|
+
"optionalDependencies": {
|
|
87
|
+
"@napi-rs/keyring": "1.3.0"
|
|
88
|
+
},
|
|
86
89
|
"peerDependencies": {
|
|
87
90
|
"commander": ">=14",
|
|
88
91
|
"marked": ">=18",
|