@theholocron/holocron-plugin-infisical 2.0.0-alpha.9 → 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
@@ -31,8 +31,10 @@ Switch by editing `holocron.config.json`.
31
31
 
32
32
  ## Install
33
33
 
34
+ <!-- prettier-ignore -->
34
35
  ```bash
35
36
  pnpm add -D @theholocron/holocron-plugin-infisical@alpha
37
+
36
38
  ```
37
39
 
38
40
  ## Auth
@@ -65,12 +67,14 @@ Universal Auth is a two-step flow (client id + client secret → login
65
67
  endpoint → short-lived access token). Support for that exchange is
66
68
  tracked as a follow-up; for now, use one of the two above.
67
69
 
70
+ <!-- prettier-ignore -->
68
71
  ```bash
69
72
  # 1. Generate the token per the note above.
70
73
  # 2. Hand it off to holocron's keyring (one-shot):
71
74
  holocron auth set infisical <TOKEN>
72
75
  # 3. Verify (calls GET /v1/workspace and reports accessible workspaces):
73
76
  holocron auth check infisical
77
+
74
78
  ```
75
79
 
76
80
  **If you have a machine identity that's Universal-Auth-only**: add a
@@ -85,24 +89,28 @@ precedence still work; step 4 quietly falls through.
85
89
 
86
90
  ## Config
87
91
 
92
+ <!-- prettier-ignore -->
88
93
  ```jsonc
89
94
  {
90
- "providers": {
91
- "vault": ["infisical", { "workspace": "<workspace-id>", "environment": "dev" }],
92
- },
95
+ "providers": {
96
+ "vault": ["infisical", { "workspace": "<workspace-id>", "environment": "dev" }],
97
+ },
93
98
  }
99
+
94
100
  ```
95
101
 
96
102
  - `workspace` (required) — Infisical workspace (project) id. Find via
97
103
  the workspace URL or the API. **Not the workspace slug** — Infisical's
