@hubble-ventures/infisicml 1.2.1
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 +103 -0
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/dist/aliases.d.ts +20 -0
- package/dist/aliases.d.ts.map +1 -0
- package/dist/aliases.js +35 -0
- package/dist/aliases.js.map +1 -0
- package/dist/ci-skip.d.ts +27 -0
- package/dist/ci-skip.d.ts.map +1 -0
- package/dist/ci-skip.js +82 -0
- package/dist/ci-skip.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +150 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/export-gha.d.ts +11 -0
- package/dist/commands/export-gha.d.ts.map +1 -0
- package/dist/commands/export-gha.js +79 -0
- package/dist/commands/export-gha.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +35 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/paths.d.ts +8 -0
- package/dist/commands/paths.d.ts.map +1 -0
- package/dist/commands/paths.js +30 -0
- package/dist/commands/paths.js.map +1 -0
- package/dist/commands/pull.d.ts +11 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +65 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/run.d.ts +9 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +30 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +33 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +68 -0
- package/dist/config.js.map +1 -0
- package/dist/dotenv.d.ts +6 -0
- package/dist/dotenv.d.ts.map +1 -0
- package/dist/dotenv.js +31 -0
- package/dist/dotenv.js.map +1 -0
- package/dist/env-slug.d.ts +3 -0
- package/dist/env-slug.d.ts.map +1 -0
- package/dist/env-slug.js +7 -0
- package/dist/env-slug.js.map +1 -0
- package/dist/github-env.d.ts +10 -0
- package/dist/github-env.d.ts.map +1 -0
- package/dist/github-env.js +37 -0
- package/dist/github-env.js.map +1 -0
- package/dist/hooks.d.ts +17 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +19 -0
- package/dist/hooks.js.map +1 -0
- package/dist/include.d.ts +46 -0
- package/dist/include.d.ts.map +1 -0
- package/dist/include.js +82 -0
- package/dist/include.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +105 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +153 -0
- package/dist/manifest.js.map +1 -0
- package/dist/optional-keys.d.ts +4 -0
- package/dist/optional-keys.d.ts.map +1 -0
- package/dist/optional-keys.js +11 -0
- package/dist/optional-keys.js.map +1 -0
- package/dist/providers/local.d.ts +38 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +76 -0
- package/dist/providers/local.js.map +1 -0
- package/dist/providers/remote.d.ts +52 -0
- package/dist/providers/remote.d.ts.map +1 -0
- package/dist/providers/remote.js +166 -0
- package/dist/providers/remote.js.map +1 -0
- package/dist/providers/types.d.ts +14 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/pull.d.ts +15 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/pull.js +67 -0
- package/dist/pull.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +35 -0
- package/dist/registry.js.map +1 -0
- package/package.json +66 -0
- package/schema/secrets.schema.json +105 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `infisicml` are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
|
|
5
|
+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.2.1] - 2026-07-17
|
|
8
|
+
|
|
9
|
+
### Changed (BREAKING)
|
|
10
|
+
|
|
11
|
+
- **Renamed the package, CLI, repo, and config from `infiscml` to `infisicml`**
|
|
12
|
+
(a typo fix — the name is derived from *Infisical*). This is a hard break with
|
|
13
|
+
**no compatibility shims** — consumers must migrate all references when they
|
|
14
|
+
adopt the new package:
|
|
15
|
+
- **npm package**: install `@hubble-ventures/infisicml`; the old
|
|
16
|
+
`@hubble-ventures/infiscml` package is deprecated and receives no updates.
|
|
17
|
+
- **CLI binary**: `infisicml` (the old `infiscml` binary is gone — update any
|
|
18
|
+
scripts, `package.json` commands, and `node_modules/.bin` references).
|
|
19
|
+
- **Config file**: only `infisicml.config.{json,mjs,js}` is discovered —
|
|
20
|
+
rename `infiscml.config.*` to `infisicml.config.*`.
|
|
21
|
+
- **Composite action input**: `infisicml-version` (the old `infiscml-version`
|
|
22
|
+
input no longer exists).
|
|
23
|
+
- **Exported type**: `InfisicmlConfig` (was `InfiscmlConfig`).
|
|
24
|
+
- **Repository**: [`hubble-ventures/infisicml`](https://github.com/hubble-ventures/infisicml)
|
|
25
|
+
(GitHub redirects the old URL).
|
|
26
|
+
|
|
27
|
+
## [1.2.0] - 2026-07-16
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **Least-privilege fetch (`fetch: "keys"`).** A new per-manifest `fetch` field
|
|
32
|
+
(default `"folder"`) controls how secrets are read. In `"keys"` mode infisicml
|
|
33
|
+
emits **only** the keys `include` resolves to. In **CI** this is enforced at
|
|
34
|
+
the wire: each key is fetched with the single-secret REST endpoint
|
|
35
|
+
(`GET /api/v3/secrets/raw/{name}`), so the vault never transmits the other
|
|
36
|
+
keys in a folder — contrast folder mode, where `include` is a post-fetch
|
|
37
|
+
filter and excluded values still travel over the wire. **Locally** the
|
|
38
|
+
`infisical` CLI has no single-secret server read, so infisicml fetches each
|
|
39
|
+
folder once and selects the keys (narrowing what's written, not what's
|
|
40
|
+
transmitted). `include` names the final (post-alias) keys, so key mode
|
|
41
|
+
**reverse-maps** each alias target to its canonical vault source before
|
|
42
|
+
fetching, and import-surfaced keys still resolve. Everything after the fetch
|
|
43
|
+
(aliases, `include` filtering,
|
|
44
|
+
unknown-key enforcement, `optionalKeys`) is unchanged, so the emitted output
|
|
45
|
+
matches folder mode. `fetch: "keys"` **requires** an `include` allowlist
|
|
46
|
+
(enforced by `validate` and the pull / CI step). A per-profile `fetch`
|
|
47
|
+
replaces the root one (same replace-not-merge as `paths` / `include`). Honored
|
|
48
|
+
by `pull` and `export-gha`; surfaced by `list`, `paths`, and `validate`.
|
|
49
|
+
Published in the exported JSON Schema. Omitting `fetch` is byte-for-byte
|
|
50
|
+
backward compatible.
|
|
51
|
+
|
|
52
|
+
## [1.1.0] - 2026-07-16
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **Key-level `include` allowlist.** `secrets.json` can now emit only a chosen
|
|
57
|
+
subset of the keys a folder yields (default-deny key selection), so a client
|
|
58
|
+
package can pull a shared vendor folder but keep server secrets out of client
|
|
59
|
+
builds and `GITHUB_ENV`. Add `include` at the manifest root and/or per profile
|
|
60
|
+
(a profile's `include` replaces the root one). Applied **after** `aliases`, to
|
|
61
|
+
the final set of names. An `include` key that no folder produced fails the pull
|
|
62
|
+
/ CI step unless it's listed in `environments.<slug>.optionalKeys` (downgraded
|
|
63
|
+
to a `::notice::`). Honored by `pull`, `export-gha`, `list`, `paths`, and
|
|
64
|
+
`validate`. Omitting `include` is fully backward compatible — every key is
|
|
65
|
+
emitted, as before. Published in the exported JSON Schema.
|
|
66
|
+
|
|
67
|
+
## [1.0.0] - 2026-07-16
|
|
68
|
+
|
|
69
|
+
First public release. The `secrets.json` manifest contract and the
|
|
70
|
+
`infisicml.config` surface are now considered stable under semver — breaking
|
|
71
|
+
changes to either ship only in a new major.
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **Published to npm as a public scoped package** (`npm i -D @hubble-ventures/infisicml`,
|
|
76
|
+
`npx --yes @hubble-ventures/infisicml@latest`). Released via npm **trusted publishing**
|
|
77
|
+
(OIDC) with build provenance — no long-lived tokens.
|
|
78
|
+
- **Stable action ref.** `hubble-ventures/infisicml/action@v1` is a floating major
|
|
79
|
+
tag that tracks the latest `v1.x`; consumers may also pin a release SHA.
|
|
80
|
+
- **CLI**: `pull`, `export-gha`, `list`, `validate`, `paths`, `run`.
|
|
81
|
+
- **Two auth lanes, one per environment.** Local dev uses the Infisical CLI user
|
|
82
|
+
session (`infisical login`); CI uses **GitHub OIDC → Infisical machine
|
|
83
|
+
identity** over the REST API, with no Infisical CLI in the runner.
|
|
84
|
+
- **Multi-target aliases.** One canonical vault key can be copied to several
|
|
85
|
+
prefixed names in a single output (e.g. `GOOGLE_MAPS_API_KEY` →
|
|
86
|
+
`EXPO_PUBLIC_GOOGLE_MAPS_API_KEY` **and** `VITE_GOOGLE_MAPS_API_KEY`). Real
|
|
87
|
+
secrets of a target name always win; the operation is idempotent.
|
|
88
|
+
- **Per-package output filename.** `output` sets the written filename (e.g.
|
|
89
|
+
`.env.local`, `.env`) next to each manifest.
|
|
90
|
+
- **Profiles** for deploy-only credentials — profile paths replace base paths, so
|
|
91
|
+
release-time creds (e.g. `fly`) never leak into runtime output.
|
|
92
|
+
- **`advertiseKeys` hooks** publish runtime key *names* (never values) to
|
|
93
|
+
`GITHUB_ENV` for deploy forwarding.
|
|
94
|
+
- **CI skip/stub** (`ci.skipWhenEnv`, `ci.stubInCi`) and **optional keys**
|
|
95
|
+
(`environments.<slug>.optionalKeys`) that downgrade a missing key to a
|
|
96
|
+
`::notice::` instead of failing CI.
|
|
97
|
+
- **Published JSON Schema** for `secrets.json`, served from
|
|
98
|
+
`https://cdn.jsdelivr.net/npm/@hubble-ventures/infisicml@1/schema/secrets.schema.json`.
|
|
99
|
+
|
|
100
|
+
[1.2.1]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.2.1
|
|
101
|
+
[1.2.0]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.2.0
|
|
102
|
+
[1.1.0]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.1.0
|
|
103
|
+
[1.0.0]: https://github.com/hubble-ventures/infisicml/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hubble Ventures
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# infisicml
|
|
2
|
+
|
|
3
|
+
**Infisical Secret Orchestration** — federated, per-package secret manifests for monorepos, unified across local development and CI.
|
|
4
|
+
|
|
5
|
+
[Infisical](https://infisical.com) gives you a vault and the primitives to read from it (a CLI, a REST API, machine-identity auth, a GitHub Action). All of those operate on **one folder / one environment / one process at a time**. Infisicml is the layer above that: it lets each package in a monorepo declare which vault folders it needs in a committed `secrets.json`, then materializes those secrets **the same way** whether you're a developer running a local pull or a CI job exporting into `GITHUB_ENV`.
|
|
6
|
+
|
|
7
|
+
> Infisicml orchestrates Infisical. It stores no secrets and never sees your vault contents at rest — the vault, auth, and secret values stay in Infisical.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
| Problem | Native Infisical | Infisicml |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| N packages each need a different slice of the vault | Hand-write export commands per app×folder | One committed `secrets.json` per package, auto-discovered |
|
|
14
|
+
| Dev uses the CLI, CI uses the API | Two integrations kept in sync by hand | One `SecretsProvider` abstraction; identical downstream logic |
|
|
15
|
+
| Vault names a secret once, but Vite wants `VITE_*` and Next wants `NEXT_PUBLIC_*` | Each workflow re-derives the prefixed copy | Declare `aliases` once; applied in dev **and** CI |
|
|
16
|
+
| Skip the pull in CI when vars are already injected | Bash guards in every workflow | `ci.skipWhenEnv` / `ci.stubInCi` policy |
|
|
17
|
+
| A deploy needs exactly the runtime keys, not the deploy creds | Hand-maintained allowlist | `profiles` + `advertiseKeys` hook — `secrets.json` is the source of truth |
|
|
18
|
+
| A client must read a shared vendor folder without ever receiving its server secrets | Split the vault or accept over-fetch | `fetch: "keys"` requests only the named keys — the vault never transmits the rest |
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm i -D @hubble-ventures/infisicml # or pnpm add -D @hubble-ventures/infisicml
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires the [`infisical` CLI](https://infisical.com/docs/cli/overview) for local pulls (`infisical login` once). CI needs no CLI — it uses the REST API.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
1. Add an `infisicml.config.ts` (or `.json`) at your monorepo root — this file marks the repo root:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { defineConfig } from "@hubble-ventures/infisicml";
|
|
34
|
+
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
projectIdEnvFile: ".env.infisical", // provides INFISICAL_PROJECT_ID
|
|
37
|
+
discovery: { roots: ["apps", "services"], packages: [{ id: "postgres", dir: "infra/postgres" }] },
|
|
38
|
+
auth: { oidcAudience: "https://github.com/your-org" },
|
|
39
|
+
hooks: { advertiseKeys: [{ envVar: "INFISCML_FLY_KEYS", scope: "runtime" }] },
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
2. Add a `secrets.json` next to each package:
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
{
|
|
47
|
+
"$schema": "https://cdn.jsdelivr.net/npm/@hubble-ventures/infisicml@1/schema/secrets.schema.json",
|
|
48
|
+
"paths": ["clerk", "posthog"],
|
|
49
|
+
"output": ".env", // written next to this manifest; defaults to .env.secrets
|
|
50
|
+
"aliases": {
|
|
51
|
+
// One canonical vault key → several prefixed copies in a single output.
|
|
52
|
+
"GOOGLE_MAPS_API_KEY": [
|
|
53
|
+
"EXPO_PUBLIC_GOOGLE_MAPS_API_KEY",
|
|
54
|
+
"VITE_GOOGLE_MAPS_API_KEY"
|
|
55
|
+
],
|
|
56
|
+
// A single target is also fine (string form).
|
|
57
|
+
"CLERK_PUBLISHABLE_KEY": "VITE_CLERK_PUBLISHABLE_KEY"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. Pull secrets into gitignored `.env.secrets` files:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx infisicml pull # every package
|
|
66
|
+
npx infisicml pull web api --force # specific ids, bypass the exists-check
|
|
67
|
+
npx infisicml validate # check every secrets.json against the schema
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See [`examples/`](./examples) for a fuller config and manifest.
|
|
71
|
+
|
|
72
|
+
## CLI
|
|
73
|
+
|
|
74
|
+
| Command | Purpose |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `pull [ids...]` | Write `.env.secrets` locally (uses `infisical export`) |
|
|
77
|
+
| `export-gha <id>` | Mask + append secrets to `GITHUB_ENV` (REST API + GitHub OIDC) |
|
|
78
|
+
| `list` | Show every manifest and its Infisical paths |
|
|
79
|
+
| `validate` | Validate every `secrets.json` against the schema |
|
|
80
|
+
| `paths <id>` | Print resolved paths (`--comma` for scripting) |
|
|
81
|
+
| `run <id> -- <cmd...>` | Thin `infisical run` wrapper (prefer `pull` + `.env.secrets`) |
|
|
82
|
+
|
|
83
|
+
Common flags: `--env`, `--profile`, `--force`, `--here` (pull the cwd package), `--turbo` (always write, for Turbo caching).
|
|
84
|
+
|
|
85
|
+
## Authentication
|
|
86
|
+
|
|
87
|
+
Infisicml supports exactly one auth lane per environment — no secrets to rotate, no fallbacks to reason about:
|
|
88
|
+
|
|
89
|
+
| Where | How | Requires |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| **Local dev** | Infisical CLI **user login** (`infisical login`) | `infisical` CLI, logged in as you |
|
|
92
|
+
| **CI (GitHub Actions)** | **GitHub OIDC** → Infisical machine identity | `permissions: id-token: write` + an Infisical machine identity with a GitHub OIDC auth method |
|
|
93
|
+
|
|
94
|
+
There is no client-id/secret (universal-auth) path and no long-lived token anywhere.
|
|
95
|
+
|
|
96
|
+
## GitHub Actions
|
|
97
|
+
|
|
98
|
+
Use the bundled composite action (runs `infisicml export-gha` via `npx`). The calling job **must** grant `id-token: write` so the runner can mint an OIDC token:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
jobs:
|
|
102
|
+
deploy:
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
permissions:
|
|
105
|
+
id-token: write # REQUIRED — OIDC is the only CI auth lane
|
|
106
|
+
contents: read
|
|
107
|
+
steps:
|
|
108
|
+
- uses: actions/checkout@v4
|
|
109
|
+
- uses: hubble-ventures/infisicml/action@v1
|
|
110
|
+
with:
|
|
111
|
+
package-id: api
|
|
112
|
+
environment: production
|
|
113
|
+
project-slug: ${{ vars.INFISICAL_PROJECT_SLUG }}
|
|
114
|
+
identity-id: ${{ vars.INFISICAL_IDENTITY_ID }}
|
|
115
|
+
oidc-audience: ${{ vars.INFISICAL_OIDC_AUDIENCE }} # optional
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Secrets are masked and appended to `GITHUB_ENV` for subsequent steps. Any configured `advertiseKeys` hook writes a plain, comma-separated list of runtime key **names** so a deploy step can forward exactly those. A full CI + deploy example is in [`examples/github-actions.yml`](./examples/github-actions.yml).
|
|
119
|
+
|
|
120
|
+
**Pinning the action.** `@v1` is a floating major tag that always points at the latest `v1.x` release — it moves forward on each release but never across a breaking major. For a fully immutable pin, use a release SHA instead: `uses: hubble-ventures/infisicml/action@<sha>`. The action shells out to `npx --yes @hubble-ventures/infisicml@latest`; pin the package too by passing `infisicml-version:` (e.g. the same `1.x` version) if you want the CLI locked as well.
|
|
121
|
+
|
|
122
|
+
## Local development
|
|
123
|
+
|
|
124
|
+
Log in once as yourself; infisicml shells out to `infisical export` under your session:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
infisical login # authenticate as you
|
|
128
|
+
npx infisicml pull # write .env.secrets next to every secrets.json
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Load `.env.secrets` however your dev runtime already loads env files. See [`examples/local-dev.md`](./examples/local-dev.md) for package-script and task-runner wiring.
|
|
132
|
+
|
|
133
|
+
## Concepts
|
|
134
|
+
|
|
135
|
+
- **Manifest (`secrets.json`)** — per package: `paths` (vault folders), optional `profiles`, `aliases`, `include`, `fetch`, `ci`, `environments`, `output`.
|
|
136
|
+
- **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
|
+
- **Profiles** — named path sets that *replace* base `paths` when `--profile` is set. Base paths are runtime secrets; profile-only paths (e.g. `fly`) are deploy/release credentials.
|
|
138
|
+
- **Aliases** — copy a canonical secret to extra tool-specific names. Each source maps to **one target (string) or many (array)**, so a single vault key can fan out to every framework prefix (`EXPO_PUBLIC_*`, `VITE_*`, `NEXT_PUBLIC_*`) in one output. Real secrets of a target name always win; the operation is idempotent and never overwrites.
|
|
139
|
+
- **Include (key allowlist)** — `include` emits **only** the listed keys from whatever the folders yielded (default-deny key selection). Omit it and every key is emitted (unchanged). See [Key selection](#key-selection-include) below.
|
|
140
|
+
- **Fetch mode** — `fetch: "keys"` requests only the keys `include` resolves to, so the vault never transmits the rest (wire-level least privilege). Default `"folder"` reads whole folders and filters locally. See [Wire-level least privilege](#wire-level-least-privilege-fetch-keys) below.
|
|
141
|
+
- **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::` in `export-gha` instead of a failure.
|
|
143
|
+
- **Advertise-keys hooks** — publish runtime key *names* (never values) to `GITHUB_ENV` for deploy forwarding.
|
|
144
|
+
|
|
145
|
+
### Key selection (`include`)
|
|
146
|
+
|
|
147
|
+
Vaults are often organized by **vendor, not by access scope** — a single `/stripe` folder
|
|
148
|
+
holds both a server secret and a client-public key:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
/stripe → STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A web/mobile client needs **only** the publishable key. With folder-only selection it would
|
|
155
|
+
be forced to pull the whole folder — writing `STRIPE_SECRET_KEY` into a Vite/Expo client
|
|
156
|
+
build's env (and, via the CI action, into `GITHUB_ENV` for jobs that shouldn't see it).
|
|
157
|
+
|
|
158
|
+
`include` is a **default-deny allowlist**: only the listed env var names are emitted.
|
|
159
|
+
|
|
160
|
+
```jsonc
|
|
161
|
+
// apps/web/secrets.json — a client package
|
|
162
|
+
{
|
|
163
|
+
"paths": ["stripe", "google", "vercel"],
|
|
164
|
+
"aliases": {
|
|
165
|
+
"STRIPE_PUBLISHABLE_KEY": ["EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY"],
|
|
166
|
+
"GOOGLE_MAPS_API_KEY": ["EXPO_PUBLIC_GOOGLE_MAPS_API_KEY"]
|
|
167
|
+
},
|
|
168
|
+
"include": [
|
|
169
|
+
"EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY",
|
|
170
|
+
"EXPO_PUBLIC_GOOGLE_MAPS_API_KEY",
|
|
171
|
+
"EXPO_PUBLIC_VERCEL_AUTOMATION_BYPASS_SECRET"
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The written `.env.secrets` (and, in CI, `GITHUB_ENV`) contains **only** those three keys —
|
|
177
|
+
server secrets in the same folders are never emitted.
|
|
178
|
+
|
|
179
|
+
- **Ordering** — folders are pulled, then `aliases` are applied, then `include` filters the
|
|
180
|
+
**final** set of names. So aliasing a canonical key to a public name and listing only the
|
|
181
|
+
public name in `include` drops the canonical key (and every other folder key). An alias
|
|
182
|
+
whose source isn't in `include` still emits its target.
|
|
183
|
+
- **Names, not paths** — `include` is a flat list of env var names; key names must be unique
|
|
184
|
+
across a package's folders.
|
|
185
|
+
- **Unknown keys** — a name in `include` that no folder produced is an error (fail the pull /
|
|
186
|
+
CI step), **unless** it's also in `environments.<slug>.optionalKeys`, which downgrades it to
|
|
187
|
+
a `::notice::`. Enforced the same way in `pull` and `export-gha`.
|
|
188
|
+
- **Profiles** — a profile may set its own `include`, which **replaces** the root `include`
|
|
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).
|
|
192
|
+
|
|
193
|
+
### Wire-level least privilege (`fetch: "keys"`)
|
|
194
|
+
|
|
195
|
+
`include` on its own is a **post-fetch filter**: infisicml pulls each whole folder, then drops
|
|
196
|
+
the unwanted keys before writing the output. The excluded values still travel over the wire
|
|
197
|
+
into your machine / the CI runner — they're just never written to `.env` or `GITHUB_ENV`. For
|
|
198
|
+
most setups that's fine. When you need the vault to **not even transmit** the secrets you
|
|
199
|
+
don't use, set `fetch: "keys"`:
|
|
200
|
+
|
|
201
|
+
```jsonc
|
|
202
|
+
// apps/web/secrets.json — client package, strict wire-level least privilege
|
|
203
|
+
{
|
|
204
|
+
"paths": ["stripe", "google"],
|
|
205
|
+
"fetch": "keys",
|
|
206
|
+
"aliases": {
|
|
207
|
+
"STRIPE_PUBLISHABLE_KEY": ["EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY"]
|
|
208
|
+
},
|
|
209
|
+
"include": [
|
|
210
|
+
"EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY",
|
|
211
|
+
"GOOGLE_MAPS_API_KEY"
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
In `keys` mode infisicml emits **only** the keys `include` resolves to. Where the guarantee
|
|
217
|
+
bites depends on the lane:
|
|
218
|
+
|
|
219
|
+
- **CI (`export-gha`)** — true **wire-level** least privilege: each key is fetched with the
|
|
220
|
+
single-secret REST endpoint (`GET /api/v3/secrets/raw/{name}`), so the other keys in
|
|
221
|
+
`/stripe` and `/google` are **never sent by the vault**. This is the security-critical lane
|
|
222
|
+
(shared runners, `GITHUB_ENV`).
|
|
223
|
+
- **Local (`pull`)** — the `infisical` CLI has no single-secret *server* read (`secrets get`
|
|
224
|
+
pulls the whole folder and filters client-side), so locally infisicml fetches each folder
|
|
225
|
+
once and selects the keys. It narrows what's **written to disk**, not what the vault
|
|
226
|
+
transmits — on your own machine, where folder mode already lands.
|
|
227
|
+
|
|
228
|
+
- **Requires `include`** — key mode fetches exactly what `include` names, so an allowlist is
|
|
229
|
+
mandatory (root or the active profile). `validate` and the pull/CI step both enforce this.
|
|
230
|
+
- **Aliases are reverse-mapped** — `include` names the *final* (post-alias) keys, so infisicml
|
|
231
|
+
fetches the canonical vault source behind each alias target. Above, listing
|
|
232
|
+
`EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY` fetches the canonical `STRIPE_PUBLISHABLE_KEY`.
|
|
233
|
+
- **Imports still resolve** — a key surfaced into a `paths` folder via an Infisical import is
|
|
234
|
+
fetched in `keys` mode too (the per-key read follows imports but returns only that one
|
|
235
|
+
secret), so enabling `keys` never silently drops an import-backed key.
|
|
236
|
+
- **Same emit result** — everything after the fetch (aliases, `include` filtering, unknown-key
|
|
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
|
|
239
|
+
advisory (`infisicml paths` notes this).
|
|
240
|
+
- **Profiles** — a profile may set its own `fetch`, which **replaces** the root value (like
|
|
241
|
+
`paths` / `include`). A deploy profile can stay in `folder` mode while the runtime default
|
|
242
|
+
is `keys`, or vice versa.
|
|
243
|
+
- **Backward compatible** — omit `fetch` (or set `"folder"`) and behavior is byte-for-byte
|
|
244
|
+
unchanged.
|
|
245
|
+
|
|
246
|
+
### Non-secret defaults (`.env.sample`)
|
|
247
|
+
|
|
248
|
+
Infisicml writes **only the secret slice** it pulls from Infisical — it does not merge a
|
|
249
|
+
committed base file. If a package keeps non-secret defaults and structure in a
|
|
250
|
+
`.env.sample`, keep both files and load them together at runtime, secrets last so they
|
|
251
|
+
win:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# node — later --env-file overrides earlier ones
|
|
255
|
+
node --env-file=.env.sample --env-file=.env.secrets server.js
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```jsonc
|
|
259
|
+
// package.json
|
|
260
|
+
{ "scripts": { "dev": "infisicml --here pull && node --env-file=.env.sample --env-file=.env.secrets server.js" } }
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Point `output` at whatever filename your loader expects (e.g. `.env` layered over a
|
|
264
|
+
committed `.env.example`). Do **not** overwrite a committed sample with the pulled file
|
|
265
|
+
— they are separate inputs, and losing the sample's defaults would break local dev.
|
|
266
|
+
|
|
267
|
+
## Programmatic API
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { loadConfig, discoverManifests, pullManifest, LocalProvider } from "@hubble-ventures/infisicml";
|
|
271
|
+
|
|
272
|
+
const config = await loadConfig();
|
|
273
|
+
const provider = new LocalProvider({ projectId: config.projectId });
|
|
274
|
+
for (const manifest of discoverManifests(config)) {
|
|
275
|
+
await pullManifest({ manifest, provider, repoRoot: config.repoRoot, envName: "development" });
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Releasing
|
|
280
|
+
|
|
281
|
+
Releases are cut by pushing a semver tag; [`.github/workflows/release.yml`](./.github/workflows/release.yml) does the rest:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
npm version 1.2.3 --no-git-tag-version
|
|
285
|
+
git commit -am "release: v1.2.3"
|
|
286
|
+
git tag v1.2.3 && git push origin main --tags
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The workflow builds + tests the tagged commit, publishes to npm via **trusted
|
|
290
|
+
publishing** (OIDC — no `NPM_TOKEN` secret, provenance attached automatically),
|
|
291
|
+
creates a GitHub Release from the matching [`CHANGELOG.md`](./CHANGELOG.md) section, and
|
|
292
|
+
moves the floating `v1` tag to the release commit. The publish step is idempotent — a
|
|
293
|
+
version already on npm is skipped.
|
|
294
|
+
|
|
295
|
+
> **First publish (one time).** The `@hubble-ventures` npm **organization** must exist,
|
|
296
|
+
> and npm trusted publishing can only be configured against an *existing* package — so
|
|
297
|
+
> the very first publish is done locally, by a member of the org with publish rights, to
|
|
298
|
+
> create the package:
|
|
299
|
+
>
|
|
300
|
+
> ```bash
|
|
301
|
+
> npm publish --access public # scoped packages default to private; --access public is required
|
|
302
|
+
> ```
|
|
303
|
+
>
|
|
304
|
+
> (`publishConfig.access: public` in `package.json` enforces this even if the flag is
|
|
305
|
+
> omitted.) Then, in the package's npm settings, add a **trusted publisher** for
|
|
306
|
+
> `hubble-ventures/infisicml` → workflow `release.yml`, with **no environment** (the release
|
|
307
|
+
> job sets none — the OIDC claims must match). Every subsequent tag push publishes
|
|
308
|
+
> automatically with no token.
|
|
309
|
+
|
|
310
|
+
## Design
|
|
311
|
+
|
|
312
|
+
Everything repo-specific lives in `infisicml.config` — package discovery, the project-id source, the OIDC audience, and deploy-time key advertisement are all configuration, so nothing about one repo's layout is baked into the tool. The manifest contract (`secrets.json`) is the stable public interface, versioned via the [published JSON Schema](./schema/secrets.schema.json).
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT © Hubble Ventures
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SecretsManifest } from "./manifest.js";
|
|
2
|
+
export type ResolvedAlias = {
|
|
3
|
+
source: string;
|
|
4
|
+
targets: string[];
|
|
5
|
+
};
|
|
6
|
+
/** Normalize the manifest's alias map to a list of {source, targets[]}. */
|
|
7
|
+
export declare function resolveAliases(manifest: SecretsManifest): ResolvedAlias[];
|
|
8
|
+
/**
|
|
9
|
+
* Copy each aliased source secret's value to its target env var name(s).
|
|
10
|
+
*
|
|
11
|
+
* Used wherever the CLI materializes secrets (CI `export-gha`, local
|
|
12
|
+
* `.env.secrets` pull) so the conventional, tool-specific name each deployment
|
|
13
|
+
* expects is always present — see the `aliases` doc in manifest.ts.
|
|
14
|
+
*
|
|
15
|
+
* Returns a new object. An absent source is skipped, and an existing target (a
|
|
16
|
+
* real secret of that name) is never overwritten, so real values win over
|
|
17
|
+
* aliases and the operation is idempotent.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyAliases(merged: Record<string, string>, manifest: SecretsManifest): Record<string, string>;
|
|
20
|
+
//# sourceMappingURL=aliases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aliases.d.ts","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAElE,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa,EAAE,CAOzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,QAAQ,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB"}
|
package/dist/aliases.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Normalize the manifest's alias map to a list of {source, targets[]}. */
|
|
2
|
+
export function resolveAliases(manifest) {
|
|
3
|
+
const aliases = manifest.aliases;
|
|
4
|
+
if (!aliases)
|
|
5
|
+
return [];
|
|
6
|
+
return Object.entries(aliases).map(([source, value]) => ({
|
|
7
|
+
source,
|
|
8
|
+
targets: Array.isArray(value) ? value : [value],
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Copy each aliased source secret's value to its target env var name(s).
|
|
13
|
+
*
|
|
14
|
+
* Used wherever the CLI materializes secrets (CI `export-gha`, local
|
|
15
|
+
* `.env.secrets` pull) so the conventional, tool-specific name each deployment
|
|
16
|
+
* expects is always present — see the `aliases` doc in manifest.ts.
|
|
17
|
+
*
|
|
18
|
+
* Returns a new object. An absent source is skipped, and an existing target (a
|
|
19
|
+
* real secret of that name) is never overwritten, so real values win over
|
|
20
|
+
* aliases and the operation is idempotent.
|
|
21
|
+
*/
|
|
22
|
+
export function applyAliases(merged, manifest) {
|
|
23
|
+
const out = { ...merged };
|
|
24
|
+
for (const { source, targets } of resolveAliases(manifest)) {
|
|
25
|
+
const value = merged[source];
|
|
26
|
+
if (value === undefined)
|
|
27
|
+
continue;
|
|
28
|
+
for (const target of targets) {
|
|
29
|
+
if (out[target] === undefined)
|
|
30
|
+
out[target] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=aliases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aliases.js","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAIA,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,QAAyB;IACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IACjC,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACvD,MAAM;QACN,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;KAChD,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAC1B,MAA8B,EAC9B,QAAyB;IAEzB,MAAM,GAAG,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1B,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SecretsManifest } from "./manifest.js";
|
|
2
|
+
import type { SecretsProvider } from "./providers/types.js";
|
|
3
|
+
export type { SecretsProvider };
|
|
4
|
+
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
|
+
/**
|
|
7
|
+
* `fetch: "keys"` counterpart to {@link fetchSecretsForPaths}: request only the
|
|
8
|
+
* given canonical keys from each folder and merge. A key absent from one folder
|
|
9
|
+
* is simply not returned by that folder; the merge (last folder wins, matching
|
|
10
|
+
* the folder path) collects it from whichever folder holds it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fetchSecretsForKeys(provider: SecretsProvider, envName: string, paths: string[], keys: string[]): Promise<Record<string, string>>;
|
|
13
|
+
/**
|
|
14
|
+
* Fetch a manifest's secrets for the given folder set, honoring its resolved
|
|
15
|
+
* `fetch` mode: `keys` requests only the canonical keys `include` resolves to
|
|
16
|
+
* (least privilege at the wire), `folder` reads whole folders. `keys` requires
|
|
17
|
+
* an `include` allowlist — the whole point is to name exactly what to fetch.
|
|
18
|
+
*
|
|
19
|
+
* Callers pass an explicit `paths` subset (export-gha splits runtime vs
|
|
20
|
+
* deploy-only) rather than re-deriving it, so the runtime/deploy provenance the
|
|
21
|
+
* caller already computed is preserved.
|
|
22
|
+
*/
|
|
23
|
+
export declare function fetchManifestSecrets(provider: SecretsProvider, envName: string, paths: string[], manifest: SecretsManifest, profile?: string): Promise<Record<string, string>>;
|
|
24
|
+
export declare function isCi(): boolean;
|
|
25
|
+
export declare function shouldSkipInfisicalPull(manifest: SecretsManifest, force: boolean): boolean;
|
|
26
|
+
export declare function keysForCiStub(manifest: SecretsManifest): string[];
|
|
27
|
+
//# sourceMappingURL=ci-skip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-skip.d.ts","sourceRoot":"","sources":["../src/ci-skip.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAMjC;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAMjC;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,eAAe,EACzB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAuBjC;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"}
|
package/dist/ci-skip.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { resolveFetchKeys } from "./include.js";
|
|
2
|
+
import { resolveFetchMode, resolveInclude, } from "./manifest.js";
|
|
3
|
+
export function mergeFolderSecrets(chunks) {
|
|
4
|
+
const merged = {};
|
|
5
|
+
for (const chunk of chunks) {
|
|
6
|
+
Object.assign(merged, chunk);
|
|
7
|
+
}
|
|
8
|
+
return merged;
|
|
9
|
+
}
|
|
10
|
+
export async function fetchSecretsForPaths(provider, envName, paths) {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
for (const folder of paths) {
|
|
13
|
+
chunks.push(await provider.exportFolder(envName, folder));
|
|
14
|
+
}
|
|
15
|
+
return mergeFolderSecrets(chunks);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `fetch: "keys"` counterpart to {@link fetchSecretsForPaths}: request only the
|
|
19
|
+
* given canonical keys from each folder and merge. A key absent from one folder
|
|
20
|
+
* is simply not returned by that folder; the merge (last folder wins, matching
|
|
21
|
+
* the folder path) collects it from whichever folder holds it.
|
|
22
|
+
*/
|
|
23
|
+
export async function fetchSecretsForKeys(provider, envName, paths, keys) {
|
|
24
|
+
const chunks = [];
|
|
25
|
+
for (const folder of paths) {
|
|
26
|
+
chunks.push(await provider.exportKeys(envName, folder, keys));
|
|
27
|
+
}
|
|
28
|
+
return mergeFolderSecrets(chunks);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fetch a manifest's secrets for the given folder set, honoring its resolved
|
|
32
|
+
* `fetch` mode: `keys` requests only the canonical keys `include` resolves to
|
|
33
|
+
* (least privilege at the wire), `folder` reads whole folders. `keys` requires
|
|
34
|
+
* an `include` allowlist — the whole point is to name exactly what to fetch.
|
|
35
|
+
*
|
|
36
|
+
* Callers pass an explicit `paths` subset (export-gha splits runtime vs
|
|
37
|
+
* deploy-only) rather than re-deriving it, so the runtime/deploy provenance the
|
|
38
|
+
* caller already computed is preserved.
|
|
39
|
+
*/
|
|
40
|
+
export async function fetchManifestSecrets(provider, envName, paths, manifest, profile) {
|
|
41
|
+
if (resolveFetchMode(manifest, profile) === "keys") {
|
|
42
|
+
const include = resolveInclude(manifest, profile);
|
|
43
|
+
if (!include) {
|
|
44
|
+
// resolveInclude already checked the profile then the root, so name the
|
|
45
|
+
// exact place(s) that need an `include` rather than a vague "root or
|
|
46
|
+
// profile" — the user should add it to whichever they're running.
|
|
47
|
+
const where = profile
|
|
48
|
+
? `neither profile "${profile}" nor the manifest root defines one`
|
|
49
|
+
: "the manifest root does not define one";
|
|
50
|
+
throw new Error(`fetch: "keys" requires an include allowlist, but ${where}. ` +
|
|
51
|
+
"Add `include: [...]` naming exactly which keys to request from the vault.");
|
|
52
|
+
}
|
|
53
|
+
return fetchSecretsForKeys(provider, envName, paths, resolveFetchKeys(include, manifest));
|
|
54
|
+
}
|
|
55
|
+
return fetchSecretsForPaths(provider, envName, paths);
|
|
56
|
+
}
|
|
57
|
+
export function isCi() {
|
|
58
|
+
const ci = process.env.CI;
|
|
59
|
+
return ci === "true" || ci === "1";
|
|
60
|
+
}
|
|
61
|
+
export function shouldSkipInfisicalPull(manifest, force) {
|
|
62
|
+
if (force)
|
|
63
|
+
return false;
|
|
64
|
+
if (!isCi())
|
|
65
|
+
return false;
|
|
66
|
+
const ci = manifest.ci;
|
|
67
|
+
if (!ci)
|
|
68
|
+
return false;
|
|
69
|
+
if (ci.stubInCi)
|
|
70
|
+
return true;
|
|
71
|
+
const keys = ci.skipWhenEnv ?? [];
|
|
72
|
+
if (keys.length === 0)
|
|
73
|
+
return false;
|
|
74
|
+
return keys.every((key) => {
|
|
75
|
+
const value = process.env[key];
|
|
76
|
+
return value !== undefined && value !== "";
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export function keysForCiStub(manifest) {
|
|
80
|
+
return manifest.ci?.skipWhenEnv ?? [];
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=ci-skip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-skip.js","sourceRoot":"","sources":["../src/ci-skip.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|