@penvhq/provider-github 0.5.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 +21 -0
- package/dist/index.cjs +369 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +159 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.js +335 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Itzfeminisce
|
|
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/dist/index.cjs
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
GhInvocationError: () => GhInvocationError,
|
|
24
|
+
GithubNameError: () => GithubNameError,
|
|
25
|
+
GithubProvider: () => GithubProvider,
|
|
26
|
+
GithubUnavailableError: () => GithubUnavailableError,
|
|
27
|
+
checkGithubNames: () => checkGithubNames,
|
|
28
|
+
createGithubProvider: () => createGithubProvider,
|
|
29
|
+
defaultGhRunner: () => defaultGhRunner,
|
|
30
|
+
penvProviderFactory: () => penvProviderFactory
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/errors.ts
|
|
35
|
+
var import_core = require("@penvhq/core");
|
|
36
|
+
var GithubNameError = class extends import_core.PenvError {
|
|
37
|
+
name = "GithubNameError";
|
|
38
|
+
reason;
|
|
39
|
+
variable;
|
|
40
|
+
/** The dotted parameter ids generating this variable — two for a case collision, one otherwise. */
|
|
41
|
+
parameters;
|
|
42
|
+
constructor(reason, variable, parameters, message, remedy) {
|
|
43
|
+
super("GITHUB_NAME", message, remedy);
|
|
44
|
+
this.reason = reason;
|
|
45
|
+
this.variable = variable;
|
|
46
|
+
this.parameters = parameters;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var GithubUnavailableError = class extends import_core.PenvError {
|
|
50
|
+
name = "GithubUnavailableError";
|
|
51
|
+
reason;
|
|
52
|
+
constructor(reason, message, remedy) {
|
|
53
|
+
super("GITHUB_UNAVAILABLE", message, remedy);
|
|
54
|
+
this.reason = reason;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/github.ts
|
|
59
|
+
var import_node_child_process = require("child_process");
|
|
60
|
+
|
|
61
|
+
// src/names.ts
|
|
62
|
+
var import_core2 = require("@penvhq/core");
|
|
63
|
+
var RESERVED_PREFIX = "GITHUB_";
|
|
64
|
+
var LEADING_DIGIT = /^[0-9]/;
|
|
65
|
+
var LEGAL_NAME = /^[A-Za-z0-9_]+$/;
|
|
66
|
+
function reservedPrefix(variable, parameter) {
|
|
67
|
+
return new GithubNameError(
|
|
68
|
+
"reserved-prefix",
|
|
69
|
+
variable,
|
|
70
|
+
[parameter],
|
|
71
|
+
`The parameter \`${parameter}\` generates the variable \`${variable}\`, which GitHub reserves \u2014 a secret name may not begin with \`${RESERVED_PREFIX}\``,
|
|
72
|
+
"GitHub keeps the `GITHUB_` prefix for its own secrets. Rename the parameter, or map it to an allowed name in the `override` block of penv.config.ts."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
function leadingDigit(variable, parameter) {
|
|
76
|
+
return new GithubNameError(
|
|
77
|
+
"leading-digit",
|
|
78
|
+
variable,
|
|
79
|
+
[parameter],
|
|
80
|
+
`The parameter \`${parameter}\` generates the variable \`${variable}\`, which GitHub rejects \u2014 a secret name may not begin with a digit`,
|
|
81
|
+
"Rename the parameter, or map it to a name that starts with a letter or `_` in the `override` block of penv.config.ts."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function charset(variable, parameter) {
|
|
85
|
+
return new GithubNameError(
|
|
86
|
+
"charset",
|
|
87
|
+
variable,
|
|
88
|
+
[parameter],
|
|
89
|
+
`The parameter \`${parameter}\` generates the variable \`${variable}\`, which GitHub rejects \u2014 a secret name may contain only letters, digits, and \`_\``,
|
|
90
|
+
"Rename the parameter, or map it to a name matching `[A-Za-z0-9_]` in the `override` block of penv.config.ts."
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
function caseCollision(upper, parameters) {
|
|
94
|
+
return new GithubNameError(
|
|
95
|
+
"case-collision",
|
|
96
|
+
upper,
|
|
97
|
+
parameters,
|
|
98
|
+
`Parameters ${parameters.map((p) => `\`${p}\``).join(" and ")} generate variables that differ only in case, and GitHub secret names are case-insensitive \u2014 they would overwrite one another`,
|
|
99
|
+
"Give one of them a distinct name \u2014 not just a different case \u2014 in the `override` block of penv.config.ts."
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
function checkGithubNames(refs, config) {
|
|
103
|
+
const named = refs.map((ref) => ({
|
|
104
|
+
variable: (0, import_core2.variableName)(ref, config),
|
|
105
|
+
parameter: (0, import_core2.parameterId)(ref)
|
|
106
|
+
}));
|
|
107
|
+
const errors = [];
|
|
108
|
+
const perName = [...named].sort(
|
|
109
|
+
(a, b) => a.variable < b.variable ? -1 : a.variable > b.variable ? 1 : a.parameter < b.parameter ? -1 : a.parameter > b.parameter ? 1 : 0
|
|
110
|
+
);
|
|
111
|
+
for (const { variable, parameter } of perName) {
|
|
112
|
+
if (variable.toUpperCase().startsWith(RESERVED_PREFIX)) {
|
|
113
|
+
errors.push(reservedPrefix(variable, parameter));
|
|
114
|
+
} else if (LEADING_DIGIT.test(variable)) {
|
|
115
|
+
errors.push(leadingDigit(variable, parameter));
|
|
116
|
+
} else if (!LEGAL_NAME.test(variable)) {
|
|
117
|
+
errors.push(charset(variable, parameter));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const byUpper = /* @__PURE__ */ new Map();
|
|
121
|
+
for (const { variable, parameter } of named) {
|
|
122
|
+
const key = variable.toUpperCase();
|
|
123
|
+
const list = byUpper.get(key);
|
|
124
|
+
if (list === void 0) {
|
|
125
|
+
byUpper.set(key, [parameter]);
|
|
126
|
+
} else {
|
|
127
|
+
list.push(parameter);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const key of [...byUpper.keys()].sort()) {
|
|
131
|
+
const parameters = byUpper.get(key);
|
|
132
|
+
if (parameters === void 0 || parameters.length < 2) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
errors.push(caseCollision(key, [...parameters].sort()));
|
|
136
|
+
}
|
|
137
|
+
return errors;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/github.ts
|
|
141
|
+
var GhInvocationError = class extends Error {
|
|
142
|
+
name = "GhInvocationError";
|
|
143
|
+
/** True when `gh` is not on PATH (spawn `ENOENT`). */
|
|
144
|
+
notFound;
|
|
145
|
+
status;
|
|
146
|
+
stderr;
|
|
147
|
+
args;
|
|
148
|
+
constructor(notFound, status, stderr, args) {
|
|
149
|
+
super(`gh ${args.join(" ")} exited ${notFound ? "not found" : String(status)}`);
|
|
150
|
+
this.notFound = notFound;
|
|
151
|
+
this.status = status;
|
|
152
|
+
this.stderr = stderr;
|
|
153
|
+
this.args = args;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
function toText(value) {
|
|
157
|
+
if (value === void 0) return "";
|
|
158
|
+
return typeof value === "string" ? value : value.toString("utf8");
|
|
159
|
+
}
|
|
160
|
+
var defaultGhRunner = (args, input) => {
|
|
161
|
+
try {
|
|
162
|
+
return (0, import_node_child_process.execFileSync)("gh", [...args], {
|
|
163
|
+
...input === void 0 ? {} : { input },
|
|
164
|
+
encoding: "utf8",
|
|
165
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
166
|
+
env: { ...process.env, GH_PROMPT_DISABLED: "1", NO_COLOR: "1" }
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const e = error;
|
|
170
|
+
const status = typeof e.status === "number" ? e.status : null;
|
|
171
|
+
throw new GhInvocationError(e.code === "ENOENT", status, toText(e.stderr), args);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var AUTH_HINT = /not logged in|authentication|gh auth login|no accounts/i;
|
|
175
|
+
function notInstalled() {
|
|
176
|
+
return new GithubUnavailableError(
|
|
177
|
+
"not-installed",
|
|
178
|
+
"The `gh` CLI is not installed or not on your PATH",
|
|
179
|
+
"penv reaches GitHub Actions through `gh` and holds no GitHub credential itself. Install it from https://cli.github.com and run `gh auth login`, then try again."
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function notAuthenticated(stderr) {
|
|
183
|
+
return new GithubUnavailableError(
|
|
184
|
+
"not-authenticated",
|
|
185
|
+
"penv could not authenticate to GitHub through `gh`",
|
|
186
|
+
`Run \`gh auth login\` (with a token that can write repository and environment secrets), then try again.${stderr.trim() === "" ? "" : `
|
|
187
|
+
gh said: ${stderr.trim()}`}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
function commandFailed(action, stderr) {
|
|
191
|
+
return new GithubUnavailableError(
|
|
192
|
+
"command-failed",
|
|
193
|
+
`gh could not ${action}`,
|
|
194
|
+
`Check that \`gh\` is authenticated with a token scoped to write this repository's secrets, and that the repository and environment exist.${stderr.trim() === "" ? "" : `
|
|
195
|
+
gh said: ${stderr.trim()}`}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
function mapFailure(error, action) {
|
|
199
|
+
if (error instanceof GhInvocationError) {
|
|
200
|
+
if (error.notFound) return notInstalled();
|
|
201
|
+
if (AUTH_HINT.test(error.stderr)) return notAuthenticated(error.stderr);
|
|
202
|
+
return commandFailed(action, error.stderr);
|
|
203
|
+
}
|
|
204
|
+
return commandFailed(action, error instanceof Error ? error.message : String(error));
|
|
205
|
+
}
|
|
206
|
+
function scopeArgs(scope, repo) {
|
|
207
|
+
const repoArgs = repo === void 0 ? [] : ["--repo", repo];
|
|
208
|
+
return scope.kind === "environment" ? ["--env", scope.environment, ...repoArgs] : [...repoArgs];
|
|
209
|
+
}
|
|
210
|
+
function parseSecretList(stdout, action) {
|
|
211
|
+
let parsed;
|
|
212
|
+
try {
|
|
213
|
+
parsed = JSON.parse(stdout);
|
|
214
|
+
} catch (cause) {
|
|
215
|
+
throw commandFailed(action, `gh returned output that is not JSON: ${toText(String(cause))}`);
|
|
216
|
+
}
|
|
217
|
+
if (!Array.isArray(parsed)) {
|
|
218
|
+
throw commandFailed(action, "gh returned JSON that is not an array of secrets");
|
|
219
|
+
}
|
|
220
|
+
const secrets = [];
|
|
221
|
+
for (const entry of parsed) {
|
|
222
|
+
if (typeof entry?.name !== "string" || typeof entry.updatedAt !== "string") {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
secrets.push({ name: entry.name, updatedAt: entry.updatedAt });
|
|
226
|
+
}
|
|
227
|
+
return secrets;
|
|
228
|
+
}
|
|
229
|
+
var GithubProvider = class {
|
|
230
|
+
type = "@penvhq/provider-github";
|
|
231
|
+
/**
|
|
232
|
+
* Both limits, declared: the store holds a resolved projection, and values
|
|
233
|
+
* never come back — GitHub's own reference says a get "without revealing its
|
|
234
|
+
* encrypted value" is all there is. The declaration is what lets `push`,
|
|
235
|
+
* `pull`, and `doctor` treat this store honestly without a separate concept.
|
|
236
|
+
*/
|
|
237
|
+
capabilities = { holds: "projection", readsValues: false };
|
|
238
|
+
#repo;
|
|
239
|
+
#run;
|
|
240
|
+
/** The `owner/repo` actually targeted, resolved once when no option named it. */
|
|
241
|
+
#resolvedRepo;
|
|
242
|
+
constructor(options = {}) {
|
|
243
|
+
this.#repo = options.repo;
|
|
244
|
+
this.#run = options.run ?? defaultGhRunner;
|
|
245
|
+
}
|
|
246
|
+
async verify() {
|
|
247
|
+
try {
|
|
248
|
+
this.#run(["auth", "status"]);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof GhInvocationError && error.notFound) {
|
|
251
|
+
throw notInstalled();
|
|
252
|
+
}
|
|
253
|
+
throw notAuthenticated(error instanceof GhInvocationError ? error.stderr : String(error));
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
this.#run([
|
|
257
|
+
"secret",
|
|
258
|
+
"list",
|
|
259
|
+
...scopeArgs({ kind: "repository" }, this.#repo),
|
|
260
|
+
"--json",
|
|
261
|
+
"name"
|
|
262
|
+
]);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
throw mapFailure(error, "reach this repository's secrets");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async push(name, value, scope) {
|
|
268
|
+
const args = ["secret", "set", name, ...scopeArgs(scope, this.#repo)];
|
|
269
|
+
try {
|
|
270
|
+
this.#run(args, value);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
throw mapFailure(error, `set the secret \`${name}\``);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async list(scope) {
|
|
276
|
+
const args = ["secret", "list", ...scopeArgs(scope, this.#repo), "--json", "name,updatedAt"];
|
|
277
|
+
let stdout;
|
|
278
|
+
try {
|
|
279
|
+
stdout = this.#run(args);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
throw mapFailure(error, "list secrets");
|
|
282
|
+
}
|
|
283
|
+
return parseSecretList(stdout, "list secrets");
|
|
284
|
+
}
|
|
285
|
+
/** GitHub's name grammar, judged before the first PUT — the provider owns its own rules. */
|
|
286
|
+
checkNames(refs, config) {
|
|
287
|
+
return checkGithubNames(refs, config);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* The `owner/repo` the API paths below need. `gh secret` resolves the repo
|
|
291
|
+
* from the working directory on its own, but `gh api` takes a literal path —
|
|
292
|
+
* so when no `location` named one, ask `gh` once and keep the answer.
|
|
293
|
+
*/
|
|
294
|
+
#targetRepo() {
|
|
295
|
+
if (this.#repo !== void 0) {
|
|
296
|
+
return this.#repo;
|
|
297
|
+
}
|
|
298
|
+
if (this.#resolvedRepo === void 0) {
|
|
299
|
+
let stdout;
|
|
300
|
+
try {
|
|
301
|
+
stdout = this.#run(["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"]);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
throw mapFailure(error, "resolve the repository this directory belongs to");
|
|
304
|
+
}
|
|
305
|
+
const repo = stdout.trim();
|
|
306
|
+
if (repo === "") {
|
|
307
|
+
throw commandFailed(
|
|
308
|
+
"resolve the repository this directory belongs to",
|
|
309
|
+
"gh reported no repository. Set `location` to `owner/repo` in the provider entry."
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
this.#resolvedRepo = repo;
|
|
313
|
+
}
|
|
314
|
+
return this.#resolvedRepo;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Whether the GitHub deployment environment exists. A 404 is an ordinary
|
|
318
|
+
* answer — the very one `ensureTarget` exists to fix — never an error.
|
|
319
|
+
*/
|
|
320
|
+
async targetExists(environment) {
|
|
321
|
+
try {
|
|
322
|
+
this.#run(["api", `repos/${this.#targetRepo()}/environments/${environment}`]);
|
|
323
|
+
return true;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (error instanceof GhInvocationError && /HTTP 404|Not Found/i.test(error.stderr)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
throw mapFailure(error, `check whether the environment \`${environment}\` exists`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Creates the deployment environment. Idempotent on GitHub's side (PUT), and
|
|
333
|
+
* only ever called after the CLI has an explicit go-ahead — the provider
|
|
334
|
+
* itself never asks, never guesses.
|
|
335
|
+
*/
|
|
336
|
+
async ensureTarget(environment) {
|
|
337
|
+
try {
|
|
338
|
+
this.#run([
|
|
339
|
+
"api",
|
|
340
|
+
"--method",
|
|
341
|
+
"PUT",
|
|
342
|
+
`repos/${this.#targetRepo()}/environments/${environment}`
|
|
343
|
+
]);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
throw mapFailure(error, `create the environment \`${environment}\``);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
function createGithubProvider(options = {}) {
|
|
350
|
+
return new GithubProvider(options);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/factory.ts
|
|
354
|
+
function penvProviderFactory(context) {
|
|
355
|
+
const location = context.providerConfig?.location;
|
|
356
|
+
return createGithubProvider(location === void 0 ? {} : { repo: location });
|
|
357
|
+
}
|
|
358
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
359
|
+
0 && (module.exports = {
|
|
360
|
+
GhInvocationError,
|
|
361
|
+
GithubNameError,
|
|
362
|
+
GithubProvider,
|
|
363
|
+
GithubUnavailableError,
|
|
364
|
+
checkGithubNames,
|
|
365
|
+
createGithubProvider,
|
|
366
|
+
defaultGhRunner,
|
|
367
|
+
penvProviderFactory
|
|
368
|
+
});
|
|
369
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/github.ts","../src/names.ts","../src/factory.ts"],"sourcesContent":["/**\n * The public surface of `@penvhq/provider-github`.\n *\n * The GitHub Actions Secrets provider — a projection-holding, value-withholding\n * store — and the name pre-flight a push runs before it. Everything\n * GitHub-specific lives here; core stays destination-agnostic.\n */\n\nexport type { GithubNameReason, GithubUnavailableReason } from \"./errors.js\";\nexport { GithubNameError, GithubUnavailableError } from \"./errors.js\";\nexport { penvProviderFactory } from \"./factory.js\";\nexport type { GhRunner, GithubProviderOptions } from \"./github.js\";\nexport {\n createGithubProvider,\n defaultGhRunner,\n GhInvocationError,\n GithubProvider,\n} from \"./github.js\";\nexport { checkGithubNames } from \"./names.js\";\n","/**\n * The provider's own errors, extending penv's base so they print with a remedy and\n * carry a stable code, exactly as core's do. They live here rather than in core\n * because they encode GitHub's grammar and GitHub's plumbing — the reserved\n * `GITHUB_` prefix, the `gh` CLI — which are someone else's product, not penv's.\n * Core stays destination-agnostic; the destination's rules live with the provider.\n */\n\nimport { PenvError } from \"@penvhq/core\";\n\n/** Which of GitHub's name rules a generated variable breaks. */\nexport type GithubNameReason = \"reserved-prefix\" | \"leading-digit\" | \"charset\" | \"case-collision\";\n\n/**\n * A generated variable the destination will not accept. Mirrors core's\n * `NameCollisionError`: the offending variable and the parameters that produce\n * it are public fields, the message names them, and the remedy points at the\n * `override` block — the one place any of these can be repaired.\n */\nexport class GithubNameError extends PenvError {\n override readonly name = \"GithubNameError\";\n readonly reason: GithubNameReason;\n readonly variable: string;\n /** The dotted parameter ids generating this variable — two for a case collision, one otherwise. */\n readonly parameters: readonly string[];\n\n constructor(\n reason: GithubNameReason,\n variable: string,\n parameters: readonly string[],\n message: string,\n remedy: string,\n ) {\n super(\"GITHUB_NAME\", message, remedy);\n this.reason = reason;\n this.variable = variable;\n this.parameters = parameters;\n }\n}\n\n/** Why penv cannot reach GitHub. Never fallen back from — see the RFC's provider-unification decision. */\nexport type GithubUnavailableReason = \"not-installed\" | \"not-authenticated\" | \"command-failed\";\n\n/**\n * The destination could not be reached or refused an operation. penv holds no\n * GitHub credential of its own and never falls back to a weaker path, so it names\n * which of \"not installed\", \"not authenticated\", or \"the command failed\" is true\n * and stops.\n */\nexport class GithubUnavailableError extends PenvError {\n override readonly name = \"GithubUnavailableError\";\n readonly reason: GithubUnavailableReason;\n\n constructor(reason: GithubUnavailableReason, message: string, remedy: string) {\n super(\"GITHUB_UNAVAILABLE\", message, remedy);\n this.reason = reason;\n }\n}\n","/**\n * The GitHub Actions Secrets provider — the first to declare both capability\n * limits: it holds a *projection* (resolved variable names, `.local` scopes\n * skipped, plaintext GitHub re-seals) and it cannot read values back, because\n * GitHub's API never returns one at any scope through any endpoint.\n *\n * penv reaches GitHub through the `gh` CLI and holds no GitHub credential of its\n * own: `gh auth login` has already happened, the token is `gh`'s to keep, and\n * penv's config gains no field for it. Values cross on **stdin**, never `--body`\n * (argv is readable by other processes) and never `--env-file` (a plaintext temp\n * file is the arrangement penv exists to delete) — one `gh` process per\n * parameter, which is slow and correct on a command that runs at deploy prep.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport type {\n ParameterRef,\n PenvConfig,\n PenvErrorLike,\n ProjectionProvider,\n ProjectionSecret,\n SecretScope,\n} from \"@penvhq/core\";\nimport { GithubUnavailableError } from \"./errors.js\";\nimport { checkGithubNames } from \"./names.js\";\n\n/** A `gh` invocation failed. Normalized so the provider maps it without touching Node's error shape. */\nexport class GhInvocationError extends Error {\n override readonly name = \"GhInvocationError\";\n /** True when `gh` is not on PATH (spawn `ENOENT`). */\n readonly notFound: boolean;\n readonly status: number | null;\n readonly stderr: string;\n readonly args: readonly string[];\n\n constructor(notFound: boolean, status: number | null, stderr: string, args: readonly string[]) {\n super(`gh ${args.join(\" \")} exited ${notFound ? \"not found\" : String(status)}`);\n this.notFound = notFound;\n this.status = status;\n this.stderr = stderr;\n this.args = args;\n }\n}\n\n/**\n * Runs `gh` with an argument array (never a shell string, so values with spaces\n * or special characters are never re-parsed) and optional stdin, returning\n * stdout. Injectable so the provider is testable without the binary; throws\n * {@link GhInvocationError} on failure.\n */\nexport type GhRunner = (args: readonly string[], input?: string) => string;\n\nfunction toText(value: string | Buffer | undefined): string {\n if (value === undefined) return \"\";\n return typeof value === \"string\" ? value : value.toString(\"utf8\");\n}\n\n/** The real runner. Keeps `gh` non-interactive so it fails loudly rather than prompting. */\nexport const defaultGhRunner: GhRunner = (args, input) => {\n try {\n return execFileSync(\"gh\", [...args], {\n ...(input === undefined ? {} : { input }),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, GH_PROMPT_DISABLED: \"1\", NO_COLOR: \"1\" },\n });\n } catch (error) {\n const e = error as NodeJS.ErrnoException & {\n status?: number | null;\n stderr?: string | Buffer;\n };\n const status = typeof e.status === \"number\" ? e.status : null;\n throw new GhInvocationError(e.code === \"ENOENT\", status, toText(e.stderr), args);\n }\n};\n\nconst AUTH_HINT = /not logged in|authentication|gh auth login|no accounts/i;\n\nfunction notInstalled(): GithubUnavailableError {\n return new GithubUnavailableError(\n \"not-installed\",\n \"The `gh` CLI is not installed or not on your PATH\",\n \"penv reaches GitHub Actions through `gh` and holds no GitHub credential itself. Install it \" +\n \"from https://cli.github.com and run `gh auth login`, then try again.\",\n );\n}\n\nfunction notAuthenticated(stderr: string): GithubUnavailableError {\n return new GithubUnavailableError(\n \"not-authenticated\",\n \"penv could not authenticate to GitHub through `gh`\",\n `Run \\`gh auth login\\` (with a token that can write repository and environment secrets), then ` +\n `try again.${stderr.trim() === \"\" ? \"\" : `\\n gh said: ${stderr.trim()}`}`,\n );\n}\n\nfunction commandFailed(action: string, stderr: string): GithubUnavailableError {\n return new GithubUnavailableError(\n \"command-failed\",\n `gh could not ${action}`,\n `Check that \\`gh\\` is authenticated with a token scoped to write this repository's secrets, and ` +\n `that the repository and environment exist.${stderr.trim() === \"\" ? \"\" : `\\n gh said: ${stderr.trim()}`}`,\n );\n}\n\n/** Maps a failed invocation to the loud, specific refusal penv owes the user. */\nfunction mapFailure(error: unknown, action: string): GithubUnavailableError {\n if (error instanceof GhInvocationError) {\n if (error.notFound) return notInstalled();\n if (AUTH_HINT.test(error.stderr)) return notAuthenticated(error.stderr);\n return commandFailed(action, error.stderr);\n }\n return commandFailed(action, error instanceof Error ? error.message : String(error));\n}\n\n/** `--env <name>` for an environment secret, nothing for a repository secret; `--repo` when declared. */\nfunction scopeArgs(scope: SecretScope, repo: string | undefined): string[] {\n const repoArgs = repo === undefined ? [] : [\"--repo\", repo];\n return scope.kind === \"environment\" ? [\"--env\", scope.environment, ...repoArgs] : [...repoArgs];\n}\n\ninterface RawSecret {\n readonly name: unknown;\n readonly updatedAt: unknown;\n}\n\nfunction parseSecretList(stdout: string, action: string): ProjectionSecret[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(stdout);\n } catch (cause) {\n throw commandFailed(action, `gh returned output that is not JSON: ${toText(String(cause))}`);\n }\n if (!Array.isArray(parsed)) {\n throw commandFailed(action, \"gh returned JSON that is not an array of secrets\");\n }\n const secrets: ProjectionSecret[] = [];\n for (const entry of parsed as RawSecret[]) {\n if (typeof entry?.name !== \"string\" || typeof entry.updatedAt !== \"string\") {\n continue;\n }\n secrets.push({ name: entry.name, updatedAt: entry.updatedAt });\n }\n return secrets;\n}\n\nexport interface GithubProviderOptions {\n /** The `owner/repo` `gh` targets. Left unset, `gh` resolves it from the working directory. */\n readonly repo?: string;\n /** The `gh` runner. Defaults to invoking the real binary; injected in tests. */\n readonly run?: GhRunner;\n}\n\nexport class GithubProvider implements ProjectionProvider {\n readonly type = \"@penvhq/provider-github\";\n /**\n * Both limits, declared: the store holds a resolved projection, and values\n * never come back — GitHub's own reference says a get \"without revealing its\n * encrypted value\" is all there is. The declaration is what lets `push`,\n * `pull`, and `doctor` treat this store honestly without a separate concept.\n */\n readonly capabilities = { holds: \"projection\", readsValues: false } as const;\n\n readonly #repo: string | undefined;\n readonly #run: GhRunner;\n /** The `owner/repo` actually targeted, resolved once when no option named it. */\n #resolvedRepo: string | undefined;\n\n constructor(options: GithubProviderOptions = {}) {\n this.#repo = options.repo;\n this.#run = options.run ?? defaultGhRunner;\n }\n\n async verify(): Promise<void> {\n try {\n this.#run([\"auth\", \"status\"]);\n } catch (error) {\n if (error instanceof GhInvocationError && error.notFound) {\n throw notInstalled();\n }\n throw notAuthenticated(error instanceof GhInvocationError ? error.stderr : String(error));\n }\n // Authenticated is not the same as able to write here. Listing the\n // repository's secrets is a live probe that the token can reach *this*\n // repository's secrets API — catching a wrong or inaccessible repo and a\n // grossly under-scoped token before the first PUT rather than mid-push. It\n // cannot prove per-environment write permission without writing, so that\n // narrower failure can still surface at push time as a loud refusal.\n try {\n this.#run([\n \"secret\",\n \"list\",\n ...scopeArgs({ kind: \"repository\" }, this.#repo),\n \"--json\",\n \"name\",\n ]);\n } catch (error) {\n throw mapFailure(error, \"reach this repository's secrets\");\n }\n }\n\n async push(name: string, value: string, scope: SecretScope): Promise<void> {\n const args = [\"secret\", \"set\", name, ...scopeArgs(scope, this.#repo)];\n try {\n this.#run(args, value);\n } catch (error) {\n throw mapFailure(error, `set the secret \\`${name}\\``);\n }\n }\n\n async list(scope: SecretScope): Promise<ProjectionSecret[]> {\n const args = [\"secret\", \"list\", ...scopeArgs(scope, this.#repo), \"--json\", \"name,updatedAt\"];\n let stdout: string;\n try {\n stdout = this.#run(args);\n } catch (error) {\n throw mapFailure(error, \"list secrets\");\n }\n return parseSecretList(stdout, \"list secrets\");\n }\n\n /** GitHub's name grammar, judged before the first PUT — the provider owns its own rules. */\n checkNames(refs: readonly ParameterRef[], config: PenvConfig): PenvErrorLike[] {\n return checkGithubNames(refs, config);\n }\n\n /**\n * The `owner/repo` the API paths below need. `gh secret` resolves the repo\n * from the working directory on its own, but `gh api` takes a literal path —\n * so when no `location` named one, ask `gh` once and keep the answer.\n */\n #targetRepo(): string {\n if (this.#repo !== undefined) {\n return this.#repo;\n }\n if (this.#resolvedRepo === undefined) {\n let stdout: string;\n try {\n stdout = this.#run([\"repo\", \"view\", \"--json\", \"nameWithOwner\", \"--jq\", \".nameWithOwner\"]);\n } catch (error) {\n throw mapFailure(error, \"resolve the repository this directory belongs to\");\n }\n const repo = stdout.trim();\n if (repo === \"\") {\n throw commandFailed(\n \"resolve the repository this directory belongs to\",\n \"gh reported no repository. Set `location` to `owner/repo` in the provider entry.\",\n );\n }\n this.#resolvedRepo = repo;\n }\n return this.#resolvedRepo;\n }\n\n /**\n * Whether the GitHub deployment environment exists. A 404 is an ordinary\n * answer — the very one `ensureTarget` exists to fix — never an error.\n */\n async targetExists(environment: string): Promise<boolean> {\n try {\n this.#run([\"api\", `repos/${this.#targetRepo()}/environments/${environment}`]);\n return true;\n } catch (error) {\n if (error instanceof GhInvocationError && /HTTP 404|Not Found/i.test(error.stderr)) {\n return false;\n }\n throw mapFailure(error, `check whether the environment \\`${environment}\\` exists`);\n }\n }\n\n /**\n * Creates the deployment environment. Idempotent on GitHub's side (PUT), and\n * only ever called after the CLI has an explicit go-ahead — the provider\n * itself never asks, never guesses.\n */\n async ensureTarget(environment: string): Promise<void> {\n try {\n this.#run([\n \"api\",\n \"--method\",\n \"PUT\",\n `repos/${this.#targetRepo()}/environments/${environment}`,\n ]);\n } catch (error) {\n throw mapFailure(error, `create the environment \\`${environment}\\``);\n }\n }\n}\n\nexport function createGithubProvider(options: GithubProviderOptions = {}): GithubProvider {\n return new GithubProvider(options);\n}\n","/**\n * The GitHub name pre-flight: judge every generated variable against GitHub's\n * secret grammar *before* anything is pushed, so a push is all or nothing and a\n * reserved name is never discovered after sixty secrets are already placed.\n *\n * The rule that matters most: validate the variable `penv generate` actually\n * emits, never the parameter name. `config.names` values reach the destination\n * verbatim with no charset or case validation in core, so a parameter named\n * `githubToken` — a perfectly good name — transforms to `GITHUB_TOKEN`, which\n * GitHub reserves. Composed from the real transform (`variableName`) rather than\n * re-deriving it, so it cannot drift from what a push actually sends.\n */\n\nimport type { ParameterRef, PenvConfig } from \"@penvhq/core\";\nimport { parameterId, variableName } from \"@penvhq/core\";\nimport { GithubNameError } from \"./errors.js\";\n\n/** GitHub reserves this prefix for its own secrets. */\nconst RESERVED_PREFIX = \"GITHUB_\";\n/** A secret name may not begin with a digit. */\nconst LEADING_DIGIT = /^[0-9]/;\n/** A secret name is letters, digits, and underscores only. */\nconst LEGAL_NAME = /^[A-Za-z0-9_]+$/;\n\nfunction reservedPrefix(variable: string, parameter: string): GithubNameError {\n return new GithubNameError(\n \"reserved-prefix\",\n variable,\n [parameter],\n `The parameter \\`${parameter}\\` generates the variable \\`${variable}\\`, which GitHub reserves — a secret name may not begin with \\`${RESERVED_PREFIX}\\``,\n \"GitHub keeps the `GITHUB_` prefix for its own secrets. Rename the parameter, or map it to an \" +\n \"allowed name in the `override` block of penv.config.ts.\",\n );\n}\n\nfunction leadingDigit(variable: string, parameter: string): GithubNameError {\n return new GithubNameError(\n \"leading-digit\",\n variable,\n [parameter],\n `The parameter \\`${parameter}\\` generates the variable \\`${variable}\\`, which GitHub rejects — a secret name may not begin with a digit`,\n \"Rename the parameter, or map it to a name that starts with a letter or `_` in the `override` \" +\n \"block of penv.config.ts.\",\n );\n}\n\nfunction charset(variable: string, parameter: string): GithubNameError {\n return new GithubNameError(\n \"charset\",\n variable,\n [parameter],\n `The parameter \\`${parameter}\\` generates the variable \\`${variable}\\`, which GitHub rejects — a secret name may contain only letters, digits, and \\`_\\``,\n \"Rename the parameter, or map it to a name matching `[A-Za-z0-9_]` in the `override` block of \" +\n \"penv.config.ts.\",\n );\n}\n\nfunction caseCollision(upper: string, parameters: readonly string[]): GithubNameError {\n return new GithubNameError(\n \"case-collision\",\n upper,\n parameters,\n `Parameters ${parameters.map((p) => `\\`${p}\\``).join(\" and \")} generate variables that differ only in case, and GitHub secret names are case-insensitive — they would overwrite one another`,\n \"Give one of them a distinct name — not just a different case — in the `override` block of \" +\n \"penv.config.ts.\",\n );\n}\n\n/**\n * Every generated variable that violates GitHub's grammar, collected rather than\n * thrown so a push reports every bad name at once and refuses before the first\n * PUT. Deterministic: per-name violations in variable order, then case\n * collisions in uppercased-key order, each parameter list sorted.\n *\n * Exact-string collisions (two parameters producing the identical variable) are\n * core's `checkNameCollisions`, run before this. Here the concern is GitHub's\n * *case-insensitivity*, which core's exact-string map cannot see.\n */\nexport function checkGithubNames(\n refs: readonly ParameterRef[],\n config: PenvConfig,\n): GithubNameError[] {\n const named = refs.map((ref) => ({\n variable: variableName(ref, config),\n parameter: parameterId(ref),\n }));\n\n const errors: GithubNameError[] = [];\n\n const perName = [...named].sort((a, b) =>\n a.variable < b.variable\n ? -1\n : a.variable > b.variable\n ? 1\n : a.parameter < b.parameter\n ? -1\n : a.parameter > b.parameter\n ? 1\n : 0,\n );\n for (const { variable, parameter } of perName) {\n // Case-insensitively, because GitHub reserves the prefix case-insensitively:\n // an `override` of `github_token` reaches GitHub verbatim and is refused\n // there, so it must be refused here — the same case-folding the collision\n // check below already applies.\n if (variable.toUpperCase().startsWith(RESERVED_PREFIX)) {\n errors.push(reservedPrefix(variable, parameter));\n } else if (LEADING_DIGIT.test(variable)) {\n errors.push(leadingDigit(variable, parameter));\n } else if (!LEGAL_NAME.test(variable)) {\n errors.push(charset(variable, parameter));\n }\n }\n\n const byUpper = new Map<string, string[]>();\n for (const { variable, parameter } of named) {\n const key = variable.toUpperCase();\n const list = byUpper.get(key);\n if (list === undefined) {\n byUpper.set(key, [parameter]);\n } else {\n list.push(parameter);\n }\n }\n for (const key of [...byUpper.keys()].sort()) {\n const parameters = byUpper.get(key);\n if (parameters === undefined || parameters.length < 2) {\n continue;\n }\n errors.push(caseCollision(key, [...parameters].sort()));\n }\n\n return errors;\n}\n","/**\n * The plugin seam: what the penv CLI calls when a `providers.*.type` names this\n * package. The factory owns the translation from the config's provider-agnostic\n * surface (`location`) to this provider's own options, so the config never\n * learns GitHub vocabulary and the provider never parses config.\n */\n\nimport type { ProjectionProvider, ProviderFactoryContext } from \"@penvhq/core\";\nimport { createGithubProvider } from \"./github.js\";\n\ndeclare module \"@penvhq/core\" {\n interface ProviderConfigMap {\n \"@penvhq/provider-github\": {\n /**\n * The repository penv maps the projection onto — `owner/repo`. Left\n * unset, `gh` resolves it from the working directory.\n */\n readonly location?: string;\n };\n }\n}\n\n/** Builds the GitHub provider for one environment's declared destination. */\nexport function penvProviderFactory(context: ProviderFactoryContext): ProjectionProvider {\n const location = context.providerConfig?.location;\n return createGithubProvider(location === undefined ? {} : { repo: location });\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { PenvError, ProviderFactoryContext, ProjectionProvider, SecretScope, ProjectionSecret, ParameterRef, PenvConfig, PenvErrorLike } from '@penvhq/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The provider's own errors, extending penv's base so they print with a remedy and
|
|
5
|
+
* carry a stable code, exactly as core's do. They live here rather than in core
|
|
6
|
+
* because they encode GitHub's grammar and GitHub's plumbing — the reserved
|
|
7
|
+
* `GITHUB_` prefix, the `gh` CLI — which are someone else's product, not penv's.
|
|
8
|
+
* Core stays destination-agnostic; the destination's rules live with the provider.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Which of GitHub's name rules a generated variable breaks. */
|
|
12
|
+
type GithubNameReason = "reserved-prefix" | "leading-digit" | "charset" | "case-collision";
|
|
13
|
+
/**
|
|
14
|
+
* A generated variable the destination will not accept. Mirrors core's
|
|
15
|
+
* `NameCollisionError`: the offending variable and the parameters that produce
|
|
16
|
+
* it are public fields, the message names them, and the remedy points at the
|
|
17
|
+
* `override` block — the one place any of these can be repaired.
|
|
18
|
+
*/
|
|
19
|
+
declare class GithubNameError extends PenvError {
|
|
20
|
+
readonly name = "GithubNameError";
|
|
21
|
+
readonly reason: GithubNameReason;
|
|
22
|
+
readonly variable: string;
|
|
23
|
+
/** The dotted parameter ids generating this variable — two for a case collision, one otherwise. */
|
|
24
|
+
readonly parameters: readonly string[];
|
|
25
|
+
constructor(reason: GithubNameReason, variable: string, parameters: readonly string[], message: string, remedy: string);
|
|
26
|
+
}
|
|
27
|
+
/** Why penv cannot reach GitHub. Never fallen back from — see the RFC's provider-unification decision. */
|
|
28
|
+
type GithubUnavailableReason = "not-installed" | "not-authenticated" | "command-failed";
|
|
29
|
+
/**
|
|
30
|
+
* The destination could not be reached or refused an operation. penv holds no
|
|
31
|
+
* GitHub credential of its own and never falls back to a weaker path, so it names
|
|
32
|
+
* which of "not installed", "not authenticated", or "the command failed" is true
|
|
33
|
+
* and stops.
|
|
34
|
+
*/
|
|
35
|
+
declare class GithubUnavailableError extends PenvError {
|
|
36
|
+
readonly name = "GithubUnavailableError";
|
|
37
|
+
readonly reason: GithubUnavailableReason;
|
|
38
|
+
constructor(reason: GithubUnavailableReason, message: string, remedy: string);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The plugin seam: what the penv CLI calls when a `providers.*.type` names this
|
|
43
|
+
* package. The factory owns the translation from the config's provider-agnostic
|
|
44
|
+
* surface (`location`) to this provider's own options, so the config never
|
|
45
|
+
* learns GitHub vocabulary and the provider never parses config.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
declare module "@penvhq/core" {
|
|
49
|
+
interface ProviderConfigMap {
|
|
50
|
+
"@penvhq/provider-github": {
|
|
51
|
+
/**
|
|
52
|
+
* The repository penv maps the projection onto — `owner/repo`. Left
|
|
53
|
+
* unset, `gh` resolves it from the working directory.
|
|
54
|
+
*/
|
|
55
|
+
readonly location?: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Builds the GitHub provider for one environment's declared destination. */
|
|
60
|
+
declare function penvProviderFactory(context: ProviderFactoryContext): ProjectionProvider;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The GitHub Actions Secrets provider — the first to declare both capability
|
|
64
|
+
* limits: it holds a *projection* (resolved variable names, `.local` scopes
|
|
65
|
+
* skipped, plaintext GitHub re-seals) and it cannot read values back, because
|
|
66
|
+
* GitHub's API never returns one at any scope through any endpoint.
|
|
67
|
+
*
|
|
68
|
+
* penv reaches GitHub through the `gh` CLI and holds no GitHub credential of its
|
|
69
|
+
* own: `gh auth login` has already happened, the token is `gh`'s to keep, and
|
|
70
|
+
* penv's config gains no field for it. Values cross on **stdin**, never `--body`
|
|
71
|
+
* (argv is readable by other processes) and never `--env-file` (a plaintext temp
|
|
72
|
+
* file is the arrangement penv exists to delete) — one `gh` process per
|
|
73
|
+
* parameter, which is slow and correct on a command that runs at deploy prep.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/** A `gh` invocation failed. Normalized so the provider maps it without touching Node's error shape. */
|
|
77
|
+
declare class GhInvocationError extends Error {
|
|
78
|
+
readonly name = "GhInvocationError";
|
|
79
|
+
/** True when `gh` is not on PATH (spawn `ENOENT`). */
|
|
80
|
+
readonly notFound: boolean;
|
|
81
|
+
readonly status: number | null;
|
|
82
|
+
readonly stderr: string;
|
|
83
|
+
readonly args: readonly string[];
|
|
84
|
+
constructor(notFound: boolean, status: number | null, stderr: string, args: readonly string[]);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Runs `gh` with an argument array (never a shell string, so values with spaces
|
|
88
|
+
* or special characters are never re-parsed) and optional stdin, returning
|
|
89
|
+
* stdout. Injectable so the provider is testable without the binary; throws
|
|
90
|
+
* {@link GhInvocationError} on failure.
|
|
91
|
+
*/
|
|
92
|
+
type GhRunner = (args: readonly string[], input?: string) => string;
|
|
93
|
+
/** The real runner. Keeps `gh` non-interactive so it fails loudly rather than prompting. */
|
|
94
|
+
declare const defaultGhRunner: GhRunner;
|
|
95
|
+
interface GithubProviderOptions {
|
|
96
|
+
/** The `owner/repo` `gh` targets. Left unset, `gh` resolves it from the working directory. */
|
|
97
|
+
readonly repo?: string;
|
|
98
|
+
/** The `gh` runner. Defaults to invoking the real binary; injected in tests. */
|
|
99
|
+
readonly run?: GhRunner;
|
|
100
|
+
}
|
|
101
|
+
declare class GithubProvider implements ProjectionProvider {
|
|
102
|
+
#private;
|
|
103
|
+
readonly type = "@penvhq/provider-github";
|
|
104
|
+
/**
|
|
105
|
+
* Both limits, declared: the store holds a resolved projection, and values
|
|
106
|
+
* never come back — GitHub's own reference says a get "without revealing its
|
|
107
|
+
* encrypted value" is all there is. The declaration is what lets `push`,
|
|
108
|
+
* `pull`, and `doctor` treat this store honestly without a separate concept.
|
|
109
|
+
*/
|
|
110
|
+
readonly capabilities: {
|
|
111
|
+
readonly holds: "projection";
|
|
112
|
+
readonly readsValues: false;
|
|
113
|
+
};
|
|
114
|
+
constructor(options?: GithubProviderOptions);
|
|
115
|
+
verify(): Promise<void>;
|
|
116
|
+
push(name: string, value: string, scope: SecretScope): Promise<void>;
|
|
117
|
+
list(scope: SecretScope): Promise<ProjectionSecret[]>;
|
|
118
|
+
/** GitHub's name grammar, judged before the first PUT — the provider owns its own rules. */
|
|
119
|
+
checkNames(refs: readonly ParameterRef[], config: PenvConfig): PenvErrorLike[];
|
|
120
|
+
/**
|
|
121
|
+
* Whether the GitHub deployment environment exists. A 404 is an ordinary
|
|
122
|
+
* answer — the very one `ensureTarget` exists to fix — never an error.
|
|
123
|
+
*/
|
|
124
|
+
targetExists(environment: string): Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Creates the deployment environment. Idempotent on GitHub's side (PUT), and
|
|
127
|
+
* only ever called after the CLI has an explicit go-ahead — the provider
|
|
128
|
+
* itself never asks, never guesses.
|
|
129
|
+
*/
|
|
130
|
+
ensureTarget(environment: string): Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
declare function createGithubProvider(options?: GithubProviderOptions): GithubProvider;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The GitHub name pre-flight: judge every generated variable against GitHub's
|
|
136
|
+
* secret grammar *before* anything is pushed, so a push is all or nothing and a
|
|
137
|
+
* reserved name is never discovered after sixty secrets are already placed.
|
|
138
|
+
*
|
|
139
|
+
* The rule that matters most: validate the variable `penv generate` actually
|
|
140
|
+
* emits, never the parameter name. `config.names` values reach the destination
|
|
141
|
+
* verbatim with no charset or case validation in core, so a parameter named
|
|
142
|
+
* `githubToken` — a perfectly good name — transforms to `GITHUB_TOKEN`, which
|
|
143
|
+
* GitHub reserves. Composed from the real transform (`variableName`) rather than
|
|
144
|
+
* re-deriving it, so it cannot drift from what a push actually sends.
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Every generated variable that violates GitHub's grammar, collected rather than
|
|
149
|
+
* thrown so a push reports every bad name at once and refuses before the first
|
|
150
|
+
* PUT. Deterministic: per-name violations in variable order, then case
|
|
151
|
+
* collisions in uppercased-key order, each parameter list sorted.
|
|
152
|
+
*
|
|
153
|
+
* Exact-string collisions (two parameters producing the identical variable) are
|
|
154
|
+
* core's `checkNameCollisions`, run before this. Here the concern is GitHub's
|
|
155
|
+
* *case-insensitivity*, which core's exact-string map cannot see.
|
|
156
|
+
*/
|
|
157
|
+
declare function checkGithubNames(refs: readonly ParameterRef[], config: PenvConfig): GithubNameError[];
|
|
158
|
+
|
|
159
|
+
export { GhInvocationError, type GhRunner, GithubNameError, type GithubNameReason, GithubProvider, type GithubProviderOptions, GithubUnavailableError, type GithubUnavailableReason, checkGithubNames, createGithubProvider, defaultGhRunner, penvProviderFactory };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { PenvError, ProviderFactoryContext, ProjectionProvider, SecretScope, ProjectionSecret, ParameterRef, PenvConfig, PenvErrorLike } from '@penvhq/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The provider's own errors, extending penv's base so they print with a remedy and
|
|
5
|
+
* carry a stable code, exactly as core's do. They live here rather than in core
|
|
6
|
+
* because they encode GitHub's grammar and GitHub's plumbing — the reserved
|
|
7
|
+
* `GITHUB_` prefix, the `gh` CLI — which are someone else's product, not penv's.
|
|
8
|
+
* Core stays destination-agnostic; the destination's rules live with the provider.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Which of GitHub's name rules a generated variable breaks. */
|
|
12
|
+
type GithubNameReason = "reserved-prefix" | "leading-digit" | "charset" | "case-collision";
|
|
13
|
+
/**
|
|
14
|
+
* A generated variable the destination will not accept. Mirrors core's
|
|
15
|
+
* `NameCollisionError`: the offending variable and the parameters that produce
|
|
16
|
+
* it are public fields, the message names them, and the remedy points at the
|
|
17
|
+
* `override` block — the one place any of these can be repaired.
|
|
18
|
+
*/
|
|
19
|
+
declare class GithubNameError extends PenvError {
|
|
20
|
+
readonly name = "GithubNameError";
|
|
21
|
+
readonly reason: GithubNameReason;
|
|
22
|
+
readonly variable: string;
|
|
23
|
+
/** The dotted parameter ids generating this variable — two for a case collision, one otherwise. */
|
|
24
|
+
readonly parameters: readonly string[];
|
|
25
|
+
constructor(reason: GithubNameReason, variable: string, parameters: readonly string[], message: string, remedy: string);
|
|
26
|
+
}
|
|
27
|
+
/** Why penv cannot reach GitHub. Never fallen back from — see the RFC's provider-unification decision. */
|
|
28
|
+
type GithubUnavailableReason = "not-installed" | "not-authenticated" | "command-failed";
|
|
29
|
+
/**
|
|
30
|
+
* The destination could not be reached or refused an operation. penv holds no
|
|
31
|
+
* GitHub credential of its own and never falls back to a weaker path, so it names
|
|
32
|
+
* which of "not installed", "not authenticated", or "the command failed" is true
|
|
33
|
+
* and stops.
|
|
34
|
+
*/
|
|
35
|
+
declare class GithubUnavailableError extends PenvError {
|
|
36
|
+
readonly name = "GithubUnavailableError";
|
|
37
|
+
readonly reason: GithubUnavailableReason;
|
|
38
|
+
constructor(reason: GithubUnavailableReason, message: string, remedy: string);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The plugin seam: what the penv CLI calls when a `providers.*.type` names this
|
|
43
|
+
* package. The factory owns the translation from the config's provider-agnostic
|
|
44
|
+
* surface (`location`) to this provider's own options, so the config never
|
|
45
|
+
* learns GitHub vocabulary and the provider never parses config.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
declare module "@penvhq/core" {
|
|
49
|
+
interface ProviderConfigMap {
|
|
50
|
+
"@penvhq/provider-github": {
|
|
51
|
+
/**
|
|
52
|
+
* The repository penv maps the projection onto — `owner/repo`. Left
|
|
53
|
+
* unset, `gh` resolves it from the working directory.
|
|
54
|
+
*/
|
|
55
|
+
readonly location?: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Builds the GitHub provider for one environment's declared destination. */
|
|
60
|
+
declare function penvProviderFactory(context: ProviderFactoryContext): ProjectionProvider;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The GitHub Actions Secrets provider — the first to declare both capability
|
|
64
|
+
* limits: it holds a *projection* (resolved variable names, `.local` scopes
|
|
65
|
+
* skipped, plaintext GitHub re-seals) and it cannot read values back, because
|
|
66
|
+
* GitHub's API never returns one at any scope through any endpoint.
|
|
67
|
+
*
|
|
68
|
+
* penv reaches GitHub through the `gh` CLI and holds no GitHub credential of its
|
|
69
|
+
* own: `gh auth login` has already happened, the token is `gh`'s to keep, and
|
|
70
|
+
* penv's config gains no field for it. Values cross on **stdin**, never `--body`
|
|
71
|
+
* (argv is readable by other processes) and never `--env-file` (a plaintext temp
|
|
72
|
+
* file is the arrangement penv exists to delete) — one `gh` process per
|
|
73
|
+
* parameter, which is slow and correct on a command that runs at deploy prep.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/** A `gh` invocation failed. Normalized so the provider maps it without touching Node's error shape. */
|
|
77
|
+
declare class GhInvocationError extends Error {
|
|
78
|
+
readonly name = "GhInvocationError";
|
|
79
|
+
/** True when `gh` is not on PATH (spawn `ENOENT`). */
|
|
80
|
+
readonly notFound: boolean;
|
|
81
|
+
readonly status: number | null;
|
|
82
|
+
readonly stderr: string;
|
|
83
|
+
readonly args: readonly string[];
|
|
84
|
+
constructor(notFound: boolean, status: number | null, stderr: string, args: readonly string[]);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Runs `gh` with an argument array (never a shell string, so values with spaces
|
|
88
|
+
* or special characters are never re-parsed) and optional stdin, returning
|
|
89
|
+
* stdout. Injectable so the provider is testable without the binary; throws
|
|
90
|
+
* {@link GhInvocationError} on failure.
|
|
91
|
+
*/
|
|
92
|
+
type GhRunner = (args: readonly string[], input?: string) => string;
|
|
93
|
+
/** The real runner. Keeps `gh` non-interactive so it fails loudly rather than prompting. */
|
|
94
|
+
declare const defaultGhRunner: GhRunner;
|
|
95
|
+
interface GithubProviderOptions {
|
|
96
|
+
/** The `owner/repo` `gh` targets. Left unset, `gh` resolves it from the working directory. */
|
|
97
|
+
readonly repo?: string;
|
|
98
|
+
/** The `gh` runner. Defaults to invoking the real binary; injected in tests. */
|
|
99
|
+
readonly run?: GhRunner;
|
|
100
|
+
}
|
|
101
|
+
declare class GithubProvider implements ProjectionProvider {
|
|
102
|
+
#private;
|
|
103
|
+
readonly type = "@penvhq/provider-github";
|
|
104
|
+
/**
|
|
105
|
+
* Both limits, declared: the store holds a resolved projection, and values
|
|
106
|
+
* never come back — GitHub's own reference says a get "without revealing its
|
|
107
|
+
* encrypted value" is all there is. The declaration is what lets `push`,
|
|
108
|
+
* `pull`, and `doctor` treat this store honestly without a separate concept.
|
|
109
|
+
*/
|
|
110
|
+
readonly capabilities: {
|
|
111
|
+
readonly holds: "projection";
|
|
112
|
+
readonly readsValues: false;
|
|
113
|
+
};
|
|
114
|
+
constructor(options?: GithubProviderOptions);
|
|
115
|
+
verify(): Promise<void>;
|
|
116
|
+
push(name: string, value: string, scope: SecretScope): Promise<void>;
|
|
117
|
+
list(scope: SecretScope): Promise<ProjectionSecret[]>;
|
|
118
|
+
/** GitHub's name grammar, judged before the first PUT — the provider owns its own rules. */
|
|
119
|
+
checkNames(refs: readonly ParameterRef[], config: PenvConfig): PenvErrorLike[];
|
|
120
|
+
/**
|
|
121
|
+
* Whether the GitHub deployment environment exists. A 404 is an ordinary
|
|
122
|
+
* answer — the very one `ensureTarget` exists to fix — never an error.
|
|
123
|
+
*/
|
|
124
|
+
targetExists(environment: string): Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Creates the deployment environment. Idempotent on GitHub's side (PUT), and
|
|
127
|
+
* only ever called after the CLI has an explicit go-ahead — the provider
|
|
128
|
+
* itself never asks, never guesses.
|
|
129
|
+
*/
|
|
130
|
+
ensureTarget(environment: string): Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
declare function createGithubProvider(options?: GithubProviderOptions): GithubProvider;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The GitHub name pre-flight: judge every generated variable against GitHub's
|
|
136
|
+
* secret grammar *before* anything is pushed, so a push is all or nothing and a
|
|
137
|
+
* reserved name is never discovered after sixty secrets are already placed.
|
|
138
|
+
*
|
|
139
|
+
* The rule that matters most: validate the variable `penv generate` actually
|
|
140
|
+
* emits, never the parameter name. `config.names` values reach the destination
|
|
141
|
+
* verbatim with no charset or case validation in core, so a parameter named
|
|
142
|
+
* `githubToken` — a perfectly good name — transforms to `GITHUB_TOKEN`, which
|
|
143
|
+
* GitHub reserves. Composed from the real transform (`variableName`) rather than
|
|
144
|
+
* re-deriving it, so it cannot drift from what a push actually sends.
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Every generated variable that violates GitHub's grammar, collected rather than
|
|
149
|
+
* thrown so a push reports every bad name at once and refuses before the first
|
|
150
|
+
* PUT. Deterministic: per-name violations in variable order, then case
|
|
151
|
+
* collisions in uppercased-key order, each parameter list sorted.
|
|
152
|
+
*
|
|
153
|
+
* Exact-string collisions (two parameters producing the identical variable) are
|
|
154
|
+
* core's `checkNameCollisions`, run before this. Here the concern is GitHub's
|
|
155
|
+
* *case-insensitivity*, which core's exact-string map cannot see.
|
|
156
|
+
*/
|
|
157
|
+
declare function checkGithubNames(refs: readonly ParameterRef[], config: PenvConfig): GithubNameError[];
|
|
158
|
+
|
|
159
|
+
export { GhInvocationError, type GhRunner, GithubNameError, type GithubNameReason, GithubProvider, type GithubProviderOptions, GithubUnavailableError, type GithubUnavailableReason, checkGithubNames, createGithubProvider, defaultGhRunner, penvProviderFactory };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
import { PenvError } from "@penvhq/core";
|
|
3
|
+
var GithubNameError = class extends PenvError {
|
|
4
|
+
name = "GithubNameError";
|
|
5
|
+
reason;
|
|
6
|
+
variable;
|
|
7
|
+
/** The dotted parameter ids generating this variable — two for a case collision, one otherwise. */
|
|
8
|
+
parameters;
|
|
9
|
+
constructor(reason, variable, parameters, message, remedy) {
|
|
10
|
+
super("GITHUB_NAME", message, remedy);
|
|
11
|
+
this.reason = reason;
|
|
12
|
+
this.variable = variable;
|
|
13
|
+
this.parameters = parameters;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var GithubUnavailableError = class extends PenvError {
|
|
17
|
+
name = "GithubUnavailableError";
|
|
18
|
+
reason;
|
|
19
|
+
constructor(reason, message, remedy) {
|
|
20
|
+
super("GITHUB_UNAVAILABLE", message, remedy);
|
|
21
|
+
this.reason = reason;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/github.ts
|
|
26
|
+
import { execFileSync } from "child_process";
|
|
27
|
+
|
|
28
|
+
// src/names.ts
|
|
29
|
+
import { parameterId, variableName } from "@penvhq/core";
|
|
30
|
+
var RESERVED_PREFIX = "GITHUB_";
|
|
31
|
+
var LEADING_DIGIT = /^[0-9]/;
|
|
32
|
+
var LEGAL_NAME = /^[A-Za-z0-9_]+$/;
|
|
33
|
+
function reservedPrefix(variable, parameter) {
|
|
34
|
+
return new GithubNameError(
|
|
35
|
+
"reserved-prefix",
|
|
36
|
+
variable,
|
|
37
|
+
[parameter],
|
|
38
|
+
`The parameter \`${parameter}\` generates the variable \`${variable}\`, which GitHub reserves \u2014 a secret name may not begin with \`${RESERVED_PREFIX}\``,
|
|
39
|
+
"GitHub keeps the `GITHUB_` prefix for its own secrets. Rename the parameter, or map it to an allowed name in the `override` block of penv.config.ts."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function leadingDigit(variable, parameter) {
|
|
43
|
+
return new GithubNameError(
|
|
44
|
+
"leading-digit",
|
|
45
|
+
variable,
|
|
46
|
+
[parameter],
|
|
47
|
+
`The parameter \`${parameter}\` generates the variable \`${variable}\`, which GitHub rejects \u2014 a secret name may not begin with a digit`,
|
|
48
|
+
"Rename the parameter, or map it to a name that starts with a letter or `_` in the `override` block of penv.config.ts."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function charset(variable, parameter) {
|
|
52
|
+
return new GithubNameError(
|
|
53
|
+
"charset",
|
|
54
|
+
variable,
|
|
55
|
+
[parameter],
|
|
56
|
+
`The parameter \`${parameter}\` generates the variable \`${variable}\`, which GitHub rejects \u2014 a secret name may contain only letters, digits, and \`_\``,
|
|
57
|
+
"Rename the parameter, or map it to a name matching `[A-Za-z0-9_]` in the `override` block of penv.config.ts."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
function caseCollision(upper, parameters) {
|
|
61
|
+
return new GithubNameError(
|
|
62
|
+
"case-collision",
|
|
63
|
+
upper,
|
|
64
|
+
parameters,
|
|
65
|
+
`Parameters ${parameters.map((p) => `\`${p}\``).join(" and ")} generate variables that differ only in case, and GitHub secret names are case-insensitive \u2014 they would overwrite one another`,
|
|
66
|
+
"Give one of them a distinct name \u2014 not just a different case \u2014 in the `override` block of penv.config.ts."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function checkGithubNames(refs, config) {
|
|
70
|
+
const named = refs.map((ref) => ({
|
|
71
|
+
variable: variableName(ref, config),
|
|
72
|
+
parameter: parameterId(ref)
|
|
73
|
+
}));
|
|
74
|
+
const errors = [];
|
|
75
|
+
const perName = [...named].sort(
|
|
76
|
+
(a, b) => a.variable < b.variable ? -1 : a.variable > b.variable ? 1 : a.parameter < b.parameter ? -1 : a.parameter > b.parameter ? 1 : 0
|
|
77
|
+
);
|
|
78
|
+
for (const { variable, parameter } of perName) {
|
|
79
|
+
if (variable.toUpperCase().startsWith(RESERVED_PREFIX)) {
|
|
80
|
+
errors.push(reservedPrefix(variable, parameter));
|
|
81
|
+
} else if (LEADING_DIGIT.test(variable)) {
|
|
82
|
+
errors.push(leadingDigit(variable, parameter));
|
|
83
|
+
} else if (!LEGAL_NAME.test(variable)) {
|
|
84
|
+
errors.push(charset(variable, parameter));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const byUpper = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const { variable, parameter } of named) {
|
|
89
|
+
const key = variable.toUpperCase();
|
|
90
|
+
const list = byUpper.get(key);
|
|
91
|
+
if (list === void 0) {
|
|
92
|
+
byUpper.set(key, [parameter]);
|
|
93
|
+
} else {
|
|
94
|
+
list.push(parameter);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const key of [...byUpper.keys()].sort()) {
|
|
98
|
+
const parameters = byUpper.get(key);
|
|
99
|
+
if (parameters === void 0 || parameters.length < 2) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
errors.push(caseCollision(key, [...parameters].sort()));
|
|
103
|
+
}
|
|
104
|
+
return errors;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/github.ts
|
|
108
|
+
var GhInvocationError = class extends Error {
|
|
109
|
+
name = "GhInvocationError";
|
|
110
|
+
/** True when `gh` is not on PATH (spawn `ENOENT`). */
|
|
111
|
+
notFound;
|
|
112
|
+
status;
|
|
113
|
+
stderr;
|
|
114
|
+
args;
|
|
115
|
+
constructor(notFound, status, stderr, args) {
|
|
116
|
+
super(`gh ${args.join(" ")} exited ${notFound ? "not found" : String(status)}`);
|
|
117
|
+
this.notFound = notFound;
|
|
118
|
+
this.status = status;
|
|
119
|
+
this.stderr = stderr;
|
|
120
|
+
this.args = args;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function toText(value) {
|
|
124
|
+
if (value === void 0) return "";
|
|
125
|
+
return typeof value === "string" ? value : value.toString("utf8");
|
|
126
|
+
}
|
|
127
|
+
var defaultGhRunner = (args, input) => {
|
|
128
|
+
try {
|
|
129
|
+
return execFileSync("gh", [...args], {
|
|
130
|
+
...input === void 0 ? {} : { input },
|
|
131
|
+
encoding: "utf8",
|
|
132
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
133
|
+
env: { ...process.env, GH_PROMPT_DISABLED: "1", NO_COLOR: "1" }
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const e = error;
|
|
137
|
+
const status = typeof e.status === "number" ? e.status : null;
|
|
138
|
+
throw new GhInvocationError(e.code === "ENOENT", status, toText(e.stderr), args);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var AUTH_HINT = /not logged in|authentication|gh auth login|no accounts/i;
|
|
142
|
+
function notInstalled() {
|
|
143
|
+
return new GithubUnavailableError(
|
|
144
|
+
"not-installed",
|
|
145
|
+
"The `gh` CLI is not installed or not on your PATH",
|
|
146
|
+
"penv reaches GitHub Actions through `gh` and holds no GitHub credential itself. Install it from https://cli.github.com and run `gh auth login`, then try again."
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
function notAuthenticated(stderr) {
|
|
150
|
+
return new GithubUnavailableError(
|
|
151
|
+
"not-authenticated",
|
|
152
|
+
"penv could not authenticate to GitHub through `gh`",
|
|
153
|
+
`Run \`gh auth login\` (with a token that can write repository and environment secrets), then try again.${stderr.trim() === "" ? "" : `
|
|
154
|
+
gh said: ${stderr.trim()}`}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
function commandFailed(action, stderr) {
|
|
158
|
+
return new GithubUnavailableError(
|
|
159
|
+
"command-failed",
|
|
160
|
+
`gh could not ${action}`,
|
|
161
|
+
`Check that \`gh\` is authenticated with a token scoped to write this repository's secrets, and that the repository and environment exist.${stderr.trim() === "" ? "" : `
|
|
162
|
+
gh said: ${stderr.trim()}`}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
function mapFailure(error, action) {
|
|
166
|
+
if (error instanceof GhInvocationError) {
|
|
167
|
+
if (error.notFound) return notInstalled();
|
|
168
|
+
if (AUTH_HINT.test(error.stderr)) return notAuthenticated(error.stderr);
|
|
169
|
+
return commandFailed(action, error.stderr);
|
|
170
|
+
}
|
|
171
|
+
return commandFailed(action, error instanceof Error ? error.message : String(error));
|
|
172
|
+
}
|
|
173
|
+
function scopeArgs(scope, repo) {
|
|
174
|
+
const repoArgs = repo === void 0 ? [] : ["--repo", repo];
|
|
175
|
+
return scope.kind === "environment" ? ["--env", scope.environment, ...repoArgs] : [...repoArgs];
|
|
176
|
+
}
|
|
177
|
+
function parseSecretList(stdout, action) {
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = JSON.parse(stdout);
|
|
181
|
+
} catch (cause) {
|
|
182
|
+
throw commandFailed(action, `gh returned output that is not JSON: ${toText(String(cause))}`);
|
|
183
|
+
}
|
|
184
|
+
if (!Array.isArray(parsed)) {
|
|
185
|
+
throw commandFailed(action, "gh returned JSON that is not an array of secrets");
|
|
186
|
+
}
|
|
187
|
+
const secrets = [];
|
|
188
|
+
for (const entry of parsed) {
|
|
189
|
+
if (typeof entry?.name !== "string" || typeof entry.updatedAt !== "string") {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
secrets.push({ name: entry.name, updatedAt: entry.updatedAt });
|
|
193
|
+
}
|
|
194
|
+
return secrets;
|
|
195
|
+
}
|
|
196
|
+
var GithubProvider = class {
|
|
197
|
+
type = "@penvhq/provider-github";
|
|
198
|
+
/**
|
|
199
|
+
* Both limits, declared: the store holds a resolved projection, and values
|
|
200
|
+
* never come back — GitHub's own reference says a get "without revealing its
|
|
201
|
+
* encrypted value" is all there is. The declaration is what lets `push`,
|
|
202
|
+
* `pull`, and `doctor` treat this store honestly without a separate concept.
|
|
203
|
+
*/
|
|
204
|
+
capabilities = { holds: "projection", readsValues: false };
|
|
205
|
+
#repo;
|
|
206
|
+
#run;
|
|
207
|
+
/** The `owner/repo` actually targeted, resolved once when no option named it. */
|
|
208
|
+
#resolvedRepo;
|
|
209
|
+
constructor(options = {}) {
|
|
210
|
+
this.#repo = options.repo;
|
|
211
|
+
this.#run = options.run ?? defaultGhRunner;
|
|
212
|
+
}
|
|
213
|
+
async verify() {
|
|
214
|
+
try {
|
|
215
|
+
this.#run(["auth", "status"]);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error instanceof GhInvocationError && error.notFound) {
|
|
218
|
+
throw notInstalled();
|
|
219
|
+
}
|
|
220
|
+
throw notAuthenticated(error instanceof GhInvocationError ? error.stderr : String(error));
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
this.#run([
|
|
224
|
+
"secret",
|
|
225
|
+
"list",
|
|
226
|
+
...scopeArgs({ kind: "repository" }, this.#repo),
|
|
227
|
+
"--json",
|
|
228
|
+
"name"
|
|
229
|
+
]);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
throw mapFailure(error, "reach this repository's secrets");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async push(name, value, scope) {
|
|
235
|
+
const args = ["secret", "set", name, ...scopeArgs(scope, this.#repo)];
|
|
236
|
+
try {
|
|
237
|
+
this.#run(args, value);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw mapFailure(error, `set the secret \`${name}\``);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async list(scope) {
|
|
243
|
+
const args = ["secret", "list", ...scopeArgs(scope, this.#repo), "--json", "name,updatedAt"];
|
|
244
|
+
let stdout;
|
|
245
|
+
try {
|
|
246
|
+
stdout = this.#run(args);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
throw mapFailure(error, "list secrets");
|
|
249
|
+
}
|
|
250
|
+
return parseSecretList(stdout, "list secrets");
|
|
251
|
+
}
|
|
252
|
+
/** GitHub's name grammar, judged before the first PUT — the provider owns its own rules. */
|
|
253
|
+
checkNames(refs, config) {
|
|
254
|
+
return checkGithubNames(refs, config);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* The `owner/repo` the API paths below need. `gh secret` resolves the repo
|
|
258
|
+
* from the working directory on its own, but `gh api` takes a literal path —
|
|
259
|
+
* so when no `location` named one, ask `gh` once and keep the answer.
|
|
260
|
+
*/
|
|
261
|
+
#targetRepo() {
|
|
262
|
+
if (this.#repo !== void 0) {
|
|
263
|
+
return this.#repo;
|
|
264
|
+
}
|
|
265
|
+
if (this.#resolvedRepo === void 0) {
|
|
266
|
+
let stdout;
|
|
267
|
+
try {
|
|
268
|
+
stdout = this.#run(["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"]);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
throw mapFailure(error, "resolve the repository this directory belongs to");
|
|
271
|
+
}
|
|
272
|
+
const repo = stdout.trim();
|
|
273
|
+
if (repo === "") {
|
|
274
|
+
throw commandFailed(
|
|
275
|
+
"resolve the repository this directory belongs to",
|
|
276
|
+
"gh reported no repository. Set `location` to `owner/repo` in the provider entry."
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
this.#resolvedRepo = repo;
|
|
280
|
+
}
|
|
281
|
+
return this.#resolvedRepo;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Whether the GitHub deployment environment exists. A 404 is an ordinary
|
|
285
|
+
* answer — the very one `ensureTarget` exists to fix — never an error.
|
|
286
|
+
*/
|
|
287
|
+
async targetExists(environment) {
|
|
288
|
+
try {
|
|
289
|
+
this.#run(["api", `repos/${this.#targetRepo()}/environments/${environment}`]);
|
|
290
|
+
return true;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (error instanceof GhInvocationError && /HTTP 404|Not Found/i.test(error.stderr)) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
throw mapFailure(error, `check whether the environment \`${environment}\` exists`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Creates the deployment environment. Idempotent on GitHub's side (PUT), and
|
|
300
|
+
* only ever called after the CLI has an explicit go-ahead — the provider
|
|
301
|
+
* itself never asks, never guesses.
|
|
302
|
+
*/
|
|
303
|
+
async ensureTarget(environment) {
|
|
304
|
+
try {
|
|
305
|
+
this.#run([
|
|
306
|
+
"api",
|
|
307
|
+
"--method",
|
|
308
|
+
"PUT",
|
|
309
|
+
`repos/${this.#targetRepo()}/environments/${environment}`
|
|
310
|
+
]);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
throw mapFailure(error, `create the environment \`${environment}\``);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
function createGithubProvider(options = {}) {
|
|
317
|
+
return new GithubProvider(options);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/factory.ts
|
|
321
|
+
function penvProviderFactory(context) {
|
|
322
|
+
const location = context.providerConfig?.location;
|
|
323
|
+
return createGithubProvider(location === void 0 ? {} : { repo: location });
|
|
324
|
+
}
|
|
325
|
+
export {
|
|
326
|
+
GhInvocationError,
|
|
327
|
+
GithubNameError,
|
|
328
|
+
GithubProvider,
|
|
329
|
+
GithubUnavailableError,
|
|
330
|
+
checkGithubNames,
|
|
331
|
+
createGithubProvider,
|
|
332
|
+
defaultGhRunner,
|
|
333
|
+
penvProviderFactory
|
|
334
|
+
};
|
|
335
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/github.ts","../src/names.ts","../src/factory.ts"],"sourcesContent":["/**\n * The provider's own errors, extending penv's base so they print with a remedy and\n * carry a stable code, exactly as core's do. They live here rather than in core\n * because they encode GitHub's grammar and GitHub's plumbing — the reserved\n * `GITHUB_` prefix, the `gh` CLI — which are someone else's product, not penv's.\n * Core stays destination-agnostic; the destination's rules live with the provider.\n */\n\nimport { PenvError } from \"@penvhq/core\";\n\n/** Which of GitHub's name rules a generated variable breaks. */\nexport type GithubNameReason = \"reserved-prefix\" | \"leading-digit\" | \"charset\" | \"case-collision\";\n\n/**\n * A generated variable the destination will not accept. Mirrors core's\n * `NameCollisionError`: the offending variable and the parameters that produce\n * it are public fields, the message names them, and the remedy points at the\n * `override` block — the one place any of these can be repaired.\n */\nexport class GithubNameError extends PenvError {\n override readonly name = \"GithubNameError\";\n readonly reason: GithubNameReason;\n readonly variable: string;\n /** The dotted parameter ids generating this variable — two for a case collision, one otherwise. */\n readonly parameters: readonly string[];\n\n constructor(\n reason: GithubNameReason,\n variable: string,\n parameters: readonly string[],\n message: string,\n remedy: string,\n ) {\n super(\"GITHUB_NAME\", message, remedy);\n this.reason = reason;\n this.variable = variable;\n this.parameters = parameters;\n }\n}\n\n/** Why penv cannot reach GitHub. Never fallen back from — see the RFC's provider-unification decision. */\nexport type GithubUnavailableReason = \"not-installed\" | \"not-authenticated\" | \"command-failed\";\n\n/**\n * The destination could not be reached or refused an operation. penv holds no\n * GitHub credential of its own and never falls back to a weaker path, so it names\n * which of \"not installed\", \"not authenticated\", or \"the command failed\" is true\n * and stops.\n */\nexport class GithubUnavailableError extends PenvError {\n override readonly name = \"GithubUnavailableError\";\n readonly reason: GithubUnavailableReason;\n\n constructor(reason: GithubUnavailableReason, message: string, remedy: string) {\n super(\"GITHUB_UNAVAILABLE\", message, remedy);\n this.reason = reason;\n }\n}\n","/**\n * The GitHub Actions Secrets provider — the first to declare both capability\n * limits: it holds a *projection* (resolved variable names, `.local` scopes\n * skipped, plaintext GitHub re-seals) and it cannot read values back, because\n * GitHub's API never returns one at any scope through any endpoint.\n *\n * penv reaches GitHub through the `gh` CLI and holds no GitHub credential of its\n * own: `gh auth login` has already happened, the token is `gh`'s to keep, and\n * penv's config gains no field for it. Values cross on **stdin**, never `--body`\n * (argv is readable by other processes) and never `--env-file` (a plaintext temp\n * file is the arrangement penv exists to delete) — one `gh` process per\n * parameter, which is slow and correct on a command that runs at deploy prep.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport type {\n ParameterRef,\n PenvConfig,\n PenvErrorLike,\n ProjectionProvider,\n ProjectionSecret,\n SecretScope,\n} from \"@penvhq/core\";\nimport { GithubUnavailableError } from \"./errors.js\";\nimport { checkGithubNames } from \"./names.js\";\n\n/** A `gh` invocation failed. Normalized so the provider maps it without touching Node's error shape. */\nexport class GhInvocationError extends Error {\n override readonly name = \"GhInvocationError\";\n /** True when `gh` is not on PATH (spawn `ENOENT`). */\n readonly notFound: boolean;\n readonly status: number | null;\n readonly stderr: string;\n readonly args: readonly string[];\n\n constructor(notFound: boolean, status: number | null, stderr: string, args: readonly string[]) {\n super(`gh ${args.join(\" \")} exited ${notFound ? \"not found\" : String(status)}`);\n this.notFound = notFound;\n this.status = status;\n this.stderr = stderr;\n this.args = args;\n }\n}\n\n/**\n * Runs `gh` with an argument array (never a shell string, so values with spaces\n * or special characters are never re-parsed) and optional stdin, returning\n * stdout. Injectable so the provider is testable without the binary; throws\n * {@link GhInvocationError} on failure.\n */\nexport type GhRunner = (args: readonly string[], input?: string) => string;\n\nfunction toText(value: string | Buffer | undefined): string {\n if (value === undefined) return \"\";\n return typeof value === \"string\" ? value : value.toString(\"utf8\");\n}\n\n/** The real runner. Keeps `gh` non-interactive so it fails loudly rather than prompting. */\nexport const defaultGhRunner: GhRunner = (args, input) => {\n try {\n return execFileSync(\"gh\", [...args], {\n ...(input === undefined ? {} : { input }),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, GH_PROMPT_DISABLED: \"1\", NO_COLOR: \"1\" },\n });\n } catch (error) {\n const e = error as NodeJS.ErrnoException & {\n status?: number | null;\n stderr?: string | Buffer;\n };\n const status = typeof e.status === \"number\" ? e.status : null;\n throw new GhInvocationError(e.code === \"ENOENT\", status, toText(e.stderr), args);\n }\n};\n\nconst AUTH_HINT = /not logged in|authentication|gh auth login|no accounts/i;\n\nfunction notInstalled(): GithubUnavailableError {\n return new GithubUnavailableError(\n \"not-installed\",\n \"The `gh` CLI is not installed or not on your PATH\",\n \"penv reaches GitHub Actions through `gh` and holds no GitHub credential itself. Install it \" +\n \"from https://cli.github.com and run `gh auth login`, then try again.\",\n );\n}\n\nfunction notAuthenticated(stderr: string): GithubUnavailableError {\n return new GithubUnavailableError(\n \"not-authenticated\",\n \"penv could not authenticate to GitHub through `gh`\",\n `Run \\`gh auth login\\` (with a token that can write repository and environment secrets), then ` +\n `try again.${stderr.trim() === \"\" ? \"\" : `\\n gh said: ${stderr.trim()}`}`,\n );\n}\n\nfunction commandFailed(action: string, stderr: string): GithubUnavailableError {\n return new GithubUnavailableError(\n \"command-failed\",\n `gh could not ${action}`,\n `Check that \\`gh\\` is authenticated with a token scoped to write this repository's secrets, and ` +\n `that the repository and environment exist.${stderr.trim() === \"\" ? \"\" : `\\n gh said: ${stderr.trim()}`}`,\n );\n}\n\n/** Maps a failed invocation to the loud, specific refusal penv owes the user. */\nfunction mapFailure(error: unknown, action: string): GithubUnavailableError {\n if (error instanceof GhInvocationError) {\n if (error.notFound) return notInstalled();\n if (AUTH_HINT.test(error.stderr)) return notAuthenticated(error.stderr);\n return commandFailed(action, error.stderr);\n }\n return commandFailed(action, error instanceof Error ? error.message : String(error));\n}\n\n/** `--env <name>` for an environment secret, nothing for a repository secret; `--repo` when declared. */\nfunction scopeArgs(scope: SecretScope, repo: string | undefined): string[] {\n const repoArgs = repo === undefined ? [] : [\"--repo\", repo];\n return scope.kind === \"environment\" ? [\"--env\", scope.environment, ...repoArgs] : [...repoArgs];\n}\n\ninterface RawSecret {\n readonly name: unknown;\n readonly updatedAt: unknown;\n}\n\nfunction parseSecretList(stdout: string, action: string): ProjectionSecret[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(stdout);\n } catch (cause) {\n throw commandFailed(action, `gh returned output that is not JSON: ${toText(String(cause))}`);\n }\n if (!Array.isArray(parsed)) {\n throw commandFailed(action, \"gh returned JSON that is not an array of secrets\");\n }\n const secrets: ProjectionSecret[] = [];\n for (const entry of parsed as RawSecret[]) {\n if (typeof entry?.name !== \"string\" || typeof entry.updatedAt !== \"string\") {\n continue;\n }\n secrets.push({ name: entry.name, updatedAt: entry.updatedAt });\n }\n return secrets;\n}\n\nexport interface GithubProviderOptions {\n /** The `owner/repo` `gh` targets. Left unset, `gh` resolves it from the working directory. */\n readonly repo?: string;\n /** The `gh` runner. Defaults to invoking the real binary; injected in tests. */\n readonly run?: GhRunner;\n}\n\nexport class GithubProvider implements ProjectionProvider {\n readonly type = \"@penvhq/provider-github\";\n /**\n * Both limits, declared: the store holds a resolved projection, and values\n * never come back — GitHub's own reference says a get \"without revealing its\n * encrypted value\" is all there is. The declaration is what lets `push`,\n * `pull`, and `doctor` treat this store honestly without a separate concept.\n */\n readonly capabilities = { holds: \"projection\", readsValues: false } as const;\n\n readonly #repo: string | undefined;\n readonly #run: GhRunner;\n /** The `owner/repo` actually targeted, resolved once when no option named it. */\n #resolvedRepo: string | undefined;\n\n constructor(options: GithubProviderOptions = {}) {\n this.#repo = options.repo;\n this.#run = options.run ?? defaultGhRunner;\n }\n\n async verify(): Promise<void> {\n try {\n this.#run([\"auth\", \"status\"]);\n } catch (error) {\n if (error instanceof GhInvocationError && error.notFound) {\n throw notInstalled();\n }\n throw notAuthenticated(error instanceof GhInvocationError ? error.stderr : String(error));\n }\n // Authenticated is not the same as able to write here. Listing the\n // repository's secrets is a live probe that the token can reach *this*\n // repository's secrets API — catching a wrong or inaccessible repo and a\n // grossly under-scoped token before the first PUT rather than mid-push. It\n // cannot prove per-environment write permission without writing, so that\n // narrower failure can still surface at push time as a loud refusal.\n try {\n this.#run([\n \"secret\",\n \"list\",\n ...scopeArgs({ kind: \"repository\" }, this.#repo),\n \"--json\",\n \"name\",\n ]);\n } catch (error) {\n throw mapFailure(error, \"reach this repository's secrets\");\n }\n }\n\n async push(name: string, value: string, scope: SecretScope): Promise<void> {\n const args = [\"secret\", \"set\", name, ...scopeArgs(scope, this.#repo)];\n try {\n this.#run(args, value);\n } catch (error) {\n throw mapFailure(error, `set the secret \\`${name}\\``);\n }\n }\n\n async list(scope: SecretScope): Promise<ProjectionSecret[]> {\n const args = [\"secret\", \"list\", ...scopeArgs(scope, this.#repo), \"--json\", \"name,updatedAt\"];\n let stdout: string;\n try {\n stdout = this.#run(args);\n } catch (error) {\n throw mapFailure(error, \"list secrets\");\n }\n return parseSecretList(stdout, \"list secrets\");\n }\n\n /** GitHub's name grammar, judged before the first PUT — the provider owns its own rules. */\n checkNames(refs: readonly ParameterRef[], config: PenvConfig): PenvErrorLike[] {\n return checkGithubNames(refs, config);\n }\n\n /**\n * The `owner/repo` the API paths below need. `gh secret` resolves the repo\n * from the working directory on its own, but `gh api` takes a literal path —\n * so when no `location` named one, ask `gh` once and keep the answer.\n */\n #targetRepo(): string {\n if (this.#repo !== undefined) {\n return this.#repo;\n }\n if (this.#resolvedRepo === undefined) {\n let stdout: string;\n try {\n stdout = this.#run([\"repo\", \"view\", \"--json\", \"nameWithOwner\", \"--jq\", \".nameWithOwner\"]);\n } catch (error) {\n throw mapFailure(error, \"resolve the repository this directory belongs to\");\n }\n const repo = stdout.trim();\n if (repo === \"\") {\n throw commandFailed(\n \"resolve the repository this directory belongs to\",\n \"gh reported no repository. Set `location` to `owner/repo` in the provider entry.\",\n );\n }\n this.#resolvedRepo = repo;\n }\n return this.#resolvedRepo;\n }\n\n /**\n * Whether the GitHub deployment environment exists. A 404 is an ordinary\n * answer — the very one `ensureTarget` exists to fix — never an error.\n */\n async targetExists(environment: string): Promise<boolean> {\n try {\n this.#run([\"api\", `repos/${this.#targetRepo()}/environments/${environment}`]);\n return true;\n } catch (error) {\n if (error instanceof GhInvocationError && /HTTP 404|Not Found/i.test(error.stderr)) {\n return false;\n }\n throw mapFailure(error, `check whether the environment \\`${environment}\\` exists`);\n }\n }\n\n /**\n * Creates the deployment environment. Idempotent on GitHub's side (PUT), and\n * only ever called after the CLI has an explicit go-ahead — the provider\n * itself never asks, never guesses.\n */\n async ensureTarget(environment: string): Promise<void> {\n try {\n this.#run([\n \"api\",\n \"--method\",\n \"PUT\",\n `repos/${this.#targetRepo()}/environments/${environment}`,\n ]);\n } catch (error) {\n throw mapFailure(error, `create the environment \\`${environment}\\``);\n }\n }\n}\n\nexport function createGithubProvider(options: GithubProviderOptions = {}): GithubProvider {\n return new GithubProvider(options);\n}\n","/**\n * The GitHub name pre-flight: judge every generated variable against GitHub's\n * secret grammar *before* anything is pushed, so a push is all or nothing and a\n * reserved name is never discovered after sixty secrets are already placed.\n *\n * The rule that matters most: validate the variable `penv generate` actually\n * emits, never the parameter name. `config.names` values reach the destination\n * verbatim with no charset or case validation in core, so a parameter named\n * `githubToken` — a perfectly good name — transforms to `GITHUB_TOKEN`, which\n * GitHub reserves. Composed from the real transform (`variableName`) rather than\n * re-deriving it, so it cannot drift from what a push actually sends.\n */\n\nimport type { ParameterRef, PenvConfig } from \"@penvhq/core\";\nimport { parameterId, variableName } from \"@penvhq/core\";\nimport { GithubNameError } from \"./errors.js\";\n\n/** GitHub reserves this prefix for its own secrets. */\nconst RESERVED_PREFIX = \"GITHUB_\";\n/** A secret name may not begin with a digit. */\nconst LEADING_DIGIT = /^[0-9]/;\n/** A secret name is letters, digits, and underscores only. */\nconst LEGAL_NAME = /^[A-Za-z0-9_]+$/;\n\nfunction reservedPrefix(variable: string, parameter: string): GithubNameError {\n return new GithubNameError(\n \"reserved-prefix\",\n variable,\n [parameter],\n `The parameter \\`${parameter}\\` generates the variable \\`${variable}\\`, which GitHub reserves — a secret name may not begin with \\`${RESERVED_PREFIX}\\``,\n \"GitHub keeps the `GITHUB_` prefix for its own secrets. Rename the parameter, or map it to an \" +\n \"allowed name in the `override` block of penv.config.ts.\",\n );\n}\n\nfunction leadingDigit(variable: string, parameter: string): GithubNameError {\n return new GithubNameError(\n \"leading-digit\",\n variable,\n [parameter],\n `The parameter \\`${parameter}\\` generates the variable \\`${variable}\\`, which GitHub rejects — a secret name may not begin with a digit`,\n \"Rename the parameter, or map it to a name that starts with a letter or `_` in the `override` \" +\n \"block of penv.config.ts.\",\n );\n}\n\nfunction charset(variable: string, parameter: string): GithubNameError {\n return new GithubNameError(\n \"charset\",\n variable,\n [parameter],\n `The parameter \\`${parameter}\\` generates the variable \\`${variable}\\`, which GitHub rejects — a secret name may contain only letters, digits, and \\`_\\``,\n \"Rename the parameter, or map it to a name matching `[A-Za-z0-9_]` in the `override` block of \" +\n \"penv.config.ts.\",\n );\n}\n\nfunction caseCollision(upper: string, parameters: readonly string[]): GithubNameError {\n return new GithubNameError(\n \"case-collision\",\n upper,\n parameters,\n `Parameters ${parameters.map((p) => `\\`${p}\\``).join(\" and \")} generate variables that differ only in case, and GitHub secret names are case-insensitive — they would overwrite one another`,\n \"Give one of them a distinct name — not just a different case — in the `override` block of \" +\n \"penv.config.ts.\",\n );\n}\n\n/**\n * Every generated variable that violates GitHub's grammar, collected rather than\n * thrown so a push reports every bad name at once and refuses before the first\n * PUT. Deterministic: per-name violations in variable order, then case\n * collisions in uppercased-key order, each parameter list sorted.\n *\n * Exact-string collisions (two parameters producing the identical variable) are\n * core's `checkNameCollisions`, run before this. Here the concern is GitHub's\n * *case-insensitivity*, which core's exact-string map cannot see.\n */\nexport function checkGithubNames(\n refs: readonly ParameterRef[],\n config: PenvConfig,\n): GithubNameError[] {\n const named = refs.map((ref) => ({\n variable: variableName(ref, config),\n parameter: parameterId(ref),\n }));\n\n const errors: GithubNameError[] = [];\n\n const perName = [...named].sort((a, b) =>\n a.variable < b.variable\n ? -1\n : a.variable > b.variable\n ? 1\n : a.parameter < b.parameter\n ? -1\n : a.parameter > b.parameter\n ? 1\n : 0,\n );\n for (const { variable, parameter } of perName) {\n // Case-insensitively, because GitHub reserves the prefix case-insensitively:\n // an `override` of `github_token` reaches GitHub verbatim and is refused\n // there, so it must be refused here — the same case-folding the collision\n // check below already applies.\n if (variable.toUpperCase().startsWith(RESERVED_PREFIX)) {\n errors.push(reservedPrefix(variable, parameter));\n } else if (LEADING_DIGIT.test(variable)) {\n errors.push(leadingDigit(variable, parameter));\n } else if (!LEGAL_NAME.test(variable)) {\n errors.push(charset(variable, parameter));\n }\n }\n\n const byUpper = new Map<string, string[]>();\n for (const { variable, parameter } of named) {\n const key = variable.toUpperCase();\n const list = byUpper.get(key);\n if (list === undefined) {\n byUpper.set(key, [parameter]);\n } else {\n list.push(parameter);\n }\n }\n for (const key of [...byUpper.keys()].sort()) {\n const parameters = byUpper.get(key);\n if (parameters === undefined || parameters.length < 2) {\n continue;\n }\n errors.push(caseCollision(key, [...parameters].sort()));\n }\n\n return errors;\n}\n","/**\n * The plugin seam: what the penv CLI calls when a `providers.*.type` names this\n * package. The factory owns the translation from the config's provider-agnostic\n * surface (`location`) to this provider's own options, so the config never\n * learns GitHub vocabulary and the provider never parses config.\n */\n\nimport type { ProjectionProvider, ProviderFactoryContext } from \"@penvhq/core\";\nimport { createGithubProvider } from \"./github.js\";\n\ndeclare module \"@penvhq/core\" {\n interface ProviderConfigMap {\n \"@penvhq/provider-github\": {\n /**\n * The repository penv maps the projection onto — `owner/repo`. Left\n * unset, `gh` resolves it from the working directory.\n */\n readonly location?: string;\n };\n }\n}\n\n/** Builds the GitHub provider for one environment's declared destination. */\nexport function penvProviderFactory(context: ProviderFactoryContext): ProjectionProvider {\n const location = context.providerConfig?.location;\n return createGithubProvider(location === undefined ? {} : { repo: location });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@penvhq/provider-github",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "The GitHub Actions Secrets provider — a projection-holding, value-withholding store penv pushes to through the gh CLI.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@penvhq/core": "0.5.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup"
|
|
25
|
+
}
|
|
26
|
+
}
|