@penvhq/cli 0.13.0 → 0.15.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/dist/bin.cjs +281 -292
- package/dist/bin.cjs.map +1 -1
- package/dist/{chunk-OBIHFXCK.js → chunk-4RR6UFVZ.js} +48 -23
- package/dist/chunk-4RR6UFVZ.js.map +1 -0
- package/dist/index.cjs +137 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -7
- package/dist/index.d.ts +1 -7
- package/dist/index.js +120 -135
- package/dist/index.js.map +1 -1
- package/dist/install.cjs +48 -22
- package/dist/install.cjs.map +1 -1
- package/dist/install.d.cts +29 -4
- package/dist/install.d.ts +29 -4
- package/dist/install.js +3 -1
- package/package.json +7 -7
- package/dist/chunk-OBIHFXCK.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
planInstall,
|
|
8
8
|
renderInstallPlan,
|
|
9
9
|
startChild
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4RR6UFVZ.js";
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import { setKeychain } from "@penvhq/core";
|
|
@@ -37,6 +37,7 @@ import { dirname, join as join2 } from "path";
|
|
|
37
37
|
import {
|
|
38
38
|
assertMigrated,
|
|
39
39
|
candidatesFor,
|
|
40
|
+
environmentEntry as environmentEntry2,
|
|
40
41
|
formatValueFile,
|
|
41
42
|
isCanonicalSegment,
|
|
42
43
|
isReservedToken,
|
|
@@ -55,24 +56,12 @@ import {
|
|
|
55
56
|
import { FilesystemProvider } from "@penvhq/provider-filesystem";
|
|
56
57
|
|
|
57
58
|
// src/env-flags.ts
|
|
58
|
-
import { PenvError } from "@penvhq/core";
|
|
59
|
+
import { environmentNames, PenvError } from "@penvhq/core";
|
|
59
60
|
var BASE_RESERVED = ["help", "version", "h", "v", "_", "--"];
|
|
60
|
-
var COMMAND_FLAGS = [
|
|
61
|
-
...BASE_RESERVED,
|
|
62
|
-
"env",
|
|
63
|
-
"out",
|
|
64
|
-
"allow-decrypt",
|
|
65
|
-
"destination",
|
|
66
|
-
"dest",
|
|
67
|
-
"d",
|
|
68
|
-
"location",
|
|
69
|
-
"l",
|
|
70
|
-
"yes",
|
|
71
|
-
"y"
|
|
72
|
-
];
|
|
61
|
+
var COMMAND_FLAGS = [...BASE_RESERVED, "env", "out", "allow-decrypt", "yes", "y"];
|
|
73
62
|
function shadowedEnvironments(config) {
|
|
74
63
|
const flags = new Set(COMMAND_FLAGS);
|
|
75
|
-
return config.
|
|
64
|
+
return environmentNames(config).filter((environment) => flags.has(environment));
|
|
76
65
|
}
|
|
77
66
|
function shorthandCandidates(args, declared) {
|
|
78
67
|
const reserved = /* @__PURE__ */ new Set([...BASE_RESERVED, ...declared]);
|
|
@@ -85,13 +74,14 @@ function environmentFromShorthand(config, candidates, explicit) {
|
|
|
85
74
|
if (candidates.length === 0) {
|
|
86
75
|
return void 0;
|
|
87
76
|
}
|
|
88
|
-
const
|
|
89
|
-
const
|
|
77
|
+
const declared = environmentNames(config);
|
|
78
|
+
const hits = candidates.filter((candidate) => declared.includes(candidate));
|
|
79
|
+
const strangers = candidates.filter((candidate) => !declared.includes(candidate));
|
|
90
80
|
if (strangers.length > 0) {
|
|
91
81
|
throw new PenvError(
|
|
92
82
|
"UNKNOWN_FLAG",
|
|
93
83
|
`${quoteList(strangers)} ${strangers.length === 1 ? "is not a flag" : "are not flags"} this command takes, and ${strangers.length === 1 ? "names" : "name"} no declared environment`,
|
|
94
|
-
`Declared environments work as bare flags: ${quoteList(
|
|
84
|
+
`Declared environments work as bare flags: ${quoteList(declared)}. Anything else needs \`--env <name>\`.`
|
|
95
85
|
);
|
|
96
86
|
}
|
|
97
87
|
if (hits.length > 1) {
|
|
@@ -118,6 +108,8 @@ import { createRequire } from "module";
|
|
|
118
108
|
import { join, resolve } from "path";
|
|
119
109
|
import { pathToFileURL } from "url";
|
|
120
110
|
import {
|
|
111
|
+
environmentEntry,
|
|
112
|
+
environmentNames as environmentNames2,
|
|
121
113
|
holdsProjection,
|
|
122
114
|
LOCAL_EXTENSIONS_PATH,
|
|
123
115
|
localExtensionsFile,
|
|
@@ -129,6 +121,7 @@ import {
|
|
|
129
121
|
parseLocalExtensions,
|
|
130
122
|
parseManifest,
|
|
131
123
|
penvHome,
|
|
124
|
+
providerMissing,
|
|
132
125
|
recordsDir
|
|
133
126
|
} from "@penvhq/core";
|
|
134
127
|
import { createFilesystemProvider } from "@penvhq/provider-filesystem";
|
|
@@ -195,14 +188,18 @@ async function loadPluginProvider(type, context) {
|
|
|
195
188
|
function assertProvidersRegistered(config, projectRoot, options) {
|
|
196
189
|
const local = localExtensions(projectRoot);
|
|
197
190
|
const ci = options?.ci ?? isCi(process.env.CI);
|
|
198
|
-
for (const
|
|
199
|
-
|
|
191
|
+
for (const environment of environmentNames2(config)) {
|
|
192
|
+
const provider = environmentEntry(config, environment)?.provider;
|
|
193
|
+
if (typeof provider !== "string" || provider.trim().length === 0) {
|
|
194
|
+
throw providerMissing(environment);
|
|
195
|
+
}
|
|
196
|
+
if (isProviderRegistered(provider)) {
|
|
200
197
|
continue;
|
|
201
198
|
}
|
|
202
|
-
if (ci && local.includes(provider
|
|
203
|
-
throw localExtensionInCi(provider
|
|
199
|
+
if (ci && local.includes(provider)) {
|
|
200
|
+
throw localExtensionInCi(provider, environment);
|
|
204
201
|
}
|
|
205
|
-
resolveExtension(provider
|
|
202
|
+
resolveExtension(provider, projectRoot, environment, local);
|
|
206
203
|
}
|
|
207
204
|
}
|
|
208
205
|
function resolveExtension(type, projectRoot, environment, local = localExtensions(projectRoot)) {
|
|
@@ -360,11 +357,11 @@ function schemaShapeFileOf(project) {
|
|
|
360
357
|
return selfContainedSchemaModule(project.root, schemaFile) ?? SCHEMA_SHAPE_FILE;
|
|
361
358
|
}
|
|
362
359
|
async function sourceProviderFor(project, environment) {
|
|
363
|
-
const providerConfig = project.config
|
|
360
|
+
const providerConfig = environmentEntry2(project.config, environment);
|
|
364
361
|
if (providerConfig === void 0) {
|
|
365
362
|
return createProvider(LOCAL_TREE_TYPE, { root: project.root, config: project.config });
|
|
366
363
|
}
|
|
367
|
-
return createSourceProvider(providerConfig.
|
|
364
|
+
return createSourceProvider(providerConfig.provider, {
|
|
368
365
|
root: project.root,
|
|
369
366
|
config: project.config,
|
|
370
367
|
providerConfig,
|
|
@@ -376,7 +373,7 @@ function targetEnvironment(project, explicit, shorthand) {
|
|
|
376
373
|
return resolveEnvironment(project.config, explicit ?? fromFlags);
|
|
377
374
|
}
|
|
378
375
|
var KEY_SEPARATOR = /[./\\]/;
|
|
379
|
-
var NO_ENVIRONMENTS = { environments:
|
|
376
|
+
var NO_ENVIRONMENTS = { environments: {} };
|
|
380
377
|
function refFromKey(key, config) {
|
|
381
378
|
const segments = key.split(KEY_SEPARATOR).filter((segment) => segment.length > 0);
|
|
382
379
|
const name = segments[segments.length - 1];
|
|
@@ -624,6 +621,8 @@ import { basename, dirname as dirname2, join as join3, resolve as resolve2 } fro
|
|
|
624
621
|
import {
|
|
625
622
|
ARTIFACT_BUILD_COMMAND,
|
|
626
623
|
assertArtifactFor,
|
|
624
|
+
environmentEntry as environmentEntry3,
|
|
625
|
+
environmentNames as environmentNames4,
|
|
627
626
|
isPublicVariable,
|
|
628
627
|
isSecret,
|
|
629
628
|
keySourceFrom,
|
|
@@ -648,7 +647,7 @@ import { defineCommand as defineCommand3 } from "citty";
|
|
|
648
647
|
|
|
649
648
|
// src/dotenv-files.ts
|
|
650
649
|
import { readdirSync } from "fs";
|
|
651
|
-
import { isLegalEnvironmentName, RESERVED_TOKENS } from "@penvhq/core";
|
|
650
|
+
import { environmentNames as environmentNames3, isLegalEnvironmentName, RESERVED_TOKENS } from "@penvhq/core";
|
|
652
651
|
var SHARED = ".env";
|
|
653
652
|
var LOCAL = "local";
|
|
654
653
|
var NOT_ENVIRONMENTS = [
|
|
@@ -721,8 +720,9 @@ function discoverDotenvFiles(root) {
|
|
|
721
720
|
return files.sort((a, b) => order(a) < order(b) ? -1 : order(a) > order(b) ? 1 : 0);
|
|
722
721
|
}
|
|
723
722
|
function activeDotenvFiles(root, config) {
|
|
723
|
+
const declared = environmentNames3(config);
|
|
724
724
|
return discoverDotenvFiles(root).filter(
|
|
725
|
-
(file) => file.environment === void 0 ||
|
|
725
|
+
(file) => file.environment === void 0 || declared.includes(file.environment)
|
|
726
726
|
);
|
|
727
727
|
}
|
|
728
728
|
function environmentsDeclaredBy(files) {
|
|
@@ -1480,11 +1480,12 @@ function packageManifest(specifier, root) {
|
|
|
1480
1480
|
}
|
|
1481
1481
|
function declaredCredentials(config, root) {
|
|
1482
1482
|
const declared = {};
|
|
1483
|
-
for (const
|
|
1484
|
-
|
|
1483
|
+
for (const environment of environmentNames4(config)) {
|
|
1484
|
+
const provider = environmentEntry3(config, environment)?.provider;
|
|
1485
|
+
if (provider === void 0 || own(declared, provider) !== void 0) {
|
|
1485
1486
|
continue;
|
|
1486
1487
|
}
|
|
1487
|
-
const penv = packageManifest(provider
|
|
1488
|
+
const penv = packageManifest(provider, root)?.penv;
|
|
1488
1489
|
const credentials = isPlainObject(penv) ? penv.credentials : void 0;
|
|
1489
1490
|
if (credentials === void 0) {
|
|
1490
1491
|
continue;
|
|
@@ -1492,11 +1493,11 @@ function declaredCredentials(config, root) {
|
|
|
1492
1493
|
if (!Array.isArray(credentials) || credentials.some((name) => typeof name !== "string" || !VARIABLE.test(name))) {
|
|
1493
1494
|
throw new PenvError5(
|
|
1494
1495
|
"PROVIDER_CREDENTIALS_INVALID",
|
|
1495
|
-
`\`${provider
|
|
1496
|
+
`\`${provider}\` declares \`penv.credentials\`, and it is not a list of variable names`,
|
|
1496
1497
|
`Its package.json should read \`"penv": { "credentials": ["ACME_TOKEN"] }\` \u2014 penv strips exactly what an extension declares, so it will not guess at a declaration it cannot read.`
|
|
1497
1498
|
);
|
|
1498
1499
|
}
|
|
1499
|
-
declared[provider
|
|
1500
|
+
declared[provider] = credentials;
|
|
1500
1501
|
}
|
|
1501
1502
|
return declared;
|
|
1502
1503
|
}
|
|
@@ -2155,6 +2156,7 @@ import {
|
|
|
2155
2156
|
accessPath as accessPath3,
|
|
2156
2157
|
assertNever,
|
|
2157
2158
|
effectiveMeta,
|
|
2159
|
+
environmentEntry as environmentEntry5,
|
|
2158
2160
|
formatValueFile as formatValueFile3,
|
|
2159
2161
|
holdsProjection as holdsProjection4,
|
|
2160
2162
|
holdsRecords,
|
|
@@ -2174,6 +2176,7 @@ import { defineCommand as defineCommand7 } from "citty";
|
|
|
2174
2176
|
// src/commands/push.ts
|
|
2175
2177
|
import {
|
|
2176
2178
|
checkNameCollisions as checkNameCollisions2,
|
|
2179
|
+
environmentEntry as environmentEntry4,
|
|
2177
2180
|
holdsProjection as holdsProjection3,
|
|
2178
2181
|
PenvError as PenvError8,
|
|
2179
2182
|
recordPath,
|
|
@@ -2227,38 +2230,23 @@ function lineReader(input = process.stdin, output = process.stdout) {
|
|
|
2227
2230
|
// src/commands/push.ts
|
|
2228
2231
|
var LAST_PUSHED_KEY = "lastPushedAt";
|
|
2229
2232
|
async function destinationFor(project, environment, options) {
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
type: options.destination,
|
|
2233
|
-
...options.location === void 0 ? {} : { location: options.location }
|
|
2234
|
-
};
|
|
2235
|
-
const provider2 = options.provider ?? await createSourceProvider(options.destination, {
|
|
2236
|
-
root: project.root,
|
|
2237
|
-
config: project.config,
|
|
2238
|
-
providerConfig,
|
|
2239
|
-
environment
|
|
2240
|
-
});
|
|
2241
|
-
return { provider: provider2, location: options.location };
|
|
2242
|
-
}
|
|
2243
|
-
const declared = project.config.providers[environment];
|
|
2244
|
-
const location = declared?.location;
|
|
2245
|
-
if (declared === void 0 || declared.type === LOCAL_TREE_TYPE) {
|
|
2233
|
+
const declared = environmentEntry4(project.config, environment);
|
|
2234
|
+
if (declared === void 0 || declared.provider === LOCAL_TREE_TYPE) {
|
|
2246
2235
|
throw new PenvError8(
|
|
2247
2236
|
"NO_DESTINATION",
|
|
2248
2237
|
`Environment ${environment}'s provider is the local records tree itself, so penv has nowhere to push`,
|
|
2249
|
-
`Declare
|
|
2238
|
+
`Declare the backend that holds it in penv.config.ts \u2014 e.g. \`${environment}: { provider: "@penvhq/provider-github", repository: "owner/repo" }\` \u2014 and run this again. A push only ever goes where the config says this environment lives.`
|
|
2250
2239
|
);
|
|
2251
2240
|
}
|
|
2252
2241
|
if (options.provider !== void 0) {
|
|
2253
|
-
return
|
|
2242
|
+
return options.provider;
|
|
2254
2243
|
}
|
|
2255
|
-
|
|
2244
|
+
return createSourceProvider(declared.provider, {
|
|
2256
2245
|
root: project.root,
|
|
2257
2246
|
config: project.config,
|
|
2258
2247
|
providerConfig: declared,
|
|
2259
2248
|
environment
|
|
2260
2249
|
});
|
|
2261
|
-
return { provider, location };
|
|
2262
2250
|
}
|
|
2263
2251
|
function plan(resolutions, config, environment, allowDecrypt) {
|
|
2264
2252
|
const outbound = [];
|
|
@@ -2342,7 +2330,7 @@ async function ensureTargetApproved(provider, environment, options) {
|
|
|
2342
2330
|
await provider.ensureTarget(environment);
|
|
2343
2331
|
return true;
|
|
2344
2332
|
}
|
|
2345
|
-
async function pushProjection(project, provider, environment,
|
|
2333
|
+
async function pushProjection(project, provider, environment, options) {
|
|
2346
2334
|
const tree = localTree(project);
|
|
2347
2335
|
const keys = keySourceFor(project, environment);
|
|
2348
2336
|
const resolutions = resolveAllSync(tree, environment, keys, true);
|
|
@@ -2373,7 +2361,6 @@ async function pushProjection(project, provider, environment, location, options)
|
|
|
2373
2361
|
environment,
|
|
2374
2362
|
destination: provider.type,
|
|
2375
2363
|
mode: "projection",
|
|
2376
|
-
location,
|
|
2377
2364
|
pushed: outbound.length,
|
|
2378
2365
|
meta: 0,
|
|
2379
2366
|
repositorySecrets,
|
|
@@ -2389,7 +2376,7 @@ function crossesToRecords(file, environment) {
|
|
|
2389
2376
|
}
|
|
2390
2377
|
return scope.kind === "environment" && scope.environment === environment;
|
|
2391
2378
|
}
|
|
2392
|
-
async function pushRecords(project, provider, environment
|
|
2379
|
+
async function pushRecords(project, provider, environment) {
|
|
2393
2380
|
const tree = localTree(project);
|
|
2394
2381
|
const files = tree.listSync().filter((file) => crossesToRecords(file, environment));
|
|
2395
2382
|
let pushed = 0;
|
|
@@ -2415,7 +2402,6 @@ async function pushRecords(project, provider, environment, location) {
|
|
|
2415
2402
|
environment,
|
|
2416
2403
|
destination: provider.type,
|
|
2417
2404
|
mode: "records",
|
|
2418
|
-
location,
|
|
2419
2405
|
pushed,
|
|
2420
2406
|
meta,
|
|
2421
2407
|
repositorySecrets: 0,
|
|
@@ -2427,11 +2413,11 @@ async function pushRecords(project, provider, environment, location) {
|
|
|
2427
2413
|
async function runPush(options) {
|
|
2428
2414
|
const project = openProject(options.cwd);
|
|
2429
2415
|
const environment = targetEnvironment(project, options.environment, options.envFlags);
|
|
2430
|
-
const
|
|
2416
|
+
const provider = await destinationFor(project, environment, options);
|
|
2431
2417
|
if (holdsProjection3(provider)) {
|
|
2432
|
-
return pushProjection(project, provider, environment,
|
|
2418
|
+
return pushProjection(project, provider, environment, options);
|
|
2433
2419
|
}
|
|
2434
|
-
return pushRecords(project, provider, environment
|
|
2420
|
+
return pushRecords(project, provider, environment);
|
|
2435
2421
|
}
|
|
2436
2422
|
function renderPush(result2) {
|
|
2437
2423
|
if (result2.pushed === 0 && result2.meta === 0) {
|
|
@@ -2443,7 +2429,7 @@ function renderPush(result2) {
|
|
|
2443
2429
|
}
|
|
2444
2430
|
]);
|
|
2445
2431
|
}
|
|
2446
|
-
const target = `${result2.destination} for environment ${result2.environment}
|
|
2432
|
+
const target = `${result2.destination} for environment ${result2.environment}`;
|
|
2447
2433
|
if (result2.mode === "records") {
|
|
2448
2434
|
return formatRows([
|
|
2449
2435
|
{
|
|
@@ -2492,6 +2478,19 @@ function renderPush(result2) {
|
|
|
2492
2478
|
}
|
|
2493
2479
|
return formatRows(rows);
|
|
2494
2480
|
}
|
|
2481
|
+
var REDIRECT_FLAGS = ["destination", "dest", "d", "location", "l"];
|
|
2482
|
+
function assertNoRedirect(args) {
|
|
2483
|
+
const given = REDIRECT_FLAGS.filter((flag) => args[flag] !== void 0);
|
|
2484
|
+
const named = given[0];
|
|
2485
|
+
if (named === void 0) {
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
throw new PenvError8(
|
|
2489
|
+
"REMOVED_FLAG",
|
|
2490
|
+
`\`--${named}\` is not a flag \`penv push\` takes`,
|
|
2491
|
+
"A push only ever goes where the config says this environment lives. Declare the destination in `environments.<env>` \u2014 the provider package, addressed in that provider's own words \u2014 and push with `--env <name>`."
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2495
2494
|
var pushCommand = defineCommand6({
|
|
2496
2495
|
meta: {
|
|
2497
2496
|
name: "push",
|
|
@@ -2503,16 +2502,6 @@ var pushCommand = defineCommand6({
|
|
|
2503
2502
|
type: "boolean",
|
|
2504
2503
|
description: "Decrypt sealed values locally and push them as plaintext for the destination to re-seal"
|
|
2505
2504
|
},
|
|
2506
|
-
destination: {
|
|
2507
|
-
type: "string",
|
|
2508
|
-
alias: ["dest", "d"],
|
|
2509
|
-
description: "Push once to this provider package instead of the declared one"
|
|
2510
|
-
},
|
|
2511
|
-
location: {
|
|
2512
|
-
type: "string",
|
|
2513
|
-
alias: "l",
|
|
2514
|
-
description: "The destination-side place, when --destination needs one"
|
|
2515
|
-
},
|
|
2516
2505
|
yes: {
|
|
2517
2506
|
type: "boolean",
|
|
2518
2507
|
alias: "y",
|
|
@@ -2521,25 +2510,13 @@ var pushCommand = defineCommand6({
|
|
|
2521
2510
|
},
|
|
2522
2511
|
run({ args }) {
|
|
2523
2512
|
return guard(async () => {
|
|
2513
|
+
assertNoRedirect(args);
|
|
2524
2514
|
const result2 = await runPush({
|
|
2525
2515
|
cwd: process.cwd(),
|
|
2526
2516
|
...args.env === void 0 ? {} : { environment: args.env },
|
|
2527
2517
|
...args["allow-decrypt"] === void 0 ? {} : { allowDecrypt: args["allow-decrypt"] },
|
|
2528
|
-
...args.destination === void 0 ? {} : { destination: args.destination },
|
|
2529
|
-
...args.location === void 0 ? {} : { location: args.location },
|
|
2530
2518
|
...args.yes === void 0 ? {} : { yes: args.yes },
|
|
2531
|
-
envFlags: shorthandCandidates(args, [
|
|
2532
|
-
"env",
|
|
2533
|
-
"allow-decrypt",
|
|
2534
|
-
"allowDecrypt",
|
|
2535
|
-
"destination",
|
|
2536
|
-
"dest",
|
|
2537
|
-
"d",
|
|
2538
|
-
"location",
|
|
2539
|
-
"l",
|
|
2540
|
-
"yes",
|
|
2541
|
-
"y"
|
|
2542
|
-
])
|
|
2519
|
+
envFlags: shorthandCandidates(args, ["env", "allow-decrypt", "allowDecrypt", "yes", "y"])
|
|
2543
2520
|
});
|
|
2544
2521
|
write(renderPush(result2));
|
|
2545
2522
|
});
|
|
@@ -2638,7 +2615,7 @@ async function runDoctor(options) {
|
|
|
2638
2615
|
check: "provider",
|
|
2639
2616
|
severity: "pass",
|
|
2640
2617
|
label: "Provider",
|
|
2641
|
-
subject: project.config
|
|
2618
|
+
subject: environmentEntry5(project.config, environment)?.provider ?? project.provider.type
|
|
2642
2619
|
});
|
|
2643
2620
|
return {
|
|
2644
2621
|
environment,
|
|
@@ -2908,8 +2885,8 @@ async function projectionFindings(project, environment, override) {
|
|
|
2908
2885
|
if (override !== void 0) {
|
|
2909
2886
|
projection = override;
|
|
2910
2887
|
} else {
|
|
2911
|
-
const declared = project.config
|
|
2912
|
-
if (declared === void 0 || declared.
|
|
2888
|
+
const declared = environmentEntry5(project.config, environment);
|
|
2889
|
+
if (declared === void 0 || declared.provider === LOCAL_TREE_TYPE) {
|
|
2913
2890
|
return [];
|
|
2914
2891
|
}
|
|
2915
2892
|
let source;
|
|
@@ -2921,7 +2898,7 @@ async function projectionFindings(project, environment, override) {
|
|
|
2921
2898
|
check: "projection-unreachable",
|
|
2922
2899
|
severity: "unknown",
|
|
2923
2900
|
label: "Destination",
|
|
2924
|
-
subject: `could not build the ${declared.
|
|
2901
|
+
subject: `could not build the ${declared.provider} provider for ${environment}`,
|
|
2925
2902
|
detail: errorDetail(error)
|
|
2926
2903
|
}
|
|
2927
2904
|
];
|
|
@@ -3194,8 +3171,8 @@ function refPathOf(ref) {
|
|
|
3194
3171
|
return [...ref.namespace, ref.name].join("/");
|
|
3195
3172
|
}
|
|
3196
3173
|
async function rotationSubjects(project, environment, local, override) {
|
|
3197
|
-
const providerConfig = project.config
|
|
3198
|
-
if (override === void 0 && (providerConfig === void 0 || providerConfig.
|
|
3174
|
+
const providerConfig = environmentEntry5(project.config, environment);
|
|
3175
|
+
if (override === void 0 && (providerConfig === void 0 || providerConfig.provider === LOCAL_TREE_TYPE)) {
|
|
3199
3176
|
return { kind: "read", subjects: local };
|
|
3200
3177
|
}
|
|
3201
3178
|
const source = override ?? await sourceProviderFor(project, environment);
|
|
@@ -3217,7 +3194,7 @@ async function rotationSubjects(project, environment, local, override) {
|
|
|
3217
3194
|
check: "rotation-overdue",
|
|
3218
3195
|
severity: "unknown",
|
|
3219
3196
|
label: "Rotation",
|
|
3220
|
-
subject: `could not reach the ${providerConfig?.
|
|
3197
|
+
subject: `could not reach the ${providerConfig?.provider ?? source.type} source of truth for ${environment}`,
|
|
3221
3198
|
detail: errorDetail(error)
|
|
3222
3199
|
}
|
|
3223
3200
|
};
|
|
@@ -3338,8 +3315,8 @@ async function readRelevant(provider, environment) {
|
|
|
3338
3315
|
return entries;
|
|
3339
3316
|
}
|
|
3340
3317
|
async function providerDriftFindings(project, environment, override) {
|
|
3341
|
-
const providerConfig = project.config
|
|
3342
|
-
if (providerConfig === void 0 || providerConfig.
|
|
3318
|
+
const providerConfig = environmentEntry5(project.config, environment);
|
|
3319
|
+
if (providerConfig === void 0 || providerConfig.provider === LOCAL_TREE_TYPE) {
|
|
3343
3320
|
return [
|
|
3344
3321
|
{
|
|
3345
3322
|
check: "provider-value-drift",
|
|
@@ -3364,7 +3341,7 @@ async function providerDriftFindings(project, environment, override) {
|
|
|
3364
3341
|
check: "provider-value-drift",
|
|
3365
3342
|
severity: "unknown",
|
|
3366
3343
|
label: "Provider values",
|
|
3367
|
-
subject: `could not reach the ${providerConfig.
|
|
3344
|
+
subject: `could not reach the ${providerConfig.provider} provider for ${environment}`,
|
|
3368
3345
|
detail: errorDetail(error)
|
|
3369
3346
|
}
|
|
3370
3347
|
];
|
|
@@ -3383,7 +3360,7 @@ async function providerDriftFindings(project, environment, override) {
|
|
|
3383
3360
|
severity: "unknown",
|
|
3384
3361
|
label: "Provider value unreadable",
|
|
3385
3362
|
subject: formatValueFile3(here.file),
|
|
3386
|
-
detail: `the local value is sealed and did not open, so it cannot be compared against the ${providerConfig.
|
|
3363
|
+
detail: `the local value is sealed and did not open, so it cannot be compared against the ${providerConfig.provider} source of truth`
|
|
3387
3364
|
});
|
|
3388
3365
|
continue;
|
|
3389
3366
|
}
|
|
@@ -3393,7 +3370,7 @@ async function providerDriftFindings(project, environment, override) {
|
|
|
3393
3370
|
severity: "failure",
|
|
3394
3371
|
label: "Provider value drift",
|
|
3395
3372
|
subject: formatValueFile3(here.file),
|
|
3396
|
-
detail: `the local tree and the ${providerConfig.
|
|
3373
|
+
detail: `the local tree and the ${providerConfig.provider} source of truth hold different values`
|
|
3397
3374
|
});
|
|
3398
3375
|
}
|
|
3399
3376
|
continue;
|
|
@@ -3407,7 +3384,7 @@ async function providerDriftFindings(project, environment, override) {
|
|
|
3407
3384
|
severity: "warning",
|
|
3408
3385
|
label: here !== void 0 ? "Only in the local tree" : "Only in the source",
|
|
3409
3386
|
subject: formatValueFile3(present.file),
|
|
3410
|
-
detail: here !== void 0 ? `present locally, absent from the ${providerConfig.
|
|
3387
|
+
detail: here !== void 0 ? `present locally, absent from the ${providerConfig.provider} source of truth` : `present in the ${providerConfig.provider} source of truth, absent from the local tree`
|
|
3411
3388
|
});
|
|
3412
3389
|
}
|
|
3413
3390
|
if (findings.length > 0) {
|
|
@@ -3418,7 +3395,7 @@ async function providerDriftFindings(project, environment, override) {
|
|
|
3418
3395
|
check: "provider-value-drift",
|
|
3419
3396
|
severity: "pass",
|
|
3420
3397
|
label: "Provider values",
|
|
3421
|
-
subject: `every value matches the ${providerConfig.
|
|
3398
|
+
subject: `every value matches the ${providerConfig.provider} source of truth for ${environment}`
|
|
3422
3399
|
}
|
|
3423
3400
|
];
|
|
3424
3401
|
}
|
|
@@ -3506,6 +3483,7 @@ import { defineCommand as defineCommand9 } from "citty";
|
|
|
3506
3483
|
|
|
3507
3484
|
// src/commands/set.ts
|
|
3508
3485
|
import {
|
|
3486
|
+
environmentNames as environmentNames5,
|
|
3509
3487
|
formatValueFile as formatValueFile4,
|
|
3510
3488
|
isSecret as isSecret4,
|
|
3511
3489
|
PenvError as PenvError9,
|
|
@@ -3535,7 +3513,7 @@ function targetScope(project, options, key) {
|
|
|
3535
3513
|
throw new PenvError9(
|
|
3536
3514
|
"ENVIRONMENT_FLAG_EMPTY",
|
|
3537
3515
|
`\`--env\` for parameter ${key} names no environment`,
|
|
3538
|
-
`Pass a declared environment \u2014 ${project.config.
|
|
3516
|
+
`Pass a declared environment \u2014 ${environmentNames5(project.config).map((e) => `\`${e}\``).join(", ")} \u2014 e.g. \`--env production\`, or drop \`--env\` to write the scope that has no environment.`
|
|
3539
3517
|
);
|
|
3540
3518
|
}
|
|
3541
3519
|
return scopeFrom({ ...options, environment: targetEnvironment(project, environment) });
|
|
@@ -3548,7 +3526,7 @@ function sealFor(project, file, value, parameter, environment) {
|
|
|
3548
3526
|
throw new PenvError9(
|
|
3549
3527
|
"SECRET_SCOPE_AMBIGUOUS",
|
|
3550
3528
|
`Parameter ${parameter} is a secret, and ${recordPath2(formatValueFile4(file))} names no environment`,
|
|
3551
|
-
"
|
|
3529
|
+
"A key is declared by its environment's `keySource` in penv.config.ts, so penv cannot tell which key should seal a file that every environment reads. Write it at an environment scope \u2014 add `--env <environment>` \u2014 or drop `secret` from the parameter's meta."
|
|
3552
3530
|
);
|
|
3553
3531
|
}
|
|
3554
3532
|
return sealValue(file, value, keySourceFor(project, environment), parameter, environment);
|
|
@@ -4127,6 +4105,7 @@ import { copyFileSync, existsSync as existsSync7, readFileSync as readFileSync9
|
|
|
4127
4105
|
import { basename as basename2, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve7 } from "path";
|
|
4128
4106
|
import {
|
|
4129
4107
|
assertNever as assertNever2,
|
|
4108
|
+
environmentNames as environmentNames7,
|
|
4130
4109
|
FilenameGrammarError,
|
|
4131
4110
|
findConfigFile as findConfigFile2,
|
|
4132
4111
|
loadConfigFrom as loadConfigFrom2,
|
|
@@ -4383,6 +4362,7 @@ import { createInterface as createInterface2 } from "readline/promises";
|
|
|
4383
4362
|
import {
|
|
4384
4363
|
CUTOVER_PATH as CUTOVER_PATH3,
|
|
4385
4364
|
DEFAULT_SCHEMA_FILE as DEFAULT_SCHEMA_FILE2,
|
|
4365
|
+
environmentNames as environmentNames6,
|
|
4386
4366
|
isLegalEnvironmentName as isLegalEnvironmentName2,
|
|
4387
4367
|
loadConfigFrom,
|
|
4388
4368
|
PENV_DIR as PENV_DIR2,
|
|
@@ -4687,8 +4667,14 @@ function environmentsFromFlag(flag) {
|
|
|
4687
4667
|
}
|
|
4688
4668
|
return [...new Set(names)];
|
|
4689
4669
|
}
|
|
4670
|
+
function environmentsRecord(names) {
|
|
4671
|
+
return Object.fromEntries(names.map((environment) => [environment, LOCAL_PROVIDER]));
|
|
4672
|
+
}
|
|
4690
4673
|
function configOf(decisions) {
|
|
4691
|
-
return {
|
|
4674
|
+
return {
|
|
4675
|
+
environments: environmentsRecord(decisions.environments),
|
|
4676
|
+
schemaFile: decisions.schemaFile
|
|
4677
|
+
};
|
|
4692
4678
|
}
|
|
4693
4679
|
function declaredIn(root) {
|
|
4694
4680
|
const file = join8(root, CONFIG_FILE);
|
|
@@ -4720,7 +4706,7 @@ function planInit(root, flags = {}) {
|
|
|
4720
4706
|
if (schemaFile.length === 0) {
|
|
4721
4707
|
throw emptyFlag("schema");
|
|
4722
4708
|
}
|
|
4723
|
-
const error = validateSchemaFile({ environments:
|
|
4709
|
+
const error = validateSchemaFile({ environments: {}, schemaFile })[0];
|
|
4724
4710
|
if (error !== void 0) {
|
|
4725
4711
|
throw error;
|
|
4726
4712
|
}
|
|
@@ -4743,7 +4729,7 @@ function planInit(root, flags = {}) {
|
|
|
4743
4729
|
}
|
|
4744
4730
|
const suggestedEnvironments = suggestEnvironments(root);
|
|
4745
4731
|
const safeDefault = flags.yes === true && declared === void 0;
|
|
4746
|
-
const environments = flags.environments ?? declared
|
|
4732
|
+
const environments = flags.environments ?? (declared === void 0 ? void 0 : environmentNames6(declared)) ?? (safeDefault ? [DEVELOPMENT] : []);
|
|
4747
4733
|
const defaultEnvironment = declared?.defaultEnvironment ?? (safeDefault ? DEVELOPMENT : void 0);
|
|
4748
4734
|
if (safeDefault) {
|
|
4749
4735
|
notes.push(
|
|
@@ -4876,27 +4862,27 @@ export { schema };
|
|
|
4876
4862
|
|
|
4877
4863
|
` + loadComment + loadCall;
|
|
4878
4864
|
}
|
|
4865
|
+
function entryKey(name) {
|
|
4866
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
4867
|
+
}
|
|
4879
4868
|
function renderEnvironments(decisions) {
|
|
4880
|
-
const shared = " // Environments are a whitelist
|
|
4869
|
+
const shared = " // Environments are a whitelist, and each entry is that environment's whole\n // declaration: which provider holds it, in that provider's own words, and\n // which key seals it. A segment is an environment only if it is named here.\n";
|
|
4881
4870
|
if (decisions.environments.length === 0) {
|
|
4882
4871
|
return `${shared} // It starts empty because which environments you deploy is not something penv
|
|
4883
4872
|
// can read off your codebase, and an environment you do not have is worse
|
|
4884
|
-
// than one you have not declared yet. Name yours
|
|
4885
|
-
// environments:
|
|
4886
|
-
//
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4873
|
+
// than one you have not declared yet. Name yours:
|
|
4874
|
+
// environments: {
|
|
4875
|
+
// development: "@penvhq/provider-filesystem",
|
|
4876
|
+
// production: { provider: "@penvhq/provider-vault", path: "penv", keySource: "env" },
|
|
4877
|
+
// },
|
|
4878
|
+
environments: {},
|
|
4890
4879
|
`;
|
|
4891
4880
|
}
|
|
4892
|
-
const
|
|
4893
|
-
const providers = decisions.environments.map((name) => ` ${JSON.stringify(name)}: { type: "@penvhq/provider-filesystem" },
|
|
4881
|
+
const entries = decisions.environments.map((name) => ` ${entryKey(name)}: ${JSON.stringify(LOCAL_PROVIDER)},
|
|
4894
4882
|
`).join("");
|
|
4895
|
-
return `${shared}
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
providers: {
|
|
4899
|
-
${providers} },
|
|
4883
|
+
return `${shared} // A provider that needs no fields is just its package name.
|
|
4884
|
+
environments: {
|
|
4885
|
+
${entries} },
|
|
4900
4886
|
`;
|
|
4901
4887
|
}
|
|
4902
4888
|
function renderConfigModule(decisions) {
|
|
@@ -5415,13 +5401,13 @@ function planCutover(input) {
|
|
|
5415
5401
|
const declared = declaredIn(root);
|
|
5416
5402
|
const named = environmentsDeclaredBy(selected);
|
|
5417
5403
|
const chosen = named.length > 0 ? named : [requireEnvironment(input)];
|
|
5418
|
-
const environments = declared === void 0 ? chosen : declared
|
|
5404
|
+
const environments = declared === void 0 ? chosen : environmentNames6(declared);
|
|
5419
5405
|
for (const environment of chosen) {
|
|
5420
5406
|
if (!environments.includes(environment)) {
|
|
5421
5407
|
throw new PenvError15(
|
|
5422
5408
|
"INIT_ENVIRONMENT_UNDECLARED",
|
|
5423
5409
|
`${CONFIG_FILE} declares ${describeList(environments)}, and adopting these files needs \`${environment}\` declared too`,
|
|
5424
|
-
`Add \`${environment}\` to \`environments\` in ${CONFIG_FILE},
|
|
5410
|
+
`Add \`${environment}: "${LOCAL_PROVIDER}"\` to \`environments\` in ${CONFIG_FILE}, or name the backend that holds it, then run \`penv init\` again. Nothing was changed.`
|
|
5425
5411
|
);
|
|
5426
5412
|
}
|
|
5427
5413
|
}
|
|
@@ -5488,10 +5474,7 @@ function distinct(refs) {
|
|
|
5488
5474
|
}
|
|
5489
5475
|
function configFor(decisions) {
|
|
5490
5476
|
return {
|
|
5491
|
-
environments: decisions.environments,
|
|
5492
|
-
providers: Object.fromEntries(
|
|
5493
|
-
decisions.environments.map((environment) => [environment, { type: LOCAL_PROVIDER }])
|
|
5494
|
-
),
|
|
5477
|
+
environments: environmentsRecord(decisions.environments),
|
|
5495
5478
|
schemaFile: decisions.schemaFile,
|
|
5496
5479
|
...decisions.defaultEnvironment === void 0 ? {} : { defaultEnvironment: decisions.defaultEnvironment },
|
|
5497
5480
|
...decisions.publicPrefixes.length === 0 ? {} : { publicPrefixes: [...decisions.publicPrefixes] }
|
|
@@ -5831,7 +5814,8 @@ function offeredEnvironment(root) {
|
|
|
5831
5814
|
if (declared === void 0) {
|
|
5832
5815
|
return DEVELOPMENT;
|
|
5833
5816
|
}
|
|
5834
|
-
|
|
5817
|
+
const names = environmentNames6(declared);
|
|
5818
|
+
return declared.defaultEnvironment ?? (names.includes(DEVELOPMENT) ? DEVELOPMENT : names[0] ?? DEVELOPMENT);
|
|
5835
5819
|
}
|
|
5836
5820
|
async function cutoverWithYes(root, base, adoption) {
|
|
5837
5821
|
const selected = selectionForYes(adoption);
|
|
@@ -5939,10 +5923,11 @@ var BACKUP_SUFFIX = ".backup";
|
|
|
5939
5923
|
var DOTENV_SEGMENT = "env";
|
|
5940
5924
|
var LOCAL2 = "local";
|
|
5941
5925
|
function assertDeclared(segment, config) {
|
|
5942
|
-
|
|
5926
|
+
const declared = environmentNames7(config);
|
|
5927
|
+
if (declared.includes(segment)) {
|
|
5943
5928
|
return segment;
|
|
5944
5929
|
}
|
|
5945
|
-
throw new UnknownEnvironmentError(segment,
|
|
5930
|
+
throw new UnknownEnvironmentError(segment, declared);
|
|
5946
5931
|
}
|
|
5947
5932
|
function scopeFromFilename(file, config) {
|
|
5948
5933
|
const name = basename2(file);
|
|
@@ -6012,7 +5997,7 @@ function explicitEnvironment(options, source, config) {
|
|
|
6012
5997
|
throw new PenvError16(
|
|
6013
5998
|
"IMPORT_ENV_FLAG_EMPTY",
|
|
6014
5999
|
`\`--env\` for the import of ${source} names no environment`,
|
|
6015
|
-
`Pass a declared environment \u2014 ${config.
|
|
6000
|
+
`Pass a declared environment \u2014 ${environmentNames7(config).map((e) => `\`${e}\``).join(", ")} \u2014 e.g. \`--env production\`, or drop \`--env\` to import ${source} as the scope that has no environment. Nothing was imported.`
|
|
6016
6001
|
);
|
|
6017
6002
|
}
|
|
6018
6003
|
function assertEnvironmentAgrees(derived, explicit, source) {
|
|
@@ -6039,7 +6024,7 @@ function environmentNamed(file, explicit) {
|
|
|
6039
6024
|
}
|
|
6040
6025
|
function decisionsOf(config, cwd) {
|
|
6041
6026
|
return {
|
|
6042
|
-
environments: config
|
|
6027
|
+
environments: environmentNames7(config),
|
|
6043
6028
|
// `import` re-scaffolds env.ts for an existing project; injection is an init
|
|
6044
6029
|
// choice, so it is not turned on here.
|
|
6045
6030
|
inject: false,
|
|
@@ -6095,7 +6080,7 @@ function importDotenv(options) {
|
|
|
6095
6080
|
backup,
|
|
6096
6081
|
scope,
|
|
6097
6082
|
environment,
|
|
6098
|
-
environments: config
|
|
6083
|
+
environments: environmentNames7(config),
|
|
6099
6084
|
variables: parsed.entries.length,
|
|
6100
6085
|
orphanComments: parsed.orphanComments,
|
|
6101
6086
|
steps
|
|
@@ -6190,7 +6175,7 @@ var importCommand = defineCommand14({
|
|
|
6190
6175
|
|
|
6191
6176
|
// src/commands/key.ts
|
|
6192
6177
|
import { randomBytes } from "crypto";
|
|
6193
|
-
import { KEY_BYTES, KEYCHAIN_SERVICE, PenvError as PenvError18 } from "@penvhq/core";
|
|
6178
|
+
import { KEY_BYTES, KEYCHAIN_SERVICE, keyConfigFor, PenvError as PenvError18 } from "@penvhq/core";
|
|
6194
6179
|
import { defineCommand as defineCommand15 } from "citty";
|
|
6195
6180
|
|
|
6196
6181
|
// src/keychain.ts
|
|
@@ -6242,12 +6227,12 @@ function envVarFor(id) {
|
|
|
6242
6227
|
function runKeyCreate(options) {
|
|
6243
6228
|
const project = openProject(options.cwd);
|
|
6244
6229
|
const environment = targetEnvironment(project, options.environment);
|
|
6245
|
-
const declared = project.config
|
|
6230
|
+
const declared = keyConfigFor(project.config, environment);
|
|
6246
6231
|
if (declared === void 0) {
|
|
6247
6232
|
throw new PenvError18(
|
|
6248
6233
|
"KEY_SOURCE_UNDECLARED",
|
|
6249
6234
|
`Environment ${environment} declares no key source, so penv does not know what a key for it would be`,
|
|
6250
|
-
`
|
|
6235
|
+
`Give its entry in penv.config.ts a \`keySource\` \u2014 e.g. \`${environment}: { provider: "@penvhq/provider-filesystem", keySource: "env" }\` \u2014 then run this again. The key is named after the environment unless the object form names another.`
|
|
6251
6236
|
);
|
|
6252
6237
|
}
|
|
6253
6238
|
const key = randomBytes(KEY_BYTES).toString("base64");
|
|
@@ -6616,7 +6601,7 @@ async function planFile(project, source, target, parameter) {
|
|
|
6616
6601
|
throw new PenvError20(
|
|
6617
6602
|
"SECRET_SCOPE_AMBIGUOUS",
|
|
6618
6603
|
`${recordPath7(formatValueFile6(source))} is encrypted at a scope that names no environment, so penv cannot tell which key would re-seal it`,
|
|
6619
|
-
"
|
|
6604
|
+
"A key is declared by its environment's `keySource` in penv.config.ts. Decrypt it with `penv decrypt`, move the parameter, then encrypt it again at its new address."
|
|
6620
6605
|
);
|
|
6621
6606
|
}
|
|
6622
6607
|
const keys = keySourceFor(project, environment);
|