98
- API rejects slug in this position ([Infisical#1894](https://github.com/Infisical/infisical/issues/1894)).
104
+ API prevents slug in this position ([Infisical#1894](https://github.com/Infisical/infisical/issues/1894)).
99
105
  - `environment` (required) — Environment slug (usually `dev`, `stg`,
100
106
  or `prd`). `list()` reads secrets from this environment.
101
107
 
102
108
  Individual `read` / `write` calls take a fully-qualified reference:
103
109
 
110
+ <!-- prettier-ignore -->
104
111
  ```
105
112
  infisical://<workspaceId>/<environment>/<name>
113
+
106
114
  ```
107
115
 
108
116
  The default `workspace` + `environment` in options apply to `list()`,
@@ -133,19 +141,21 @@ convention):
133
141
 
134
142
  Set the `baseUrl` option in `holocron.config.json`:
135
143
 
144
+ <!-- prettier-ignore -->
136
145
  ```jsonc
137
146
  {
138
- "providers": {
139
- "vault": [
140
- "infisical",
141
- {
142
- "workspace": "<id>",
143
- "environment": "dev",
144
- "baseUrl": "https://infisical.internal.example.com/api",
145
- },
146
- ],
147
- },
147
+ "providers": {
148
+ "vault": [
149
+ "infisical",
150
+ {
151
+ "workspace": "<id>",
152
+ "environment": "dev",
153
+ "baseUrl": "https://infisical.internal.example.com/api",
154
+ },
155
+ ],
156
+ },
148
157
  }
158
+
149
159
  ```
150
160
 
151
161
  ## Status
package/dist/index.d.mts CHANGED
@@ -1,57 +1,8 @@
1
- import { EnsureResult, Vault } from "@theholocron/cli";
1
+ import { AuthError, EnsureResult, ResolveTokenInput, Vault } from "@theholocron/cli";
2
+ import { InfisicalClient, InfisicalClient as InfisicalClient$1, createInfisicalClient } from "@theholocron/infisical-client";
2
3
 
3
4
  //#region src/auth.d.ts
4
- /**
5
- * Token resolution for the Infisical 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_INFISICAL_TOKEN env var (preferred — explicit intent)
11
- * 3. INFISICAL_TOKEN env var (vendor-native)
12
- * 4. keyring (com.theholocron.cli / "infisical")
13
- * 5. AuthError naming all four options + the bootstrap hint
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 https://app.infisical.com/api.
31
- *
32
- * Bearer auth, JSON-only bodies, transport-failure wrapping with
33
- * `status: 0` so orchestrator soft-skip paths see a clear message
34
- * instead of a generic `TypeError: fetch failed`.
35
- */
36
- interface RestClientOptions {
37
- token: string;
38
- fetch?: typeof fetch;
39
- baseUrl?: string;
40
- }
41
- interface RequestOptions {
42
- method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
43
- body?: unknown;
44
- query?: Record<string, string>;
45
- /** Treat this response as void even if 200 is returned. */
46
- expectNoContent?: boolean;
47
- }
48
- declare class InfisicalRestClient {
49
- private readonly token;
50
- private readonly fetchImpl;
51
- readonly baseUrl: string;
52
- constructor(opts: RestClientOptions);
53
- request<T>(path: string, opts?: RequestOptions): Promise<T>;
54
- }
5
+ declare const resolveToken: (input?: import("@theholocron/http-client").ResolveTokenInput) => string;
55
6
  //#endregion
56
7
  //#region src/capabilities/vault.d.ts
57
8
  interface InfisicalVaultOptions {
@@ -61,7 +12,7 @@ interface InfisicalVaultOptions {
61
12
  environment: string;
62
13
  }
63
14
  declare class InfisicalVault implements Vault {
64
- private readonly rest;
15
+ private readonly client;
65
16
  readonly key: "vault";
66
17
  readonly providerName = "infisical";
67
18
  private readonly workspace;
@@ -69,11 +20,11 @@ declare class InfisicalVault implements Vault {
69
20
  /**
70
21
  * Cache of resolved workspace name/slug → id. `ensureEnvironment`
71
22
  * is called once per environment during `holocron setup` (dev / stg
72
- * / prd), so a single \`GET /v1/workspace\` lookup covers all three
23
+ * / prd), so a single `GET /v1/workspace` lookup covers all three
73
24
  * calls in the same run.
74
25
  */
75
26
  private readonly workspaceIdCache;
76
- constructor(rest: InfisicalRestClient, opts: InfisicalVaultOptions);
27
+ constructor(client: InfisicalClient$1, opts: InfisicalVaultOptions);
77
28
  read(reference: string): Promise<string>;
78
29
  write(reference: string, value: string): Promise<void>;
79
30
  list(): Promise<string[]>;
@@ -81,26 +32,6 @@ declare class InfisicalVault implements Vault {
81
32
  readEnvironment(environmentId: string): Promise<Record<string, string>>;
82
33
  ensureProject(name: string): Promise<EnsureResult>;
83
34
  ensureEnvironment(project: string, name: string): Promise<EnsureResult>;
84
- /**
85
- * Resolve a workspace name / slug to its Infisical workspace id.
86
- *
87
- * `runSetup` calls `ensureEnvironment(config.project.name, envName)`
88
- * — for Doppler that's directly the API's input, but Infisical's
89
- * environments endpoint takes the workspace **id** (UUID/nanoid),
90
- * not the name. This helper does the translation via
91
- * `GET /v1/workspace` (which the token needs org-level list scope
92
- * to call).
93
- *
94
- * Graceful degrade: for workspace-scoped tokens that 403 on the
95
- * org-level list, we fall back to treating the input as an id
96
- * directly. If the operator's `holocron.config.json` names a real
97
- * id, the subsequent POST works. If they named a slug, the POST
98
- * 404s and the operator gets a clear "workspace not found" error
99
- * from the API — better than swallowing the 403 silently.
100
- *
101
- * Results are cached per-instance so the 3 back-to-back
102
- * `ensureEnvironment` calls in `runSetup` share one lookup.
103
- */
104
35
  private resolveWorkspaceId;
105
36
  }
106
37
  //#endregion
@@ -120,16 +51,6 @@ declare class InfisicalVault implements Vault {
120
51
  * will name the specific workspace + environment the token
121
52
  * actually has access to.
122
53
  * - 401 or other → token is invalid.
123
- *
124
- * The distinction matters: verifyToken answers "is this a real
125
- * Infisical token?", not "does it have every possible permission?"
126
- * — per-capability permission failures surface at the actual read /
127
- * write / list call sites.
128
- *
129
- * Kept as a standalone function (not a capability method) so the auth
130
- * command can call it without initializing the full plugin — plugin
131
- * construction requires an already-resolved token, which is exactly
132
- * what we don't have yet at bootstrap time.
133
54
  */
134
55
  interface VerifyTokenSuccess {
135
56
  ok: true;
@@ -155,7 +76,7 @@ interface InfisicalPluginOptions extends ResolveTokenInput, InfisicalVaultOption
155
76
  }
156
77
  interface PluginContext {
157
78
  options: InfisicalPluginOptions;
158
- rest: InfisicalRestClient;
79
+ client: InfisicalClient;
159
80
  }
160
81
  declare function createContext(options: InfisicalPluginOptions): PluginContext;
161
82
  declare function vault(ctx: PluginContext): Vault;
@@ -168,14 +89,7 @@ declare function createPlugin(options: InfisicalPluginOptions): {
168
89
  /**
169
90
  * One-line hint printed by `holocron auth set infisical` when no
170
91
  * token is supplied or the supplied token is rejected.
171
- *
172
- * IMPORTANT — the token must be usable as a `Authorization: Bearer`
173
- * directly. Two token types work: **Personal API Token** (inherits
174
- * user perms) or a **Token Auth** token on a machine identity (a
175
- * single long-lived string). Universal Auth's Client Secret is NOT
176
- * a bearer — it needs a `/v1/auth/universal-auth/login` exchange
177
- * step this plugin doesn't yet do (Phase 2 follow-up).
178
92
  */
179
93
  declare const AUTH_HINT: string;
180
94
  //#endregion
181
- export { AUTH_HINT, AuthError, InfisicalPluginOptions, InfisicalRestClient, InfisicalVault, PluginContext, ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createPlugin, resolveToken, vault, verifyToken };
95
+ export { AUTH_HINT, AuthError, type InfisicalClient, InfisicalPluginOptions, InfisicalVault, PluginContext, type ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createInfisicalClient, 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 { createInfisicalClient } from "@theholocron/infisical-client";
2
3
  //#region src/auth.ts
3
- /**
4
- * Token resolution for the Infisical 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_INFISICAL_TOKEN env var (preferred — explicit intent)
10
- * 3. INFISICAL_TOKEN env var (vendor-native)
11
- * 4. keyring (com.theholocron.cli / "infisical")
12
- * 5. AuthError naming all four options + the bootstrap hint
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_INFISICAL_TOKEN"] || env["INFISICAL_TOKEN"] || keyring("infisical");
21
- if (!token) throw new AuthError("no Infisical token found. Pass --token <TOKEN>, set HOLOCRON_INFISICAL_TOKEN / INFISICAL_TOKEN, or run: holocron auth set infisical <TOKEN>");
22
- return token;
23
- }
4
+ const resolveToken = createResolveToken({
5
+ envName: "HOLOCRON_INFISICAL_TOKEN",
6
+ vendorEnvName: "INFISICAL_TOKEN",
7
+ keyringService: "infisical",
8
+ errorMessage: "no Infisical token found. Pass --token <TOKEN>, set HOLOCRON_INFISICAL_TOKEN / INFISICAL_TOKEN, or run: holocron auth set infisical <TOKEN>"
9
+ });
24
10
  //#endregion
25
11
  //#region src/capabilities/vault.ts
26
12
  /**
@@ -32,13 +18,6 @@ function resolveToken(input = {}) {
32
18
  * `environment` (slug, typically `dev`/`stg`/`prd`) so `list()` /
33
19
  * `environments()` / `readEnvironment()` don't need a three-part ref.
34
20
  *
35
- * Infisical's data model:
36
- * - Workspace (aka project) — top-level container
37
- * - Environments — dev / stg / prd, scoped to a workspace
38
- * - Secrets — scoped to a workspace + environment, with a path
39
- * (defaults to root `/` in this plugin; a `secretPath` option
40
- * would extend it but Phase 1 keeps it flat)
41
- *
42
21
  * Write semantics: Infisical's REST API separates CREATE (`POST`) and
43
22
  * UPDATE (`PATCH`). We attempt POST first; on the vendor's "already
44
23
  * exists" response we PATCH to upsert. The isConflict helper follows
@@ -49,7 +28,7 @@ function resolveToken(input = {}) {
49
28
  * workspace / environment create endpoints.
50
29
  */
51
30
  var InfisicalVault = class {
52
- rest;
31
+ client;
53
32
  key = "vault";
54
33
  providerName = "infisical";
55
34
  workspace;
@@ -57,12 +36,12 @@ var InfisicalVault = class {
57
36
  /**
58
37
  * Cache of resolved workspace name/slug → id. `ensureEnvironment`
59
38
  * is called once per environment during `holocron setup` (dev / stg
60
- * / prd), so a single \`GET /v1/workspace\` lookup covers all three
39
+ * / prd), so a single `GET /v1/workspace` lookup covers all three
61
40
  * calls in the same run.
62
41
  */
63
42
  workspaceIdCache = /* @__PURE__ */ new Map();
64
- constructor(rest, opts) {
65
- this.rest = rest;
43
+ constructor(client, opts) {
44
+ this.client = client;
66
45
  if (!opts.workspace) throw new Error("InfisicalVault requires `workspace` in options");
67
46
  if (!opts.environment) throw new Error("InfisicalVault requires `environment` in options");
68
47
  this.workspace = opts.workspace;
@@ -70,11 +49,12 @@ var InfisicalVault = class {
70
49
  }
71
50
  async read(reference) {
72
51
  const parsed = parseReference(reference);
73
- return (await this.rest.request(`/v3/secrets/raw/${encodeURIComponent(parsed.name)}`, { query: {
52
+ const { secret } = await this.client.secrets.get(parsed.name, {
74
53
  workspaceId: parsed.workspace,
75
54
  environment: parsed.environment,
76
55
  secretPath: "/"
77
- } })).secret?.secretValue ?? "";
56
+ });
57
+ return secret?.secretValue ?? "";
78
58
  }
79
59
  async write(reference, value) {
80
60
  const parsed = parseReference(reference);
@@ -85,51 +65,40 @@ var InfisicalVault = class {
85
65
  secretValue: value
86
66
  };
87
67
  try {
88
- await this.rest.request(`/v3/secrets/raw/${encodeURIComponent(parsed.name)}`, {
89
- method: "POST",
90
- body: {
91
- ...scope,
92
- type: "shared"
93
- }
68
+ await this.client.secrets.create(parsed.name, {
69
+ ...scope,
70
+ type: "shared"
94
71
  });
95
- return;
96
72
  } catch (err) {
97
73
  if (!isConflict(err)) throw err;
98
- await this.rest.request(`/v3/secrets/raw/${encodeURIComponent(parsed.name)}`, {
99
- method: "PATCH",
100
- body: scope
101
- });
74
+ await this.client.secrets.update(parsed.name, scope);
102
75
  }
103
76
  }
104
77
  async list() {
105
- return ((await this.rest.request("/v3/secrets/raw", { query: {
78
+ const { secrets } = await this.client.secrets.list({
106
79
  workspaceId: this.workspace,
107
80
  environment: this.environment,
108
81
  secretPath: "/"
109
- } })).secrets ?? []).map((s) => s.secretKey ?? "").filter(Boolean);
82
+ });
83
+ return (secrets ?? []).map((s) => s.secretKey ?? "").filter(Boolean);
110
84
  }
111
85
  async environments() {
112
- return ((await this.rest.request(`/v1/workspace/${encodeURIComponent(this.workspace)}`)).workspace?.environments ?? []).map((e) => e.slug ?? e.name ?? "").filter(Boolean);
86
+ const { workspace } = await this.client.workspaces.get(this.workspace);
87
+ return (workspace?.environments ?? []).map((e) => e.slug ?? e.name ?? "").filter(Boolean);
113
88
  }
114
89
  async readEnvironment(environmentId) {
115
- const res = await this.rest.request("/v3/secrets/raw", { query: {
90
+ const { secrets } = await this.client.secrets.list({
116
91
  workspaceId: this.workspace,
117
92
  environment: environmentId,
118
93
  secretPath: "/"
119
- } });
94
+ });
120
95
  const out = {};
121
- for (const s of res.secrets ?? []) if (s.secretKey && typeof s.secretValue === "string") out[s.secretKey] = s.secretValue;
96
+ for (const s of secrets ?? []) if (s.secretKey && typeof s.secretValue === "string") out[s.secretKey] = s.secretValue;
122
97
  return out;
123
98
  }
124
99
  async ensureProject(name) {
125
100
  try {
126
- await this.rest.request("/v2/workspace", {
127
- method: "POST",
128
- body: {
129
- projectName: name,
130
- slug: name
131
- }
132
- });
101
+ await this.client.workspaces.create(name, name);
133
102
  return { alreadyExists: false };
134
103
  } catch (err) {
135
104
  if (isConflict(err)) return { alreadyExists: true };
@@ -139,44 +108,19 @@ var InfisicalVault = class {
139
108
  async ensureEnvironment(project, name) {
140
109
  const workspaceId = await this.resolveWorkspaceId(project);
141
110
  try {
142
- await this.rest.request(`/v1/workspace/${encodeURIComponent(workspaceId)}/environments`, {
143
- method: "POST",
144
- body: {
145
- environmentName: name,
146
- environmentSlug: name
147
- }
148
- });
111
+ await this.client.workspaces.createEnvironment(workspaceId, name, name);
149
112
  return { alreadyExists: false };
150
113
  } catch (err) {
151
114
  if (isConflict(err)) return { alreadyExists: true };
152
115
  throw err;
153
116
  }
154
117
  }
155
- /**
156
- * Resolve a workspace name / slug to its Infisical workspace id.
157
- *
158
- * `runSetup` calls `ensureEnvironment(config.project.name, envName)`
159
- * — for Doppler that's directly the API's input, but Infisical's
160
- * environments endpoint takes the workspace **id** (UUID/nanoid),
161
- * not the name. This helper does the translation via
162
- * `GET /v1/workspace` (which the token needs org-level list scope
163
- * to call).
164
- *
165
- * Graceful degrade: for workspace-scoped tokens that 403 on the
166
- * org-level list, we fall back to treating the input as an id
167
- * directly. If the operator's `holocron.config.json` names a real
168
- * id, the subsequent POST works. If they named a slug, the POST
169
- * 404s and the operator gets a clear "workspace not found" error
170
- * from the API — better than swallowing the 403 silently.
171
- *
172
- * Results are cached per-instance so the 3 back-to-back
173
- * `ensureEnvironment` calls in `runSetup` share one lookup.
174
- */
175
118
  async resolveWorkspaceId(nameOrId) {
176
119
  const cached = this.workspaceIdCache.get(nameOrId);
177
120
  if (cached) return cached;
178
121
  try {
179
- const match = ((await this.rest.request("/v1/workspace"))?.workspaces ?? []).find((w) => w.name === nameOrId || w.slug === nameOrId);
122
+ const { workspaces } = await this.client.workspaces.list();
123
+ const match = (workspaces ?? []).find((w) => w.name === nameOrId || w.slug === nameOrId);
180
124
  const resolvedId = match?._id ?? match?.id;
181
125
  if (resolvedId) {
182
126
  this.workspaceIdCache.set(nameOrId, resolvedId);
@@ -188,10 +132,6 @@ var InfisicalVault = class {
188
132
  return nameOrId;
189
133
  }
190
134
  };
191
- /**
192
- * Parse `infisical://<workspaceId>/<environment>/<name>` into its
193
- * parts. Throws when the shape doesn't match.
194
- */
195
135
  function parseReference(reference) {
196
136
  if (!reference.startsWith("infisical://")) throw new ProviderApiError(`Infisical references must start with "infisical://": got "${reference}"`, 400, void 0);
197
137
  const parts = reference.slice(12).split("/");
@@ -203,13 +143,6 @@ function parseReference(reference) {
203
143
  name: nameParts.join("/")
204
144
  };
205
145
  }
206
- /**
207
- * Infisical returns duplicate-create errors as 400 or 409 depending
208
- * on the endpoint, with a body message that includes "already exists"
209
- * (paraphrased). The REST client wraps both as ProviderApiError. We
210
- * accept 409 outright and treat 400/422 with an "already exists" body
211
- * as idempotent conflict — same pattern as the Doppler plugin.
212
- */
213
146
  function isConflict(err) {
214
147
  if (!(err instanceof ProviderApiError)) return false;
215
148
  if (err.status === 409) return true;
@@ -221,59 +154,6 @@ function hasAlreadyExistsBody(details) {
221
154
  return /already exists|duplicate/i.test(details);
222
155
  }
223
156
  //#endregion
224
- //#region src/rest.ts
225
- /**
226
- * Thin REST wrapper around https://app.infisical.com/api.
227
- *
228
- * Bearer auth, JSON-only bodies, transport-failure wrapping with
229
- * `status: 0` so orchestrator soft-skip paths see a clear message
230
- * instead of a generic `TypeError: fetch failed`.
231
- */
232
- var InfisicalRestClient = class {
233
- token;
234
- fetchImpl;
235
- baseUrl;
236
- constructor(opts) {
237
- this.token = opts.token;
238
- this.fetchImpl = opts.fetch ?? globalThis.fetch;
239
- let url = opts.baseUrl ?? "https://app.infisical.com/api";
240
- while (url.endsWith("/")) url = url.slice(0, -1);
241
- this.baseUrl = url;
242
- }
243
- async request(path, opts = {}) {
244
- const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`);
245
- for (const [k, v] of Object.entries(opts.query ?? {})) url.searchParams.set(k, v);
246
- const fullUrl = url.toString();
247
- const headers = {
248
- authorization: `Bearer ${this.token}`,
249
- accept: "application/json"
250
- };
251
- const init = {
252
- method: opts.method ?? "GET",
253
- headers
254
- };
255
- if (opts.body !== void 0) {
256
- headers["content-type"] = "application/json";
257
- init.body = JSON.stringify(opts.body);
258
- }
259
- let res;
260
- try {
261
- res = await this.fetchImpl(fullUrl, init);
262
- } catch (err) {
263
- const detail = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
264
- throw new ProviderApiError(`Infisical ${init.method} ${path} failed: ${detail}`, 0, void 0);
265
- }
266
- if (!res.ok) {
267
- const body = await res.text().catch(() => "");
268
- throw new ProviderApiError(`Infisical ${init.method} ${path} → ${res.status}`, res.status, body);
269
- }
270
- if (opts.expectNoContent || res.status === 204) return void 0;
271
- const text = await res.text();
272
- if (!text) return void 0;
273
- return JSON.parse(text);
274
- }
275
- };
276
- //#endregion
277
157
  //#region src/verify-token.ts
278
158
  /**
279
159
  * `verifyToken` — plugin-level export used by `holocron auth set` +
@@ -290,26 +170,17 @@ var InfisicalRestClient = class {
290
170
  * will name the specific workspace + environment the token
291
171
  * actually has access to.
292
172
  * - 401 or other → token is invalid.
293
- *
294
- * The distinction matters: verifyToken answers "is this a real
295
- * Infisical token?", not "does it have every possible permission?"
296
- * — per-capability permission failures surface at the actual read /
297
- * write / list call sites.
298
- *
299
- * Kept as a standalone function (not a capability method) so the auth
300
- * command can call it without initializing the full plugin — plugin
301
- * construction requires an already-resolved token, which is exactly
302
- * what we don't have yet at bootstrap time.
303
173
  */
304
174
  async function verifyToken(token, opts = {}) {
305
- const restOpts = { token };
306
- if (opts.baseUrl !== void 0) restOpts.baseUrl = opts.baseUrl;
307
- if (opts.fetch !== void 0) restOpts.fetch = opts.fetch;
308
- const rest = new InfisicalRestClient(restOpts);
175
+ const client = createInfisicalClient({
176
+ token,
177
+ baseUrl: opts.baseUrl,
178
+ fetch: opts.fetch
179
+ });
309
180
  try {
310
- const res = await rest.request("/v1/workspace");
311
- const count = res?.workspaces?.length ?? 0;
312
- const first = res?.workspaces?.[0];
181
+ const { workspaces } = await client.workspaces.list();
182
+ const count = workspaces?.length ?? 0;
183
+ const first = workspaces?.[0];
313
184
  const label = first?.name ?? first?.slug ?? "no accessible workspaces";
314
185
  return {
315
186
  ok: true,
@@ -329,16 +200,17 @@ async function verifyToken(token, opts = {}) {
329
200
  //#endregion
330
201
  //#region src/index.ts
331
202
  function createContext(options) {
332
- const restOpts = { token: resolveToken(options) };
333
- if (options.baseUrl !== void 0) restOpts.baseUrl = options.baseUrl;
334
- if (options.fetch !== void 0) restOpts.fetch = options.fetch;
335
203
  return {
336
204
  options,
337
- rest: new InfisicalRestClient(restOpts)
205
+ client: createInfisicalClient({
206
+ token: resolveToken(options),
207
+ baseUrl: options.baseUrl,
208
+ fetch: options.fetch
209
+ })
338
210
  };
339
211
  }
340
212
  function vault(ctx) {
341
- return new InfisicalVault(ctx.rest, {
213
+ return new InfisicalVault(ctx.client, {
342
214
  workspace: ctx.options.workspace,
343
215
  environment: ctx.options.environment
344
216
  });
@@ -353,14 +225,7 @@ function createPlugin(options) {
353
225
  /**
354
226
  * One-line hint printed by `holocron auth set infisical` when no
355
227
  * token is supplied or the supplied token is rejected.
356
- *
357
- * IMPORTANT — the token must be usable as a `Authorization: Bearer`
358
- * directly. Two token types work: **Personal API Token** (inherits
359
- * user perms) or a **Token Auth** token on a machine identity (a
360
- * single long-lived string). Universal Auth's Client Secret is NOT
361
- * a bearer — it needs a `/v1/auth/universal-auth/login` exchange
362
- * step this plugin doesn't yet do (Phase 2 follow-up).
363
228
  */
364
229
  const AUTH_HINT = "generate a Token Auth token on your machine identity (organization → access control → identities → your identity → add auth method → Token Auth → create token) OR a Personal API Token, then: holocron auth set infisical <TOKEN>. NOT Universal Auth's Client Secret — that needs a login exchange this plugin doesn't do yet.";
365
230
  //#endregion
366
- export { AUTH_HINT, AuthError, InfisicalRestClient, InfisicalVault, createContext, createPlugin, resolveToken, vault, verifyToken };
231
+ export { AUTH_HINT, AuthError, InfisicalVault, createContext, createInfisicalClient, createPlugin, resolveToken, vault, verifyToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-infisical",
3
- "version": "2.0.0-alpha.9",
3
+ "version": "2.0.0",
4
4
  "description": "Holocron plugin for Infisical. Implements the vault capability against Infisical's REST API, plus exports verifyToken + AUTH_HINT for `holocron auth`.",
5
5
  "homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-infisical#readme",
6
6
  "bugs": "https://github.com/theholocron/holocron/issues",
@@ -21,19 +21,31 @@
21
21
  }
22
22
  },
23
23
  "peerDependencies": {
24
- "@theholocron/cli": "2.0.0-alpha.9"
24
+ "@theholocron/infisical-client": "^1.1.0",
25
+ "@theholocron/cli": "2.0.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@theholocron/infisical-client": {
29
+ "optional": false
30
+ }
25
31
  },
26
32
  "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",
33
+ "@theholocron/eslint-config": "^7.3.0",
34
+ "@theholocron/infisical-client": "^1.1.0",
35
+ "@theholocron/tsconfig": "^7.3.0",
36
+ "@theholocron/tsdown-config": "^7.3.0",
37
+ "@theholocron/vitest-config": "^7.3.0",
38
+ "@types/node": "^26",
39
+ "@vitest/coverage-v8": "^4.1.10",
40
+ "@vitest/eslint-plugin": "^1.6.23",
41
+ "eslint": "^10.7.0",
42
+ "eslint-plugin-n": "^18.2.2",
43
+ "globals": "^17.7.0",
32
44
  "tsdown": "^0.22.3",
33
45
  "tsx": "^4.22.4",
34
46
  "typescript": "^5.9.3",
35
- "vitest": "^3.2.6",
36
- "@theholocron/cli": "2.0.0-alpha.9"
47
+ "vitest": "^4.1.10",
48
+ "@theholocron/cli": "2.0.0"
37
49
  },
38
50
  "publishConfig": {
39
51
  "access": "public"