@theholocron/holocron-plugin-doppler 2.0.0-alpha.8 → 2.0.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/README.md CHANGED
@@ -8,8 +8,10 @@ plus exports `verifyToken` + `AUTH_HINT` for use by `holocron auth`.
8
8
 
9
9
  ## Install
10
10
 
11
+ <!-- prettier-ignore -->
11
12
  ```bash
12
13
  pnpm add -D @theholocron/holocron-plugin-doppler@alpha
14
+
13
15
  ```
14
16
 
15
17
  ## Auth
@@ -28,6 +30,7 @@ Token resolution order (matches the standard 4-step precedence set by
28
30
  Doppler's free tier does not expose Service Accounts (Team+ only). Use
29
31
  a Personal Token or CLI token instead.
30
32
 
33
+ <!-- prettier-ignore -->
31
34
  ```bash
32
35
  # 1. Install the Doppler CLI
33
36
  brew install dopplerhq/cli/doppler
@@ -43,6 +46,7 @@ holocron auth set doppler $(doppler configure get token --plain)
43
46
 
44
47
  # 4. Verify:
45
48
  holocron auth check doppler
49
+
46
50
  ```
47
51
 
48
52
  **CI**: the keyring is not available in headless containers. Expose
@@ -52,12 +56,14 @@ precedence still work; step 4 quietly falls through.
52
56
 
53
57
  ## Config
54
58
 
59
+ <!-- prettier-ignore -->
55
60
  ```jsonc
56
61
  {
57
- "providers": {
58
- "vault": ["doppler", { "project": "my-app", "config": "dev" }],
59
- },
62
+ "providers": {
63
+ "vault": ["doppler", { "project": "my-app", "config": "dev" }],
64
+ },
60
65
  }
66
+
61
67
  ```
62
68
 
63
69
  - `project` (required) — Doppler project name. `read` / `list` /
@@ -67,8 +73,10 @@ precedence still work; step 4 quietly falls through.
67
73
 
68
74
  Individual `read` / `write` calls take a fully-qualified reference:
69
75
 
76
+ <!-- prettier-ignore -->
70
77
  ```
71
78
  doppler://<project>/<config>/<name>
79
+
72
80
  ```
73
81
 
74
82
  The default project + config in options apply to `list()`,
package/dist/index.d.mts CHANGED
@@ -1,58 +1,8 @@
1
- import { EnsureResult, Vault } from "@theholocron/cli";
1
+ import { AuthError, EnsureResult, ResolveTokenInput, Vault } from "@theholocron/cli";
2
+ import { DopplerClient, createDopplerClient } from "@theholocron/doppler-client";
2
3
 
3
4
  //#region src/auth.d.ts
4
- /**
5
- * Token resolution for the Doppler plugin.
6
- *
7
- * Resolution order (matches the standard 4-step precedence set by
8
- * `.notes/tech-auth-bootstrap.spec.md`):
9
- * 1. explicit `cliToken` argument (from `--token` flag)
10
- * 2. HOLOCRON_DOPPLER_TOKEN env var (preferred — explicit intent)
11
- * 3. DOPPLER_TOKEN env var (Doppler-native, works in CI)
12
- * 4. keyring (com.theholocron.cli / "doppler")
13
- * 5. AuthError with a hint pointing at `doppler configure`
14
- */
15
- declare class AuthError extends Error {
16
- name: string;
17
- }
18
- interface ResolveTokenInput {
19
- /** From `--token` CLI flag. */
20
- cliToken?: string;
21
- /** Env vars; passed in for testability. Defaults to `process.env`. */
22
- env?: NodeJS.ProcessEnv;
23
- /** Keyring lookup fn; passed in for testability. Defaults to `getToken(provider)`. */
24
- keyring?: (provider: string) => string | null;
25
- }
26
- declare function resolveToken(input?: ResolveTokenInput): string;
27
- //#endregion
28
- //#region src/rest.d.ts
29
- /**
30
- * Thin REST wrapper around api.doppler.com/v3.
31
- *
32
- * Same pattern as the GitHub / Vercel / Neon REST clients — bearer
33
- * auth, JSON-only bodies, transport-failure wrapping with `status: 0`
34
- * so the orchestrator's soft-skip path sees a clear "Doppler GET /path
35
- * failed" message instead of a generic `TypeError: fetch failed`.
36
- */
37
- interface RestClientOptions {
38
- token: string;
39
- fetch?: typeof fetch;
40
- baseUrl?: string;
41
- }
42
- interface RequestOptions {
43
- method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
44
- body?: unknown;
45
- query?: Record<string, string>;
46
- /** Treat this response as void even if 200 is returned. */
47
- expectNoContent?: boolean;
48
- }
49
- declare class DopplerRestClient {
50
- private readonly token;
51
- private readonly fetchImpl;
52
- readonly baseUrl: string;
53
- constructor(opts: RestClientOptions);
54
- request<T>(path: string, opts?: RequestOptions): Promise<T>;
55
- }
5
+ declare const resolveToken: (input?: import("@theholocron/http-client").ResolveTokenInput) => string;
56
6
  //#endregion
57
7
  //#region src/capabilities/vault.d.ts
58
8
  interface DopplerVaultOptions {
@@ -62,12 +12,12 @@ interface DopplerVaultOptions {
62
12
  config: string;
63
13
  }
64
14
  declare class DopplerVault implements Vault {
65
- private readonly rest;
15
+ private readonly client;
66
16
  readonly key: "vault";
67
17
  readonly providerName = "doppler";
68
18
  private readonly project;
69
19
  private readonly config;
70
- constructor(rest: DopplerRestClient, opts: DopplerVaultOptions);
20
+ constructor(client: DopplerClient, opts: DopplerVaultOptions);
71
21
  read(reference: string): Promise<string>;
72
22
  write(reference: string, value: string): Promise<void>;
73
23
  list(): Promise<string[]>;
@@ -112,7 +62,7 @@ interface DopplerPluginOptions extends ResolveTokenInput, DopplerVaultOptions {
112
62
  }
113
63
  interface PluginContext {
114
64
  options: DopplerPluginOptions;
115
- rest: DopplerRestClient;
65
+ client: DopplerClient;
116
66
  }
117
67
  declare function createContext(options: DopplerPluginOptions): PluginContext;
118
68
  declare function vault(ctx: PluginContext): Vault;
@@ -128,4 +78,4 @@ declare function createPlugin(options: DopplerPluginOptions): {
128
78
  */
129
79
  declare const AUTH_HINT: string;
130
80
  //#endregion
131
- export { AUTH_HINT, AuthError, DopplerPluginOptions, DopplerRestClient, DopplerVault, PluginContext, ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createPlugin, resolveToken, vault, verifyToken };
81
+ export { AUTH_HINT, AuthError, DopplerPluginOptions, DopplerVault, PluginContext, type ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createDopplerClient, createPlugin, resolveToken, vault, verifyToken };
package/dist/index.mjs CHANGED
@@ -1,26 +1,12 @@
1
- import { ProviderApiError, getToken } from "@theholocron/cli";
1
+ import { AuthError, ProviderApiError, createResolveToken } from "@theholocron/cli";
2
+ import { createDopplerClient } from "@theholocron/doppler-client";
2
3
  //#region src/auth.ts
3
- /**
4
- * Token resolution for the Doppler plugin.
5
- *
6
- * Resolution order (matches the standard 4-step precedence set by
7
- * `.notes/tech-auth-bootstrap.spec.md`):
8
- * 1. explicit `cliToken` argument (from `--token` flag)
9
- * 2. HOLOCRON_DOPPLER_TOKEN env var (preferred — explicit intent)
10
- * 3. DOPPLER_TOKEN env var (Doppler-native, works in CI)
11
- * 4. keyring (com.theholocron.cli / "doppler")
12
- * 5. AuthError with a hint pointing at `doppler configure`
13
- */
14
- var AuthError = class extends Error {
15
- name = "AuthError";
16
- };
17
- function resolveToken(input = {}) {
18
- const env = input.env ?? process.env;
19
- const keyring = input.keyring ?? getToken;
20
- const token = input.cliToken || env.HOLOCRON_DOPPLER_TOKEN || env.DOPPLER_TOKEN || keyring("doppler");
21
- if (!token) throw new AuthError("no Doppler token found. Pass --token <TOKEN>, set HOLOCRON_DOPPLER_TOKEN / DOPPLER_TOKEN, or run: holocron auth set doppler $(doppler configure get token --plain)");
22
- return token;
23
- }
4
+ const resolveToken = createResolveToken({
5
+ envName: "HOLOCRON_DOPPLER_TOKEN",
6
+ vendorEnvName: "DOPPLER_TOKEN",
7
+ keyringService: "doppler",
8
+ errorMessage: "no Doppler token found. Pass --token <TOKEN>, set HOLOCRON_DOPPLER_TOKEN / DOPPLER_TOKEN, or run: holocron auth set doppler $(doppler configure get token --plain)"
9
+ });
24
10
  //#endregion
25
11
  //#region src/capabilities/vault.ts
26
12
  /**
@@ -48,13 +34,13 @@ function resolveToken(input = {}) {
48
34
  * ProviderApiError with `status: 409` which we catch and swallow.
49
35
  */
50
36
  var DopplerVault = class {
51
- rest;
37
+ client;
52
38
  key = "vault";
53
39
  providerName = "doppler";
54
40
  project;
55
41
  config;
56
- constructor(rest, opts) {
57
- this.rest = rest;
42
+ constructor(client, opts) {
43
+ this.client = client;
58
44
  if (!opts.project) throw new Error("DopplerVault requires `project` in options");
59
45
  if (!opts.config) throw new Error("DopplerVault requires `config` in options");
60
46
  this.project = opts.project;
@@ -62,53 +48,29 @@ var DopplerVault = class {
62
48
  }
63
49
  async read(reference) {
64
50
  const parsed = parseReference(reference);
65
- const res = await this.rest.request("/configs/config/secret", { query: {
66
- project: parsed.project,
67
- config: parsed.config,
68
- name: parsed.name
69
- } });
51
+ const res = await this.client.secrets.get(parsed.project, parsed.config, parsed.name);
70
52
  return res.value?.computed ?? res.value?.raw ?? "";
71
53
  }
72
54
  async write(reference, value) {
73
55
  const parsed = parseReference(reference);
74
- await this.rest.request("/configs/config/secrets", {
75
- method: "POST",
76
- body: {
77
- project: parsed.project,
78
- config: parsed.config,
79
- secrets: { [parsed.name]: value }
80
- }
81
- });
56
+ await this.client.secrets.update(parsed.project, parsed.config, { [parsed.name]: value });
82
57
  }
83
58
  async list() {
84
- const res = await this.rest.request("/configs/config/secrets", { query: {
85
- project: this.project,
86
- config: this.config
87
- } });
59
+ const res = await this.client.secrets.list(this.project, this.config);
88
60
  return Object.keys(res.secrets ?? {});
89
61
  }
90
62
  async environments() {
91
- return ((await this.rest.request("/environments", { query: { project: this.project } })).environments ?? []).map((e) => e.slug ?? e.name ?? "").filter(Boolean);
63
+ return ((await this.client.environments.list(this.project)).environments ?? []).map((e) => e.slug ?? e.name ?? "").filter(Boolean);
92
64
  }
93
65
  async readEnvironment(environmentId) {
94
- const res = await this.rest.request("/configs/config/secrets/download", { query: {
95
- project: this.project,
96
- config: environmentId,
97
- format: "json"
98
- } });
66
+ const res = await this.client.secrets.download(this.project, environmentId);
99
67
  const out = {};
100
68
  for (const [k, v] of Object.entries(res)) if (typeof v === "string") out[k] = v;
101
69
  return out;
102
70
  }
103
71
  async ensureProject(name) {
104
72
  try {
105
- await this.rest.request("/projects", {
106
- method: "POST",
107
- body: {
108
- name,
109
- description: `Managed by holocron`
110
- }
111
- });
73
+ await this.client.projects.create(name, "Managed by holocron");
112
74
  return { alreadyExists: false };
113
75
  } catch (err) {
114
76
  if (isConflict(err)) return { alreadyExists: true };
@@ -117,14 +79,7 @@ var DopplerVault = class {
117
79
  }
118
80
  async ensureEnvironment(project, name) {
119
81
  try {
120
- await this.rest.request("/environments", {
121
- method: "POST",
122
- body: {
123
- project,
124
- name,
125
- slug: name
126
- }
127
- });
82
+ await this.client.environments.create(project, name, name);
128
83
  return { alreadyExists: false };
129
84
  } catch (err) {
130
85
  if (isConflict(err)) return { alreadyExists: true };
@@ -171,60 +126,6 @@ function hasAlreadyExistsBody(details) {
171
126
  return /already exists/i.test(details);
172
127
  }
173
128
  //#endregion
174
- //#region src/rest.ts
175
- /**
176
- * Thin REST wrapper around api.doppler.com/v3.
177
- *
178
- * Same pattern as the GitHub / Vercel / Neon REST clients — bearer
179
- * auth, JSON-only bodies, transport-failure wrapping with `status: 0`
180
- * so the orchestrator's soft-skip path sees a clear "Doppler GET /path
181
- * failed" message instead of a generic `TypeError: fetch failed`.
182
- */
183
- var DopplerRestClient = class {
184
- token;
185
- fetchImpl;
186
- baseUrl;
187
- constructor(opts) {
188
- this.token = opts.token;
189
- this.fetchImpl = opts.fetch ?? globalThis.fetch;
190
- let url = opts.baseUrl ?? "https://api.doppler.com/v3";
191
- while (url.endsWith("/")) url = url.slice(0, -1);
192
- this.baseUrl = url;
193
- }
194
- async request(path, opts = {}) {
195
- const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`);
196
- for (const [k, v] of Object.entries(opts.query ?? {})) url.searchParams.set(k, v);
197
- const fullUrl = url.toString();
198
- const headers = {
199
- authorization: `Bearer ${this.token}`,
200
- accept: "application/json"
201
- };
202
- const init = {
203
- method: opts.method ?? "GET",
204
- headers
205
- };
206
- if (opts.body !== void 0) {
207
- headers["content-type"] = "application/json";
208
- init.body = JSON.stringify(opts.body);
209
- }
210
- let res;
211
- try {
212
- res = await this.fetchImpl(fullUrl, init);
213
- } catch (err) {
214
- const detail = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
215
- throw new ProviderApiError(`Doppler ${init.method} ${path} failed: ${detail}`, 0, void 0);
216
- }
217
- if (!res.ok) {
218
- const body = await res.text().catch(() => "");
219
- throw new ProviderApiError(`Doppler ${init.method} ${path} → ${res.status}`, res.status, body);
220
- }
221
- if (opts.expectNoContent || res.status === 204) return void 0;
222
- const text = await res.text();
223
- if (!text) return void 0;
224
- return JSON.parse(text);
225
- }
226
- };
227
- //#endregion
228
129
  //#region src/verify-token.ts
229
130
  /**
230
131
  * `verifyToken` — plugin-level export used by `holocron auth set` +
@@ -237,12 +138,13 @@ var DopplerRestClient = class {
237
138
  * what we don't have yet at bootstrap time.
238
139
  */
239
140
  async function verifyToken(token, opts = {}) {
240
- const restOpts = { token };
241
- if (opts.baseUrl !== void 0) restOpts.baseUrl = opts.baseUrl;
242
- if (opts.fetch !== void 0) restOpts.fetch = opts.fetch;
243
- const rest = new DopplerRestClient(restOpts);
141
+ const client = createDopplerClient({
142
+ token,
143
+ baseUrl: opts.baseUrl,
144
+ fetch: opts.fetch
145
+ });
244
146
  try {
245
- const me = await rest.request("/me");
147
+ const me = await client.me.get();
246
148
  const workplace = me.workplace?.name ?? me.slug ?? "unknown";
247
149
  return {
248
150
  ok: true,
@@ -258,16 +160,17 @@ async function verifyToken(token, opts = {}) {
258
160
  //#endregion
259
161
  //#region src/index.ts
260
162
  function createContext(options) {
261
- const restOpts = { token: resolveToken(options) };
262
- if (options.baseUrl !== void 0) restOpts.baseUrl = options.baseUrl;
263
- if (options.fetch !== void 0) restOpts.fetch = options.fetch;
264
163
  return {
265
164
  options,
266
- rest: new DopplerRestClient(restOpts)
165
+ client: createDopplerClient({
166
+ token: resolveToken(options),
167
+ baseUrl: options.baseUrl,
168
+ fetch: options.fetch
169
+ })
267
170
  };
268
171
  }
269
172
  function vault(ctx) {
270
- return new DopplerVault(ctx.rest, {
173
+ return new DopplerVault(ctx.client, {
271
174
  project: ctx.options.project,
272
175
  config: ctx.options.config
273
176
  });
@@ -285,4 +188,4 @@ function createPlugin(options) {
285
188
  */
286
189
  const AUTH_HINT = "install the Doppler CLI (`brew install dopplerhq/cli/doppler`), run `doppler login`, then `holocron auth set doppler $(doppler configure get token --plain)`";
287
190
  //#endregion
288
- export { AUTH_HINT, AuthError, DopplerRestClient, DopplerVault, createContext, createPlugin, resolveToken, vault, verifyToken };
191
+ export { AUTH_HINT, AuthError, DopplerVault, createContext, createDopplerClient, createPlugin, resolveToken, vault, verifyToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-doppler",
3
- "version": "2.0.0-alpha.8",
3
+ "version": "2.0.0",
4
4
  "description": "Holocron plugin for Doppler. Implements the vault capability against Doppler's REST API — projects, configs, secrets, plus verifyToken + AUTH_HINT for `holocron auth`.",
5
5
  "homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-doppler#readme",
6
6
  "bugs": "https://github.com/theholocron/holocron/issues",
@@ -21,19 +21,26 @@
21
21
  }
22
22
  },
23
23
  "peerDependencies": {
24
- "@theholocron/cli": "2.0.0-alpha.8"
24
+ "@theholocron/doppler-client": "^1.1.0",
25
+ "@theholocron/cli": "2.0.0"
25
26
  },
26
27
  "devDependencies": {
27
- "@theholocron/tsconfig": "^4.1.0",
28
- "@tsconfig/node-lts": "^24.0.0",
29
- "@vitest/coverage-v8": "^3.2.6",
30
- "eslint": "^9.36.0",
31
- "globals": "^16.5.0",
32
- "typescript": "^5.9.3",
33
- "vitest": "^3.2.6",
28
+ "@theholocron/doppler-client": "^1.1.0",
29
+ "@theholocron/eslint-config": "^7.3.0",
30
+ "@theholocron/tsconfig": "^7.3.0",
31
+ "@theholocron/tsdown-config": "^7.3.0",
32
+ "@theholocron/vitest-config": "^7.3.0",
33
+ "@types/node": "^26",
34
+ "@vitest/coverage-v8": "^4.1.10",
35
+ "@vitest/eslint-plugin": "^1.6.23",
36
+ "eslint": "^10.7.0",
37
+ "eslint-plugin-n": "^18.2.2",
38
+ "globals": "^17.7.0",
34
39
  "tsdown": "^0.22.3",
35
40
  "tsx": "^4.22.4",
36
- "@theholocron/cli": "2.0.0-alpha.8"
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^4.1.10",
43
+ "@theholocron/cli": "2.0.0"
37
44
  },
38
45
  "publishConfig": {
39
46
  "access": "public"