@initx-plugin/env 0.1.0 → 0.1.3
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/LICENSE +21 -21
- package/README.md +157 -34
- package/dist/index.d.mts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.mjs +38 -30
- package/package.json +11 -13
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 imba97 <https://github.com/imba97>
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 imba97 <https://github.com/imba97>
|
|
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## @initx-plugin/env
|
|
2
2
|
|
|
3
|
-
`initx` plugin to manage env vars and secrets from
|
|
3
|
+
`initx` plugin to manage env vars and secrets from a private GitHub or GitLab repository.
|
|
4
4
|
|
|
5
5
|
One private repository holds **one JSON file per store** (`stores/<name>.json`). Pull a store into a
|
|
6
6
|
local `.env`, inject it into a child process or CI job, or write it into GitHub Actions secrets.
|
|
@@ -9,15 +9,10 @@ local `.env`, inject it into a child process or CI job, or write it into GitHub
|
|
|
9
9
|
initx plugin add env
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
plugin fills. It also keeps one honest boundary:
|
|
17
|
-
|
|
18
|
-
> **CI → local sync is impossible.** GitHub Actions secrets are write-only: the API returns
|
|
19
|
-
> `name` / `created_at` / `updated_at` and never the value. So the vault repo is always the source of
|
|
20
|
-
> truth and CI is always a one-way projection.
|
|
12
|
+
The vault is the source of truth. Storage and execution platforms are independent: GitLab CI can
|
|
13
|
+
read a GitHub vault, and a GitLab vault can supply GitHub Actions secrets via `apply`. GitHub
|
|
14
|
+
Actions secrets are write-only; this plugin projects values from the vault into that target.
|
|
15
|
+
GitLab CI/CD Variables synchronization is a separate future target, not part of this release.
|
|
21
16
|
|
|
22
17
|
## Setup
|
|
23
18
|
|
|
@@ -35,6 +30,62 @@ The config lives in `~/.initx/stores/@initx-plugin/env/store.json`. The token is
|
|
|
35
30
|
plaintext** there (same trade-off as `@initx-plugin/cp`) — set `INITX_ENV_TOKEN` instead if you would
|
|
36
31
|
rather keep it out of a file.
|
|
37
32
|
|
|
33
|
+
## Project configuration (GitHub or GitLab)
|
|
34
|
+
|
|
35
|
+
Commit a non-sensitive `.initx-env.json` in the business project's working directory:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"schema": 1,
|
|
40
|
+
"storage": {
|
|
41
|
+
"type": "gitlab",
|
|
42
|
+
"apiBaseUrl": "https://gitlab.example.com/api/v4",
|
|
43
|
+
"repo": "example-group/subgroup/env-vault",
|
|
44
|
+
"branch": "main",
|
|
45
|
+
"path": "stores"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`type` and `repo` are required. GitLab accepts a numeric project ID (a string such as `"12345"`)
|
|
51
|
+
or the full namespace, including multiple subgroups. `apiBaseUrl` means the API base, including
|
|
52
|
+
`/api/v4` for GitLab or `/api/v3` for GitHub Enterprise. Defaults are
|
|
53
|
+
`https://gitlab.com/api/v4` and `https://api.github.com`. Branch defaults to `main`, directory to
|
|
54
|
+
`stores`; `"path": ""` explicitly selects repository root. For a GitHub vault, use `"type": "github"`
|
|
55
|
+
and `"repo": "owner/vault"`. Examples are in [examples](examples/).
|
|
56
|
+
|
|
57
|
+
All storage commands and `env-config status` accept `--config <file>`. Relative paths resolve
|
|
58
|
+
against the caller's cwd. Without the flag, only cwd's `.initx-env.json` is checked; parents are
|
|
59
|
+
never searched. An explicitly missing file, invalid JSON, unknown schema or unknown field fails
|
|
60
|
+
instead of falling back. Project files cannot contain tokens, credentials or `vars`; business
|
|
61
|
+
values belong to the remote store (whose schema remains 1). Config files are only read.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
initx env-config status --config .initx-env.json --offline
|
|
65
|
+
initx env run wukong-content-production --config .initx-env.json -- npm run pipeline
|
|
66
|
+
# When running from an isolated HOME/cwd, pass the business config's absolute path.
|
|
67
|
+
initx env run demo --config /workspace/business/.initx-env.json -- node /workspace/business/task.mjs
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The config's location never changes the child cwd. `npm run pipeline` above is an example business
|
|
71
|
+
entry point, implemented by the consuming project. Set only `INITX_ENV_TOKEN` in CI; tokens and
|
|
72
|
+
business values do not belong in this file. Existing Slides invocations using `INITX_ENV_REPO`,
|
|
73
|
+
`INITX_ENV_BRANCH`, `INITX_ENV_PATH` and a GitHub token continue to work without new flags.
|
|
74
|
+
|
|
75
|
+
For local persisted GitLab settings (independent of a project file):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
initx env-config set type gitlab
|
|
79
|
+
initx env-config set apiBaseUrl https://gitlab.example.com/api/v4
|
|
80
|
+
initx env-config set repo example-group/subgroup/env-vault
|
|
81
|
+
# Prefer INITX_ENV_TOKEN; if saving a token locally, select type and API address first.
|
|
82
|
+
initx env-config set token <gitlab-access-token>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Changing local type/API clears the old repo/branch/path to prevent accidental reuse. New persisted
|
|
86
|
+
tokens are keyed by provider and normalized full API base (including its path), not just hostname.
|
|
87
|
+
Legacy flat config remains GitHub config; its token is accepted only on the default GitHub API.
|
|
88
|
+
|
|
38
89
|
## Commands
|
|
39
90
|
|
|
40
91
|
| Command | What it does |
|
|
@@ -72,7 +123,7 @@ initx env run acewill-slidev-production -- pnpm deploy
|
|
|
72
123
|
initx env run demo -- bash scripts/deploy.sh
|
|
73
124
|
```
|
|
74
125
|
|
|
75
|
-
The store and the first `--` are required. Put
|
|
126
|
+
The store and the first `--` are required. Put optional `--token <token>` and `--config <file>` before `--`;
|
|
76
127
|
every argument after it belongs to the child, including `--help`, `--version`, `--token`, another
|
|
77
128
|
`--`, empty strings and arguments with spaces. Extra positional arguments and other plugin flags
|
|
78
129
|
(including `--repo`, `--json`, `--stdout`, `--only`, `--shell`, `--cwd`) are rejected before reading
|
|
@@ -119,16 +170,25 @@ The stdout form of `setup` remains intended for inspection/legacy use; use `run`
|
|
|
119
170
|
|
|
120
171
|
| Setting | Highest to lowest priority |
|
|
121
172
|
| --- | --- |
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
173
|
+
| Provider | Explicit override → `INITX_ENV_TYPE` → project `storage.type` → local storage type → `github` |
|
|
174
|
+
| API base | Explicit override → `INITX_ENV_API_BASE_URL` → project API → same-provider local API → provider default |
|
|
175
|
+
| Token | Explicit override / `--token` → `INITX_ENV_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` (last two only for default GitHub API) → scoped local token |
|
|
176
|
+
| Vault repo | Explicit override → `INITX_ENV_REPO` → project `storage.repo` → same-provider/API local `repo` |
|
|
177
|
+
| Branch | Explicit override → `INITX_ENV_BRANCH` → project branch → same-provider/API local branch → `main` |
|
|
178
|
+
| Store path | Explicit override → `INITX_ENV_PATH` → project path → same-provider/API local path → `stores` |
|
|
126
179
|
|
|
127
180
|
Empty strings fall back to the next source. Environment settings affect the current invocation
|
|
128
181
|
only and are never written back to the plugin store. `env-config status --offline` shows the
|
|
129
182
|
effective settings with a masked token; `get` / `list` show the persisted settings. `env apply
|
|
130
183
|
--repo o/r` still selects the destination Actions repository, not the vault. `run` has no `--repo`.
|
|
131
184
|
|
|
185
|
+
The provider is resolved first; values from another provider/API are never inherited. `GH_TOKEN`
|
|
186
|
+
and `GITHUB_TOKEN` are never sent to GitLab or custom API bases. `--token` and `INITX_ENV_TOKEN`
|
|
187
|
+
explicitly authorize the selected current target. Authenticated requests do not follow redirects,
|
|
188
|
+
have a 30-second timeout, and are not automatically retried (a failed write may have committed).
|
|
189
|
+
Status shows per-field sources, provider, API base, repo/ref/path, config file and fully masked
|
|
190
|
+
token source. `get/list/set/rm` operate only on persisted local settings, never project files.
|
|
191
|
+
|
|
132
192
|
## Use in GitLab CI
|
|
133
193
|
|
|
134
194
|
Preinstall fixed versions of initx and this plugin in the CI image, or prepare them with an explicit
|
|
@@ -137,28 +197,26 @@ implementation is smoke-tested with **initx 0.4.3**; compatible versions must ex
|
|
|
137
197
|
tail as `ctx.cliOptions['--']: string[]`, keep child flags out of prefix help/version handling, and
|
|
138
198
|
preserve `process.exitCode` after handlers finish. Older versions have not been verified.
|
|
139
199
|
|
|
140
|
-
|
|
141
|
-
|
|
200
|
+
Commit `.initx-env.json` as above and set a protected, masked `INITX_ENV_TOKEN` in GitLab. For a
|
|
201
|
+
GitLab vault, use a Personal/Project Access Token with `read_api` and access to the vault project;
|
|
202
|
+
local writes require `api` plus branch permissions. Check both Files and Tree access on your
|
|
203
|
+
instance. For a GitHub vault, use **Contents: read** access. Runner platform does not select storage.
|
|
142
204
|
|
|
143
205
|
```yaml
|
|
144
|
-
|
|
145
|
-
stage: deploy
|
|
146
|
-
variables:
|
|
147
|
-
INITX_ENV_REPO: example/private-env-vault
|
|
148
|
-
INITX_ENV_BRANCH: main
|
|
149
|
-
INITX_ENV_PATH: stores
|
|
206
|
+
pipeline:
|
|
150
207
|
script:
|
|
151
|
-
- initx env run
|
|
208
|
+
- initx env run wukong-content-production --config "$CI_PROJECT_DIR/.initx-env.json" -- npm run pipeline
|
|
152
209
|
```
|
|
153
210
|
|
|
154
|
-
This illustrates the integration point
|
|
155
|
-
|
|
156
|
-
|
|
211
|
+
This illustrates the integration point; prepare pinned initx/plugin versions noninteractively in
|
|
212
|
+
your CI image first. This feature is not published by this change: use a tested local build until
|
|
213
|
+
a separate release exists, then pin that released version. Let the business script orchestrate its
|
|
214
|
+
own workflow; the plugin does not hard-code business variable names or perform deployments. Scripts may
|
|
157
215
|
materialize multiline SSH keys as needed, set file permissions and clean them up; `run` does not
|
|
158
216
|
create key files or pass them through artifacts/cache.
|
|
159
217
|
|
|
160
218
|
Fetched variables are **not automatically registered as GitLab masked variables**. Disable command
|
|
161
|
-
tracing that could print them. Vault
|
|
219
|
+
tracing that could print them. Vault read permissions apply to the entire repository, not to a
|
|
162
220
|
store filename: use separate repositories for separate trust boundaries. The private JSON store and
|
|
163
221
|
its Git history remain plaintext.
|
|
164
222
|
|
|
@@ -182,6 +240,64 @@ repo needs its own token (one secret):
|
|
|
182
240
|
`env setup` writes to `$GITHUB_ENV` and emits `::add-mask::` for every value before it does, so the
|
|
183
241
|
values do not show up in logs. Values with newlines use the heredoc (`KEY<<EOF`) form.
|
|
184
242
|
|
|
243
|
+
## Apply to GitHub Actions
|
|
244
|
+
|
|
245
|
+
`apply --repo owner/target` always selects the destination GitHub repository. Its vault is resolved
|
|
246
|
+
independently via `--config`/environment/local settings. For a GitLab or custom-host source, set
|
|
247
|
+
`INITX_ENV_TARGET_TOKEN` (recommended for CI) or `--target-token <github-token>` with Secrets write
|
|
248
|
+
access to the target. The source token never crosses providers/hosts. `GH_TOKEN`/`GITHUB_TOKEN`
|
|
249
|
+
are not implicit target credentials for these cross-platform calls. `--dry-run` also validates
|
|
250
|
+
that independent target credentials are configured, but makes no target API call.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
initx env apply demo --config .initx-env.json --repo owner/target --dry-run
|
|
254
|
+
# Same invocation without --dry-run writes GitHub Actions secrets.
|
|
255
|
+
# Supply INITX_ENV_TOKEN for the vault and INITX_ENV_TARGET_TOKEN for the target in the environment.
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Existing default GitHub API vault → default GitHub Actions calls may continue reusing the source
|
|
259
|
+
token. Explicit target credentials take precedence. `targets.secrets` retains its schema-1 shape.
|
|
260
|
+
GitLab CI Variables writing and CI_JOB_TOKEN support are deferred. Deploy Tokens used for git clone
|
|
261
|
+
are not Files/Tree API tokens.
|
|
262
|
+
|
|
263
|
+
## Storage guarantees and GitLab compatibility
|
|
264
|
+
|
|
265
|
+
Commands use an explicit adapter factory; the store service deals only in UTF-8 content and opaque
|
|
266
|
+
`version` values. GitHub uses the file SHA; GitLab uses the file's `last_commit_id`, never Tree blob
|
|
267
|
+
IDs. Creates are explicit, updates/deletes carry the expected version. Conflict responses are
|
|
268
|
+
verified by a safe reread; GitHub 422 and GitLab 400 are not automatically conflicts. GitLab's
|
|
269
|
+
unchanged-content 400 is accepted only if the file still has the expected version and exact content.
|
|
270
|
+
`push --force` rereads and retries once; permission/validation/network errors do not trigger it.
|
|
271
|
+
The existing force semantics overwrite using the locally prepared store, including its routing
|
|
272
|
+
hints; concurrent remote-only changes can be replaced. Pull/review before force if those matter.
|
|
273
|
+
|
|
274
|
+
GitLab uses Files GET/POST/PUT, a conditional Commits delete action, and Tree offset pagination
|
|
275
|
+
(`per_page=100`). It supports full subgroups and numeric IDs, and does not require keyset pagination, newer Tree metadata or job-token
|
|
276
|
+
features. Missing paths are empty only after validating repository/ref access. Tree accepts both
|
|
277
|
+
legacy 200 empty arrays and newer 404 responses. Directory listing is not recursive. Errors expose
|
|
278
|
+
only a category, safe hint and HTTP status, never upstream response text or request URLs.
|
|
279
|
+
|
|
280
|
+
The implementation targets GitLab CE **15.9.4**, with mock coverage for its API behavior. This is
|
|
281
|
+
not a claim of real-instance acceptance. Use a temporary vault with non-sensitive dummy values to
|
|
282
|
+
verify read_api Files/Tree/commit access, pagination, encoded refs/paths, protected-branch errors,
|
|
283
|
+
create races and stale update/delete rejection on your actual instance. Record token type, scope,
|
|
284
|
+
role and instance version. See [implementation and acceptance record](docs/gitlab-storage-implementation.md).
|
|
285
|
+
API references: [Files](https://docs.gitlab.com/api/repository_files/),
|
|
286
|
+
[Tree](https://docs.gitlab.com/api/repositories/),
|
|
287
|
+
[Deploy Token limitations](https://docs.gitlab.com/user/project/deploy_tokens/).
|
|
288
|
+
|
|
289
|
+
One deliberate change from the initial plan: 15.9.4's Files DELETE does not declare
|
|
290
|
+
`last_commit_id`, so it discards that condition. Deletion therefore uses the existing Commits API
|
|
291
|
+
with one `delete` action containing `last_commit_id`; it does not rely on a client-side precheck.
|
|
292
|
+
The versioned [Files route](https://github.com/gitlabhq/gitlabhq/blob/v15.9.4/lib/api/files.rb),
|
|
293
|
+
[Commits route](https://github.com/gitlabhq/gitlabhq/blob/v15.9.4/lib/api/commits.rb) and
|
|
294
|
+
[MultiService check](https://github.com/gitlabhq/gitlabhq/blob/v15.9.4/app/services/files/multi_service.rb)
|
|
295
|
+
provide the compatibility basis; real-instance testing remains required.
|
|
296
|
+
|
|
297
|
+
The known legacy protected-branch 400 message is recognized as permission failure using an exact
|
|
298
|
+
literal from [v15.9.4 CreateService](https://github.com/gitlabhq/gitlabhq/blob/v15.9.4/app/services/commits/create_service.rb).
|
|
299
|
+
Arbitrary response messages are never forwarded to the caller or printed.
|
|
300
|
+
|
|
185
301
|
## Store format
|
|
186
302
|
|
|
187
303
|
```jsonc
|
|
@@ -209,13 +325,18 @@ values do not show up in logs. Values with newlines use the heredoc (`KEY<<EOF`)
|
|
|
209
325
|
- **`apply` constraints** (GitHub's, not ours): secret values are write-only; a single secret is
|
|
210
326
|
capped at 48 KB; names may only contain `[A-Za-z0-9_]`, cannot start with a digit or `GITHUB_`, and
|
|
211
327
|
are stored uppercased. Illegal names abort the command unless you pass `--skip-invalid`.
|
|
212
|
-
- **Token permissions**:
|
|
213
|
-
|
|
214
|
-
|
|
328
|
+
- **Token permissions**: GitHub vaults need **Contents** read/write; GitLab vaults use `read_api`
|
|
329
|
+
for reads or `api` for writes, plus project/branch access. `apply` needs **Secrets: write** on the
|
|
330
|
+
GitHub target. Default GitHub API calls can share a classic PAT with `repo`; other sources use
|
|
331
|
+
independent target credentials. Organization secrets are not supported.
|
|
215
332
|
- **Output is masked**: tokens print as `***`, values are never printed by `list` / `get` / `status`.
|
|
216
333
|
|
|
217
334
|
## Development
|
|
218
335
|
|
|
336
|
+
Do not install the `initx` CLI as a dependency or test tool in this project. Use the existing
|
|
337
|
+
core/utils SDKs, mocks and command-handler fixtures. Historical CLI checks and business CI
|
|
338
|
+
installation examples do not change this rule; see [AGENTS.md](AGENTS.md).
|
|
339
|
+
|
|
219
340
|
```bash
|
|
220
341
|
pnpm install
|
|
221
342
|
pnpm stub # unbuild --stub, then: initx plugin add .
|
|
@@ -225,9 +346,11 @@ pnpm lint
|
|
|
225
346
|
pnpm typecheck
|
|
226
347
|
```
|
|
227
348
|
|
|
228
|
-
`pnpm test` runs unit tests and isolated child-process fixtures with fake GitHub responses and
|
|
229
|
-
temporary directories.
|
|
230
|
-
|
|
349
|
+
`pnpm test` runs unit tests and isolated child-process fixtures with fake GitHub/GitLab responses and
|
|
350
|
+
temporary directories. Tests use the existing core matcher and direct command handlers, without
|
|
351
|
+
installing or importing the initx CLI. Child-process tests check execution markers and nonzero
|
|
352
|
+
exit codes. No real vault, DNS, SSH or
|
|
353
|
+
GitHub/GitLab write endpoint is used. The Linux/Windows CI matrix runs the same checks. Platform-specific
|
|
231
354
|
coverage and the local verification record are in [env run implementation](docs/env-run-implementation.md).
|
|
232
355
|
|
|
233
356
|
## License
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { InitxPlugin, InitxMatcherRules, InitxContext } from '@initx-plugin/core';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type StorageType = 'github' | 'gitlab';
|
|
4
|
+
interface StorageLocation {
|
|
5
|
+
type: StorageType;
|
|
6
|
+
apiBaseUrl: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
branch: string;
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Legacy flat fields remain GitHub-only; new local locations and credentials are scoped. */
|
|
4
13
|
interface EnvPluginStore {
|
|
5
14
|
repo?: string;
|
|
6
15
|
token?: string;
|
|
7
16
|
branch?: string;
|
|
8
17
|
path?: string;
|
|
18
|
+
storage?: Partial<StorageLocation>;
|
|
19
|
+
credentials?: Record<string, string>;
|
|
9
20
|
}
|
|
10
21
|
|
|
11
22
|
declare class EnvPlugin extends InitxPlugin<EnvPluginStore> {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { InitxPlugin, InitxMatcherRules, InitxContext } from '@initx-plugin/core';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type StorageType = 'github' | 'gitlab';
|
|
4
|
+
interface StorageLocation {
|
|
5
|
+
type: StorageType;
|
|
6
|
+
apiBaseUrl: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
branch: string;
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Legacy flat fields remain GitHub-only; new local locations and credentials are scoped. */
|
|
4
13
|
interface EnvPluginStore {
|
|
5
14
|
repo?: string;
|
|
6
15
|
token?: string;
|
|
7
16
|
branch?: string;
|
|
8
17
|
path?: string;
|
|
18
|
+
storage?: Partial<StorageLocation>;
|
|
19
|
+
credentials?: Record<string, string>;
|
|
9
20
|
}
|
|
10
21
|
|
|
11
22
|
declare class EnvPlugin extends InitxPlugin<EnvPluginStore> {
|
package/dist/index.mjs
CHANGED
|
@@ -1,50 +1,52 @@
|
|
|
1
|
-
import{InitxPlugin as du}from"@initx-plugin/core";import{readFile as W,copyFile as Au,writeFile as Y,stat as wu,readdir as Du}from"node:fs/promises";import p from"node:process";import{logger as F,inquirer as g}from"@initx-plugin/utils";import{Buffer as j}from"node:buffer";import{z as D}from"zod";import{randomBytes as M}from"node:crypto";import{appendFileSync as Cu}from"node:fs";import z from"libsodium-wrappers";import{constants as gu}from"node:os";import Bu from"cross-spawn";function S(u){return u.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function G(u,e){return u[e]??u[S(e)]}function $u(u){if(typeof u=="string")return u===""?void 0:u;if(typeof u=="number")return String(u);if(Array.isArray(u))return u.length?u.map(String).join(","):void 0}function yu(u={},e=[]){const t=new Set(e);return{raw:u,string:n=>$u(G(u,n)),boolean:n=>{const r=G(u,n);return typeof r=="boolean"?r:t.has(`--${n}`)||t.has(`--${S(n)}`)},has:n=>G(u,n)!==void 0?!0:t.has(`--${n}`)||t.has(`--${S(n)}`)}}const b="main",x="stores",vu=["INITX_ENV_TOKEN","GH_TOKEN","GITHUB_TOKEN"];function mu(u,e={}){const t=e.env??p.env;if(e.token)return{token:e.token,source:"flag"};for(const n of vu){const r=t[n];if(r)return{token:r,source:n}}return u.token?{token:u.token,source:"config"}:{token:"",source:"none"}}function T(u,e={}){const t=e.env??p.env,n=e.repo||t.INITX_ENV_REPO||u.repo||"",{token:r,source:o}=mu(u,e);if(!n)throw new Error("\u8FD8\u6CA1\u914D vault \u4ED3\u5E93\uFF0C\u5148\u8DD1\uFF1Anpx initx env-config set repo <owner/repo>");if(!/^[^/\s]+\/[^/\s]+$/.test(n))throw new Error(`\u4ED3\u5E93\u8981\u5199\u6210 owner/repo \u5F62\u5F0F\uFF0C\u5F53\u524D\u662F "${n}"`);if(!r)throw new Error(`\u6CA1\u6709\u53EF\u7528\u7684 GitHub token\u3002\u4E09\u9009\u4E00\uFF1A
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
`)){const n=/[ \t]#/.exec(t);if(n!==null)return t.slice(0,n.index).replace(/[ \t]+$/,"")}return e.replace(/[ \t]+$/,"")}function Lu(u){if(u==="")return[];const e=u.split(/\r?\n/);return u.endsWith(`
|
|
8
|
-
`)&&e.pop(),e}function ou(u){for(let e=0;e<u.length;e+=1){if(u[e]==="\\"){e+=1;continue}if(u[e]==='"')return e}return-1}function Pu(u,e){const t=u[e];if(Hu.test(t))return null;const n=nu.test(t),r=Su.exec(n?t.replace(nu,""):t);if(r===null)return null;const o=Gu(r[1]);if(o===null)return null;const s=r[2];let i=e,c="none",l;if(s.startsWith('"')){let a=s.slice(1),E=ou(a);for(;E===-1&&i+1<u.length;)i+=1,a+=`
|
|
9
|
-
${u[i]}`,E=ou(a);c="double",l=Ru(E===-1?a:a.slice(0,E))}else if(s.startsWith("'")){const a=s.indexOf("'",1);c="single",l=a===-1?s.slice(1):s.slice(1,a)}else l=Ku(s);return{key:o,value:l,exportPrefix:n,quote:c,raw:t,startLine:e,endLine:i}}function _(u){const e=u.includes(`\r
|
|
1
|
+
import{InitxPlugin as Nu}from"@initx-plugin/core";import{readFile as su,copyFile as Iu,writeFile as au,stat as Su,readdir as Uu}from"node:fs/promises";import f from"node:process";import{logger as F,inquirer as b}from"@initx-plugin/utils";import{readFileSync as Ou,appendFileSync as ju}from"node:fs";import{resolve as Gu}from"node:path";import{z as A}from"zod";import{Buffer as I}from"node:buffer";import{randomBytes as cu}from"node:crypto";import Fu from"libsodium-wrappers";import{constants as Pu}from"node:os";import Ru from"cross-spawn";function M(u){return u.replace(/-([a-z])/g,(t,e)=>e.toUpperCase())}function z(u,t){return u[t]??u[M(t)]}function Vu(u){if(typeof u=="string")return u===""?void 0:u;if(typeof u=="number")return String(u);if(Array.isArray(u))return u.length?u.map(String).join(","):void 0}function Lu(u={},t=[]){const e=new Set(t);return{raw:u,string:n=>Vu(z(u,n)),boolean:n=>{const r=z(u,n);return typeof r=="boolean"?r:e.has(`--${n}`)||e.has(`--${M(n)}`)},has:n=>z(u,n)!==void 0?!0:e.has(`--${n}`)||e.has(`--${M(n)}`)}}function P(u){if(!u.has("config"))return;const t=u.raw.config;if(typeof t!="string"||!t.trim())throw new Error("--config \u9700\u8981\u5355\u4E2A\u6587\u4EF6\u8DEF\u5F84");return t}const Hu={authentication:"\u8BA4\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u5F53\u524D\u540E\u7AEF\u7684 token",permission:"\u6CA1\u6709\u6743\u9650\uFF0C\u8BF7\u68C0\u67E5 token \u6388\u6743\u4E0E\u5206\u652F\u4FDD\u62A4","not-found":"\u4ED3\u5E93\u6216 ref \u4E0D\u5B58\u5728\uFF0C\u6216\u5F53\u524D token \u65E0\u6743\u8BBF\u95EE",conflict:"\u8FDC\u7AEF\u7248\u672C\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\uFF1Bpush \u53EF\u7528 --force \u91CD\u8BFB\u540E\u91CD\u8BD5\u4E00\u6B21",validation:"\u8BF7\u6C42\u6216\u54CD\u5E94\u4E0D\u5408\u6CD5\uFF0C\u8BF7\u68C0\u67E5\u8DEF\u5F84\u3001ref \u4E0E\u914D\u7F6E","rate-limit":"API \u8BF7\u6C42\u88AB\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",network:"\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\u6216\u8D85\u65F6\uFF1B\u5199\u5165\u7ED3\u679C\u53EF\u80FD\u672A\u77E5\uFF0C\u8BF7\u5148\u91CD\u8BFB\u786E\u8BA4",unsupported:"\u5B58\u50A8\u540E\u7AEF\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C"};class d extends Error{constructor(t,e){super(`${Hu[t]}${e?`\uFF08HTTP ${e}\uFF09`:""}`),this.code=t,this.status=e,this.name="StorageError"}}function Ku(u){return u instanceof d&&u.code==="conflict"}function q(u){const t=u.status,e=t===429||t===403&&(u.headers.get("x-ratelimit-remaining")==="0"||u.headers.has("retry-after"));return new d(e?"rate-limit":t===401?"authentication":t===403?"permission":t===404?"not-found":t>=500?"network":"validation",t)}const $={github:"https://api.github.com",gitlab:"https://gitlab.com/api/v4"};function D(u,t=!1){if(!(u===""&&t)){if(u.split("/").some(e=>e==="."||e===".."))throw Object.assign(new d("validation"),{message:"\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B . \u6216 .."});if(!u||/[\\\x00-\x1F\x7F]/.test(u)||u.startsWith("/")||/^[A-Z]:/i.test(u)||u.split("/").some(e=>!e||e==="."||e===".."))throw new d("validation")}}function k(u){try{const t=new URL(u);if(!["https:","http:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid API base URL");return t.href.replace(/\/+$/,"")}catch{throw new d("validation")}}function x(u,t){if(D(t),/\s/.test(t)||(u==="github"?t.split("/").length!==2:!/^\d+$/.test(t)&&t.split("/").length<2))throw new Error(u==="github"?"\u4ED3\u5E93\u8981\u5199\u6210 owner/repo \u5F62\u5F0F":"GitLab \u4ED3\u5E93\u5FC5\u987B\u662F\u6570\u5B57\u9879\u76EE ID \u6216 group/subgroup/project")}const Xu=A.object({schema:A.literal(1),storage:A.object({type:A.enum(["github","gitlab"]),apiBaseUrl:A.string().min(1).optional(),repo:A.string().min(1),branch:A.string().min(1).optional(),path:A.string().optional()}).strict()}).strict();class R extends Error{constructor(t){super(t),this.name="ProjectConfigError"}}function Ju(u,t=f.cwd()){const e=Gu(t,u||".initx-env.json");let n;try{n=Ou(e,"utf8")}catch(o){if(!u&&o.code==="ENOENT")return;throw new R("\u65E0\u6CD5\u8BFB\u53D6\u9879\u76EE\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5 --config \u8DEF\u5F84\u4E0E\u6587\u4EF6\u6743\u9650")}let r;try{r=Xu.parse(JSON.parse(n))}catch{throw new R("\u9879\u76EE\u914D\u7F6E\u5FC5\u987B\u662F schema 1 \u7684\u5408\u6CD5 JSON\uFF0C\u4E14\u53EA\u80FD\u5305\u542B\u975E\u654F\u611F storage \u5B9A\u4F4D\u5B57\u6BB5\uFF08\u7981\u6B62 token / vars\uFF09")}try{const o={...r.storage,apiBaseUrl:k(r.storage.apiBaseUrl||$[r.storage.type])};return x(o.type,o.repo),o.path!==void 0&&D(o.path,!0),{file:e,storage:o}}catch{throw new R("\u9879\u76EE storage \u7684 API \u5730\u5740\u3001\u4ED3\u5E93\u6216\u8DEF\u5F84\u4E0D\u5408\u6CD5")}}const V="main",L="stores";function H(u,t){return`${u}:${k(t)}`}function S(u){const t=u.storage?.type||"github";if(t!=="github"&&t!=="gitlab")throw new Error("\u672C\u5730 storage.type \u5FC5\u987B\u662F github \u6216 gitlab");return{type:t,apiBaseUrl:k(u.storage?.apiBaseUrl||$[t])}}function Yu(u,t){const e=t.env??f.env,n=Ju(t.config,t.cwd),r=S(u),o=t.type||e.INITX_ENV_TYPE||n?.storage.type||r.type;if(o!=="github"&&o!=="gitlab")throw new Error("storage.type \u5FC5\u987B\u662F github \u6216 gitlab");const i=o,s=n?.storage.type===i,a=t.apiBaseUrl||e.INITX_ENV_API_BASE_URL||(s?n.storage.apiBaseUrl:void 0)||(r.type===i?r.apiBaseUrl:$[i]),E=k(a),c=r.type===i&&r.apiBaseUrl===E,l=s&&n.storage.apiBaseUrl===E,h=c?u.storage||u:{},p=l?n.storage:void 0,w={type:t.type?"override":e.INITX_ENV_TYPE?"INITX_ENV_TYPE":n?"project":"local/default",apiBaseUrl:t.apiBaseUrl?"override":e.INITX_ENV_API_BASE_URL?"INITX_ENV_API_BASE_URL":l?"project":c?"local/default":"default",repo:"",branch:"",path:""};function B(y,g){const _=`INITX_ENV_${y.toUpperCase()}`,m=[[t[y]||void 0,"override"],[e[_]||void 0,_],[p?.[y],"project"],[h[y]||void 0,"local"],[g,"default"]].find(([_u])=>_u!==void 0);return w[y]=m[1],m[0]}return{type:i,apiBaseUrl:E,repo:B("repo",""),branch:B("branch",V),path:B("path",L),sources:w,configSource:n?.file||"local/env/default"}}function Wu(u,t,e){const n=t.env??f.env;if(t.token)return{token:t.token,source:"flag"};if(n.INITX_ENV_TOKEN)return{token:n.INITX_ENV_TOKEN,source:"INITX_ENV_TOKEN"};const r=e.type==="github"&&e.apiBaseUrl===$.github;if(r){for(const i of["GH_TOKEN","GITHUB_TOKEN"])if(n[i])return{token:n[i],source:i}}const o=u.credentials?.[H(e.type,e.apiBaseUrl)];return o?{token:o,source:"config"}:r&&u.token?{token:u.token,source:"config"}:{token:"",source:"none"}}function K(u,t={}){const e=Yu(u,t);if(!e.repo)throw new Error("\u8FD8\u6CA1\u914D vault \u4ED3\u5E93\uFF0C\u8BF7\u914D\u7F6E .initx-env.json \u6216 env-config set repo <owner/repo>");x(e.type,e.repo),D(e.path,!0);const{token:n,source:r}=Wu(u,t,e);if(!n)throw new Error("\u6CA1\u6709\u53EF\u7528\u7684\u5F53\u524D\u540E\u7AEF token\uFF0C\u8BF7\u8BBE\u7F6E INITX_ENV_TOKEN\u3001--token \u6216\u7ED1\u5B9A\u5F53\u524D API \u5730\u5740\u7684\u672C\u5730\u51ED\u636E");return{...e,token:n,tokenSource:r}}function Mu(u){return u?"***":"(\u672A\u8BBE\u7F6E)"}function Z(u){return Mu(u)}async function C(u,t={}){try{return await fetch(u,{...t,redirect:"error",signal:AbortSignal.timeout(3e4)})}catch{throw new d("network")}}async function N(u){try{return await u.json()}catch(t){throw new d(t instanceof SyntaxError?"validation":"network")}}function v(u){if(!u.ok)throw q(u)}function lu(u){return{Authorization:`Bearer ${u}`,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28","User-Agent":"initx-plugin-env"}}class zu{constructor(t,e){this.location=t,x("github",t.repo),this.base=`${k(t.apiBaseUrl)}/repos/${t.repo.split("/").map(encodeURIComponent).join("/")}`,this.headers=lu(e)}capabilities={read:!0,list:!0,write:!0,delete:!0,conditionalWrite:!0};base;headers;url(t){D(t,!0);const e=t?`/${t.split("/").map(encodeURIComponent).join("/")}`:"";return`${this.base}/contents${e}?${new URLSearchParams({ref:this.location.branch})}`}async checkTarget(){const t=await C(`${this.base}/commits/${encodeURIComponent(this.location.branch)}`,{headers:this.headers});v(t)}async read(t){D(t);const e=await C(this.url(t),{headers:this.headers});if(e.status===404)return await this.checkTarget(),null;v(e);const n=await N(e);if(!n||n.type!=="file"||typeof n.sha!="string"||!n.sha||typeof n.content!="string"||n.encoding!=="base64")throw new d("validation");return{content:I.from(n.content,"base64").toString("utf8"),version:n.sha}}async list(t){const e=await C(this.url(t),{headers:this.headers});if(e.status===404)return await this.checkTarget(),[];v(e);const n=await N(e);if(!Array.isArray(n)||n.some(r=>!r||typeof r.name!="string"||typeof r.path!="string"||typeof r.type!="string"))throw new d("validation");return n.map(r=>({name:r.name,path:r.path,kind:r.type==="file"?"file":r.type==="dir"?"directory":"other"}))}async write(t,e,n){D(t);const r=n.condition;if(r.kind!=="create"&&(r.kind!=="update"||!r.expectedVersion))throw new d("validation");const o=await C(this.url(t),{method:"PUT",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({branch:this.location.branch,message:n.message||"env: write store",content:I.from(e).toString("base64"),...r.kind==="update"?{sha:r.expectedVersion}:{}})});if([404,409,422].includes(o.status)){const i=await this.read(t);if(r.kind==="create"?i!==null:i?.version!==r.expectedVersion)throw new d("conflict",o.status)}if(!o.ok)throw q(o)}async remove(t,e){if(D(t),!e.expectedVersion)throw new d("validation");const n=await C(this.url(t),{method:"DELETE",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({branch:this.location.branch,message:e.message||"env: remove store",sha:e.expectedVersion})});if([404,409,422].includes(n.status)&&(await this.read(t))?.version!==e.expectedVersion)throw new d("conflict",n.status);v(n)}}async function Eu(u){if(u.status!==400)return;let t;try{t=await N(u)}catch(e){if(e instanceof d&&e.code==="network")throw e}if(t?.message==="You are not allowed to push into this branch")throw new d("permission",400)}class qu{constructor(t,e){this.location=t,x("gitlab",t.repo),this.base=`${k(t.apiBaseUrl)}/projects/${encodeURIComponent(t.repo)}/repository`,this.headers={"PRIVATE-TOKEN":e,"Content-Type":"application/json"}}capabilities={read:!0,list:!0,write:!0,delete:!0,conditionalWrite:!0};base;headers;fileUrl(t){return D(t),`${this.base}/files/${encodeURIComponent(t)}`}async checkTarget(){const t=await C(`${this.base}/commits/${encodeURIComponent(this.location.branch)}`,{headers:this.headers});v(t)}async read(t){const e=await C(`${this.fileUrl(t)}?${new URLSearchParams({ref:this.location.branch})}`,{headers:this.headers});if(e.status===404)return await this.checkTarget(),null;v(e);const n=await N(e);if(!n||typeof n.content!="string"||n.encoding!=="base64"||typeof n.last_commit_id!="string"||!n.last_commit_id)throw new d("validation");return{content:I.from(n.content,"base64").toString("utf8"),version:n.last_commit_id}}async list(t){D(t,!0);const e=[];await this.checkTarget();for(let n=1;;n++){const r=new URLSearchParams({ref:this.location.branch,per_page:"100",page:String(n),...t?{path:t}:{}}),o=await C(`${this.base}/tree?${r}`,{headers:this.headers});if(o.status===404)return await this.checkTarget(),e;v(o);const i=await N(o);if(!Array.isArray(i)||i.some(a=>!a||typeof a.name!="string"||typeof a.path!="string"||typeof a.type!="string"))throw new d("validation");e.push(...i.map(a=>({name:a.name,path:a.path,kind:a.type==="blob"?"file":a.type==="tree"?"directory":"other"})));const s=o.headers.get("x-next-page");if(!i.length||s===""||s===null&&i.length<100)return e;if(s!==null&&s!==String(n+1))throw new d("validation")}}async write(t,e,n){const r=n.condition;if(r.kind!=="create"&&(r.kind!=="update"||!r.expectedVersion))throw new d("validation");const o=await C(this.fileUrl(t),{method:r.kind==="create"?"POST":"PUT",headers:this.headers,body:JSON.stringify({branch:this.location.branch,commit_message:n.message||"env: write store",content:I.from(e).toString("base64"),encoding:"base64",...r.kind==="update"?{last_commit_id:r.expectedVersion}:{}})});if(await Eu(o),[400,404,409,422].includes(o.status)){const i=await this.read(t);if(r.kind==="create"?i!==null:i?.version!==r.expectedVersion)throw new d("conflict",o.status);if(r.kind==="update"&&o.status===400&&i?.content===e)return}if(!o.ok)throw q(o)}async remove(t,e){if(D(t),!e.expectedVersion)throw new d("validation");const n=await C(`${this.base}/commits`,{method:"POST",headers:this.headers,body:JSON.stringify({branch:this.location.branch,commit_message:e.message||"env: remove store",actions:[{action:"delete",file_path:t,last_commit_id:e.expectedVersion}]})});if(await Eu(n),[400,404,409,422].includes(n.status)&&(await this.read(t))?.version!==e.expectedVersion)throw new d("conflict",n.status);v(n)}}const Zu={github:zu,gitlab:qu};function X(u){const t=Zu[u.type];if(!t)throw new d("unsupported");return new t(u,u.token)}function J(u,t){if(!u.capabilities[t])throw new d("unsupported")}const Q=1,Qu=A.object({repo:A.string().regex(/^[^/\s]+\/[^/\s]+$/,"\u5FC5\u987B\u662F owner/repo \u5F62\u5F0F"),env:A.string().nullish()}),ut=A.object({schema:A.number().int().positive().default(Q),vars:A.record(A.string(),A.string()).default({}),targets:A.object({envFiles:A.array(A.string().min(1)).optional(),secrets:A.array(Qu).optional()}).optional()});function pu(){return{schema:Q,vars:{}}}function T(u){const t=u.trim().replace(/\.json$/i,"");if(!t)throw new Error("store \u540D\u4E0D\u80FD\u4E3A\u7A7A");if(t.includes("/")||t.includes("\\"))throw new Error(`store \u540D\u4E0D\u80FD\u5305\u542B\u8DEF\u5F84\u5206\u9694\u7B26\uFF1A"${u}"`);if(t==="."||t==="..")throw new Error(`store \u540D\u4E0D\u80FD\u662F "${t}"`);if(/[\u0000-\u001F\u007F]/.test(t))throw new Error("store \u540D\u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26");if(t.length>100)throw new Error("store \u540D\u592A\u957F\uFF08\u4E0A\u9650 100 \u5B57\u7B26\uFF09");return t}function tt(u){return/^[A-Z_]\w*$/i.test(u)?u.toUpperCase().startsWith("GITHUB_")?"GITHUB_ \u524D\u7F00\u662F GitHub \u4FDD\u7559\u7684":null:"\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\uFF0C\u4E14\u4E0D\u80FD\u4EE5\u6570\u5B57\u5F00\u5934"}function et(u){return/^[A-Z_][\w.-]*$/i.test(u)}function fu(u,t){const e=T(t),n=(u||"").trim().replace(/^\/+|\/+$/g,""),r=n?`${n}/${e}.json`:`${e}.json`;return D(r),r}function nt(u,t){let e;try{e=JSON.parse(u)}catch{throw new Error(`store "${t}" \u4E0D\u662F\u5408\u6CD5 JSON`)}const n=ut.safeParse(e);if(!n.success)throw new Error(`store "${t}" \u7ED3\u6784\u4E0D\u5408\u6CD5\uFF1A${n.error.issues.map(r=>`${r.path.join(".")||"(root)"} ${r.message}`).join("; ")}`);return n.data}function rt(u){return`${JSON.stringify({...u,schema:u.schema??Q},null,2)}
|
|
2
|
+
`}async function uu(u,t){J(u,"list");const e=(t||"").trim().replace(/^\/+|\/+$/g,"");return(await u.list(e)).filter(n=>n.kind==="file"&&n.name.toLowerCase().endsWith(".json")).map(n=>n.name.replace(/\.json$/i,"")).sort((n,r)=>n.localeCompare(r))}async function U(u,t,e){const n=fu(t,e);J(u,"read");const r=await u.read(n);if(!r)return null;const o=T(e);return{name:o,path:n,version:r.version,store:nt(r.content,o)}}async function tu(u,t,e,n,r,o){J(u,"write"),J(u,"conditionalWrite"),await u.write(fu(t,e),rt(n),{message:r,condition:o===void 0?{kind:"create"}:{kind:"update",expectedVersion:o}})}function hu(u,t){const e={added:[],updated:[],unchanged:[]};for(const[n,r]of Object.entries(t)){const o=u.vars[n];o===void 0?(u.vars[n]=r,e.added.push(n)):o===r?e.unchanged.push(n):(u.vars[n]=r,e.updated.push(n))}return e}const du=/^export[ \t]+/,ot=/^([^=]*)=(.*)$/;function it(u){const t=u.replace(/[ \t]+$/,"");return t===""||/^[ \t]/.test(t)?null:t}const st=/^[ \t]*#/;function gu(u){return typeof u=="object"&&u!==null&&u.code==="ENOENT"}async function at(u){try{return await Su(u)}catch(t){if(gu(t))return null;throw t}}function ct(u){return u.replace(/\\([nrt"\\])/g,(t,e)=>e==="n"?`
|
|
3
|
+
`:e==="r"?"\r":e==="t"?" ":e)}function Ft(u){const t=u.replace(/^[ \t]+/,"");for(const e of t.split(`
|
|
4
|
+
`)){const n=/[ \t]#/.exec(e);if(n!==null)return e.slice(0,n.index).replace(/[ \t]+$/,"")}return t.replace(/[ \t]+$/,"")}function lt(u){if(u==="")return[];const t=u.split(/\r?\n/);return u.endsWith(`
|
|
5
|
+
`)&&t.pop(),t}function wu(u){for(let t=0;t<u.length;t+=1){if(u[t]==="\\"){t+=1;continue}if(u[t]==='"')return t}return-1}function Et(u,t){const e=u[t];if(st.test(e))return null;const n=du.test(e),r=ot.exec(n?e.replace(du,""):e);if(r===null)return null;const o=it(r[1]);if(o===null)return null;const i=r[2];let s=t,a="none",E;if(i.startsWith('"')){let c=i.slice(1),l=wu(c);for(;l===-1&&s+1<u.length;)s+=1,c+=`
|
|
6
|
+
${u[s]}`,l=wu(c);a="double",E=ct(l===-1?c:c.slice(0,l))}else if(i.startsWith("'")){const c=i.indexOf("'",1);a="single",E=c===-1?i.slice(1):i.slice(1,c)}else E=Ft(i);return{key:o,value:E,exportPrefix:n,quote:a,raw:e,startLine:t,endLine:s}}function Y(u){const t=u.includes(`\r
|
|
10
7
|
`)?`\r
|
|
11
8
|
`:`
|
|
12
|
-
`,
|
|
13
|
-
`),n=
|
|
14
|
-
`){const
|
|
15
|
-
`))return`${
|
|
9
|
+
`,e=u.endsWith(`
|
|
10
|
+
`),n=lt(u),r=new Map,o=[],i=[];for(let s=0;s<n.length;){const a=Et(n,s);if(a===null){s+=1;continue}r.has(a.key)&&!o.includes(a.key)&&o.push(a.key),r.set(a.key,a.value),i.push(a),s=a.endLine+1}return{lines:n,pairs:r,duplicates:o,eol:t,trailingNewline:e,entries:i}}const pt=/[\s"'\\#$`]/;function ft(u){return u!==""&&!pt.test(u)}function ht(u){return ft(u)?u:`"${u.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}"`}function eu(u,t,e){return`${e?"export ":""}${u}=${ht(t)}`}function Au(u,t,e){return`${u.join(t)}${e?t:""}`}function Bu(u,t){const{pairs:e}=Y(u),n=[],r=[],o=[],i=new Map;for(const[s,a]of Object.entries(t))i.set(s,a),e.has(s)?e.get(s)===a?o.push(s):r.push(s):n.push(s);return{added:n,updated:r,unchanged:o,target:i}}function dt(u,t){if(u===""){const c=gt(t);return{content:c,added:Object.keys(t),updated:[],unchanged:[],duplicates:[],changed:c!==u}}const e=Y(u),{added:n,updated:r,unchanged:o,target:i}=Bu(u,t),s=e.lines,a=new Map;e.entries.forEach((c,l)=>a.set(c.key,l));for(let c=e.entries.length-1;c>=0;c-=1){const l=e.entries[c];if(a.get(l.key)!==c)continue;const h=i.get(l.key);if(h===void 0||h===l.value)continue;const p=eu(l.key,h,l.exportPrefix);s.splice(l.startLine,l.endLine-l.startLine+1,p)}if(n.length>0){s.length>0&&s[s.length-1].trim()!==""&&s.push("");for(const c of n)s.push(eu(c,t[c],!1))}const E=Au(s,e.eol,e.trailingNewline);return{content:E,added:n,updated:r,unchanged:o,duplicates:e.duplicates,changed:E!==u}}function gt(u,t=`
|
|
11
|
+
`){const e=Object.entries(u).map(([n,r])=>eu(n,r,!1));return e.length===0?"":Au(e,t,!0)}function wt(u,t){const{added:e,updated:n,unchanged:r}=Bu(u,t),o=[...Y(u).pairs.keys()].filter(i=>!(i in t));return{added:e,updated:n,unchanged:r,localOnly:o}}async function nu(u){try{return await su(u,"utf8")}catch(t){if(gu(t))return null;throw t}}async function At(u,t){const e=await at(u);if(e!==null&&e.isFile()){if(await su(u,"utf8")!==t){const n=`${u}.bak`;return await Iu(u,n),await au(u,t,"utf8"),{backupPath:n}}return{backupPath:null}}return await au(u,t,"utf8"),{backupPath:null}}function Du(){return f.env.GITHUB_ACTIONS==="true"?!0:typeof f.env.GITHUB_ENV=="string"&&f.env.GITHUB_ENV.length>0}function Bt(u){let t=`EOF_${cu(4).toString("hex")}`;for(;u.includes(t);)t=`EOF_${cu(4).toString("hex")}`;return t}function Dt(u){return Object.entries(u).map(([t,e])=>{if(!e.includes(`
|
|
12
|
+
`))return`${t}=${e}`;const n=Bt(e);return[`${t}<<${n}`,e,n].join(`
|
|
16
13
|
`)}).join(`
|
|
17
|
-
`)}const
|
|
18
|
-
`)||
|
|
19
|
-
`)}function
|
|
20
|
-
`)||u.includes("\r"))for(const
|
|
21
|
-
${
|
|
14
|
+
`)}const Ct=/[\s'"$]/,yt=/[\\"$`]/g;function mt(u){return Object.entries(u).map(([t,e])=>{if(e.includes(`
|
|
15
|
+
`)||e.includes("\r")){const n=e.replace(yt,r=>`\\${r}`).replace(/\r\n|\r|\n/g,"\\n");return`${t}="${n}"`}return Ct.test(e)?`${t}='${e.replace(/'/g,"'\\''")}'`:`${t}=${e}`}).join(`
|
|
16
|
+
`)}function vt(u){if(u.includes(`
|
|
17
|
+
`)||u.includes("\r"))for(const t of u.split(/\r?\n/))t!==""&&console.log(`::add-mask::${t}`);console.log(`::add-mask::${u}`)}function $t(u,t={}){const{forceStdout:e=!1,mask:n=!0}=t,r=Object.keys(u),o=[];if(n&&r.length>0&&Du())for(const a of r)vt(u[a]),o.push(a);const i=e?void 0:f.env.GITHUB_ENV;if(i){const a=Dt(u);return a!==""&&ju(i,`
|
|
18
|
+
${a}`,{encoding:"utf8"}),{target:"github_env",injected:r,masked:o}}const s=mt(u);return s!==""&&console.log(s),{target:"stdout",injected:r,masked:o}}const bt=/^\.env(?:\..+)?$/,kt=[".bak",".example",".sample",".template",".keys",".enc"];async function xt(u,t){const[e="list",...n]=t;switch(e){case"list":case"ls":return Tt(u);case"pull":return It(u,n);case"set":return Ut(u,n);case"push":return Ot(u,n);case"setup":return Gt(u,n);default:throw new Error(`\u672A\u77E5\u5B50\u547D\u4EE4 "${e}"\uFF0C\u53EF\u7528\uFF1Alist | pull | set | push | setup | apply\uFF08npx initx env help\uFF09`)}}function O(u,t={}){const e=K(u.store,{token:u.options.string("token"),config:P(u.options),repo:t.repo});return{config:e,client:X(e)}}async function Tt(u){const{config:t,client:e}=O(u),n=await uu(e,t.path);if(!n.length){F.warn(`${t.repo} \u7684 ${t.path}/ \u4E0B\u8FD8\u6CA1\u6709 store`),F.info("\u5EFA\u4E00\u4E2A\uFF1Anpx initx env set <\u540D\u5B57> KEY=value");return}F.info(`${t.repo} \xB7 ${t.path}/ \u5171 ${n.length} \u4E2A store\uFF1A`);for(const r of n)F.info(` - ${r}`)}async function Cu(u,t,e){if(e)return T(e);const n=await uu(u,t);if(!n.length)throw new Error("\u4ED3\u5E93\u91CC\u8FD8\u6CA1\u6709 store\u3002\u5148\u5EFA\u4E00\u4E2A\uFF1Anpx initx env set <\u540D\u5B57> KEY=value");if(n.length===1)return n[0];if(!f.stdin.isTTY)throw new Error(`\u6709\u591A\u4E2A store\uFF0C\u975E\u4EA4\u4E92\u73AF\u5883\u4E0B\u5FC5\u987B\u6307\u5B9A\u540D\u5B57\uFF1A
|
|
22
19
|
${n.join(`
|
|
23
|
-
`)}`);return
|
|
20
|
+
`)}`);return b.search("\u9009\u4E00\u4E2A store",n)}async function ru(u,t,e){const n=await U(u,t,e);if(!n)throw new Error(`\u4ED3\u5E93\u91CC\u6CA1\u6709 store "${e}"\u3002\u7528 npx initx env list \u770B\u770B\u6709\u54EA\u4E9B`);return n}async function _t(u,t){const e=u.options.string("to");if(e)return e.split(",").map(i=>i.trim()).filter(Boolean);const n=t.store.targets?.envFiles;if(n?.length)return n;const r=await Nt();if(r.length===1)return r;if(r.length===0){if(!f.stdin.isTTY)return[".env.local"];if(!await b.confirm("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709 .env \u6587\u4EF6\uFF0C\u65B0\u5EFA .env.local\uFF1F"))throw new Error("\u5DF2\u53D6\u6D88\uFF08\u4E5F\u53EF\u4EE5\u7528 --to <\u6587\u4EF6> \u6307\u5B9A\u76EE\u6807\uFF09");return[".env.local"]}if(!f.stdin.isTTY)throw new Error(`\u5F53\u524D\u76EE\u5F55\u6709\u591A\u4E2A .env \u6587\u4EF6\uFF0C\u975E\u4EA4\u4E92\u73AF\u5883\u4E0B\u8BF7\u7528 --to \u6307\u5B9A\uFF1A
|
|
24
21
|
${r.join(`
|
|
25
|
-
`)}`);const o=await g.select("\u5199\u5230\u54EA\u4E2A\u6587\u4EF6",r);return[r[o]]}async function Ft(){return(await Du(p.cwd(),{withFileTypes:!0})).filter(u=>u.isFile()&&rt.test(u.name)).map(u=>u.name).filter(u=>!ot.some(e=>u.endsWith(e))).sort((u,e)=>{const t=n=>n===".env.local"?0:n===".env"?1:2;return t(u)-t(e)||u.localeCompare(e)})}async function ct(u,e){const{config:t,client:n}=m(u),r=await Fu(n,t.path,e[0]),o=await V(n,t.path,r),s=o.store.vars,i=Object.keys(s);if(!i.length){F.warn(`store "${r}" \u91CC\u6CA1\u6709\u4EFB\u4F55\u53D8\u91CF`);return}const c=await at(u,o),l=u.options.boolean("dry-run");for(const a of c){const E=await P(a)??"",f=Wu(E,s);if(!f.changed){F.info(`${a}: \u5DF2\u7ECF\u662F\u6700\u65B0\u7684\uFF08${i.length} \u4E2A\u53D8\u91CF\uFF0C\u65E0\u9700\u6539\u52A8\uFF09`);continue}if(l){const d=Mu(E,s);F.info(`${a}\uFF08--dry-run\uFF0C\u672A\u5199\u5165\uFF09\uFF1A`);for(const w of d.added)F.info(` + ${w}`);for(const w of d.updated)F.info(` ~ ${w}`);continue}const{backupPath:h}=await zu(a,f.content);F.success(`${a}: \u65B0\u589E ${f.added.length} \xB7 \u66F4\u65B0 ${f.updated.length} \xB7 \u672A\u53D8 ${f.unchanged.length}`),h&&F.info(` \u65E7\u6587\u4EF6\u5907\u4EFD\u5728 ${h}`),f.duplicates.length&&F.warn(` \u6CE8\u610F\uFF1A${f.duplicates.join(", ")} \u5728\u6587\u4EF6\u91CC\u51FA\u73B0\u591A\u6B21\uFF0C\u53EA\u66F4\u65B0\u4E86\u6700\u540E\u4E00\u884C`)}}function Et(u){const e={};for(const t of u){const n=t.indexOf("=");if(n<=0)throw new Error(`\u53C2\u6570\u8981\u5199\u6210 KEY=value \u5F62\u5F0F\uFF1A"${t}"`);const r=t.slice(0,n).trim(),o=t.slice(n+1);if(!_u(r))throw new Error(`\u53D8\u91CF\u540D\u4E0D\u5408\u6CD5\uFF1A"${r}"`);e[r]=o}if(!Object.keys(e).length)throw new Error("\u81F3\u5C11\u7ED9\u4E00\u4E2A KEY=value");return e}async function lt(u,e){const[t,...n]=e;if(!t)throw new Error("\u7528\u6CD5\uFF1Anpx initx env set <store \u540D> KEY=value [KEY2=value2 ...]");const r=B(t),o=Et(n),{config:s,client:i}=m(u),c=await v(i,s.path,r),l=c?.store??uu(),a=eu(l,o);if(!a.added.length&&!a.updated.length){F.info(`store "${r}" \u65E0\u9700\u6539\u52A8\uFF08${a.unchanged.length} \u4E2A\u53D8\u91CF\u503C\u76F8\u540C\uFF09`);return}await K(i,s.path,r,l,`env: set ${Object.keys(o).join(", ")} in ${r}`,c?.sha),F.success(`store "${r}" \u5DF2\u66F4\u65B0\uFF1A\u65B0\u589E ${a.added.length} \xB7 \u4FEE\u6539 ${a.updated.length}`);for(const E of a.added)F.info(` + ${E}`);for(const E of a.updated)F.info(` ~ ${E}`)}async function ft(u,e){const[t]=e;if(!t)throw new Error("\u7528\u6CD5\uFF1Anpx initx env push <store \u540D> [--from .env.local] [--prune] [--force]");const n=B(t),r=u.options.string("from")??await pt(),o=await P(r);if(o===null)throw new Error(`\u627E\u4E0D\u5230\u6587\u4EF6 "${r}"\uFF08\u7528 --from \u6307\u5B9A\uFF09`);const s=Object.fromEntries(_(o).pairs);if(!Object.keys(s).length)throw new Error(`"${r}" \u91CC\u6CA1\u89E3\u6790\u51FA\u4EFB\u4F55 KEY=value`);const{config:i,client:c}=m(u),l=await v(c,i.path,n),a=l?.store??uu(),E=eu(a,s),f=[];if(u.options.boolean("prune"))for(const d of Object.keys(a.vars))d in s||(delete a.vars[d],f.push(d));if(!E.added.length&&!E.updated.length&&!f.length){F.info(`store "${n}" \u65E0\u9700\u6539\u52A8\uFF08${r} \u91CC\u7684\u503C\u90FD\u4E00\u6837\uFF09`);return}const h=`env: push ${n} from ${r}`;try{await K(c,i.path,n,a,h,l?.sha)}catch(d){if(!ku(d)||!u.options.boolean("force"))throw d;const w=await V(c,i.path,n);F.warn("\u8FDC\u7AEF\u5DF2\u66F4\u65B0\uFF0C--force\uFF1A\u4EE5\u672C\u5730\u6587\u4EF6\u4E3A\u51C6\u91CD\u65B0\u5199\u5165"),await K(c,i.path,n,a,h,w.sha)}F.success(`store "${n}" \u5DF2\u66F4\u65B0\uFF1A\u65B0\u589E ${E.added.length} \xB7 \u4FEE\u6539 ${E.updated.length}${f.length?` \xB7 \u5220\u9664 ${f.length}`:""}`)}async function pt(){for(const u of[".env.local",".env"])if(await P(u)!==null)return u;throw new Error("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709 .env.local / .env\uFF08\u7528 --from \u6307\u5B9A\u6587\u4EF6\uFF09")}async function ht(u,e){const{config:t,client:n}=m(u),r=await Fu(n,t.path,e[0]),o=(await V(n,t.path,r)).store.vars;if(!Object.keys(o).length)throw new Error(`store "${r}" \u91CC\u6CA1\u6709\u4EFB\u4F55\u53D8\u91CF`);if(u.options.boolean("json")){console.log(JSON.stringify(o)),F.info(`\u5DF2\u8F93\u51FA ${Object.keys(o).length} \u4E2A\u53D8\u91CF\uFF08JSON\uFF09`);return}const s=nt(o,{forceStdout:u.options.boolean("stdout"),mask:!u.options.boolean("no-mask")});if(s.target==="github_env"){F.success(`\u5DF2\u6CE8\u5165\u672C\u6B21 job \u7684\u540E\u7EED\u6B65\u9AA4\uFF08${s.injected.length} \u4E2A\u53D8\u91CF\uFF0C${s.masked.length} \u4E2A\u5DF2\u8131\u654F\uFF09`);return}au()||F.warn("\u5F53\u524D\u4E0D\u5728 GitHub Actions \u91CC\uFF0C\u53D8\u91CF\u6253\u5230 stdout\uFF08\u53EF\u4EE5 eval \u6216\u91CD\u5B9A\u5411\uFF09")}const cu=32;let X;function dt(){return X||(X=z.ready.then(()=>z)),X}function At(u,e){let t;try{t=u.from_base64(e.trim(),u.base64_variants.ORIGINAL)}catch{throw new Error("GitHub \u516C\u94A5\u89E3\u7801\u5931\u8D25\uFF1A\u5B83\u4E0D\u662F\u5408\u6CD5\u7684 base64\uFF08ORIGINAL \u53D8\u4F53\uFF09\u5B57\u7B26\u4E32\uFF1B\u8BF7\u786E\u8BA4\u4F20\u5165\u7684\u662F public-key \u63A5\u53E3\u8FD4\u56DE\u7684 key \u5B57\u6BB5\uFF0C\u800C\u4E0D\u662F key_id")}if(t.length!==cu)throw new Error(`GitHub \u516C\u94A5\u957F\u5EA6\u4E0D\u6B63\u786E\uFF1AX25519 \u516C\u94A5\u5FC5\u987B\u662F ${cu} \u5B57\u8282\uFF0C\u5B9E\u9645 ${t.length} \u5B57\u8282\u3002 \u8FD9\u79CD\u60C5\u51B5\u901A\u5E38\u662F key_id \u4E0E key \u4E24\u4E2A\u5B57\u6BB5\u5F04\u53CD\u4E86\uFF08\u6216\u628A key_id \u5F53\u6210\u4E86\u516C\u94A5\uFF09\uFF1B\u8BF7\u6539\u7528 public-key \u63A5\u53E3\u8FD4\u56DE\u7684 key \u5B57\u6BB5`);return t}async function wt(u,e){const t=await dt(),n=At(t,u),r=t.crypto_box_seal(t.from_string(e),n);return t.to_base64(r,t.base64_variants.ORIGINAL)}function Eu(u,e){const[t,n]=u.split("/",2),r=`https://api.github.com/repos/${encodeURIComponent(t)}/${encodeURIComponent(n)}`;return e?`${r}/environments/${encodeURIComponent(e)}/secrets`:`${r}/actions/secrets`}async function Dt(u,e,t){const n=await(await Z(`${Eu(u,t)}/public-key`,e)).json();if(!n.key_id||!n.key)throw new Error(`\u62FF\u4E0D\u5230 ${u} \u7684\u516C\u94A5\uFF08\u8FD4\u56DE\u4F53\u91CC\u6CA1\u6709 key_id/key\uFF09`);return{keyId:n.key_id,key:n.key}}async function Ct(u,e,t,n,r={}){const{keyId:o,key:s}=await Dt(u,e,r.env),i=await wt(s,n);return(await Z(`${Eu(u,r.env)}/${encodeURIComponent(t)}`,e,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({encrypted_value:i,key_id:o})})).status===201?"created":"updated"}const gt=48*1024;async function Bt(u,e){const[t]=e;if(!t)throw new Error("\u7528\u6CD5\uFF1Anpx initx env apply <store \u540D> --repo <owner/repo> [--env <\u73AF\u5883\u540D>] [--only A,B] [--dry-run]");const n=B(t),r=T(u.store,{token:u.options.string("token")}),o=new I(r.repo,r.token,r.branch),s=await v(o,r.path,n);if(!s)throw new Error(`\u4ED3\u5E93\u91CC\u6CA1\u6709 store "${n}"\u3002\u7528 npx initx env list \u770B\u770B\u6709\u54EA\u4E9B`);const i=s.store.targets?.secrets?.[0],c=u.options.string("repo")??i?.repo;if(!c)throw new Error("\u8981\u6307\u5B9A\u5199\u5165\u76EE\u6807\uFF1A--repo <owner/repo>\uFF0C\u6216\u5728 store \u91CC\u5199 targets.secrets");const l=u.options.string("env")??i?.env??null,a=u.options.string("only")?.split(",").map(A=>A.trim()).filter(Boolean),E=Object.entries(s.store.vars).filter(([A])=>!a||a.includes(A));if(!E.length)throw new Error(a?.length?`--only \u91CC\u6CA1\u6709\u4EFB\u4F55\u53D8\u91CF\u547D\u4E2D store "${n}"`:`store "${n}" \u91CC\u6CA1\u6709\u53D8\u91CF`);const f=[];for(const[A,O]of E){const $=Iu(A);if($){f.push(`${A} \u2014 \u540D\u5B57\u4E0D\u5408\u6CD5\uFF1A${$}`);continue}j.byteLength(O,"utf8")>gt&&f.push(`${A} \u2014 \u503C\u8D85\u8FC7 48 KB \u4E0A\u9650`)}if(f.length){F.error("\u8FD9\u4E9B\u53D8\u91CF\u4E0D\u80FD\u5199\u8FDB Actions secrets\uFF1A");for(const A of f)F.error(` ${A}`);if(!u.options.boolean("skip-invalid"))throw new Error(`\u6709 ${f.length} \u4E2A\u53D8\u91CF\u4E0D\u5408\u6CD5\uFF0C\u5DF2\u4E2D\u6B62\uFF08\u6539\u540D\u540E\u7528 --only \u6392\u9664\uFF0C\u6216\u52A0 --skip-invalid \u8DF3\u8FC7\uFF09`);F.warn("--skip-invalid\uFF1A\u8DF3\u8FC7\u8FD9\u4E9B\u53D8\u91CF")}const h=new Set(f.map(A=>A.split(" \u2014 ")[0])),d=E.filter(([A])=>!h.has(A));if(u.options.boolean("dry-run")){F.info(`--dry-run\uFF1A\u5C06\u5199\u5165 ${d.length} \u4E2A secret \u2192 ${c}${l?`\uFF08\u73AF\u5883 ${l}\uFF09`:"\uFF08\u4ED3\u5E93\u7EA7\uFF09"}`);for(const[A]of d)F.info(` ${A}`);return}F.info(`\u5199\u5165 ${c}${l?`\uFF08\u73AF\u5883 ${l}\uFF09`:"\uFF08\u4ED3\u5E93\u7EA7\uFF09"} \u2026`);const w=[];for(const[A,O]of d)try{const $=await Ct(c,r.token,A,O,{env:l});F.success(`${A}: ${$==="created"?"\u65B0\u5EFA":"\u66F4\u65B0"}`)}catch($){w.push(A),F.error(`${A}: \u5931\u8D25 \u2014 ${$.message}`)}if(w.length)throw new Error(`${w.length} \u4E2A secret \u5199\u5165\u5931\u8D25\uFF1A${w.join(", ")}`);F.success(`\u5B8C\u6210\uFF1A${d.length} \u4E2A secret \u2192 ${c}`),l&&F.info("\u73AF\u5883\u7EA7 secret \u9700\u8981\u8BE5 environment \u5DF2\u5B58\u5728\uFF0C\u4E14 job \u5F15\u7528\u5B83\u65F6\u624D\u53EF\u7528")}const k=["repo","token","branch","path"],lu="token \u4F1A\u4EE5**\u660E\u6587**\u5B58\u5728 ~/.initx/stores/@initx-plugin/env/store.json\uFF1B\u4ECB\u610F\u7684\u8BDD\u6539\u7528\u73AF\u5883\u53D8\u91CF INITX_ENV_TOKEN\uFF0C\u6216\u6BCF\u6B21\u7528 --token \u4F20\u3002";function J(u){return k.includes(u)}async function $t(u,e,t){const[n="status",...r]=e;switch(n){case"setup":return yt(u);case"set":return vt(u,r);case"get":return mt(u,r);case"list":case"ls":return kt(u);case"rm":case"remove":return bt(u,r);case"status":return xt(u,t);default:fu()}}async function yt(u){if(!p.stdin.isTTY)throw new Error(`setup \u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u3002\u975E\u4EA4\u4E92\u73AF\u5883\u8BF7\u7528\uFF1A
|
|
22
|
+
`)}`);const o=await b.select("\u5199\u5230\u54EA\u4E2A\u6587\u4EF6",r);return[r[o]]}async function Nt(){return(await Uu(f.cwd(),{withFileTypes:!0})).filter(u=>u.isFile()&&bt.test(u.name)).map(u=>u.name).filter(u=>!kt.some(t=>u.endsWith(t))).sort((u,t)=>{const e=n=>n===".env.local"?0:n===".env"?1:2;return e(u)-e(t)||u.localeCompare(t)})}async function It(u,t){const{config:e,client:n}=O(u),r=await Cu(n,e.path,t[0]),o=await ru(n,e.path,r),i=o.store.vars,s=Object.keys(i);if(!s.length){F.warn(`store "${r}" \u91CC\u6CA1\u6709\u4EFB\u4F55\u53D8\u91CF`);return}const a=await _t(u,o),E=u.options.boolean("dry-run");for(const c of a){const l=await nu(c)??"",h=dt(l,i);if(!h.changed){F.info(`${c}: \u5DF2\u7ECF\u662F\u6700\u65B0\u7684\uFF08${s.length} \u4E2A\u53D8\u91CF\uFF0C\u65E0\u9700\u6539\u52A8\uFF09`);continue}if(E){const w=wt(l,i);F.info(`${c}\uFF08--dry-run\uFF0C\u672A\u5199\u5165\uFF09\uFF1A`);for(const B of w.added)F.info(` + ${B}`);for(const B of w.updated)F.info(` ~ ${B}`);continue}const{backupPath:p}=await At(c,h.content);F.success(`${c}: \u65B0\u589E ${h.added.length} \xB7 \u66F4\u65B0 ${h.updated.length} \xB7 \u672A\u53D8 ${h.unchanged.length}`),p&&F.info(` \u65E7\u6587\u4EF6\u5907\u4EFD\u5728 ${p}`),h.duplicates.length&&F.warn(` \u6CE8\u610F\uFF1A${h.duplicates.join(", ")} \u5728\u6587\u4EF6\u91CC\u51FA\u73B0\u591A\u6B21\uFF0C\u53EA\u66F4\u65B0\u4E86\u6700\u540E\u4E00\u884C`)}}function St(u){const t={};for(const e of u){const n=e.indexOf("=");if(n<=0)throw new Error("\u53C2\u6570\u8981\u5199\u6210 KEY=value \u5F62\u5F0F");const r=e.slice(0,n).trim(),o=e.slice(n+1);if(!et(r))throw new Error(`\u53D8\u91CF\u540D\u4E0D\u5408\u6CD5\uFF1A"${r}"`);t[r]=o}if(!Object.keys(t).length)throw new Error("\u81F3\u5C11\u7ED9\u4E00\u4E2A KEY=value");return t}async function Ut(u,t){const[e,...n]=t;if(!e)throw new Error("\u7528\u6CD5\uFF1Anpx initx env set <store \u540D> KEY=value [KEY2=value2 ...]");const r=T(e),o=St(n),{config:i,client:s}=O(u),a=await U(s,i.path,r),E=a?.store??pu(),c=hu(E,o);if(!c.added.length&&!c.updated.length){F.info(`store "${r}" \u65E0\u9700\u6539\u52A8\uFF08${c.unchanged.length} \u4E2A\u53D8\u91CF\u503C\u76F8\u540C\uFF09`);return}await tu(s,i.path,r,E,`env: set ${Object.keys(o).join(", ")} in ${r}`,a?.version),F.success(`store "${r}" \u5DF2\u66F4\u65B0\uFF1A\u65B0\u589E ${c.added.length} \xB7 \u4FEE\u6539 ${c.updated.length}`);for(const l of c.added)F.info(` + ${l}`);for(const l of c.updated)F.info(` ~ ${l}`)}async function Ot(u,t){const[e]=t;if(!e)throw new Error("\u7528\u6CD5\uFF1Anpx initx env push <store \u540D> [--from .env.local] [--prune] [--force]");const n=T(e),r=u.options.string("from")??await jt(),o=await nu(r);if(o===null)throw new Error(`\u627E\u4E0D\u5230\u6587\u4EF6 "${r}"\uFF08\u7528 --from \u6307\u5B9A\uFF09`);const i=Object.fromEntries(Y(o).pairs);if(!Object.keys(i).length)throw new Error(`"${r}" \u91CC\u6CA1\u89E3\u6790\u51FA\u4EFB\u4F55 KEY=value`);const{config:s,client:a}=O(u),E=await U(a,s.path,n),c=E?.store??pu(),l=hu(c,i),h=[];if(u.options.boolean("prune"))for(const w of Object.keys(c.vars))w in i||(delete c.vars[w],h.push(w));if(!l.added.length&&!l.updated.length&&!h.length){F.info(`store "${n}" \u65E0\u9700\u6539\u52A8\uFF08${r} \u91CC\u7684\u503C\u90FD\u4E00\u6837\uFF09`);return}const p=`env: push ${n} from ${r}`;try{await tu(a,s.path,n,c,p,E?.version)}catch(w){if(!Ku(w)||!u.options.boolean("force"))throw w;const B=await ru(a,s.path,n);F.warn("\u8FDC\u7AEF\u5DF2\u66F4\u65B0\uFF0C--force\uFF1A\u4EE5\u672C\u5730\u6587\u4EF6\u4E3A\u51C6\u91CD\u65B0\u5199\u5165"),await tu(a,s.path,n,c,p,B.version)}F.success(`store "${n}" \u5DF2\u66F4\u65B0\uFF1A\u65B0\u589E ${l.added.length} \xB7 \u4FEE\u6539 ${l.updated.length}${h.length?` \xB7 \u5220\u9664 ${h.length}`:""}`)}async function jt(){for(const u of[".env.local",".env"])if(await nu(u)!==null)return u;throw new Error("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709 .env.local / .env\uFF08\u7528 --from \u6307\u5B9A\u6587\u4EF6\uFF09")}async function Gt(u,t){const{config:e,client:n}=O(u),r=await Cu(n,e.path,t[0]),o=(await ru(n,e.path,r)).store.vars;if(!Object.keys(o).length)throw new Error(`store "${r}" \u91CC\u6CA1\u6709\u4EFB\u4F55\u53D8\u91CF`);if(u.options.boolean("json")){console.log(JSON.stringify(o)),F.info(`\u5DF2\u8F93\u51FA ${Object.keys(o).length} \u4E2A\u53D8\u91CF\uFF08JSON\uFF09`);return}const i=$t(o,{forceStdout:u.options.boolean("stdout"),mask:!u.options.boolean("no-mask")});if(i.target==="github_env"){F.success(`\u5DF2\u6CE8\u5165\u672C\u6B21 job \u7684\u540E\u7EED\u6B65\u9AA4\uFF08${i.injected.length} \u4E2A\u53D8\u91CF\uFF0C${i.masked.length} \u4E2A\u5DF2\u8131\u654F\uFF09`);return}Du()||F.warn("\u5F53\u524D\u4E0D\u5728 GitHub Actions \u91CC\uFF0C\u53D8\u91CF\u6253\u5230 stdout\uFF08\u53EF\u4EE5 eval \u6216\u91CD\u5B9A\u5411\uFF09")}const yu=32;let ou;function Pt(){return ou||(ou=Fu.ready.then(()=>Fu)),ou}function Rt(u,t){let e;try{e=u.from_base64(t.trim(),u.base64_variants.ORIGINAL)}catch{throw new Error("GitHub \u516C\u94A5\u89E3\u7801\u5931\u8D25\uFF1A\u5B83\u4E0D\u662F\u5408\u6CD5\u7684 base64\uFF08ORIGINAL \u53D8\u4F53\uFF09\u5B57\u7B26\u4E32\uFF1B\u8BF7\u786E\u8BA4\u4F20\u5165\u7684\u662F public-key \u63A5\u53E3\u8FD4\u56DE\u7684 key \u5B57\u6BB5\uFF0C\u800C\u4E0D\u662F key_id")}if(e.length!==yu)throw new Error(`GitHub \u516C\u94A5\u957F\u5EA6\u4E0D\u6B63\u786E\uFF1AX25519 \u516C\u94A5\u5FC5\u987B\u662F ${yu} \u5B57\u8282\uFF0C\u5B9E\u9645 ${e.length} \u5B57\u8282\u3002 \u8FD9\u79CD\u60C5\u51B5\u901A\u5E38\u662F key_id \u4E0E key \u4E24\u4E2A\u5B57\u6BB5\u5F04\u53CD\u4E86\uFF08\u6216\u628A key_id \u5F53\u6210\u4E86\u516C\u94A5\uFF09\uFF1B\u8BF7\u6539\u7528 public-key \u63A5\u53E3\u8FD4\u56DE\u7684 key \u5B57\u6BB5`);return e}async function Vt(u,t){const e=await Pt(),n=Rt(e,u),r=e.crypto_box_seal(e.from_string(t),n);return e.to_base64(r,e.base64_variants.ORIGINAL)}async function mu(u,t,e={}){const n=await C(u,{...e,headers:{...lu(t),...e.headers??{}}});return v(n),n}function vu(u,t){const[e,n]=u.split("/",2),r=`https://api.github.com/repos/${encodeURIComponent(e)}/${encodeURIComponent(n)}`;return t?`${r}/environments/${encodeURIComponent(t)}/secrets`:`${r}/actions/secrets`}async function Lt(u,t,e){const n=await mu(`${vu(u,e)}/public-key`,t),r=await N(n);if(!r.key_id||!r.key)throw new Error(`\u62FF\u4E0D\u5230 ${u} \u7684\u516C\u94A5\uFF08\u8FD4\u56DE\u4F53\u91CC\u6CA1\u6709 key_id/key\uFF09`);return{keyId:r.key_id,key:r.key}}async function Ht(u,t,e,n,r={}){const{keyId:o,key:i}=await Lt(u,t,r.env),s=await Vt(i,n);return(await mu(`${vu(u,r.env)}/${encodeURIComponent(e)}`,t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({encrypted_value:s,key_id:o})})).status===201?"created":"updated"}function Kt(u,t,e=f.env){const n=t||e.INITX_ENV_TARGET_TOKEN||(u.type==="github"&&u.apiBaseUrl===$.github?u.token:"");if(!n)throw new Error("GitHub Actions \u76EE\u6807\u9700\u8981\u72EC\u7ACB\u51ED\u636E\uFF1A--target-token \u6216 INITX_ENV_TARGET_TOKEN");return{write(r,o,i,s){return x("github",r),Ht(r,n,o,i,{env:s})}}}const Xt=48*1024;async function Jt(u,t){const[e]=t;if(!e)throw new Error("\u7528\u6CD5\uFF1Anpx initx env apply <store \u540D> --repo <owner/repo> [--env <\u73AF\u5883\u540D>] [--only A,B] [--dry-run]");const n=T(e),r=K(u.store,{token:u.options.string("token"),config:P(u.options)}),o=X(r),i=Kt(r,u.options.string("target-token")),s=await U(o,r.path,n);if(!s)throw new Error(`\u4ED3\u5E93\u91CC\u6CA1\u6709 store "${n}"\u3002\u7528 npx initx env list \u770B\u770B\u6709\u54EA\u4E9B`);const a=s.store.targets?.secrets?.[0],E=u.options.string("repo")??a?.repo;if(!E)throw new Error("\u8981\u6307\u5B9A\u5199\u5165\u76EE\u6807\uFF1A--repo <owner/repo>\uFF0C\u6216\u5728 store \u91CC\u5199 targets.secrets");x("github",E);const c=u.options.string("env")??a?.env??null,l=u.options.string("only")?.split(",").map(g=>g.trim()).filter(Boolean),h=Object.entries(s.store.vars).filter(([g])=>!l||l.includes(g));if(!h.length)throw new Error(l?.length?`--only \u91CC\u6CA1\u6709\u4EFB\u4F55\u53D8\u91CF\u547D\u4E2D store "${n}"`:`store "${n}" \u91CC\u6CA1\u6709\u53D8\u91CF`);const p=[];for(const[g,_]of h){const m=tt(g);if(m){p.push(`${g} \u2014 \u540D\u5B57\u4E0D\u5408\u6CD5\uFF1A${m}`);continue}I.byteLength(_,"utf8")>Xt&&p.push(`${g} \u2014 \u503C\u8D85\u8FC7 48 KB \u4E0A\u9650`)}if(p.length){F.error("\u8FD9\u4E9B\u53D8\u91CF\u4E0D\u80FD\u5199\u8FDB Actions secrets\uFF1A");for(const g of p)F.error(` ${g}`);if(!u.options.boolean("skip-invalid"))throw new Error(`\u6709 ${p.length} \u4E2A\u53D8\u91CF\u4E0D\u5408\u6CD5\uFF0C\u5DF2\u4E2D\u6B62\uFF08\u6539\u540D\u540E\u7528 --only \u6392\u9664\uFF0C\u6216\u52A0 --skip-invalid \u8DF3\u8FC7\uFF09`);F.warn("--skip-invalid\uFF1A\u8DF3\u8FC7\u8FD9\u4E9B\u53D8\u91CF")}const w=new Set(p.map(g=>g.split(" \u2014 ")[0])),B=h.filter(([g])=>!w.has(g));if(u.options.boolean("dry-run")){F.info(`--dry-run\uFF1A\u5C06\u5199\u5165 ${B.length} \u4E2A secret \u2192 ${E}${c?`\uFF08\u73AF\u5883 ${c}\uFF09`:"\uFF08\u4ED3\u5E93\u7EA7\uFF09"}`);for(const[g]of B)F.info(` ${g}`);return}F.info(`\u5199\u5165 ${E}${c?`\uFF08\u73AF\u5883 ${c}\uFF09`:"\uFF08\u4ED3\u5E93\u7EA7\uFF09"} \u2026`);const y=[];for(const[g,_]of B)try{const m=await i.write(E,g,_,c);F.success(`${g}: ${m==="created"?"\u65B0\u5EFA":"\u66F4\u65B0"}`)}catch(m){y.push(g),F.error(`${g}: \u5931\u8D25 \u2014 ${m.message}`)}if(y.length)throw new Error(`${y.length} \u4E2A secret \u5199\u5165\u5931\u8D25\uFF1A${y.join(", ")}`);F.success(`\u5B8C\u6210\uFF1A${B.length} \u4E2A secret \u2192 ${E}`),c&&F.info("\u73AF\u5883\u7EA7 secret \u9700\u8981\u8BE5 environment \u5DF2\u5B58\u5728\uFF0C\u4E14 job \u5F15\u7528\u5B83\u65F6\u624D\u53EF\u7528")}const j=["type","apiBaseUrl","repo","token","branch","path"],$u="token \u4F1A\u4EE5**\u660E\u6587**\u5B58\u5728 ~/.initx/stores/@initx-plugin/env/store.json\uFF1B\u4ECB\u610F\u7684\u8BDD\u6539\u7528\u73AF\u5883\u53D8\u91CF INITX_ENV_TOKEN\uFF0C\u6216\u6BCF\u6B21\u7528 --token \u4F20\u3002";function iu(u){return j.includes(u)}async function Yt(u,t,e){const[n="status",...r]=t;switch(n){case"setup":return Wt(u);case"set":return G(u,r);case"get":return Mt(u,r);case"list":case"ls":return zt(u);case"rm":case"remove":return qt(u,r);case"status":return Zt(u,e);default:ku()}}async function Wt(u){if(!f.stdin.isTTY)throw new Error(`setup \u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u3002\u975E\u4EA4\u4E92\u73AF\u5883\u8BF7\u7528\uFF1A
|
|
26
23
|
npx initx env-config set repo <owner/repo>
|
|
27
|
-
npx initx env-config set token <pat>`);const e=await
|
|
24
|
+
npx initx env-config set token <pat>`);const t=S(u),e=await b.input(`vault \u4ED3\u5E93\uFF08${t.type}\uFF09`,{default:W(u,"repo"),required:!0});G(u,["repo",e]);const n=await b.password(`${t.type} token\uFF08\u56DE\u8F66\u4FDD\u7559\u5F53\u524D\u503C\uFF09`,{mask:"*"});n&&G(u,["token",n]);const r=await b.input("\u5206\u652F",{default:W(u,"branch")||V});G(u,["branch",r||V]);const o=await b.input("store \u76EE\u5F55",{default:W(u,"path")||L});G(u,["path",o||L]),F.success("\u672C\u5730 vault \u914D\u7F6E\u5DF2\u4FDD\u5B58"),F.warn($u)}function G(u,t){const[e,...n]=t;if(!e||!iu(e))throw new Error(`\u7528\u6CD5\uFF1Anpx initx env-config set <${j.join("|")}> <\u503C>`);const r=n.join(" ").trim();if(!r)throw new Error(`${e} \u7684\u503C\u4E0D\u80FD\u4E3A\u7A7A`);const o=S(u);if(e==="type"||e==="apiBaseUrl"){if(e==="type"&&r!=="github"&&r!=="gitlab")throw new Error("type \u5FC5\u987B\u662F github \u6216 gitlab");const i=e==="type"?r:o.type,s=e==="apiBaseUrl"?k(r):i===o.type?o.apiBaseUrl:$[i],a=o.type===i&&o.apiBaseUrl===s;u.storage={...a?u.storage||{repo:u.repo,branch:u.branch,path:u.path}:{},type:i,apiBaseUrl:s}}else e==="token"?u.storage?(u.credentials??={})[H(o.type,o.apiBaseUrl)]=r:u.token=r:(e==="repo"&&x(o.type,r),e==="path"&&D(r),u.storage?u.storage[e]=r:u[e]=r);if(e==="token"){F.success(`token \u5DF2\u4FDD\u5B58\uFF1A${Z(r)}`),F.warn($u);return}F.success(`${e} = ${r}`)}function Mt(u,t){const[e]=t;if(!e||!iu(e))throw new Error(`\u7528\u6CD5\uFF1Anpx initx env-config get <${j.join("|")}>`);F.info(`${e} = ${bu(u,e)}`)}function zt(u){for(const t of j)F.info(`${t}: ${bu(u,t)}`)}function qt(u,t){const[e]=t;if(!e||!iu(e))throw new Error(`\u7528\u6CD5\uFF1Anpx initx env-config rm <${j.join("|")}>`);if(e==="token"){const n=S(u);delete u.credentials?.[H(n.type,n.apiBaseUrl)],n.type==="github"&&n.apiBaseUrl===$.github&&delete u.token}else e==="type"||e==="apiBaseUrl"?delete u.storage:u.storage?delete u.storage[e]:delete u[e];F.success(`\u5DF2\u6E05\u9664 ${e}`)}async function Zt(u,t){const e=P(t);let n;try{n=K(u,{token:t.string("token"),config:e})}catch(r){if(r instanceof R)throw r;F.warn(r.message);return}if(F.info(`\u5B58\u50A8\u540E\u7AEF: ${n.type}`),F.info(`API \u5730\u5740: ${n.apiBaseUrl}`),F.info(`\u914D\u7F6E\u6765\u6E90: ${n.configSource}`),F.info(`\u5B57\u6BB5\u6765\u6E90: ${JSON.stringify(n.sources)}`),F.info(`vault \u4ED3\u5E93: ${n.repo}`),F.info(`\u5206\u652F: ${n.branch}`),F.info(`store \u76EE\u5F55: ${n.path}`),F.info(`token: ${Z(n.token)}\uFF08\u6765\u81EA ${n.tokenSource}\uFF09`),t.boolean("offline")){F.info("\uFF08--offline\uFF1A\u8DF3\u8FC7\u8FDE\u901A\u6027\u68C0\u67E5\uFF09");return}try{const r=X(n),o=await uu(r,n.path);F.success(`\u8FDE\u901A\u6B63\u5E38\uFF0C${n.path}/ \u4E0B\u6709 ${o.length} \u4E2A store`)}catch(r){F.error(`\u8FDE\u4E0D\u4E0A\u6216\u6CA1\u6743\u9650\uFF1A${r.message}`),F.warn("\u68C0\u67E5\uFF1A\u4ED3\u5E93\u3001ref\u3001\u5F53\u524D\u540E\u7AEF token \u7684\u8BFB\u53D6\u6743\u9650\u4E0E API \u5730\u5740")}}function W(u,t){const e=S(u);return t==="type"||t==="apiBaseUrl"?e[t]:t==="token"?u.credentials?.[H(e.type,e.apiBaseUrl)]||(e.type==="github"&&e.apiBaseUrl===$.github?u.token:void 0):u.storage?u.storage[t]:u[t]}function bu(u,t){const e=W(u,t);return t==="token"?Z(e||""):e??"(\u672A\u8BBE\u7F6E)"}function Qt(){ku()}function ku(){console.log(`
|
|
28
25
|
\u7528\u6CD5\uFF1Anpx initx env-config <\u547D\u4EE4>
|
|
29
26
|
|
|
30
|
-
setup \u4EA4\u4E92\u5F0F\u914D\u7F6E\uFF08repo / token / branch / path\uFF09
|
|
31
|
-
set <key> <value> \u8BBE\u7F6E\u5355\u9879\uFF0Ckey \u53D6 repo | token | branch | path
|
|
27
|
+
setup \u4EA4\u4E92\u5F0F\u914D\u7F6E\u5F53\u524D\u672C\u5730\u540E\u7AEF\uFF08repo / token / branch / path\uFF09
|
|
28
|
+
set <key> <value> \u8BBE\u7F6E\u5355\u9879\uFF0Ckey \u53D6 type | apiBaseUrl | repo | token | branch | path
|
|
32
29
|
get <key> \u67E5\u770B\u6301\u4E45\u5316\u5355\u9879\uFF08token \u4F1A\u8131\u654F\uFF09
|
|
33
30
|
list \u67E5\u770B\u5168\u90E8\u6301\u4E45\u5316\u914D\u7F6E\uFF08token \u4F1A\u8131\u654F\uFF09
|
|
34
31
|
rm <key> \u6E05\u9664\u5355\u9879
|
|
35
|
-
status [--offline]
|
|
32
|
+
status [--config <file>] [--offline] \u67E5\u770B\u5B9E\u9645\u751F\u6548\u914D\u7F6E\u3001\u6765\u6E90 + \u8FDE\u901A\u6027\u68C0\u67E5
|
|
36
33
|
|
|
37
|
-
\u89E3\u6790\u4F18\u5148\u7EA7\uFF1A\u663E\u5F0F override >
|
|
38
|
-
Token\uFF1A--token > INITX_ENV_TOKEN > GH_TOKEN
|
|
34
|
+
\u89E3\u6790\u4F18\u5148\u7EA7\uFF1A\u663E\u5F0F override > INITX_ENV_* > \u9879\u76EE .initx-env.json > \u540C\u540E\u7AEF/API \u7684\u6301\u4E45\u5316\u914D\u7F6E > \u9ED8\u8BA4\u503C\u3002
|
|
35
|
+
Token\uFF1A--token > INITX_ENV_TOKEN > GH_TOKEN / GITHUB_TOKEN\uFF08\u4EC5 GitHub \u9ED8\u8BA4 API\uFF09> \u5F53\u524D API \u7ED1\u5B9A\u7684\u6301\u4E45\u5316\u51ED\u636E\u3002
|
|
36
|
+
get/list/set/rm \u4EC5\u64CD\u4F5C\u672C\u5730\u914D\u7F6E\uFF1B\u5207\u6362 type / apiBaseUrl \u4F1A\u6E05\u9664\u65E7\u76EE\u6807\u5B9A\u4F4D\uFF0C\u51ED\u636E\u4ECD\u7ED1\u5B9A\u539F API\u3002
|
|
37
|
+
\u9879\u76EE\u6587\u4EF6 schema 1\uFF0C\u4EC5\u53EF\u5B58 storage.type/apiBaseUrl/repo/branch/path\uFF0C\u7981\u6B62 token \u4E0E\u4E1A\u52A1\u53D8\u91CF\u3002
|
|
39
38
|
|
|
40
39
|
\u793A\u4F8B\uFF1A
|
|
41
40
|
npx initx env-config set repo imba97/env-vault
|
|
42
41
|
npx initx env-config set token github_pat_xxx
|
|
43
42
|
npx initx env-config set path stores
|
|
44
43
|
npx initx env-config status
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
npx initx env-config set type gitlab
|
|
45
|
+
npx initx env-config set apiBaseUrl https://gitlab.example.com/api/v4
|
|
46
|
+
npx initx env-config set repo group/subgroup/vault
|
|
47
|
+
`)}function xu(u){return 128+(Pu.signals[u]??1)}function ue(u,t,e){return new Promise(n=>{let r,o,i,s=!1;const a=p=>{const w=p.code;return{code:w==="ENOENT"?127:w==="EACCES"?126:1,errorCode:w??"UNKNOWN"}},E=p=>{s||(s=!0,clearTimeout(o),f.removeListener("SIGINT",l),f.removeListener("SIGTERM",h),n(p))},c=p=>{if(!(s||i||r.exitCode!==null||r.signalCode!==null)){i=p;try{r.kill(p)}catch{}o=setTimeout(()=>{try{r.kill("SIGKILL")}catch{}finally{r.unref(),E({code:xu(p),signal:p})}},5e3)}};function l(){c("SIGINT")}function h(){c("SIGTERM")}try{r=Ru(u,t,{env:e,stdio:"inherit",shell:!1})}catch(p){E(a(p));return}f.on("SIGINT",l),f.on("SIGTERM",h),r.once("error",p=>E(a(p))),r.once("close",(p,w)=>{const B=i??w??void 0;E(B?{code:xu(B),signal:B}:{code:p??1})})})}function te(u){if(f.exitCode=u.code,u.signal&&f.platform!=="win32"){const t=u.signal;f.once("beforeExit",()=>{try{f.kill(f.pid,t)}catch{f.exitCode=u.code}})}}const Tu="\u7528\u6CD5\uFF1Ainitx env run <store> [--config <file>] [--token <token>] -- <command> [...args]";function ee(u,t){if(u.length!==1||!u[0])throw new Error(Tu);const e=T(u[0]);if(!Array.isArray(t)||!t.every(n=>typeof n=="string")||!t[0]?.trim())throw new Error(Tu);if(t.some(n=>n.includes("\0")))throw new Error("\u547D\u4EE4\u6216\u53C2\u6570\u4E0D\u80FD\u5305\u542B NUL");return{name:e,command:t[0],args:t.slice(1)}}function ne(u,t=f.env,e=f.platform){const n={...t},r=new Set;for(const[o,i]of Object.entries(u)){if(!/^[A-Z_]\w*$/i.test(o))throw new Error(`\u53D8\u91CF\u540D ${JSON.stringify(o)} \u4E0D\u5408\u6CD5\uFF1A\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\uFF0C\u4E0D\u80FD\u4EE5\u6570\u5B57\u5F00\u5934`);if(typeof i!="string"||i.includes("\0"))throw new Error(`\u53D8\u91CF ${JSON.stringify(o)} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u4E14\u4E0D\u80FD\u5305\u542B NUL`);const s=e==="win32"?o.toUpperCase():o;if(r.has(s))throw new Error(`\u53D8\u91CF ${JSON.stringify(o)} \u5728 Windows \u4E0A\u5B58\u5728\u5927\u5C0F\u5199\u51B2\u7A81`);r.add(s)}if(e==="win32"){const o=new Set;for(const i of Object.keys(n)){const s=i.toUpperCase();r.has(s)||o.has(s)?delete n[i]:o.add(s)}}return{...n,...u}}async function re(u,t){let e="\u53C2\u6570";try{const n=ee(t,u.options.raw["--"]),r=new Set(["--","token","config"]);if(Object.keys(u.options.raw).some(h=>!r.has(h)))throw new Error("run \u53EA\u652F\u6301 --token / --config\uFF1B\u5B50\u8FDB\u7A0B\u53C2\u6570\u5FC5\u987B\u653E\u5728 -- \u540E");const o=u.options.raw.token;if(o!==void 0&&typeof o!="string"&&typeof o!="number")throw new Error("--token \u9700\u8981\u5355\u4E2A\u503C");const i=u.options.raw.config;if(i!==void 0&&(typeof i!="string"||!i))throw new Error("--config \u9700\u8981\u5355\u4E2A\u6587\u4EF6\u8DEF\u5F84");e="\u914D\u7F6E";const s=K(u.store,{token:u.options.string("token"),config:P(u.options)});e="\u8BFB\u53D6 store";const a=X(s),E=await U(a,s.path,n.name);if(!E)throw new Error("store \u4E0D\u5B58\u5728");e="\u53D8\u91CF\u6821\u9A8C";const c=ne(E.store.vars);e="\u542F\u52A8\u5B50\u8FDB\u7A0B";const l=await ue(n.command,n.args,c);l.errorCode&&f.stderr.write(`env run: \u542F\u52A8\u5931\u8D25\uFF08${l.errorCode}\uFF09
|
|
48
|
+
`),te(l)}catch(n){const r=e==="\u53D8\u91CF\u6821\u9A8C"||e==="\u53C2\u6570"?`\uFF1A${n.message}`:"";f.stderr.write(`env run: ${e}\u5931\u8D25${r}
|
|
49
|
+
`),f.exitCode=1}}const oe=["list","ls","pull","set","push","setup","apply","run","help"],ie=["setup","set","get","list","ls","rm","remove","status","help"];class se extends Nu{defaultStore={branch:V,path:L};rules=[{matching:"env",description:"Env vars from a private repo: env list | pull | set | push | setup | apply | run",optional:[void 0,...oe]},{matching:"env-config",description:"Configure GitHub/GitLab vault storage and check connectivity",optional:[void 0,...ie]}];async handle(t,...e){const n=e.filter(a=>typeof a=="string"),r=Lu(t.cliOptions,t.optionsList),o=t.store??{};if(t.key==="env-config"){if(n[0]==="help"||r.boolean("help")){Qt();return}await Yt(o,n,r);return}if(n[0]==="run"&&!r.boolean("help")&&!r.boolean("h")){await re({store:o,options:r},n.slice(1));return}if(!n.length||n[0]==="help"||r.boolean("help")||r.boolean("h")){ae();return}const[i,...s]=n;if(i==="apply"){await Jt({store:o,options:r},s);return}await xt({store:o,options:r},[i,...s])}}function ae(){console.log(`
|
|
48
50
|
\u7528\u6CD5\uFF1Anpx initx env <\u547D\u4EE4>
|
|
49
51
|
|
|
50
52
|
list \u5217\u51FA vault \u4ED3\u5E93\u91CC\u6240\u6709 store
|
|
@@ -60,6 +62,7 @@ Token\uFF1A--token > INITX_ENV_TOKEN > GH_TOKEN > GITHUB_TOKEN > \u6301\u4E45\u5
|
|
|
60
62
|
GitHub Actions \u91CC\u5199 $GITHUB_ENV\uFF08\u540E\u7EED step \u53EF\u89C1\uFF09\u5E76\u81EA\u52A8\u8131\u654F
|
|
61
63
|
\u672C\u5730\u5219\u6253\u5230 stdout\uFF08--stdout \u5F3A\u5236\uFF1B--json \u8F93\u51FA JSON\uFF09
|
|
62
64
|
apply <store> --repo o/r \u628A store \u5199\u8FDB\u67D0\u4E2A\u4ED3\u5E93\u7684 Actions secrets
|
|
65
|
+
--target-token <pat> \u72EC\u7ACB GitHub \u76EE\u6807\u51ED\u636E
|
|
63
66
|
--env <\u73AF\u5883\u540D> \u5199\u73AF\u5883\u7EA7 secret
|
|
64
67
|
--only A,B \u53EA\u5199\u8FD9\u51E0\u4E2A
|
|
65
68
|
--dry-run \u53EA\u5217\u51FA\u5C06\u5199\u5165\u7684\u540D\u5B57
|
|
@@ -67,6 +70,11 @@ Token\uFF1A--token > INITX_ENV_TOKEN > GH_TOKEN > GITHUB_TOKEN > \u6301\u4E45\u5
|
|
|
67
70
|
run <store> -- cmd [args] \u76F4\u63A5\u6CE8\u5165\u5B50\u8FDB\u7A0B\uFF0C\u7EE7\u627F stdio / cwd\uFF0C\u4F20\u9012\u9000\u51FA\u72B6\u6001
|
|
68
71
|
--token <pat> \u4E34\u65F6\u51ED\u636E\uFF08CI \u63A8\u8350 INITX_ENV_TOKEN\uFF09
|
|
69
72
|
|
|
73
|
+
\u5168\u90E8\u5B58\u50A8\u547D\u4EE4\u652F\u6301 --config <file>\uFF08\u76F8\u5BF9\u8C03\u7528\u8005 cwd\uFF1B\u9ED8\u8BA4\u53EA\u8BFB cwd \u7684 .initx-env.json\uFF09\u3002
|
|
74
|
+
run \u7684 --config / --token \u653E\u5728 -- \u524D\uFF1B\u4E4B\u540E\u7684\u5168\u90E8\u53C2\u6570\u539F\u6837\u900F\u4F20\u5B50\u8FDB\u7A0B\u3002
|
|
75
|
+
\u652F\u6301 GitHub / GitLab vault\uFF1Bapply \u7684 --repo \u59CB\u7EC8\u662F GitHub Actions \u76EE\u6807\u3002
|
|
76
|
+
\u8DE8\u5E73\u53F0 apply \u5FC5\u987B\u8BBE\u7F6E --target-token \u6216 INITX_ENV_TARGET_TOKEN\u3002
|
|
77
|
+
|
|
70
78
|
\u914D\u7F6E vault \u4ED3\u5E93\u7528\uFF1Anpx initx env-config --help
|
|
71
79
|
|
|
72
80
|
\u793A\u4F8B\uFF1A
|
|
@@ -75,4 +83,4 @@ Token\uFF1A--token > INITX_ENV_TOKEN > GH_TOKEN > GITHUB_TOKEN > \u6301\u4E45\u5
|
|
|
75
83
|
npx initx env pull mail-peon-publish
|
|
76
84
|
npx initx env setup mail-peon-publish --token "$GITHUB_TOKEN"
|
|
77
85
|
npx initx env apply mail-peon-publish --repo mail-peon/mail-peon
|
|
78
|
-
`)}export{
|
|
86
|
+
`)}export{se as default};
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@initx-plugin/env",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"
|
|
6
|
-
"description": "initx plugin to manage env vars and secrets from one private GitHub repo",
|
|
4
|
+
"version": "0.1.3",
|
|
5
|
+
"description": "initx plugin to manage env vars and secrets from private GitHub or GitLab repositories",
|
|
7
6
|
"author": "imba97",
|
|
8
7
|
"license": "MIT",
|
|
9
8
|
"homepage": "https://github.com/initx-collective/initx-plugin-env#readme",
|
|
@@ -28,15 +27,6 @@
|
|
|
28
27
|
"files": [
|
|
29
28
|
"dist"
|
|
30
29
|
],
|
|
31
|
-
"scripts": {
|
|
32
|
-
"stub": "unbuild --stub",
|
|
33
|
-
"build": "unbuild",
|
|
34
|
-
"release": "bumpp",
|
|
35
|
-
"lint": "eslint --cache",
|
|
36
|
-
"typecheck": "tsc --noEmit",
|
|
37
|
-
"test": "vitest run",
|
|
38
|
-
"prepare": "simple-git-hooks"
|
|
39
|
-
},
|
|
40
30
|
"peerDependencies": {
|
|
41
31
|
"@initx-plugin/core": "*",
|
|
42
32
|
"@initx-plugin/utils": "*"
|
|
@@ -66,5 +56,13 @@
|
|
|
66
56
|
},
|
|
67
57
|
"nano-staged": {
|
|
68
58
|
"*": "eslint --cache --fix"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"stub": "unbuild --stub",
|
|
62
|
+
"build": "unbuild",
|
|
63
|
+
"release": "bumpp",
|
|
64
|
+
"lint": "eslint --cache",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"test": "vitest run"
|
|
69
67
|
}
|
|
70
|
-
}
|
|
68
|
+
}
|