@theholocron/holocron-plugin-infisical 2.0.0-alpha.52 → 2.0.0-alpha.54

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/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { AuthError, EnsureResult, ResolveTokenInput, RestClient, RestClient as RestClient$1, 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
  declare const resolveToken: (input?: import("@theholocron/http-client").ResolveTokenInput) => string;
@@ -11,7 +12,7 @@ interface InfisicalVaultOptions {
11
12
  environment: string;
12
13
  }
13
14
  declare class InfisicalVault implements Vault {
14
- private readonly rest;
15
+ private readonly client;
15
16
  readonly key: "vault";
16
17
  readonly providerName = "infisical";
17
18
  private readonly workspace;
@@ -19,11 +20,11 @@ declare class InfisicalVault implements Vault {
19
20
  /**
20
21
  * Cache of resolved workspace name/slug → id. `ensureEnvironment`
21
22
  * is called once per environment during `holocron setup` (dev / stg
22
- * / prd), so a single \`GET /v1/workspace\` lookup covers all three
23
+ * / prd), so a single `GET /v1/workspace` lookup covers all three
23
24
  * calls in the same run.
24
25
  */
25
26
  private readonly workspaceIdCache;
26
- constructor(rest: RestClient, opts: InfisicalVaultOptions);
27
+ constructor(client: InfisicalClient$1, opts: InfisicalVaultOptions);
27
28
  read(reference: string): Promise<string>;
28
29
  write(reference: string, value: string): Promise<void>;
29
30
  list(): Promise<string[]>;
@@ -31,36 +32,9 @@ declare class InfisicalVault implements Vault {
31
32
  readEnvironment(environmentId: string): Promise<Record<string, string>>;
32
33
  ensureProject(name: string): Promise<EnsureResult>;
33
34
  ensureEnvironment(project: string, name: string): Promise<EnsureResult>;
34
- /**
35
- * Resolve a workspace name / slug to its Infisical workspace id.
36
- *
37
- * `runSetup` calls `ensureEnvironment(config.project.name, envName)`
38
- * — for Doppler that's directly the API's input, but Infisical's
39
- * environments endpoint takes the workspace **id** (UUID/nanoid),
40
- * not the name. This helper does the translation via
41
- * `GET /v1/workspace` (which the token needs org-level list scope
42
- * to call).
43
- *
44
- * Graceful degrade: for workspace-scoped tokens that 403 on the
45
- * org-level list, we fall back to treating the input as an id
46
- * directly. If the operator's `holocron.config.json` names a real
47
- * id, the subsequent POST works. If they named a slug, the POST
48
- * 404s and the operator gets a clear "workspace not found" error
49
- * from the API — better than swallowing the 403 silently.
50
- *
51
- * Results are cached per-instance so the 3 back-to-back
52
- * `ensureEnvironment` calls in `runSetup` share one lookup.
53
- */
54
35
  private resolveWorkspaceId;
55
36
  }
56
37
  //#endregion
57
- //#region src/rest.d.ts
58
- declare function createInfisicalRestClient(opts: {
59
- token: string;
60
- baseUrl?: string;
61
- fetch?: typeof fetch;
62
- }): RestClient$1;
63
- //#endregion
64
38
  //#region src/verify-token.d.ts
65
39
  /**
66
40
  * `verifyToken` — plugin-level export used by `holocron auth set` +
@@ -77,16 +51,6 @@ declare function createInfisicalRestClient(opts: {
77
51
  * will name the specific workspace + environment the token
78
52
  * actually has access to.
79
53
  * - 401 or other → token is invalid.
80
- *
81
- * The distinction matters: verifyToken answers "is this a real
82
- * Infisical token?", not "does it have every possible permission?"
83
- * — per-capability permission failures surface at the actual read /
84
- * write / list call sites.
85
- *
86
- * Kept as a standalone function (not a capability method) so the auth
87
- * command can call it without initializing the full plugin — plugin
88
- * construction requires an already-resolved token, which is exactly
89
- * what we don't have yet at bootstrap time.
90
54
  */
91
55
  interface VerifyTokenSuccess {
92
56
  ok: true;
@@ -112,7 +76,7 @@ interface InfisicalPluginOptions extends ResolveTokenInput, InfisicalVaultOption
112
76
  }
113
77
  interface PluginContext {
114
78
  options: InfisicalPluginOptions;
115
- rest: RestClient;
79
+ client: InfisicalClient;
116
80
  }
117
81
  declare function createContext(options: InfisicalPluginOptions): PluginContext;
118
82
  declare function vault(ctx: PluginContext): Vault;
@@ -125,14 +89,7 @@ declare function createPlugin(options: InfisicalPluginOptions): {
125
89
  /**
126
90
  * One-line hint printed by `holocron auth set infisical` when no
127
91
  * token is supplied or the supplied token is rejected.
128
- *
129
- * IMPORTANT — the token must be usable as a `Authorization: Bearer`
130
- * directly. Two token types work: **Personal API Token** (inherits
131
- * user perms) or a **Token Auth** token on a machine identity (a
132
- * single long-lived string). Universal Auth's Client Secret is NOT
133
- * a bearer — it needs a `/v1/auth/universal-auth/login` exchange
134
- * step this plugin doesn't yet do (Phase 2 follow-up).
135
92
  */
136
93
  declare const AUTH_HINT: string;
137
94
  //#endregion
138
- export { AUTH_HINT, AuthError, InfisicalPluginOptions, InfisicalVault, PluginContext, type ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createContext, createInfisicalRestClient, 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,4 +1,5 @@
1
- import { AuthError, ProviderApiError, createResolveToken, createRestClient } 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
  const resolveToken = createResolveToken({
4
5
  envName: "HOLOCRON_INFISICAL_TOKEN",
@@ -17,13 +18,6 @@ const resolveToken = createResolveToken({
17
18
  * `environment` (slug, typically `dev`/`stg`/`prd`) so `list()` /
18
19
  * `environments()` / `readEnvironment()` don't need a three-part ref.
19
20
  *
20
- * Infisical's data model:
21
- * - Workspace (aka project) — top-level container
22
- * - Environments — dev / stg / prd, scoped to a workspace
23
- * - Secrets — scoped to a workspace + environment, with a path
24
- * (defaults to root `/` in this plugin; a `secretPath` option
25
- * would extend it but Phase 1 keeps it flat)
26
- *
27
21
  * Write semantics: Infisical's REST API separates CREATE (`POST`) and
28
22
  * UPDATE (`PATCH`). We attempt POST first; on the vendor's "already
29
23
  * exists" response we PATCH to upsert. The isConflict helper follows
@@ -34,7 +28,7 @@ const resolveToken = createResolveToken({
34
28
  * workspace / environment create endpoints.
35
29
  */
36
30
  var InfisicalVault = class {
37
- rest;
31
+ client;
38
32
  key = "vault";
39
33
  providerName = "infisical";
40
34
  workspace;
@@ -42,12 +36,12 @@ var InfisicalVault = class {
42
36
  /**
43
37
  * Cache of resolved workspace name/slug → id. `ensureEnvironment`
44
38
  * is called once per environment during `holocron setup` (dev / stg
45
- * / prd), so a single \`GET /v1/workspace\` lookup covers all three
39
+ * / prd), so a single `GET /v1/workspace` lookup covers all three
46
40
  * calls in the same run.
47
41
  */
48
42
  workspaceIdCache = /* @__PURE__ */ new Map();
49
- constructor(rest, opts) {
50
- this.rest = rest;
43
+ constructor(client, opts) {
44
+ this.client = client;
51
45
  if (!opts.workspace) throw new Error("InfisicalVault requires `workspace` in options");
52
46
  if (!opts.environment) throw new Error("InfisicalVault requires `environment` in options");
53
47
  this.workspace = opts.workspace;
@@ -55,11 +49,12 @@ var InfisicalVault = class {
55
49
  }
56
50
  async read(reference) {
57
51
  const parsed = parseReference(reference);
58
- return (await this.rest.request(`/v3/secrets/raw/${encodeURIComponent(parsed.name)}`, { query: {
52
+ const { secret } = await this.client.secrets.get(parsed.name, {
59
53
  workspaceId: parsed.workspace,
60
54
  environment: parsed.environment,
61
55
  secretPath: "/"
62
- } })).secret?.secretValue ?? "";
56
+ });
57
+ return secret?.secretValue ?? "";
63
58
  }
64
59
  async write(reference, value) {
65
60
  const parsed = parseReference(reference);
@@ -70,51 +65,40 @@ var InfisicalVault = class {
70
65
  secretValue: value
71
66
  };
72
67
  try {
73
- await this.rest.request(`/v3/secrets/raw/${encodeURIComponent(parsed.name)}`, {
74
- method: "POST",
75
- body: {
76
- ...scope,
77
- type: "shared"
78
- }
68
+ await this.client.secrets.create(parsed.name, {
69
+ ...scope,
70
+ type: "shared"
79
71
  });
80
- return;
81
72
  } catch (err) {
82
73
  if (!isConflict(err)) throw err;
83
- await this.rest.request(`/v3/secrets/raw/${encodeURIComponent(parsed.name)}`, {
84
- method: "PATCH",
85
- body: scope
86
- });
74
+ await this.client.secrets.update(parsed.name, scope);
87
75
  }
88
76
  }
89
77
  async list() {
90
- return ((await this.rest.request("/v3/secrets/raw", { query: {
78
+ const { secrets } = await this.client.secrets.list({
91
79
  workspaceId: this.workspace,
92
80
  environment: this.environment,
93
81
  secretPath: "/"
94
- } })).secrets ?? []).map((s) => s.secretKey ?? "").filter(Boolean);
82
+ });
83
+ return (secrets ?? []).map((s) => s.secretKey ?? "").filter(Boolean);
95
84
  }
96
85
  async environments() {
97
- 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);
98
88
  }
99
89
  async readEnvironment(environmentId) {
100
- const res = await this.rest.request("/v3/secrets/raw", { query: {
90
+ const { secrets } = await this.client.secrets.list({
101
91
  workspaceId: this.workspace,
102
92
  environment: environmentId,
103
93
  secretPath: "/"
104
- } });
94
+ });
105
95
  const out = {};
106
- 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;
107
97
  return out;
108
98
  }
109
99
  async ensureProject(name) {
110
100
  try {
111
- await this.rest.request("/v2/workspace", {
112
- method: "POST",
113
- body: {
114
- projectName: name,
115
- slug: name
116
- }
117
- });
101
+ await this.client.workspaces.create(name, name);
118
102
  return { alreadyExists: false };
119
103
  } catch (err) {
120
104
  if (isConflict(err)) return { alreadyExists: true };
@@ -124,44 +108,19 @@ var InfisicalVault = class {
124
108
  async ensureEnvironment(project, name) {
125
109
  const workspaceId = await this.resolveWorkspaceId(project);
126
110
  try {
127
- await this.rest.request(`/v1/workspace/${encodeURIComponent(workspaceId)}/environments`, {
128
- method: "POST",
129
- body: {
130
- environmentName: name,
131
- environmentSlug: name
132
- }
133
- });
111
+ await this.client.workspaces.createEnvironment(workspaceId, name, name);
134
112
  return { alreadyExists: false };
135
113
  } catch (err) {
136
114
  if (isConflict(err)) return { alreadyExists: true };
137
115
  throw err;
138
116
  }
139
117
  }
140
- /**
141
- * Resolve a workspace name / slug to its Infisical workspace id.
142
- *
143
- * `runSetup` calls `ensureEnvironment(config.project.name, envName)`
144
- * — for Doppler that's directly the API's input, but Infisical's
145
- * environments endpoint takes the workspace **id** (UUID/nanoid),
146
- * not the name. This helper does the translation via
147
- * `GET /v1/workspace` (which the token needs org-level list scope
148
- * to call).
149
- *
150
- * Graceful degrade: for workspace-scoped tokens that 403 on the
151
- * org-level list, we fall back to treating the input as an id
152
- * directly. If the operator's `holocron.config.json` names a real
153
- * id, the subsequent POST works. If they named a slug, the POST
154
- * 404s and the operator gets a clear "workspace not found" error
155
- * from the API — better than swallowing the 403 silently.
156
- *
157
- * Results are cached per-instance so the 3 back-to-back
158
- * `ensureEnvironment` calls in `runSetup` share one lookup.
159
- */
160
118
  async resolveWorkspaceId(nameOrId) {
161
119
  const cached = this.workspaceIdCache.get(nameOrId);
162
120
  if (cached) return cached;
163
121
  try {
164
- 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);
165
124
  const resolvedId = match?._id ?? match?.id;
166
125
  if (resolvedId) {
167
126
  this.workspaceIdCache.set(nameOrId, resolvedId);
@@ -173,10 +132,6 @@ var InfisicalVault = class {
173
132
  return nameOrId;
174
133
  }
175
134
  };
176
- /**
177
- * Parse `infisical://<workspaceId>/<environment>/<name>` into its
178
- * parts. Throws when the shape doesn't match.
179
- */
180
135
  function parseReference(reference) {
181
136
  if (!reference.startsWith("infisical://")) throw new ProviderApiError(`Infisical references must start with "infisical://": got "${reference}"`, 400, void 0);
182
137
  const parts = reference.slice(12).split("/");
@@ -188,13 +143,6 @@ function parseReference(reference) {
188
143
  name: nameParts.join("/")
189
144
  };
190
145
  }
191
- /**
192
- * Infisical returns duplicate-create errors as 400 or 409 depending
193
- * on the endpoint, with a body message that includes "already exists"
194
- * (paraphrased). The REST client wraps both as ProviderApiError. We
195
- * accept 409 outright and treat 400/422 with an "already exists" body
196
- * as idempotent conflict — same pattern as the Doppler plugin.
197
- */
198
146
  function isConflict(err) {
199
147
  if (!(err instanceof ProviderApiError)) return false;
200
148
  if (err.status === 409) return true;
@@ -206,16 +154,6 @@ function hasAlreadyExistsBody(details) {
206
154
  return /already exists|duplicate/i.test(details);
207
155
  }
208
156
  //#endregion
209
- //#region src/rest.ts
210
- function createInfisicalRestClient(opts) {
211
- return createRestClient({
212
- baseUrl: opts.baseUrl ?? "https://app.infisical.com/api",
213
- token: opts.token,
214
- vendor: "Infisical",
215
- fetch: opts.fetch
216
- });
217
- }
218
- //#endregion
219
157
  //#region src/verify-token.ts
220
158
  /**
221
159
  * `verifyToken` — plugin-level export used by `holocron auth set` +
@@ -232,27 +170,17 @@ function createInfisicalRestClient(opts) {
232
170
  * will name the specific workspace + environment the token
233
171
  * actually has access to.
234
172
  * - 401 or other → token is invalid.
235
- *
236
- * The distinction matters: verifyToken answers "is this a real
237
- * Infisical token?", not "does it have every possible permission?"
238
- * — per-capability permission failures surface at the actual read /
239
- * write / list call sites.
240
- *
241
- * Kept as a standalone function (not a capability method) so the auth
242
- * command can call it without initializing the full plugin — plugin
243
- * construction requires an already-resolved token, which is exactly
244
- * what we don't have yet at bootstrap time.
245
173
  */
246
174
  async function verifyToken(token, opts = {}) {
247
- const rest = createInfisicalRestClient({
175
+ const client = createInfisicalClient({
248
176
  token,
249
177
  baseUrl: opts.baseUrl,
250
178
  fetch: opts.fetch
251
179
  });
252
180
  try {
253
- const res = await rest.request("/v1/workspace");
254
- const count = res?.workspaces?.length ?? 0;
255
- const first = res?.workspaces?.[0];
181
+ const { workspaces } = await client.workspaces.list();
182
+ const count = workspaces?.length ?? 0;
183
+ const first = workspaces?.[0];
256
184
  const label = first?.name ?? first?.slug ?? "no accessible workspaces";
257
185
  return {
258
186
  ok: true,
@@ -274,7 +202,7 @@ async function verifyToken(token, opts = {}) {
274
202
  function createContext(options) {
275
203
  return {
276
204
  options,
277
- rest: createInfisicalRestClient({
205
+ client: createInfisicalClient({
278
206
  token: resolveToken(options),
279
207
  baseUrl: options.baseUrl,
280
208
  fetch: options.fetch
@@ -282,7 +210,7 @@ function createContext(options) {
282
210
  };
283
211
  }
284
212
  function vault(ctx) {
285
- return new InfisicalVault(ctx.rest, {
213
+ return new InfisicalVault(ctx.client, {
286
214
  workspace: ctx.options.workspace,
287
215
  environment: ctx.options.environment
288
216
  });
@@ -297,14 +225,7 @@ function createPlugin(options) {
297
225
  /**
298
226
  * One-line hint printed by `holocron auth set infisical` when no
299
227
  * token is supplied or the supplied token is rejected.
300
- *
301
- * IMPORTANT — the token must be usable as a `Authorization: Bearer`
302
- * directly. Two token types work: **Personal API Token** (inherits
303
- * user perms) or a **Token Auth** token on a machine identity (a
304
- * single long-lived string). Universal Auth's Client Secret is NOT
305
- * a bearer — it needs a `/v1/auth/universal-auth/login` exchange
306
- * step this plugin doesn't yet do (Phase 2 follow-up).
307
228
  */
308
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.";
309
230
  //#endregion
310
- export { AUTH_HINT, AuthError, InfisicalVault, createContext, createInfisicalRestClient, 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.52",
3
+ "version": "2.0.0-alpha.54",
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,9 +21,16 @@
21
21
  }
22
22
  },
23
23
  "peerDependencies": {
24
- "@theholocron/cli": "2.0.0-alpha.52"
24
+ "@theholocron/infisical-client": "^0.10.0",
25
+ "@theholocron/cli": "2.0.0-alpha.54"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@theholocron/infisical-client": {
29
+ "optional": false
30
+ }
25
31
  },
26
32
  "devDependencies": {
33
+ "@theholocron/infisical-client": "^0.10.0",
27
34
  "@types/node": "^26",
28
35
  "@theholocron/eslint-config": "^7.0.0",
29
36
  "@theholocron/tsconfig": "^7.0.0",
@@ -37,7 +44,7 @@
37
44
  "tsx": "^4.22.4",
38
45
  "typescript": "^5.9.3",
39
46
  "vitest": "^4.1.10",
40
- "@theholocron/cli": "2.0.0-alpha.52"
47
+ "@theholocron/cli": "2.0.0-alpha.54"
41
48
  },
42
49
  "publishConfig": {
43
50
  "access": "public"