@hubble-ventures/infisicml 1.2.1 → 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/CHANGELOG.md +66 -1
- package/README.md +94 -94
- package/dist/aliases.d.ts +11 -5
- package/dist/aliases.d.ts.map +1 -1
- package/dist/aliases.js +20 -12
- package/dist/aliases.js.map +1 -1
- package/dist/ci-skip.d.ts +32 -15
- package/dist/ci-skip.d.ts.map +1 -1
- package/dist/ci-skip.js +48 -39
- package/dist/ci-skip.js.map +1 -1
- package/dist/commands/export-gha.d.ts +16 -0
- package/dist/commands/export-gha.d.ts.map +1 -1
- package/dist/commands/export-gha.js +48 -39
- package/dist/commands/export-gha.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +17 -25
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/paths.d.ts.map +1 -1
- package/dist/commands/paths.js +11 -10
- package/dist/commands/paths.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +6 -3
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +1 -11
- package/dist/commands/validate.js.map +1 -1
- package/dist/github-env.js +1 -1
- package/dist/github-env.js.map +1 -1
- package/dist/include.d.ts +6 -43
- package/dist/include.d.ts.map +1 -1
- package/dist/include.js +11 -67
- package/dist/include.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +14 -35
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +26 -85
- package/dist/manifest.js.map +1 -1
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +11 -16
- package/dist/pull.js.map +1 -1
- package/dist/tree.d.ts +49 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +190 -0
- package/dist/tree.js.map +1 -0
- package/package.json +4 -2
- package/schema/secrets.schema.json +49 -43
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,72 @@ All notable changes to `infisicml` are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
|
|
5
5
|
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.0.0] - 2026-07-17
|
|
8
|
+
|
|
9
|
+
### Changed (breaking — manifest format)
|
|
10
|
+
|
|
11
|
+
- **The manifest is now a `secrets` array of folders.** The flat `paths` +
|
|
12
|
+
`include` + `aliases` trio is replaced by a single `secrets` array that mirrors
|
|
13
|
+
your Infisical structure. Each of the three old fields was really an attribute
|
|
14
|
+
of a specific `(folder, key)` pair; the tree binds them together, so a key's
|
|
15
|
+
allowlist membership and its alias live in one place, provenance is explicit,
|
|
16
|
+
and the manifest reads like the vault. `secrets` is an array of folder objects
|
|
17
|
+
`{ name: [ ...contents ] }`; inside a folder's array, a **bare string** is a
|
|
18
|
+
plain key, an object with a **string value** `{ SOURCE: "TARGET" }` is an
|
|
19
|
+
alias, and an object with an **array value** `{ sub: [ ... ] }` is a **real
|
|
20
|
+
subfolder** (nesting mirrors the vault tree — `sub` under `posthog` reads
|
|
21
|
+
`/posthog/sub`). Value type discriminates alias vs subfolder, so subfolders
|
|
22
|
+
need no leading `/`. Fan a key out to several targets by repeating the alias,
|
|
23
|
+
one target each.
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
// before (v1) // after (v2)
|
|
27
|
+
{ {
|
|
28
|
+
"paths": ["posthog"], "secrets": [
|
|
29
|
+
"aliases": { { "posthog": [
|
|
30
|
+
"POSTHOG_PROJECT_TOKEN": { "POSTHOG_PROJECT_TOKEN": "VITE_POSTHOG_KEY" }
|
|
31
|
+
"VITE_POSTHOG_KEY" ] }
|
|
32
|
+
}, ]
|
|
33
|
+
"include": ["POSTHOG_PROJECT_TOKEN"] }
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **Default-deny everywhere.** There is no wildcard: every emitted key must be
|
|
38
|
+
named, and an empty folder array is a schema error. The tree *is* the
|
|
39
|
+
allowlist, so the separate `include` field is **removed** — a folder emits
|
|
40
|
+
exactly the keys it declares, from that folder (provenance-aware selection).
|
|
41
|
+
- **`fetch: "keys"` no longer needs an allowlist.** Because the tree always
|
|
42
|
+
names every canonical key (an alias entry's key *is* the real vault key),
|
|
43
|
+
key mode requests those names directly — the v1 reverse-mapping from alias
|
|
44
|
+
target to source is gone, and `keys` is always satisfiable. The
|
|
45
|
+
`fetch`-requires-`include` cross-check is removed.
|
|
46
|
+
- **Optional keys stay env-scoped** via `environments.<slug>.optionalKeys`
|
|
47
|
+
(unchanged), now referencing declared canonical key names.
|
|
48
|
+
- **Profiles** carry an alternate `secrets` array (replacing the base tree when
|
|
49
|
+
`--profile` is set) plus an optional `fetch`; `profiles.<name>.paths` /
|
|
50
|
+
`.include` are gone.
|
|
51
|
+
- **Programmatic API.** `resolvePaths` / `resolveInclude` / `applyInclude` /
|
|
52
|
+
`resolveFetchKeys` / `checkFetchIncludeConsistency` / `fetchManifestSecrets` /
|
|
53
|
+
`selectEmittedSecrets` are replaced by `resolveCompiledFolders`, `compileTree`,
|
|
54
|
+
`fetchCompiledFolders`, and `materializeSecrets` (the latter applies aliases
|
|
55
|
+
and enforces missing keys **per folder**, before merging, so a key name shared
|
|
56
|
+
across folders keeps each folder's value and miss); `enforceIncludeKnown` is
|
|
57
|
+
renamed `enforceKnownKeys`. New exports: `compileTree`, `treeSchema`,
|
|
58
|
+
`materializeSecrets`, and the `CompiledFolder` / `CompiledKey` / `FolderEntry` /
|
|
59
|
+
`FolderArray` / `SecretsTree` / `FolderSecrets` types.
|
|
60
|
+
- **Schema** rewritten around the `secrets` array and published at `@2`.
|
|
61
|
+
|
|
62
|
+
### Migration
|
|
63
|
+
|
|
64
|
+
Convert each `secrets.json`: replace `paths`/`include`/`aliases` with a
|
|
65
|
+
`secrets` array of `{ folder: [ ...keys ] }` objects; list plain keys as bare
|
|
66
|
+
strings and each `aliases` source as a `{ SOURCE: "TARGET" }` object (repeat for
|
|
67
|
+
several targets); keep only the keys you want (the array is the allowlist).
|
|
68
|
+
Point `$schema` at `…/infisicml@2/…`.
|
|
69
|
+
Behavior note: an alias entry now emits **both** its canonical name and the
|
|
70
|
+
target(s) — v1 could suppress the canonical via `include`; v2 does not (the
|
|
71
|
+
security boundary is default-deny provenance, not alias suppression).
|
|
72
|
+
|
|
7
73
|
## [1.2.1] - 2026-07-17
|
|
8
74
|
|
|
9
75
|
### Changed (BREAKING)
|
|
@@ -97,7 +163,6 @@ changes to either ship only in a new major.
|
|
|
97
163
|
- **Published JSON Schema** for `secrets.json`, served from
|
|
98
164
|
`https://cdn.jsdelivr.net/npm/@hubble-ventures/infisicml@1/schema/secrets.schema.json`.
|
|
99
165
|
|
|
100
|
-
[1.2.1]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.2.1
|
|
101
166
|
[1.2.0]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.2.0
|
|
102
167
|
[1.1.0]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.1.0
|
|
103
168
|
[1.0.0]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -36,29 +36,43 @@ Requires the [`infisical` CLI](https://infisical.com/docs/cli/overview) for loca
|
|
|
36
36
|
projectIdEnvFile: ".env.infisical", // provides INFISICAL_PROJECT_ID
|
|
37
37
|
discovery: { roots: ["apps", "services"], packages: [{ id: "postgres", dir: "infra/postgres" }] },
|
|
38
38
|
auth: { oidcAudience: "https://github.com/your-org" },
|
|
39
|
-
hooks: { advertiseKeys: [{ envVar: "
|
|
39
|
+
hooks: { advertiseKeys: [{ envVar: "INFISICML_FLY_KEYS", scope: "runtime" }] },
|
|
40
40
|
});
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
2. Add a `secrets.json` next to each package
|
|
43
|
+
2. Add a `secrets.json` next to each package. `secrets` is an **array of
|
|
44
|
+
folders** mirroring your Infisical structure; each folder is
|
|
45
|
+
`{ "name": [ ...contents ] }`. Inside a folder's array, a **bare string** is a
|
|
46
|
+
plain key, an object with a **string value** `{ "SOURCE": "TARGET" }` is an
|
|
47
|
+
alias, and an object with an **array value** `{ "sub": [ ... ] }` is a
|
|
48
|
+
subfolder. Every emitted key is named (default-deny; there is no wildcard).
|
|
44
49
|
|
|
45
50
|
```jsonc
|
|
46
51
|
{
|
|
47
|
-
"$schema": "https://cdn.jsdelivr.net/npm/@hubble-ventures/infisicml@
|
|
48
|
-
"paths": ["clerk", "posthog"],
|
|
52
|
+
"$schema": "https://cdn.jsdelivr.net/npm/@hubble-ventures/infisicml@2/schema/secrets.schema.json",
|
|
49
53
|
"output": ".env", // written next to this manifest; defaults to .env.secrets
|
|
50
|
-
"
|
|
51
|
-
//
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
"secrets": [
|
|
55
|
+
// Canonical vault key → the prefixed name a build tool expects.
|
|
56
|
+
{ "clerk": [{ "CLERK_PUBLISHABLE_KEY": "VITE_CLERK_PUBLISHABLE_KEY" }] },
|
|
57
|
+
// Fan one canonical key out to several prefixes: repeat the alias, one
|
|
58
|
+
// target each (an array value would mean a subfolder, not multi-target).
|
|
59
|
+
{ "google": [
|
|
60
|
+
{ "GOOGLE_MAPS_API_KEY": "EXPO_PUBLIC_GOOGLE_MAPS_API_KEY" },
|
|
61
|
+
{ "GOOGLE_MAPS_API_KEY": "VITE_GOOGLE_MAPS_API_KEY" }
|
|
62
|
+
] },
|
|
63
|
+
{ "posthog": [
|
|
64
|
+
"POSTHOG_PROJECT_TOKEN",
|
|
65
|
+
// A real Infisical subfolder at /posthog/eu.
|
|
66
|
+
{ "eu": ["POSTHOG_EU_HOST"] }
|
|
67
|
+
] }
|
|
68
|
+
]
|
|
59
69
|
}
|
|
60
70
|
```
|
|
61
71
|
|
|
72
|
+
An alias entry emits **both** its canonical name and the target. Keys you
|
|
73
|
+
don't name are never pulled, so a client package can share a vendor folder yet
|
|
74
|
+
emit only the public key — the server secret is simply undeclared.
|
|
75
|
+
|
|
62
76
|
3. Pull secrets into gitignored `.env.secrets` files:
|
|
63
77
|
|
|
64
78
|
```bash
|
|
@@ -75,9 +89,9 @@ See [`examples/`](./examples) for a fuller config and manifest.
|
|
|
75
89
|
|---|---|
|
|
76
90
|
| `pull [ids...]` | Write `.env.secrets` locally (uses `infisical export`) |
|
|
77
91
|
| `export-gha <id>` | Mask + append secrets to `GITHUB_ENV` (REST API + GitHub OIDC) |
|
|
78
|
-
| `list` | Show every manifest and
|
|
92
|
+
| `list` | Show every manifest's folder tree and declared keys |
|
|
79
93
|
| `validate` | Validate every `secrets.json` against the schema |
|
|
80
|
-
| `paths <id>` | Print resolved paths (`--comma` for scripting) |
|
|
94
|
+
| `paths <id>` | Print resolved folder paths (`--comma` for scripting) |
|
|
81
95
|
| `run <id> -- <cmd...>` | Thin `infisical run` wrapper (prefer `pull` + `.env.secrets`) |
|
|
82
96
|
|
|
83
97
|
Common flags: `--env`, `--profile`, `--force`, `--here` (pull the cwd package), `--turbo` (always write, for Turbo caching).
|
|
@@ -132,89 +146,75 @@ Load `.env.secrets` however your dev runtime already loads env files. See [`exam
|
|
|
132
146
|
|
|
133
147
|
## Concepts
|
|
134
148
|
|
|
135
|
-
- **Manifest (`secrets.json`)** — per package: `
|
|
149
|
+
- **Manifest (`secrets.json`)** — per package: `secrets` (an array of folders → keys), optional `profiles`, `fetch`, `ci`, `environments`, `output`.
|
|
136
150
|
- **Output file** — `output` sets the written filename (default `.env.secrets`), placed **next to the manifest**. Because each package owns its own `secrets.json`, this gives a distinct file per package: a root manifest with `"output": ".env.local"` writes the repo-root `.env.local`; a manifest in `apps/backend` with `"output": ".env"` writes `apps/backend/.env`. `output` is a filename only (no path separators) — to target a different directory, place the manifest in that directory.
|
|
137
|
-
- **
|
|
138
|
-
- **
|
|
139
|
-
- **
|
|
140
|
-
- **Fetch mode** — `fetch: "keys"` requests only the keys
|
|
151
|
+
- **Secrets tree** — an **array of folders** mirroring your Infisical vault; each folder is `{ "name": [ ...contents ] }`. Inside the array: a bare string is a plain key, an object with a string value `{ "SOURCE": "TARGET" }` is an alias, and an object with an array value `{ "sub": [ ... ] }` is a real subfolder. Every emitted key is named — there is no wildcard, so a folder never leaks a key you didn't declare (default-deny). See [Key selection](#key-selection-the-tree-is-the-allowlist) below.
|
|
152
|
+
- **Profiles** — named alternate trees that *replace* the base `secrets` when `--profile` is set. The base tree is runtime secrets; a profile can add deploy/release folders (e.g. `fly`).
|
|
153
|
+
- **Aliases** — an alias entry `{ "SOURCE": "TARGET" }` maps a canonical vault key to one target. To fan a single key out to every framework prefix (`EXPO_PUBLIC_*`, `VITE_*`, `NEXT_PUBLIC_*`), repeat the entry — one target each (an array value means a subfolder). Both the canonical name and each target are emitted. Real secrets of a target name always win; the operation is idempotent and never overwrites.
|
|
154
|
+
- **Fetch mode** — `fetch: "keys"` requests only the declared keys, so the vault never transmits the rest (wire-level least privilege). Default `"folder"` reads whole folders and selects the declared keys locally. See [Wire-level least privilege](#wire-level-least-privilege-fetch-keys) below.
|
|
141
155
|
- **CI skip/stub** — `ci.skipWhenEnv` skips the pull when all listed vars are already set in CI; `ci.stubInCi` always stubs in CI. Both write a `.env.secrets` from `process.env` instead of calling Infisical.
|
|
142
|
-
- **Optional keys** — `environments.<slug>.optionalKeys` downgrade a missing key to a `::notice::`
|
|
156
|
+
- **Optional keys** — `environments.<slug>.optionalKeys` downgrade a missing declared key to a `::notice::` instead of a failure.
|
|
143
157
|
- **Advertise-keys hooks** — publish runtime key *names* (never values) to `GITHUB_ENV` for deploy forwarding.
|
|
144
158
|
|
|
145
|
-
### Key selection (
|
|
159
|
+
### Key selection (the tree is the allowlist)
|
|
146
160
|
|
|
147
161
|
Vaults are often organized by **vendor, not by access scope** — a single `/stripe` folder
|
|
148
162
|
holds both a server secret and a client-public key:
|
|
149
163
|
|
|
150
164
|
```
|
|
151
|
-
/stripe → STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET,
|
|
165
|
+
/stripe → STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PUBLISHABLE_KEY
|
|
152
166
|
```
|
|
153
167
|
|
|
154
|
-
A web/mobile client needs **only** the publishable key.
|
|
155
|
-
|
|
156
|
-
build's env (
|
|
157
|
-
|
|
158
|
-
`include` is a **default-deny allowlist**: only the listed env var names are emitted.
|
|
168
|
+
A web/mobile client needs **only** the publishable key. Because the tree is default-deny, you
|
|
169
|
+
name that one key and nothing else is pulled — `STRIPE_SECRET_KEY` is simply never declared,
|
|
170
|
+
so it can't reach a Vite/Expo client build's env (or, via the CI action, `GITHUB_ENV`).
|
|
159
171
|
|
|
160
172
|
```jsonc
|
|
161
173
|
// apps/web/secrets.json — a client package
|
|
162
174
|
{
|
|
163
|
-
"
|
|
164
|
-
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
},
|
|
168
|
-
"include": [
|
|
169
|
-
"EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY",
|
|
170
|
-
"EXPO_PUBLIC_GOOGLE_MAPS_API_KEY",
|
|
171
|
-
"EXPO_PUBLIC_VERCEL_AUTOMATION_BYPASS_SECRET"
|
|
175
|
+
"secrets": [
|
|
176
|
+
{ "stripe": [{ "STRIPE_PUBLISHABLE_KEY": "EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY" }] },
|
|
177
|
+
{ "google": [{ "GOOGLE_MAPS_API_KEY": "EXPO_PUBLIC_GOOGLE_MAPS_API_KEY" }] },
|
|
178
|
+
{ "vercel": ["VERCEL_AUTOMATION_BYPASS_SECRET"] }
|
|
172
179
|
]
|
|
173
180
|
}
|
|
174
181
|
```
|
|
175
182
|
|
|
176
|
-
The written `.env.secrets` (and, in CI, `GITHUB_ENV`) contains **only**
|
|
177
|
-
server secrets in the same folders are never emitted.
|
|
178
|
-
|
|
179
|
-
- **
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
for that profile (like `profiles.<name>.paths`). A profile without `include` inherits the
|
|
190
|
-
root one.
|
|
191
|
-
- **Backward compatible** — omit `include` and behavior is unchanged (every key is emitted).
|
|
183
|
+
The written `.env.secrets` (and, in CI, `GITHUB_ENV`) contains **only** the declared keys and
|
|
184
|
+
their alias targets — server secrets in the same folders are never emitted.
|
|
185
|
+
|
|
186
|
+
- **Provenance** — a key is emitted from the folder it's declared under, and only that folder.
|
|
187
|
+
Selection is per-folder, so the same name in two folders doesn't cross-contaminate.
|
|
188
|
+
- **Alias emits both names** — an alias entry copies the canonical key to its target; *both*
|
|
189
|
+
the canonical name and the target land in the output. (To emit only a prefixed name, don't
|
|
190
|
+
declare the canonical one — but the canonical publishable key is usually harmless.)
|
|
191
|
+
- **Unknown keys** — a declared key that its folder didn't produce is an error (fail the pull /
|
|
192
|
+
CI step), **unless** it's in `environments.<slug>.optionalKeys`, which downgrades it to a
|
|
193
|
+
`::notice::`. Enforced the same way in `pull` and `export-gha`.
|
|
194
|
+
- **Profiles** — a profile supplies its own `secrets`, which **replaces** the root tree for that
|
|
195
|
+
profile.
|
|
192
196
|
|
|
193
197
|
### Wire-level least privilege (`fetch: "keys"`)
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
By default (`fetch: "folder"`) infisicml pulls each whole folder, then selects the declared
|
|
200
|
+
keys before writing the output. The undeclared values still travel over the wire into your
|
|
201
|
+
machine / the CI runner — they're just never written to `.env` or `GITHUB_ENV`. For most
|
|
202
|
+
setups that's fine. When you need the vault to **not even transmit** the secrets you don't
|
|
203
|
+
use, set `fetch: "keys"`:
|
|
200
204
|
|
|
201
205
|
```jsonc
|
|
202
206
|
// apps/web/secrets.json — client package, strict wire-level least privilege
|
|
203
207
|
{
|
|
204
|
-
"paths": ["stripe", "google"],
|
|
205
208
|
"fetch": "keys",
|
|
206
|
-
"
|
|
207
|
-
"
|
|
208
|
-
|
|
209
|
-
"include": [
|
|
210
|
-
"EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY",
|
|
211
|
-
"GOOGLE_MAPS_API_KEY"
|
|
209
|
+
"secrets": [
|
|
210
|
+
{ "stripe": [{ "STRIPE_PUBLISHABLE_KEY": "EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY" }] },
|
|
211
|
+
{ "google": ["GOOGLE_MAPS_API_KEY"] }
|
|
212
212
|
]
|
|
213
213
|
}
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
In `keys` mode infisicml
|
|
217
|
-
|
|
216
|
+
In `keys` mode infisicml reads **only** the declared keys. Where the guarantee bites depends on
|
|
217
|
+
the lane:
|
|
218
218
|
|
|
219
219
|
- **CI (`export-gha`)** — true **wire-level** least privilege: each key is fetched with the
|
|
220
220
|
single-secret REST endpoint (`GET /api/v3/secrets/raw/{name}`), so the other keys in
|
|
@@ -225,23 +225,18 @@ bites depends on the lane:
|
|
|
225
225
|
once and selects the keys. It narrows what's **written to disk**, not what the vault
|
|
226
226
|
transmits — on your own machine, where folder mode already lands.
|
|
227
227
|
|
|
228
|
-
- **
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
`
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
- **
|
|
237
|
-
enforcement, `optionalKeys`) is identical to folder mode; only what's read narrows.
|
|
238
|
-
- **Cost** — in CI, one request **per key** instead of one per folder; the `paths` list becomes
|
|
228
|
+
- **No allowlist needed** — the tree already names every canonical key (an alias entry's key
|
|
229
|
+
*is* the real vault key), so `keys` mode requests exactly those names directly — no reverse
|
|
230
|
+
mapping, and `keys` is always satisfiable.
|
|
231
|
+
- **Imports still resolve** — a key surfaced into a folder via an Infisical import is fetched in
|
|
232
|
+
`keys` mode too (the per-key read follows imports but returns only that one secret), so
|
|
233
|
+
enabling `keys` never silently drops an import-backed key.
|
|
234
|
+
- **Same emit result** — everything after the fetch (aliases, unknown-key enforcement,
|
|
235
|
+
`optionalKeys`) is identical to folder mode; only what's read narrows.
|
|
236
|
+
- **Cost** — in CI, one request **per key** instead of one per folder; the folder list becomes
|
|
239
237
|
advisory (`infisicml paths` notes this).
|
|
240
|
-
- **Profiles** — a profile may set its own `fetch`, which **replaces** the root value
|
|
241
|
-
|
|
242
|
-
is `keys`, or vice versa.
|
|
243
|
-
- **Backward compatible** — omit `fetch` (or set `"folder"`) and behavior is byte-for-byte
|
|
244
|
-
unchanged.
|
|
238
|
+
- **Profiles** — a profile may set its own `fetch`, which **replaces** the root value. A deploy
|
|
239
|
+
profile can stay in `folder` mode while the runtime default is `keys`, or vice versa.
|
|
245
240
|
|
|
246
241
|
### Non-secret defaults (`.env.sample`)
|
|
247
242
|
|
|
@@ -278,19 +273,24 @@ for (const manifest of discoverManifests(config)) {
|
|
|
278
273
|
|
|
279
274
|
## Releasing
|
|
280
275
|
|
|
281
|
-
Releases are
|
|
276
|
+
**Releases are automatic — you don't tag anything.** Bump the version in a PR
|
|
277
|
+
and merging it to `main` cuts the release:
|
|
282
278
|
|
|
283
279
|
```bash
|
|
284
|
-
npm
|
|
285
|
-
|
|
286
|
-
git
|
|
280
|
+
npm run release -- minor # or patch / major / 2.1.0 — bumps package.json + rebuilds dist
|
|
281
|
+
# edit CHANGELOG.md: add the new version's section
|
|
282
|
+
git commit -am "release: v2.1.0"
|
|
283
|
+
# open a PR, merge it → release fires
|
|
287
284
|
```
|
|
288
285
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
286
|
+
On the push to `main`, [`.github/workflows/release.yml`](./.github/workflows/release.yml)
|
|
287
|
+
reads `package.json`; if that version isn't tagged yet it builds + tests, **creates and
|
|
288
|
+
pushes the `vX.Y.Z` tag**, publishes to npm via **trusted publishing** (OIDC — no
|
|
289
|
+
`NPM_TOKEN` secret, provenance attached automatically), creates a GitHub Release from the
|
|
290
|
+
matching [`CHANGELOG.md`](./CHANGELOG.md) section, and moves the floating major tag (e.g.
|
|
291
|
+
`v2`). A main push that doesn't bump the version is a no-op, and the publish step is
|
|
292
|
+
idempotent — a version already on npm is skipped. Manually pushing a `vX.Y.Z` tag still
|
|
293
|
+
works if you ever need it.
|
|
294
294
|
|
|
295
295
|
> **First publish (one time).** The `@hubble-ventures` npm **organization** must exist,
|
|
296
296
|
> and npm trusted publishing can only be configured against an *existing* package — so
|
|
@@ -304,8 +304,8 @@ version already on npm is skipped.
|
|
|
304
304
|
> (`publishConfig.access: public` in `package.json` enforces this even if the flag is
|
|
305
305
|
> omitted.) Then, in the package's npm settings, add a **trusted publisher** for
|
|
306
306
|
> `hubble-ventures/infisicml` → workflow `release.yml`, with **no environment** (the release
|
|
307
|
-
> job sets none — the OIDC claims must match). Every subsequent
|
|
308
|
-
> automatically with no token.
|
|
307
|
+
> job sets none — the OIDC claims must match). Every subsequent version bump merged to
|
|
308
|
+
> `main` publishes automatically with no token.
|
|
309
309
|
|
|
310
310
|
## Design
|
|
311
311
|
|
package/dist/aliases.d.ts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CompiledFolder } from "./tree.js";
|
|
2
2
|
export type ResolvedAlias = {
|
|
3
3
|
source: string;
|
|
4
4
|
targets: string[];
|
|
5
5
|
};
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Collect the alias `source -> targets` pairs from the compiled folders. Each
|
|
8
|
+
* `aliased` entry in the tree names a canonical vault key (the source) and the
|
|
9
|
+
* extra env var name(s) a build/runtime expects (the targets).
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveAliases(folders: CompiledFolder[]): ResolvedAlias[];
|
|
8
12
|
/**
|
|
9
13
|
* Copy each aliased source secret's value to its target env var name(s).
|
|
10
14
|
*
|
|
11
15
|
* Used wherever the CLI materializes secrets (CI `export-gha`, local
|
|
12
16
|
* `.env.secrets` pull) so the conventional, tool-specific name each deployment
|
|
13
|
-
* expects is always present —
|
|
17
|
+
* expects is always present — the vault names a secret once (e.g. /posthog
|
|
18
|
+
* exposes POSTHOG_PROJECT_TOKEN), but build tools inline it by a tool-specific
|
|
19
|
+
* name (Vite reads VITE_*, Next reads NEXT_PUBLIC_*).
|
|
14
20
|
*
|
|
15
21
|
* Returns a new object. An absent source is skipped, and an existing target (a
|
|
16
22
|
* real secret of that name) is never overwritten, so real values win over
|
|
17
23
|
* aliases and the operation is idempotent.
|
|
18
24
|
*/
|
|
19
|
-
export declare function applyAliases(merged: Record<string, string>,
|
|
25
|
+
export declare function applyAliases(merged: Record<string, string>, folders: CompiledFolder[]): Record<string, string>;
|
|
20
26
|
//# sourceMappingURL=aliases.d.ts.map
|
package/dist/aliases.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aliases.d.ts","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"aliases.d.ts","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,aAAa,EAAE,CAUzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,EAAE,cAAc,EAAE,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB"}
|
package/dist/aliases.js
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Collect the alias `source -> targets` pairs from the compiled folders. Each
|
|
3
|
+
* `aliased` entry in the tree names a canonical vault key (the source) and the
|
|
4
|
+
* extra env var name(s) a build/runtime expects (the targets).
|
|
5
|
+
*/
|
|
6
|
+
export function resolveAliases(folders) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const folder of folders) {
|
|
9
|
+
for (const key of folder.keys) {
|
|
10
|
+
if (key.aliases.length > 0) {
|
|
11
|
+
out.push({ source: key.key, targets: key.aliases });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
12
18
|
* Copy each aliased source secret's value to its target env var name(s).
|
|
13
19
|
*
|
|
14
20
|
* Used wherever the CLI materializes secrets (CI `export-gha`, local
|
|
15
21
|
* `.env.secrets` pull) so the conventional, tool-specific name each deployment
|
|
16
|
-
* expects is always present —
|
|
22
|
+
* expects is always present — the vault names a secret once (e.g. /posthog
|
|
23
|
+
* exposes POSTHOG_PROJECT_TOKEN), but build tools inline it by a tool-specific
|
|
24
|
+
* name (Vite reads VITE_*, Next reads NEXT_PUBLIC_*).
|
|
17
25
|
*
|
|
18
26
|
* Returns a new object. An absent source is skipped, and an existing target (a
|
|
19
27
|
* real secret of that name) is never overwritten, so real values win over
|
|
20
28
|
* aliases and the operation is idempotent.
|
|
21
29
|
*/
|
|
22
|
-
export function applyAliases(merged,
|
|
30
|
+
export function applyAliases(merged, folders) {
|
|
23
31
|
const out = { ...merged };
|
|
24
|
-
for (const { source, targets } of resolveAliases(
|
|
32
|
+
for (const { source, targets } of resolveAliases(folders)) {
|
|
25
33
|
const value = merged[source];
|
|
26
34
|
if (value === undefined)
|
|
27
35
|
continue;
|
package/dist/aliases.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aliases.js","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"aliases.js","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAyB;IACtD,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,MAA8B,EAC9B,OAAyB;IAEzB,MAAM,GAAG,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1B,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7B,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,SAAS;gBAAE,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/ci-skip.d.ts
CHANGED
|
@@ -1,26 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SecretsManifest } from "./manifest.js";
|
|
2
2
|
import type { SecretsProvider } from "./providers/types.js";
|
|
3
|
+
import type { CompiledFolder } from "./tree.js";
|
|
3
4
|
export type { SecretsProvider };
|
|
5
|
+
/** A folder's declared keys paired with the values that folder actually held. */
|
|
6
|
+
export type FolderSecrets = {
|
|
7
|
+
folder: CompiledFolder;
|
|
8
|
+
/** Declared keys present in this folder (canonical, pre-alias). */
|
|
9
|
+
selected: Record<string, string>;
|
|
10
|
+
};
|
|
4
11
|
export declare function mergeFolderSecrets(chunks: Record<string, string>[]): Record<string, string>;
|
|
5
|
-
export declare function fetchSecretsForPaths(provider: SecretsProvider, envName: string, paths: string[]): Promise<Record<string, string>>;
|
|
6
12
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
13
|
+
* Fetch each compiled folder and select *that folder's* declared keys from it,
|
|
14
|
+
* honoring the resolved `fetch` mode. `keys` mode requests only the declared
|
|
15
|
+
* keys per folder (the tree names the exact canonical vault keys, so no
|
|
16
|
+
* allowlist reverse-map is needed — the alias *source* is the real key);
|
|
17
|
+
* `folder` mode reads the whole folder and picks the declared keys locally.
|
|
18
|
+
*
|
|
19
|
+
* Returns one {@link FolderSecrets} per folder so downstream steps keep folder
|
|
20
|
+
* provenance: two folders declaring the same key name are distinct entries here,
|
|
21
|
+
* and only collapse (last-wins) at the final merge — see {@link materializeSecrets}.
|
|
11
22
|
*/
|
|
12
|
-
export declare function
|
|
23
|
+
export declare function fetchCompiledFolders(provider: SecretsProvider, envName: string, folders: CompiledFolder[], fetchMode: "folder" | "keys"): Promise<FolderSecrets[]>;
|
|
13
24
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
25
|
+
* Turn per-folder fetched secrets into the final emit map, preserving folder
|
|
26
|
+
* provenance:
|
|
27
|
+
*
|
|
28
|
+
* - **Missing keys are checked per folder/key pair, before merging.** A key
|
|
29
|
+
* declared in `/a` but absent from `/a` is unknown even if a same-named key in
|
|
30
|
+
* `/b` was produced — so a genuine miss can't be masked by another folder.
|
|
31
|
+
* - **Aliases expand from the folder-local value.** `/a`'s `TOKEN -> A_TOKEN`
|
|
32
|
+
* uses `/a`'s `TOKEN`, and `/b`'s `TOKEN -> B_TOKEN` uses `/b`'s — the flat
|
|
33
|
+
* merge (which keeps only one `TOKEN`) can no longer route one folder's secret
|
|
34
|
+
* to another folder's alias.
|
|
18
35
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
36
|
+
* Folder-local aliased maps then merge in tree order (last-wins on a genuine
|
|
37
|
+
* cross-folder name collision). A declared key absent from its folder fails
|
|
38
|
+
* unless its name is in `optionalKeys` for the environment.
|
|
22
39
|
*/
|
|
23
|
-
export declare function
|
|
40
|
+
export declare function materializeSecrets(folderSecrets: FolderSecrets[], optionalKeys: string[]): Record<string, string>;
|
|
24
41
|
export declare function isCi(): boolean;
|
|
25
42
|
export declare function shouldSkipInfisicalPull(manifest: SecretsManifest, force: boolean): boolean;
|
|
26
43
|
export declare function keysForCiStub(manifest: SecretsManifest): string[];
|
package/dist/ci-skip.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ci-skip.d.ts","sourceRoot":"","sources":["../src/ci-skip.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ci-skip.d.ts","sourceRoot":"","sources":["../src/ci-skip.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,EAAE,EACzB,SAAS,EAAE,QAAQ,GAAG,MAAM,GAC3B,OAAO,CAAC,aAAa,EAAE,CAAC,CAe1B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,aAAa,EAAE,EAC9B,YAAY,EAAE,MAAM,EAAE,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB;AAED,wBAAgB,IAAI,IAAI,OAAO,CAG9B;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,OAAO,GACb,OAAO,CAgBT;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,EAAE,CAEjE"}
|