@initx-plugin/env 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +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.
package/README.md CHANGED
@@ -1,3 +1,235 @@
1
- # Temporary Holding Version
1
+ ## @initx-plugin/env
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `initx` plugin to manage env vars and secrets from one private GitHub repo.
4
+
5
+ One private repository holds **one JSON file per store** (`stores/<name>.json`). Pull a store into a
6
+ local `.env`, inject it into a child process or CI job, or write it into GitHub Actions secrets.
7
+
8
+ ```bash
9
+ initx plugin add env
10
+ ```
11
+
12
+ ## Why another secrets tool
13
+
14
+ Almost every existing tool (dotenvx, sops, 1Password, Doppler, Infisical, Vault) **injects** secrets
15
+ into a process. None of them writes values back into GitHub's secret storage — that is the gap this
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.
21
+
22
+ ## Setup
23
+
24
+ ```bash
25
+ npm i -g initx # optional, otherwise use npx
26
+ initx plugin add env
27
+
28
+ initx env-config set repo <owner/repo> # the private repo that holds your stores
29
+ initx env-config set token <pat> # classic PAT with the `repo` scope is enough
30
+ initx env-config set path stores # optional, default: stores
31
+ initx env-config status # prints the config (token masked) + checks connectivity
32
+ ```
33
+
34
+ The config lives in `~/.initx/stores/@initx-plugin/env/store.json`. The token is stored **in
35
+ plaintext** there (same trade-off as `@initx-plugin/cp`) — set `INITX_ENV_TOKEN` instead if you would
36
+ rather keep it out of a file.
37
+
38
+ ## Commands
39
+
40
+ | Command | What it does |
41
+ | --- | --- |
42
+ | `initx env list` | List the stores in the vault repo |
43
+ | `initx env pull [store]` | Pull a store into a local `.env` (asks which store / which file when ambiguous) |
44
+ | `initx env set <store> K=V ...` | Write variables into a store (the store name **is** the file name) |
45
+ | `initx env push <store>` | Update a store from a local `.env` |
46
+ | `initx env setup [store]` | Inject a store into **this** environment (CI job or shell) |
47
+ | `initx env run <store> -- <command> [...args]` | Inject a store directly into one child process and return its exit status |
48
+ | `initx env apply <store> --repo o/r` | Write a store into a repo's GitHub Actions secrets |
49
+
50
+ ```bash
51
+ # write / update a store
52
+ initx env set mail-peon-publish EXTENSION_ID=abc CHROME_REFRESH_TOKEN=1//0g...
53
+
54
+ # pull it into a local .env (keeps your other keys, comments and order)
55
+ initx env pull mail-peon-publish
56
+ initx env pull mail-peon-publish --to .env.local --dry-run
57
+
58
+ # push local changes back
59
+ initx env push mail-peon-publish --from .env.local
60
+ initx env push mail-peon-publish --prune # also drop remote keys missing locally
61
+
62
+ # run a script with the store's original values (including real newlines)
63
+ initx env run mail-peon-publish -- node scripts/publish.mjs
64
+ ```
65
+
66
+ ## Run a command locally or in CI
67
+
68
+ ```bash
69
+ initx env run demo -- node scripts/check.mjs
70
+ initx env run demo -- node --version
71
+ initx env run acewill-slidev-production -- pnpm deploy
72
+ initx env run demo -- bash scripts/deploy.sh
73
+ ```
74
+
75
+ The store and the first `--` are required. Put the plugin's optional `--token <token>` before `--`;
76
+ every argument after it belongs to the child, including `--help`, `--version`, `--token`, another
77
+ `--`, empty strings and arguments with spaces. Extra positional arguments and other plugin flags
78
+ (including `--repo`, `--json`, `--stdout`, `--only`, `--shell`, `--cwd`) are rejected before reading
79
+ the vault. Help before `--` is handled by initx; help after it is passed to the child.
80
+
81
+ The child inherits the caller's working directory, stdin, stdout, stderr and environment. Store
82
+ variables override inherited values without changing the calling shell. Empty values, CRLF, real
83
+ newlines, Unicode and punctuation are preserved; values are never expanded. Empty stores are valid.
84
+ Variable names must match `^[A-Za-z_][A-Za-z0-9_]*$`, and values cannot contain NUL. All values are
85
+ validated before starting the child. Windows names are case insensitive: a store's `PATH` replaces
86
+ an inherited `Path`, and conflicting store names such as `FOO` / `foo` abort. Other commands retain
87
+ their existing name validation rules.
88
+
89
+ Arguments are passed as an array. The plugin does not interpret shell expressions, pipelines or
90
+ redirection. Explicitly run a shell or, preferably, write a script for multiple operations. One
91
+ `run` affects only that child and its descendants; the next independent CI command does not inherit
92
+ the fetched store. `cross-spawn` is the runtime dependency used to resolve executables and escape
93
+ Windows npm/pnpm `.cmd` launchers without enabling `shell: true` for every command
94
+ ([upstream behavior](https://github.com/moxystudio/node-cross-spawn#why)).
95
+
96
+ Normal exits preserve the child's code, including nonzero codes. Missing commands return 127,
97
+ permission failures return 126, and parameter/configuration/read/validation/other startup failures
98
+ return 1. On POSIX, signal termination is reproduced after initx has finished its store cleanup,
99
+ with a nonzero `128 + signal number` fallback. Parent SIGINT/SIGTERM events are forwarded once to
100
+ the direct child, with a 5-second grace period followed by best-effort SIGKILL. Listeners are removed
101
+ when execution finishes. Background processes detached from the direct child are outside this
102
+ contract; deployment scripts should stay in the foreground.
103
+
104
+ On Windows, Node's process termination APIs do not deliver catchable POSIX signals: forwarding
105
+ SIGINT/SIGTERM can terminate the direct child immediately, and the wrapper returns 130/143. A
106
+ console Ctrl+C event received by Node uses the same forwarding handler. `.cmd` launchers can have
107
+ descendant processes; this version does not guarantee termination of that whole tree. Windows
108
+ tests exercise the real direct-child termination and listener cleanup by triggering the parent's
109
+ signal event over IPC; interactive console Ctrl+C still needs a manual console check.
110
+
111
+ `run` writes no `.env`, `$GITHUB_ENV`, dotenv artifact or secret cache, and emits no GitHub masking
112
+ protocol. Its own diagnostics go to stderr without values, tokens, complete response bodies or
113
+ command argument lists. **Child output is inherited without masking**: a script can still print
114
+ secrets. The parent environment, including inherited GitHub tokens, is retained. This is process
115
+ injection for trusted scripts and stores, not an isolation sandbox or encrypted secret manager.
116
+ The stdout form of `setup` remains intended for inspection/legacy use; use `run` for exact values.
117
+
118
+ ## Configuration precedence
119
+
120
+ | Setting | Highest to lowest priority |
121
+ | --- | --- |
122
+ | Token | Explicit override / `--token` → `INITX_ENV_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` → persisted token |
123
+ | Vault repo | Explicit override → `INITX_ENV_REPO` → persisted `repo` |
124
+ | Branch | Explicit override → `INITX_ENV_BRANCH` → persisted `branch` → `main` |
125
+ | Store path | Explicit override → `INITX_ENV_PATH` → persisted `path` → `stores` |
126
+
127
+ Empty strings fall back to the next source. Environment settings affect the current invocation
128
+ only and are never written back to the plugin store. `env-config status --offline` shows the
129
+ effective settings with a masked token; `get` / `list` show the persisted settings. `env apply
130
+ --repo o/r` still selects the destination Actions repository, not the vault. `run` has no `--repo`.
131
+
132
+ ## Use in GitLab CI
133
+
134
+ Preinstall fixed versions of initx and this plugin in the CI image, or prepare them with an explicit
135
+ noninteractive installation step. Do not rely on a first-run plugin installation prompt. This
136
+ implementation is smoke-tested with **initx 0.4.3**; compatible versions must expose the untouched
137
+ tail as `ctx.cliOptions['--']: string[]`, keep child flags out of prefix help/version handling, and
138
+ preserve `process.exitCode` after handlers finish. Older versions have not been verified.
139
+
140
+ Set a protected, masked `INITX_ENV_TOKEN` in GitLab with **Contents: read** access to the vault only.
141
+ Repository, branch and path can be ordinary YAML variables:
142
+
143
+ ```yaml
144
+ deploy:
145
+ stage: deploy
146
+ variables:
147
+ INITX_ENV_REPO: example/private-env-vault
148
+ INITX_ENV_BRANCH: main
149
+ INITX_ENV_PATH: stores
150
+ script:
151
+ - initx env run acewill-slidev-production -- pnpm deploy
152
+ ```
153
+
154
+ This illustrates the integration point, not a complete pipeline. Keep deployment variables out of
155
+ the build job. Let `pnpm deploy` orchestrate flash-dns, uploads, release switching and verification;
156
+ the plugin does not hard-code their variable names or perform deployment operations. Scripts may
157
+ materialize multiline SSH keys as needed, set file permissions and clean them up; `run` does not
158
+ create key files or pass them through artifacts/cache.
159
+
160
+ Fetched variables are **not automatically registered as GitLab masked variables**. Disable command
161
+ tracing that could print them. Vault Contents permissions apply to the entire repository, not to a
162
+ store filename: use separate repositories for separate trust boundaries. The private JSON store and
163
+ its Git history remain plaintext.
164
+
165
+ ## Use in GitHub Actions
166
+
167
+ The built-in `GITHUB_TOKEN` can only read **the repository the workflow runs in**, so a separate vault
168
+ repo needs its own token (one secret):
169
+
170
+ ```yaml
171
+ - run: npx --yes initx plugin add env
172
+
173
+ # inject into this job: every later step sees $EXTENSION_ID / $CHROME_REFRESH_TOKEN
174
+ - run: npx --yes initx env setup mail-peon-publish --token "${{ secrets.ENV_VAULT_TOKEN }}"
175
+
176
+ # or write them into the repo's Actions secrets once (needs secrets write permission)
177
+ - run: npx --yes initx env apply mail-peon-publish --repo "${{ github.repository }}"
178
+ env:
179
+ INITX_ENV_TOKEN: ${{ secrets.ENV_VAULT_TOKEN }}
180
+ ```
181
+
182
+ `env setup` writes to `$GITHUB_ENV` and emits `::add-mask::` for every value before it does, so the
183
+ values do not show up in logs. Values with newlines use the heredoc (`KEY<<EOF`) form.
184
+
185
+ ## Store format
186
+
187
+ ```jsonc
188
+ // stores/mail-peon-publish.json
189
+ {
190
+ "schema": 1,
191
+ "vars": {
192
+ "EXTENSION_ID": "abcdefghijklmnopabcdefghijklmnop",
193
+ "CHROME_REFRESH_TOKEN": "1//0g..."
194
+ },
195
+ "targets": { // optional routing hints
196
+ "envFiles": [".env.local"], // pull: skip the file picker
197
+ "secrets": [{ "repo": "mail-peon/mail-peon", "env": null }] // apply: default destination
198
+ }
199
+ }
200
+ ```
201
+
202
+ ## Good to know
203
+
204
+ - **Values are stored as plaintext JSON in v1.** The repo is private, but anything committed stays in
205
+ git history forever — rotate the values if that repo ever becomes public. Encryption is planned and
206
+ will be orthogonal to the command surface (dotenvx / sops + age).
207
+ - **No local cache.** Values are fetched on every invocation. `pull` writes its target `.env` file
208
+ and backup; `setup` may write `$GITHUB_ENV`; `run` does not persist fetched values.
209
+ - **`apply` constraints** (GitHub's, not ours): secret values are write-only; a single secret is
210
+ capped at 48 KB; names may only contain `[A-Za-z0-9_]`, cannot start with a digit or `GITHUB_`, and
211
+ are stored uppercased. Illegal names abort the command unless you pass `--skip-invalid`.
212
+ - **Token permissions**: read/write **Contents** on the vault repo, plus **Secrets: write** on the
213
+ target repo for `apply`. A classic PAT with `repo` covers both; organisation-level secrets would
214
+ additionally need `admin:org` (not supported in v1).
215
+ - **Output is masked**: tokens print as `***`, values are never printed by `list` / `get` / `status`.
216
+
217
+ ## Development
218
+
219
+ ```bash
220
+ pnpm install
221
+ pnpm stub # unbuild --stub, then: initx plugin add .
222
+ pnpm test # vitest
223
+ pnpm build
224
+ pnpm lint
225
+ pnpm typecheck
226
+ ```
227
+
228
+ `pnpm test` runs unit tests and isolated child-process fixtures with fake GitHub responses and
229
+ temporary directories. It does not install or invoke the initx CLI. No real vault, DNS, SSH or
230
+ GitHub write endpoint is used. The Linux/Windows CI matrix runs the same checks. Platform-specific
231
+ coverage and the local verification record are in [env run implementation](docs/env-run-implementation.md).
232
+
233
+ ## License
234
+
235
+ MIT
@@ -0,0 +1,21 @@
1
+ import { InitxPlugin, InitxMatcherRules, InitxContext } from '@initx-plugin/core';
2
+
3
+ /** 插件自己的持久化配置(`src/index.ts` 的 defaultStore 就是它) */
4
+ interface EnvPluginStore {
5
+ repo?: string;
6
+ token?: string;
7
+ branch?: string;
8
+ path?: string;
9
+ }
10
+
11
+ declare class EnvPlugin extends InitxPlugin<EnvPluginStore> {
12
+ /**
13
+ * 插件自己的配置(vault 仓库地址 + token)。
14
+ * initx 会把它存到 `~/.initx/stores/@initx-plugin/env/store.json`,退出时自动落盘。
15
+ */
16
+ defaultStore: EnvPluginStore;
17
+ rules: InitxMatcherRules;
18
+ handle(ctx: InitxContext<EnvPluginStore>, ...args: (string | undefined)[]): Promise<void>;
19
+ }
20
+
21
+ export { EnvPlugin as default };
@@ -0,0 +1,21 @@
1
+ import { InitxPlugin, InitxMatcherRules, InitxContext } from '@initx-plugin/core';
2
+
3
+ /** 插件自己的持久化配置(`src/index.ts` 的 defaultStore 就是它) */
4
+ interface EnvPluginStore {
5
+ repo?: string;
6
+ token?: string;
7
+ branch?: string;
8
+ path?: string;
9
+ }
10
+
11
+ declare class EnvPlugin extends InitxPlugin<EnvPluginStore> {
12
+ /**
13
+ * 插件自己的配置(vault 仓库地址 + token)。
14
+ * initx 会把它存到 `~/.initx/stores/@initx-plugin/env/store.json`,退出时自动落盘。
15
+ */
16
+ defaultStore: EnvPluginStore;
17
+ rules: InitxMatcherRules;
18
+ handle(ctx: InitxContext<EnvPluginStore>, ...args: (string | undefined)[]): Promise<void>;
19
+ }
20
+
21
+ export { EnvPlugin as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,78 @@
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
+ 1) npx initx env-config set token <pat>
3
+ 2) \u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF INITX_ENV_TOKEN\uFF08\u6216 GH_TOKEN / GITHUB_TOKEN\uFF09
4
+ 3) \u4EFB\u4F55\u547D\u4EE4\u90FD\u652F\u6301 --token <pat> \u4E34\u65F6\u4F20\u5165`);return{repo:n,token:r,branch:e.branch||t.INITX_ENV_BRANCH||u.branch||b,path:e.path||t.INITX_ENV_PATH||u.path||x,tokenSource:o}}function N(u){return u?u.length<=12?`***\uFF08${u.length} \u5B57\u7B26\uFF09`:`${u.slice(0,4)}\u2026${u.slice(-4)}\uFF08${u.length} \u5B57\u7B26\uFF09`:"(\u672A\u8BBE\u7F6E)"}class C extends Error{constructor(e,t,n){super(`GitHub API ${e} ${t}${n?` - ${n}`:""}`),this.status=e,this.statusText=t,this.body=n,this.name="GitHubApiError"}}class H extends Error{constructor(e){super(e),this.name="GitHubConflictError"}}function ku(u){return u instanceof H||u instanceof C&&(u.status===409||u.status===422)}function bu(u){if(!/^[^/\s]+\/[^/\s]+$/.test(u))throw new Error(`\u4ED3\u5E93\u8981\u5199\u6210 owner/repo \u5F62\u5F0F\uFF0C\u5F53\u524D\u662F "${u}"`)}function q(u){if(!u||u.startsWith("/")||/^[A-Z]:/i.test(u))throw new Error(`\u8DEF\u5F84\u5FC5\u987B\u662F\u4ED3\u5E93\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84\uFF1A"${u}"`);if(u.includes("\\"))throw new Error(`\u8DEF\u5F84\u5206\u9694\u7B26\u53EA\u80FD\u7528 /\uFF1A"${u}"`);if(u.split("/").some(e=>e===""||e==="."||e===".."))throw new Error(`\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B\u7A7A\u6BB5\u6216 ..\uFF1A"${u}"`)}function y(u){return{Authorization:`Bearer ${u}`,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28","User-Agent":"initx-plugin-env"}}async function Z(u,e,t={}){const n=await fetch(u,{...t,headers:{...y(e),...t.headers??{}}});if(!n.ok)throw new C(n.status,n.statusText,await n.text().catch(()=>{}));return n}function xu(u){return u.split("/").map(encodeURIComponent).join("/")}class I{constructor(e,t,n="main"){this.repo=e,this.token=t,this.branch=n,bu(e)}get repoName(){return this.repo}get branchName(){return this.branch}apiUrl(e){q(e);const[t,n]=this.repo.split("/",2);return`https://api.github.com/repos/${encodeURIComponent(t)}/${encodeURIComponent(n)}/contents/${xu(e)}?ref=${encodeURIComponent(this.branch)}`}async getFile(e){const t=await fetch(this.apiUrl(e),{headers:y(this.token)});if(t.status===404)return null;if(!t.ok)throw new C(t.status,t.statusText,await t.text().catch(()=>{}));const n=await t.json();if(n.type!=="file")throw new C(t.status,t.statusText,`\u8DEF\u5F84 "${e}" \u4E0D\u662F\u6587\u4EF6`);return n}async listDir(e){const t=await fetch(this.apiUrl(e),{headers:y(this.token)});if(t.status===404)return[];if(!t.ok)throw new C(t.status,t.statusText,await t.text().catch(()=>{}));const n=await t.json();if(!Array.isArray(n))throw new C(t.status,t.statusText,`\u8DEF\u5F84 "${e}" \u4E0D\u662F\u76EE\u5F55`);return n}async getText(e){const t=await this.getFile(e);return t?Q(t):null}async putFile(e,t,n,r){const o={message:n,content:j.from(t,"utf8").toString("base64"),branch:this.branch,...r?{sha:r}:{}},s=await fetch(this.apiUrl(e),{method:"PUT",headers:{...y(this.token),"Content-Type":"application/json"},body:JSON.stringify(o)});if(s.status===409||s.status===422)throw new H(`"${e}" \u5728\u8FDC\u7AEF\u5DF2\u7ECF\u88AB\u6539\u8FC7\uFF08sha \u8FC7\u671F\uFF09`);if(!s.ok)throw new C(s.status,s.statusText,await s.text().catch(()=>{}))}async deleteFile(e,t,n){const r=await fetch(this.apiUrl(e),{method:"DELETE",headers:{...y(this.token),"Content-Type":"application/json"},body:JSON.stringify({message:n,sha:t,branch:this.branch})});if(r.status===409||r.status===422)throw new H(`"${e}" \u5728\u8FDC\u7AEF\u5DF2\u7ECF\u88AB\u6539\u8FC7\uFF08sha \u8FC7\u671F\uFF09`);if(!r.ok)throw new C(r.status,r.statusText,await r.text().catch(()=>{}))}}function Q(u){return j.from(u.content.replace(/\n/g,""),u.encoding||"base64").toString("utf8")}const U=1,Tu=D.object({repo:D.string().regex(/^[^/\s]+\/[^/\s]+$/,"\u5FC5\u987B\u662F owner/repo \u5F62\u5F0F"),env:D.string().nullish()}),Nu=D.object({schema:D.number().int().positive().default(U),vars:D.record(D.string(),D.string()).default({}),targets:D.object({envFiles:D.array(D.string().min(1)).optional(),secrets:D.array(Tu).optional()}).optional()});function uu(){return{schema:U,vars:{}}}function B(u){const e=u.trim().replace(/\.json$/i,"");if(!e)throw new Error("store \u540D\u4E0D\u80FD\u4E3A\u7A7A");if(e.includes("/")||e.includes("\\"))throw new Error(`store \u540D\u4E0D\u80FD\u5305\u542B\u8DEF\u5F84\u5206\u9694\u7B26\uFF1A"${u}"`);if(e==="."||e==="..")throw new Error(`store \u540D\u4E0D\u80FD\u662F "${e}"`);if(/[\u0000-\u001F\u007F]/.test(e))throw new Error("store \u540D\u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26");if(e.length>100)throw new Error("store \u540D\u592A\u957F\uFF08\u4E0A\u9650 100 \u5B57\u7B26\uFF09");return e}function Iu(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 _u(u){return/^[A-Z_][\w.-]*$/i.test(u)}function tu(u,e){const t=B(e),n=(u||"").trim().replace(/^\/+|\/+$/g,""),r=n?`${n}/${t}.json`:`${t}.json`;return q(r),r}function Ou(u,e){let t;try{t=JSON.parse(u)}catch{throw new Error(`store "${e}" \u4E0D\u662F\u5408\u6CD5 JSON`)}const n=Nu.safeParse(t);if(!n.success)throw new Error(`store "${e}" \u7ED3\u6784\u4E0D\u5408\u6CD5\uFF1A${n.error.issues.map(r=>`${r.path.join(".")||"(root)"} ${r.message}`).join("; ")}`);return n.data}function ju(u){return`${JSON.stringify({...u,schema:u.schema??U},null,2)}
5
+ `}async function R(u,e){const t=(e||"").trim().replace(/^\/+|\/+$/g,"");return(await u.listDir(t)).filter(n=>n.type==="file"&&n.name.toLowerCase().endsWith(".json")).map(n=>n.name.replace(/\.json$/i,"")).sort((n,r)=>n.localeCompare(r))}async function v(u,e,t){const n=tu(e,t),r=await u.getFile(n);if(!r)return null;const o=B(t);return{name:o,path:n,sha:r.sha,store:Ou(Q(r),o)}}async function K(u,e,t,n,r,o){await u.putFile(tu(e,t),ju(n),r,o)}function eu(u,e){const t={added:[],updated:[],unchanged:[]};for(const[n,r]of Object.entries(e)){const o=u.vars[n];o===void 0?(u.vars[n]=r,t.added.push(n)):o===r?t.unchanged.push(n):(u.vars[n]=r,t.updated.push(n))}return t}const nu=/^export[ \t]+/,Su=/^([^=]*)=(.*)$/;function Gu(u){const e=u.replace(/[ \t]+$/,"");return e===""||/^[ \t]/.test(e)?null:e}const Hu=/^[ \t]*#/;function ru(u){return typeof u=="object"&&u!==null&&u.code==="ENOENT"}async function Uu(u){try{return await wu(u)}catch(e){if(ru(e))return null;throw e}}function Ru(u){return u.replace(/\\([nrt"\\])/g,(e,t)=>t==="n"?`
6
+ `:t==="r"?"\r":t==="t"?" ":t)}function Ku(u){const e=u.replace(/^[ \t]+/,"");for(const t of e.split(`
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
10
+ `)?`\r
11
+ `:`
12
+ `,t=u.endsWith(`
13
+ `),n=Lu(u),r=new Map,o=[],s=[];for(let i=0;i<n.length;){const c=Pu(n,i);if(c===null){i+=1;continue}r.has(c.key)&&!o.includes(c.key)&&o.push(c.key),r.set(c.key,c.value),s.push(c),i=c.endLine+1}return{lines:n,pairs:r,duplicates:o,eol:e,trailingNewline:t,entries:s}}const Vu=/[\s"'\\#$`]/;function Xu(u){return u!==""&&!Vu.test(u)}function Ju(u){return Xu(u)?u:`"${u.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}"`}function L(u,e,t){return`${t?"export ":""}${u}=${Ju(e)}`}function su(u,e,t){return`${u.join(e)}${t?e:""}`}function iu(u,e){const{pairs:t}=_(u),n=[],r=[],o=[],s=new Map;for(const[i,c]of Object.entries(e))s.set(i,c),t.has(i)?t.get(i)===c?o.push(i):r.push(i):n.push(i);return{added:n,updated:r,unchanged:o,target:s}}function Wu(u,e){if(u===""){const a=Yu(e);return{content:a,added:Object.keys(e),updated:[],unchanged:[],duplicates:[],changed:a!==u}}const t=_(u),{added:n,updated:r,unchanged:o,target:s}=iu(u,e),i=t.lines,c=new Map;t.entries.forEach((a,E)=>c.set(a.key,E));for(let a=t.entries.length-1;a>=0;a-=1){const E=t.entries[a];if(c.get(E.key)!==a)continue;const f=s.get(E.key);if(f===void 0||f===E.value)continue;const h=L(E.key,f,E.exportPrefix);i.splice(E.startLine,E.endLine-E.startLine+1,h)}if(n.length>0){i.length>0&&i[i.length-1].trim()!==""&&i.push("");for(const a of n)i.push(L(a,e[a],!1))}const l=su(i,t.eol,t.trailingNewline);return{content:l,added:n,updated:r,unchanged:o,duplicates:t.duplicates,changed:l!==u}}function Yu(u,e=`
14
+ `){const t=Object.entries(u).map(([n,r])=>L(n,r,!1));return t.length===0?"":su(t,e,!0)}function Mu(u,e){const{added:t,updated:n,unchanged:r}=iu(u,e),o=[..._(u).pairs.keys()].filter(s=>!(s in e));return{added:t,updated:n,unchanged:r,localOnly:o}}async function P(u){try{return await W(u,"utf8")}catch(e){if(ru(e))return null;throw e}}async function zu(u,e){const t=await Uu(u);if(t!==null&&t.isFile()){if(await W(u,"utf8")!==e){const n=`${u}.bak`;return await Au(u,n),await Y(u,e,"utf8"),{backupPath:n}}return{backupPath:null}}return await Y(u,e,"utf8"),{backupPath:null}}function au(){return p.env.GITHUB_ACTIONS==="true"?!0:typeof p.env.GITHUB_ENV=="string"&&p.env.GITHUB_ENV.length>0}function qu(u){let e=`EOF_${M(4).toString("hex")}`;for(;u.includes(e);)e=`EOF_${M(4).toString("hex")}`;return e}function Zu(u){return Object.entries(u).map(([e,t])=>{if(!t.includes(`
15
+ `))return`${e}=${t}`;const n=qu(t);return[`${e}<<${n}`,t,n].join(`
16
+ `)}).join(`
17
+ `)}const Qu=/[\s'"$]/,ut=/[\\"$`]/g;function tt(u){return Object.entries(u).map(([e,t])=>{if(t.includes(`
18
+ `)||t.includes("\r")){const n=t.replace(ut,r=>`\\${r}`).replace(/\r\n|\r|\n/g,"\\n");return`${e}="${n}"`}return Qu.test(t)?`${e}='${t.replace(/'/g,"'\\''")}'`:`${e}=${t}`}).join(`
19
+ `)}function et(u){if(u.includes(`
20
+ `)||u.includes("\r"))for(const e of u.split(/\r?\n/))e!==""&&console.log(`::add-mask::${e}`);console.log(`::add-mask::${u}`)}function nt(u,e={}){const{forceStdout:t=!1,mask:n=!0}=e,r=Object.keys(u),o=[];if(n&&r.length>0&&au())for(const c of r)et(u[c]),o.push(c);const s=t?void 0:p.env.GITHUB_ENV;if(s){const c=Zu(u);return c!==""&&Cu(s,`
21
+ ${c}`,{encoding:"utf8"}),{target:"github_env",injected:r,masked:o}}const i=tt(u);return i!==""&&console.log(i),{target:"stdout",injected:r,masked:o}}const rt=/^\.env(?:\..+)?$/,ot=[".bak",".example",".sample",".template",".keys",".enc"];async function st(u,e){const[t="list",...n]=e;switch(t){case"list":case"ls":return it(u);case"pull":return ct(u,n);case"set":return lt(u,n);case"push":return ft(u,n);case"setup":return ht(u,n);default:throw new Error(`\u672A\u77E5\u5B50\u547D\u4EE4 "${t}"\uFF0C\u53EF\u7528\uFF1Alist | pull | set | push | setup | apply\uFF08npx initx env help\uFF09`)}}function m(u,e={}){const t=T(u.store,{token:u.options.string("token"),repo:e.repo});return{config:t,client:new I(t.repo,t.token,t.branch)}}async function it(u){const{config:e,client:t}=m(u),n=await R(t,e.path);if(!n.length){F.warn(`${e.repo} \u7684 ${e.path}/ \u4E0B\u8FD8\u6CA1\u6709 store`),F.info("\u5EFA\u4E00\u4E2A\uFF1Anpx initx env set <\u540D\u5B57> KEY=value");return}F.info(`${e.repo} \xB7 ${e.path}/ \u5171 ${n.length} \u4E2A store\uFF1A`);for(const r of n)F.info(` - ${r}`)}async function Fu(u,e,t){if(t)return B(t);const n=await R(u,e);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(!p.stdin.isTTY)throw new Error(`\u6709\u591A\u4E2A store\uFF0C\u975E\u4EA4\u4E92\u73AF\u5883\u4E0B\u5FC5\u987B\u6307\u5B9A\u540D\u5B57\uFF1A
22
+ ${n.join(`
23
+ `)}`);return g.search("\u9009\u4E00\u4E2A store",n)}async function V(u,e,t){const n=await v(u,e,t);if(!n)throw new Error(`\u4ED3\u5E93\u91CC\u6CA1\u6709 store "${t}"\u3002\u7528 npx initx env list \u770B\u770B\u6709\u54EA\u4E9B`);return n}async function at(u,e){const t=u.options.string("to");if(t)return t.split(",").map(s=>s.trim()).filter(Boolean);const n=e.store.targets?.envFiles;if(n?.length)return n;const r=await Ft();if(r.length===1)return r;if(r.length===0){if(!p.stdin.isTTY)return[".env.local"];if(!await g.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(!p.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
+ ${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
26
+ npx initx env-config set repo <owner/repo>
27
+ npx initx env-config set token <pat>`);const e=await g.input("vault \u4ED3\u5E93\uFF08owner/repo\uFF09",{default:u.repo,required:!0,pattern:/^[^/\s]+\/[^/\s]+$/,patternError:"\u8981\u5199\u6210 owner/repo \u5F62\u5F0F"});u.repo=e;const t=await g.password("GitHub token\uFF08\u56DE\u8F66\u4FDD\u7559\u5F53\u524D\u503C\uFF09",{mask:"*"});t&&(u.token=t);const n=await g.input("\u5206\u652F",{default:u.branch||b});u.branch=n||b;const r=await g.input("store \u76EE\u5F55",{default:u.path||x});u.path=r||x,F.success(`\u5DF2\u4FDD\u5B58\uFF1A${u.repo}\uFF08\u5206\u652F ${u.branch}\uFF0C\u76EE\u5F55 ${u.path}\uFF09`),F.warn(lu)}function vt(u,e){const[t,...n]=e;if(!t||!J(t))throw new Error(`\u7528\u6CD5\uFF1Anpx initx env-config set <${k.join("|")}> <\u503C>`);const r=n.join(" ").trim();if(!r)throw new Error(`${t} \u7684\u503C\u4E0D\u80FD\u4E3A\u7A7A`);if(t==="repo"&&!/^[^/\s]+\/[^/\s]+$/.test(r))throw new Error("repo \u8981\u5199\u6210 owner/repo \u5F62\u5F0F");if(u[t]=r,t==="token"){F.success(`token \u5DF2\u4FDD\u5B58\uFF1A${N(r)}`),F.warn(lu);return}F.success(`${t} = ${r}`)}function mt(u,e){const[t]=e;if(!t||!J(t))throw new Error(`\u7528\u6CD5\uFF1Anpx initx env-config get <${k.join("|")}>`);F.info(`${t} = ${t==="token"?N(u.token??""):u[t]??"(\u672A\u8BBE\u7F6E)"}`)}function kt(u){for(const e of k)F.info(`${e}: ${e==="token"?N(u.token??""):u[e]??"(\u672A\u8BBE\u7F6E)"}`)}function bt(u,e){const[t]=e;if(!t||!J(t))throw new Error(`\u7528\u6CD5\uFF1Anpx initx env-config rm <${k.join("|")}>`);delete u[t],F.success(`\u5DF2\u6E05\u9664 ${t}`)}async function xt(u,e){let t;try{t=T(u,{token:e.string("token")})}catch(n){F.warn(n.message);return}if(F.info(`vault \u4ED3\u5E93: ${t.repo}`),F.info(`\u5206\u652F: ${t.branch}`),F.info(`store \u76EE\u5F55: ${t.path}`),F.info(`token: ${N(t.token)}\uFF08\u6765\u81EA ${t.tokenSource}\uFF09`),e.boolean("offline")){F.info("\uFF08--offline\uFF1A\u8DF3\u8FC7\u8FDE\u901A\u6027\u68C0\u67E5\uFF09");return}try{const n=new I(t.repo,t.token,t.branch),r=await R(n,t.path);F.success(`\u8FDE\u901A\u6B63\u5E38\uFF0C${t.path}/ \u4E0B\u6709 ${r.length} \u4E2A store`)}catch(n){F.error(`\u8FDE\u4E0D\u4E0A\u6216\u6CA1\u6743\u9650\uFF1A${n.message}`),F.warn("\u68C0\u67E5\uFF1A\u4ED3\u5E93\u540D\u5BF9\u4E0D\u5BF9\u3001token \u6709\u6CA1\u6709 Contents \u8BFB\u5199\u6743\u9650\u3001\u5206\u652F\u662F\u5426\u5B58\u5728")}}function Tt(){fu()}function fu(){console.log(`
28
+ \u7528\u6CD5\uFF1Anpx initx env-config <\u547D\u4EE4>
29
+
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
32
+ get <key> \u67E5\u770B\u6301\u4E45\u5316\u5355\u9879\uFF08token \u4F1A\u8131\u654F\uFF09
33
+ list \u67E5\u770B\u5168\u90E8\u6301\u4E45\u5316\u914D\u7F6E\uFF08token \u4F1A\u8131\u654F\uFF09
34
+ rm <key> \u6E05\u9664\u5355\u9879
35
+ status [--offline] \u67E5\u770B\u5B9E\u9645\u751F\u6548\u914D\u7F6E + \u8FDE\u901A\u6027\u68C0\u67E5
36
+
37
+ \u89E3\u6790\u4F18\u5148\u7EA7\uFF1A\u663E\u5F0F override > INITX_ENV_REPO / INITX_ENV_BRANCH / INITX_ENV_PATH > \u6301\u4E45\u5316\u914D\u7F6E > \u9ED8\u8BA4\u503C\u3002
38
+ Token\uFF1A--token > INITX_ENV_TOKEN > GH_TOKEN > GITHUB_TOKEN > \u6301\u4E45\u5316\u914D\u7F6E\u3002
39
+
40
+ \u793A\u4F8B\uFF1A
41
+ npx initx env-config set repo imba97/env-vault
42
+ npx initx env-config set token github_pat_xxx
43
+ npx initx env-config set path stores
44
+ npx initx env-config status
45
+ `)}function pu(u){return 128+(gu.signals[u]??1)}function Nt(u,e,t){return new Promise(n=>{let r,o,s,i=!1;const c=h=>{const d=h.code;return{code:d==="ENOENT"?127:d==="EACCES"?126:1,errorCode:d??"UNKNOWN"}},l=h=>{i||(i=!0,clearTimeout(o),p.removeListener("SIGINT",E),p.removeListener("SIGTERM",f),n(h))},a=h=>{if(!(i||s||r.exitCode!==null||r.signalCode!==null)){s=h;try{r.kill(h)}catch{}o=setTimeout(()=>{try{r.kill("SIGKILL")}catch{}finally{r.unref(),l({code:pu(h),signal:h})}},5e3)}};function E(){a("SIGINT")}function f(){a("SIGTERM")}try{r=Bu(u,e,{env:t,stdio:"inherit",shell:!1})}catch(h){l(c(h));return}p.on("SIGINT",E),p.on("SIGTERM",f),r.once("error",h=>l(c(h))),r.once("close",(h,d)=>{const w=s??d??void 0;l(w?{code:pu(w),signal:w}:{code:h??1})})})}function It(u){if(p.exitCode=u.code,u.signal&&p.platform!=="win32"){const e=u.signal;p.once("beforeExit",()=>{try{p.kill(p.pid,e)}catch{p.exitCode=u.code}})}}const hu="\u7528\u6CD5\uFF1Ainitx env run <store> [--token <token>] -- <command> [...args]";function _t(u,e){if(u.length!==1||!u[0])throw new Error(hu);const t=B(u[0]);if(!Array.isArray(e)||!e.every(n=>typeof n=="string")||!e[0]?.trim())throw new Error(hu);if(e.some(n=>n.includes("\0")))throw new Error("\u547D\u4EE4\u6216\u53C2\u6570\u4E0D\u80FD\u5305\u542B NUL");return{name:t,command:e[0],args:e.slice(1)}}function Ot(u,e=p.env,t=p.platform){const n={...e},r=new Set;for(const[o,s]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 s!="string"||s.includes("\0"))throw new Error(`\u53D8\u91CF ${JSON.stringify(o)} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u4E14\u4E0D\u80FD\u5305\u542B NUL`);const i=t==="win32"?o.toUpperCase():o;if(r.has(i))throw new Error(`\u53D8\u91CF ${JSON.stringify(o)} \u5728 Windows \u4E0A\u5B58\u5728\u5927\u5C0F\u5199\u51B2\u7A81`);r.add(i)}if(t==="win32"){const o=new Set;for(const s of Object.keys(n)){const i=s.toUpperCase();r.has(i)||o.has(i)?delete n[s]:o.add(i)}}return{...n,...u}}async function jt(u,e){let t="\u53C2\u6570";try{const n=_t(e,u.options.raw["--"]),r=new Set(["--","token"]);if(Object.keys(u.options.raw).some(E=>!r.has(E)))throw new Error("run \u53EA\u652F\u6301 --token\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");t="\u914D\u7F6E";const s=T(u.store,{token:u.options.string("token")});t="\u8BFB\u53D6 store";const i=new I(s.repo,s.token,s.branch),c=await v(i,s.path,n.name);if(!c)throw new Error("store \u4E0D\u5B58\u5728");t="\u53D8\u91CF\u6821\u9A8C";const l=Ot(c.store.vars);t="\u542F\u52A8\u5B50\u8FDB\u7A0B";const a=await Nt(n.command,n.args,l);a.errorCode&&p.stderr.write(`env run: \u542F\u52A8\u5931\u8D25\uFF08${a.errorCode}\uFF09
46
+ `),It(a)}catch(n){const r=t==="\u53D8\u91CF\u6821\u9A8C"||t==="\u53C2\u6570"?`\uFF1A${n.message}`:"";p.stderr.write(`env run: ${t}\u5931\u8D25${r}
47
+ `),p.exitCode=1}}const St=["list","ls","pull","set","push","setup","apply","run","help"],Gt=["setup","set","get","list","ls","rm","remove","status","help"];class Ht extends du{defaultStore={branch:b,path:x};rules=[{matching:"env",description:"Env vars from a private repo: env list | pull | set | push | setup | apply | run",optional:[void 0,...St]},{matching:"env-config",description:"Configure the vault repo (repo / token / branch / path) and check connectivity",optional:[void 0,...Gt]}];async handle(e,...t){const n=t.filter(c=>typeof c=="string"),r=yu(e.cliOptions,e.optionsList),o=e.store??{};if(e.key==="env-config"){if(n[0]==="help"||r.boolean("help")){Tt();return}await $t(o,n,r);return}if(n[0]==="run"&&!r.boolean("help")&&!r.boolean("h")){await jt({store:o,options:r},n.slice(1));return}if(!n.length||n[0]==="help"||r.boolean("help")||r.boolean("h")){Ut();return}const[s,...i]=n;if(s==="apply"){await Bt({store:o,options:r},i);return}await st({store:o,options:r},[s,...i])}}function Ut(){console.log(`
48
+ \u7528\u6CD5\uFF1Anpx initx env <\u547D\u4EE4>
49
+
50
+ list \u5217\u51FA vault \u4ED3\u5E93\u91CC\u6240\u6709 store
51
+ pull [store] \u62C9\u53D6\u5230\u672C\u5730 .env\uFF08\u4E0D\u5199\u540D\u5B57\u5C31\u5217\u51FA\u6765\u9009\uFF09
52
+ --to .env.local \u6307\u5B9A\u76EE\u6807\u6587\u4EF6\uFF08\u53EF\u9017\u53F7\u5206\u9694\u591A\u4E2A\uFF09
53
+ --dry-run \u53EA\u770B\u4F1A\u6539\u54EA\u4E9B\u952E\uFF0C\u4E0D\u5199\u6587\u4EF6
54
+ set <store> KEY=value ... \u76F4\u63A5\u5F80\u4ED3\u5E93\u91CC\u7684 store \u5199\u53D8\u91CF\uFF08store \u540D\u5C31\u662F\u6587\u4EF6\u540D\uFF09
55
+ push <store> \u4ECE\u672C\u5730 .env \u53CD\u5411\u66F4\u65B0 store
56
+ --from .env.local \u6307\u5B9A\u6765\u6E90\u6587\u4EF6\uFF08\u9ED8\u8BA4 .env.local \u2192 .env\uFF09
57
+ --prune \u5220\u6389\u4ED3\u5E93\u91CC\u672C\u5730\u6CA1\u6709\u7684\u952E
58
+ --force \u8FDC\u7AEF\u88AB\u6539\u8FC7\u65F6\u5F3A\u5236\u8986\u76D6
59
+ setup [store] \u628A store \u6CE8\u5165**\u672C\u6B21**\u8FD0\u884C\u7684\u73AF\u5883\uFF1A
60
+ GitHub Actions \u91CC\u5199 $GITHUB_ENV\uFF08\u540E\u7EED step \u53EF\u89C1\uFF09\u5E76\u81EA\u52A8\u8131\u654F
61
+ \u672C\u5730\u5219\u6253\u5230 stdout\uFF08--stdout \u5F3A\u5236\uFF1B--json \u8F93\u51FA JSON\uFF09
62
+ apply <store> --repo o/r \u628A store \u5199\u8FDB\u67D0\u4E2A\u4ED3\u5E93\u7684 Actions secrets
63
+ --env <\u73AF\u5883\u540D> \u5199\u73AF\u5883\u7EA7 secret
64
+ --only A,B \u53EA\u5199\u8FD9\u51E0\u4E2A
65
+ --dry-run \u53EA\u5217\u51FA\u5C06\u5199\u5165\u7684\u540D\u5B57
66
+ --skip-invalid \u8DF3\u8FC7\u540D\u5B57\u4E0D\u5408\u6CD5\u7684\u53D8\u91CF\uFF08\u9ED8\u8BA4\u4E2D\u6B62\uFF09
67
+ run <store> -- cmd [args] \u76F4\u63A5\u6CE8\u5165\u5B50\u8FDB\u7A0B\uFF0C\u7EE7\u627F stdio / cwd\uFF0C\u4F20\u9012\u9000\u51FA\u72B6\u6001
68
+ --token <pat> \u4E34\u65F6\u51ED\u636E\uFF08CI \u63A8\u8350 INITX_ENV_TOKEN\uFF09
69
+
70
+ \u914D\u7F6E vault \u4ED3\u5E93\u7528\uFF1Anpx initx env-config --help
71
+
72
+ \u793A\u4F8B\uFF1A
73
+ npx initx env-config set repo imba97/env-vault
74
+ npx initx env set mail-peon-publish EXTENSION_ID=abc CHROME_REFRESH_TOKEN=1//0g...
75
+ npx initx env pull mail-peon-publish
76
+ npx initx env setup mail-peon-publish --token "$GITHUB_TOKEN"
77
+ npx initx env apply mail-peon-publish --repo mail-peon/mail-peon
78
+ `)}export{Ht as default};
package/package.json CHANGED
@@ -1,6 +1,70 @@
1
1
  {
2
2
  "name": "@initx-plugin/env",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "packageManager": "pnpm@12.10.1",
6
+ "description": "initx plugin to manage env vars and secrets from one private GitHub repo",
7
+ "author": "imba97",
8
+ "license": "MIT",
9
+ "homepage": "https://github.com/initx-collective/initx-plugin-env#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git@github.com:initx-collective/initx-plugin-env.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/initx-collective/initx-plugin-env/issues"
16
+ },
17
+ "keywords": [
18
+ "initx",
19
+ "initx-plugin",
20
+ "env",
21
+ "dotenv",
22
+ "secrets",
23
+ "github-actions"
24
+ ],
25
+ "main": "dist/index.mjs",
26
+ "module": "dist/index.mjs",
27
+ "types": "dist/index.d.ts",
28
+ "files": [
29
+ "dist"
30
+ ],
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
+ "peerDependencies": {
41
+ "@initx-plugin/core": "*",
42
+ "@initx-plugin/utils": "*"
43
+ },
44
+ "dependencies": {
45
+ "cross-spawn": "^7.0.6",
46
+ "libsodium-wrappers": "^0.8.4",
47
+ "zod": "^4.6.5"
48
+ },
49
+ "devDependencies": {
50
+ "@antfu/eslint-config": "^9.5.1",
51
+ "@initx-plugin/core": "^0.4.3",
52
+ "@initx-plugin/utils": "^0.4.3",
53
+ "@types/cross-spawn": "^6.0.6",
54
+ "@types/node": "^26.6.4",
55
+ "bumpp": "^12.3.0",
56
+ "esbuild": "^0.28.2",
57
+ "eslint": "^10.12.0",
58
+ "nano-staged": "^1.0.2",
59
+ "simple-git-hooks": "^2.14.0",
60
+ "typescript": "6.0.3",
61
+ "unbuild": "^3.6.1",
62
+ "vitest": "^5.0.3"
63
+ },
64
+ "simple-git-hooks": {
65
+ "pre-commit": "pnpm nano-staged"
66
+ },
67
+ "nano-staged": {
68
+ "*": "eslint --cache --fix"
69
+ }
70
+ }