@penvhq/cli 0.2.0 → 0.3.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/index.cjs CHANGED
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  insertEnvAlias: () => insertEnvAlias,
27
27
  main: () => main,
28
28
  renderDoctor: () => renderDoctor,
29
+ renderFill: () => renderFill,
29
30
  renderMove: () => renderMove,
30
31
  renderPull: () => renderPull,
31
32
  renderPush: () => renderPush,
@@ -35,6 +36,7 @@ __export(index_exports, {
35
36
  runDoctor: () => runDoctor,
36
37
  runEncrypt: () => runEncrypt,
37
38
  runExplain: () => runExplain,
39
+ runFill: () => runFill,
38
40
  runGenerate: () => runGenerate,
39
41
  runGet: () => runGet,
40
42
  runInit: () => runInit,
@@ -50,8 +52,8 @@ __export(index_exports, {
50
52
  runWatch: () => runWatch
51
53
  });
52
54
  module.exports = __toCommonJS(index_exports);
53
- var import_core21 = require("@penvhq/core");
54
- var import_citty17 = require("citty");
55
+ var import_core22 = require("@penvhq/core");
56
+ var import_citty18 = require("citty");
55
57
 
56
58
  // src/commands/doctor.ts
57
59
  var import_core7 = require("@penvhq/core");
@@ -69,13 +71,20 @@ var import_node_path = require("path");
69
71
  var import_node_url = require("url");
70
72
  var import_core = require("@penvhq/core");
71
73
  var import_provider_filesystem = require("@penvhq/provider-filesystem");
74
+ var import_provider_kubernetes = require("@penvhq/provider-kubernetes");
72
75
  var import_provider_mock = require("@penvhq/provider-mock");
76
+ var import_provider_ssm = require("@penvhq/provider-ssm");
73
77
  var import_provider_vault = require("@penvhq/provider-vault");
74
78
  var PLUGIN_FACTORY_EXPORT = "penvProviderFactory";
75
79
  var LOCAL_TREE_TYPE = "filesystem";
76
80
  var REGISTRY = /* @__PURE__ */ new Map([
77
81
  [LOCAL_TREE_TYPE, ({ root, config }) => (0, import_provider_filesystem.createFilesystemProvider)({ root, config })],
78
82
  ["vault", ({ providerConfig }) => (0, import_provider_vault.createVaultProvider)({ path: providerConfig?.path ?? "penv" })],
83
+ ["ssm", ({ providerConfig }) => (0, import_provider_ssm.createSsmProvider)({ path: providerConfig?.path ?? "penv" })],
84
+ [
85
+ "kubernetes",
86
+ ({ providerConfig }) => (0, import_provider_kubernetes.createKubernetesProvider)(kubernetesOptions(providerConfig))
87
+ ],
79
88
  ["mock", ({ root }) => (0, import_provider_mock.createMockProvider)({ storePath: (0, import_node_path.resolve)(root, ".penv-mock.json") })]
80
89
  ]);
81
90
  var CONTRACT_METHODS = [
@@ -178,6 +187,14 @@ function assertSatisfiesContract(provider, specifier) {
178
187
  }
179
188
  }
180
189
  }
190
+ function kubernetesOptions(providerConfig) {
191
+ const path = providerConfig?.path ?? "penv";
192
+ const slash = path.indexOf("/");
193
+ if (slash === -1) return { secretName: path };
194
+ const namespace = path.slice(0, slash);
195
+ const secretName = path.slice(slash + 1) || "penv";
196
+ return namespace === "" ? { secretName } : { namespace, secretName };
197
+ }
181
198
  function unknownProvider(type, environment, specifier) {
182
199
  const known = [...REGISTRY.keys()].map((name) => `\`${name}\``).join(", ");
183
200
  const where = environment === void 0 ? "" : ` for environment ${environment}`;
@@ -246,6 +263,26 @@ function refFromKey(key, config) {
246
263
  }
247
264
  return { namespace: segments.slice(0, -1), name };
248
265
  }
266
+ function assertWritableKey(key) {
267
+ const segments = key.split(KEY_SEPARATOR).filter((s) => s.length > 0);
268
+ if (segments.length === 0 || segments.every((s) => (0, import_core2.isCanonicalSegment)(s))) {
269
+ return;
270
+ }
271
+ const ref = (0, import_core2.refFromAccessPath)(segments);
272
+ if (ref !== void 0) {
273
+ const suggestion = [...ref.namespace, ref.name].join("/");
274
+ throw new import_core2.PenvError(
275
+ "PARAMETER_KEY_CASING",
276
+ `\`${key}\` is not a canonical parameter name`,
277
+ `Parameter files are lower-case and hyphenated. Did you mean \`${suggestion}\`? That is the file that backs the \`${key}\` key in your schema.`
278
+ );
279
+ }
280
+ throw new import_core2.PenvError(
281
+ "PARAMETER_KEY_UNREACHABLE",
282
+ `No value file can be named that reaches \`${key}\``,
283
+ "Parameter files are lower-case and hyphenated, and this key maps to no such file \u2014 a run of capitals like `apiURL` cannot be reached (use `api-url`, which the schema reads as `apiUrl`). Run `penv validate` or `penv fill` to see the names penv expects."
284
+ );
285
+ }
249
286
  function keySourceFor(project, environment) {
250
287
  return (0, import_core2.resolveKeySource)(project.config, environment);
251
288
  }
@@ -1739,6 +1776,7 @@ async function sealAwareWrite(options) {
1739
1776
  }
1740
1777
  async function runSet(options) {
1741
1778
  const project = openProject(options.cwd);
1779
+ assertWritableKey(options.key);
1742
1780
  const ref = refFromKey(options.key, project.config);
1743
1781
  const scope = targetScope(project, options, options.key);
1744
1782
  const environment = policyEnvironment(project, options);
@@ -1941,17 +1979,122 @@ var decryptCommand = (0, import_citty5.defineCommand)({
1941
1979
  }
1942
1980
  });
1943
1981
 
1982
+ // src/commands/fill.ts
1983
+ var import_promises = require("readline/promises");
1984
+ var import_core10 = require("@penvhq/core");
1985
+ var import_citty6 = require("citty");
1986
+ var BLOCKING = /* @__PURE__ */ new Set(["config", "collision", "reserved"]);
1987
+ async function runFill(options) {
1988
+ const validation = await runValidate({
1989
+ cwd: options.cwd,
1990
+ ...options.environment === void 0 ? {} : { environment: options.environment }
1991
+ });
1992
+ const environment = validation.environment;
1993
+ const blockers = validation.issues.filter((issue) => BLOCKING.has(issue.kind));
1994
+ if (blockers.length > 0) {
1995
+ const detail = blockers.map(
1996
+ (issue) => ` - ${issue.message}${issue.remedy === void 0 ? "" : ` (${issue.remedy})`}`
1997
+ ).join("\n");
1998
+ throw new import_core10.PenvError(
1999
+ "FILL_BLOCKED",
2000
+ `penv fill cannot run: environment ${environment} has ${blockers.length} unresolved configuration ${blockers.length === 1 ? "issue" : "issues"}:
2001
+ ${detail}`,
2002
+ `Fix these \u2014 \`penv validate\` reports them \u2014 then run \`penv fill\`. If you have not written a schema yet, declare the required parameters in ${PENV_DIR}/env.ts.`
2003
+ );
2004
+ }
2005
+ const written = [];
2006
+ const skipped2 = [];
2007
+ const unreachable = [];
2008
+ for (const drift of validation.drift.declared) {
2009
+ if (drift.ref === void 0) {
2010
+ unreachable.push({ subject: drift.subject, remedy: drift.remedy });
2011
+ continue;
2012
+ }
2013
+ const ref = drift.ref;
2014
+ const key = [...ref.namespace, ref.name].join("/");
2015
+ const value = await options.ask({ parameter: key, environment, secret: false });
2016
+ if (value === void 0 || value === "") {
2017
+ skipped2.push(drift.subject);
2018
+ continue;
2019
+ }
2020
+ const result2 = await runSet({ cwd: options.cwd, key, value, environment });
2021
+ written.push({
2022
+ parameter: drift.subject,
2023
+ location: result2.location,
2024
+ encrypted: result2.encrypted
2025
+ });
2026
+ }
2027
+ return { environment, written, skipped: skipped2, unreachable };
2028
+ }
2029
+ function summaryLine(result2) {
2030
+ const filled = result2.written.length;
2031
+ if (filled === 0 && result2.skipped.length === 0 && result2.unreachable.length === 0) {
2032
+ return `Nothing to fill for environment ${result2.environment}: every declared parameter has a value`;
2033
+ }
2034
+ const parts = [`${filled} written`];
2035
+ if (result2.skipped.length > 0) {
2036
+ parts.push(`${result2.skipped.length} skipped`);
2037
+ }
2038
+ if (result2.unreachable.length > 0) {
2039
+ parts.push(`${result2.unreachable.length} unreachable`);
2040
+ }
2041
+ return `${parts.join(", ")} for environment ${result2.environment}`;
2042
+ }
2043
+ function renderFill(result2) {
2044
+ const rows = result2.written.map((entry) => ({
2045
+ glyph: CHECK,
2046
+ label: "Wrote",
2047
+ subject: `${PENV_DIR}/${entry.location}`,
2048
+ // Meta decided the seal, not the answer, so the line says so — as `set` does.
2049
+ ...entry.encrypted ? { detail: "encrypted, per the parameter's meta policy" } : {}
2050
+ }));
2051
+ for (const entry of result2.unreachable) {
2052
+ rows.push({ glyph: WARN, label: "Unreachable", subject: entry.subject, detail: entry.remedy });
2053
+ }
2054
+ const lines = formatRows(rows);
2055
+ lines.push(summaryLine(result2));
2056
+ return lines;
2057
+ }
2058
+ var fillCommand = (0, import_citty6.defineCommand)({
2059
+ meta: {
2060
+ name: "fill",
2061
+ description: "Prompt for each declared parameter the tree has no value for"
2062
+ },
2063
+ args: {
2064
+ env: { type: "string", description: "The environment to fill" }
2065
+ },
2066
+ run({ args }) {
2067
+ return guard(async () => {
2068
+ const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
2069
+ const ask = (prompt) => rl.question(`${prompt.parameter} (${prompt.environment}): `);
2070
+ try {
2071
+ write(
2072
+ renderFill(
2073
+ await runFill({
2074
+ cwd: process.cwd(),
2075
+ ...args.env === void 0 ? {} : { environment: args.env },
2076
+ ask
2077
+ })
2078
+ )
2079
+ );
2080
+ } finally {
2081
+ rl.close();
2082
+ }
2083
+ });
2084
+ }
2085
+ });
2086
+
1944
2087
  // src/commands/generate.ts
1945
2088
  var import_node_fs = require("fs");
1946
2089
  var import_node_path4 = require("path");
1947
- var import_core10 = require("@penvhq/core");
1948
- var import_citty6 = require("citty");
2090
+ var import_core11 = require("@penvhq/core");
2091
+ var import_citty7 = require("citty");
1949
2092
  var DEFAULT_OUTPUT = ".env";
1950
2093
  function entriesFor(project, environment, allowDecrypt) {
1951
2094
  const keys = keySourceFor(project, environment);
1952
2095
  const tree = localTree(project);
1953
2096
  const resolutions = resolveAllSync(tree, environment, keys);
1954
- const collision = (0, import_core10.checkNameCollisions)(
2097
+ const collision = (0, import_core11.checkNameCollisions)(
1955
2098
  refsFrom(resolutions.map((resolution) => resolution.ref)),
1956
2099
  project.config
1957
2100
  )[0];
@@ -1964,21 +2107,21 @@ function entriesFor(project, environment, allowDecrypt) {
1964
2107
  const winner = resolution.winner;
1965
2108
  if (winner?.file.encrypted === true) {
1966
2109
  if (!allowDecrypt) {
1967
- throw new import_core10.PenvError(
2110
+ throw new import_core11.PenvError(
1968
2111
  "ENCRYPTED_VALUE_REFUSED",
1969
2112
  `Parameter ${resolution.parameter} for environment ${environment} resolves to the encrypted value file ${PENV_DIR}/${winner.location}, and \`penv generate\` writes plaintext`,
1970
2113
  `Re-run with \`--allow-decrypt\` to write the decrypted value into the artifact, or generate for an environment whose values are plaintext. The artifact is gitignored; a committed plaintext secret is a \`penv doctor\` failure.`
1971
2114
  );
1972
2115
  }
1973
- (0, import_core10.requireValue)(resolution, environment);
2116
+ (0, import_core11.requireValue)(resolution, environment);
1974
2117
  decrypted += 1;
1975
2118
  }
1976
2119
  if (resolution.value === void 0) {
1977
2120
  continue;
1978
2121
  }
1979
- const description = (0, import_core10.effectiveMeta)(tree.readMetaSync(resolution.ref), environment).description;
2122
+ const description = (0, import_core11.effectiveMeta)(tree.readMetaSync(resolution.ref), environment).description;
1980
2123
  entries.push({
1981
- key: (0, import_core10.variableName)(resolution.ref, project.config),
2124
+ key: (0, import_core11.variableName)(resolution.ref, project.config),
1982
2125
  value: resolution.value,
1983
2126
  ...typeof description === "string" ? { description } : {}
1984
2127
  });
@@ -1989,14 +2132,14 @@ function entriesFor(project, environment, allowDecrypt) {
1989
2132
  function generateDotenv(options) {
1990
2133
  const project = openProject(options.cwd);
1991
2134
  const environment = targetEnvironment(project, options.environment);
1992
- return (0, import_core10.serializeDotenv)(entriesFor(project, environment, options.allowDecrypt === true).entries);
2135
+ return (0, import_core11.serializeDotenv)(entriesFor(project, environment, options.allowDecrypt === true).entries);
1993
2136
  }
1994
2137
  function runGenerate(options) {
1995
2138
  const project = openProject(options.cwd);
1996
2139
  const environment = targetEnvironment(project, options.environment);
1997
2140
  const { entries, decrypted } = entriesFor(project, environment, options.allowDecrypt === true);
1998
2141
  const file = options.out === void 0 ? (0, import_node_path4.resolve)(project.root, DEFAULT_OUTPUT) : (0, import_node_path4.isAbsolute)(options.out) ? options.out : (0, import_node_path4.resolve)(options.cwd, options.out);
1999
- (0, import_node_fs.writeFileSync)(file, (0, import_core10.serializeDotenv)(entries), "utf8");
2142
+ (0, import_node_fs.writeFileSync)(file, (0, import_core11.serializeDotenv)(entries), "utf8");
2000
2143
  return { file, environment, entries: entries.length, decrypted };
2001
2144
  }
2002
2145
  function displayPath(cwd, file) {
@@ -2022,7 +2165,7 @@ function renderGenerate(result2, cwd) {
2022
2165
  }
2023
2166
  return formatRows(rows);
2024
2167
  }
2025
- var generateCommand = (0, import_citty6.defineCommand)({
2168
+ var generateCommand = (0, import_citty7.defineCommand)({
2026
2169
  meta: { name: "generate", description: "Write a standard .env artifact for deploy targets" },
2027
2170
  args: {
2028
2171
  env: { type: "string", description: "The environment to generate for" },
@@ -2047,17 +2190,17 @@ var generateCommand = (0, import_citty6.defineCommand)({
2047
2190
  });
2048
2191
 
2049
2192
  // src/commands/get.ts
2050
- var import_core11 = require("@penvhq/core");
2051
- var import_citty7 = require("citty");
2193
+ var import_core12 = require("@penvhq/core");
2194
+ var import_citty8 = require("citty");
2052
2195
  async function runGet(options) {
2053
2196
  const project = openProject(options.cwd);
2054
2197
  const environment = targetEnvironment(project, options.environment);
2055
2198
  const ref = refFromKey(options.key);
2056
2199
  const keys = keySourceFor(project, environment);
2057
- const resolution = await (0, import_core11.resolveParameter)(ref, environment, project.provider, keys);
2058
- const value = (0, import_core11.requireValue)(resolution, environment);
2200
+ const resolution = await (0, import_core12.resolveParameter)(ref, environment, project.provider, keys);
2201
+ const value = (0, import_core12.requireValue)(resolution, environment);
2059
2202
  if (value === void 0) {
2060
- throw new import_core11.PenvError(
2203
+ throw new import_core12.PenvError(
2061
2204
  "PARAMETER_ABSENT",
2062
2205
  `Parameter ${resolution.parameter} resolves to no value for environment ${environment}`,
2063
2206
  `Set it with \`penv set ${options.key} --env ${environment}\`, or run \`penv get ${options.key} --env ${environment} --explain\` to see every file penv looked at.`
@@ -2079,7 +2222,7 @@ async function runExplain(options) {
2079
2222
  const environment = targetEnvironment(project, options.environment);
2080
2223
  const ref = refFromKey(options.key);
2081
2224
  const keys = keySourceFor(project, environment);
2082
- const resolution = await (0, import_core11.resolveParameter)(ref, environment, project.provider, keys);
2225
+ const resolution = await (0, import_core12.resolveParameter)(ref, environment, project.provider, keys);
2083
2226
  const winner = resolution.winner;
2084
2227
  return {
2085
2228
  parameter: resolution.parameter,
@@ -2107,7 +2250,7 @@ function renderExplain(explanation) {
2107
2250
  ...columns(rows).map((line) => ` ${line}`)
2108
2251
  ];
2109
2252
  }
2110
- var getCommand = (0, import_citty7.defineCommand)({
2253
+ var getCommand = (0, import_citty8.defineCommand)({
2111
2254
  meta: { name: "get", description: "Read a parameter" },
2112
2255
  args: {
2113
2256
  key: { type: "positional", required: true, description: "The parameter, e.g. redis/password" },
@@ -2133,13 +2276,13 @@ var getCommand = (0, import_citty7.defineCommand)({
2133
2276
  // src/commands/import.ts
2134
2277
  var import_node_fs4 = require("fs");
2135
2278
  var import_node_path7 = require("path");
2136
- var import_core14 = require("@penvhq/core");
2137
- var import_citty9 = require("citty");
2279
+ var import_core15 = require("@penvhq/core");
2280
+ var import_citty10 = require("citty");
2138
2281
 
2139
2282
  // src/detect.ts
2140
2283
  var import_node_fs2 = require("fs");
2141
2284
  var import_node_path5 = require("path");
2142
- var import_core12 = require("@penvhq/core");
2285
+ var import_core13 = require("@penvhq/core");
2143
2286
  var SIGNATURES = [
2144
2287
  { name: "Next.js", packages: ["next"], publicPrefixes: ["NEXT_PUBLIC_"] },
2145
2288
  {
@@ -2177,7 +2320,7 @@ function schemaFileFor(cwd) {
2177
2320
  if (!occupied(cwd, beside)) {
2178
2321
  return { file: beside, displaced: preferred };
2179
2322
  }
2180
- return { file: import_core12.DEFAULT_SCHEMA_FILE, displaced: preferred };
2323
+ return { file: import_core13.DEFAULT_SCHEMA_FILE, displaced: preferred };
2181
2324
  }
2182
2325
  function dependenciesOf(cwd) {
2183
2326
  const manifest = manifestOf(cwd);
@@ -2240,9 +2383,9 @@ function hasImportsBlock(cwd) {
2240
2383
  // src/commands/init.ts
2241
2384
  var import_node_fs3 = require("fs");
2242
2385
  var import_node_path6 = require("path");
2243
- var import_promises = require("readline/promises");
2244
- var import_core13 = require("@penvhq/core");
2245
- var import_citty8 = require("citty");
2386
+ var import_promises2 = require("readline/promises");
2387
+ var import_core14 = require("@penvhq/core");
2388
+ var import_citty9 = require("citty");
2246
2389
  var CONFIG_FILE = "penv.config.ts";
2247
2390
  var TSCONFIG_FILE = "tsconfig.json";
2248
2391
  var GITIGNORE_FILE = ".gitignore";
@@ -2252,11 +2395,11 @@ var IMPORTS_PREFIX = "#";
2252
2395
  var PACKAGE_FILE = "package.json";
2253
2396
  var DEFAULT_DECISIONS = {
2254
2397
  environments: [],
2255
- schemaFile: import_core13.DEFAULT_SCHEMA_FILE,
2398
+ schemaFile: import_core14.DEFAULT_SCHEMA_FILE,
2256
2399
  publicPrefixes: [],
2257
2400
  alias: DEFAULT_ALIAS
2258
2401
  };
2259
- var NOT_ENVIRONMENTS = [...import_core13.RESERVED_TOKENS, "example", "sample", "template"];
2402
+ var NOT_ENVIRONMENTS = [...import_core14.RESERVED_TOKENS, "example", "sample", "template"];
2260
2403
  function suggestEnvironments(root) {
2261
2404
  let entries;
2262
2405
  try {
@@ -2272,7 +2415,7 @@ function suggestEnvironments(root) {
2272
2415
  const segments = entry.slice(".env.".length).split(".");
2273
2416
  const withoutLocal = segments.at(-1) === "local" ? segments.slice(0, -1) : segments;
2274
2417
  const name = withoutLocal.length === 1 ? withoutLocal[0] : void 0;
2275
- if (name === void 0 || !(0, import_core13.isLegalEnvironmentName)(name) || NOT_ENVIRONMENTS.includes(name)) {
2418
+ if (name === void 0 || !(0, import_core14.isLegalEnvironmentName)(name) || NOT_ENVIRONMENTS.includes(name)) {
2276
2419
  continue;
2277
2420
  }
2278
2421
  found.add(name);
@@ -2280,10 +2423,10 @@ function suggestEnvironments(root) {
2280
2423
  return [...found].sort();
2281
2424
  }
2282
2425
  function emptyFlag(flag) {
2283
- return new import_core13.PenvError(
2426
+ return new import_core14.PenvError(
2284
2427
  "INIT_FLAG_EMPTY",
2285
2428
  `\`--${flag}\` was given without a value`,
2286
- flag === "schema" ? `Name the module that exports the schema, e.g. \`--schema src/env.ts\`, or drop the flag to use ${import_core13.DEFAULT_SCHEMA_FILE}.` : "Name the environment, e.g. `--env production`, or drop the flag to leave the whitelist empty and declare it in penv.config.ts."
2429
+ flag === "schema" ? `Name the module that exports the schema, e.g. \`--schema src/env.ts\`, or drop the flag to use ${import_core14.DEFAULT_SCHEMA_FILE}.` : "Name the environment, e.g. `--env production`, or drop the flag to leave the whitelist empty and declare it in penv.config.ts."
2287
2430
  );
2288
2431
  }
2289
2432
  function splitEnvironments(value) {
@@ -2308,7 +2451,7 @@ function declaredIn(root) {
2308
2451
  if (!(0, import_node_fs3.existsSync)(file)) {
2309
2452
  return void 0;
2310
2453
  }
2311
- return (0, import_core13.loadConfigFrom)(file);
2454
+ return (0, import_core14.loadConfigFrom)(file);
2312
2455
  }
2313
2456
  function planInit(root, flags = {}) {
2314
2457
  const declared = declaredIn(root);
@@ -2318,7 +2461,7 @@ function planInit(root, flags = {}) {
2318
2461
  notes.push(`${CONFIG_FILE} already exists \u2014 init keeps every decision it records.`);
2319
2462
  } else if (detected === void 0) {
2320
2463
  notes.push(
2321
- `No framework detected in package.json \u2014 the schema goes to ${import_core13.DEFAULT_SCHEMA_FILE}.`
2464
+ `No framework detected in package.json \u2014 the schema goes to ${import_core14.DEFAULT_SCHEMA_FILE}.`
2322
2465
  );
2323
2466
  } else {
2324
2467
  notes.push(`Detected ${detected.name}.`);
@@ -2328,12 +2471,12 @@ function planInit(root, flags = {}) {
2328
2471
  );
2329
2472
  }
2330
2473
  }
2331
- const schemaFile = flags.schema === void 0 ? declared !== void 0 ? (0, import_core13.schemaFileOf)(declared) : detected?.schemaFile ?? import_core13.DEFAULT_SCHEMA_FILE : flags.schema.trim();
2474
+ const schemaFile = flags.schema === void 0 ? declared !== void 0 ? (0, import_core14.schemaFileOf)(declared) : detected?.schemaFile ?? import_core14.DEFAULT_SCHEMA_FILE : flags.schema.trim();
2332
2475
  if (flags.schema !== void 0) {
2333
2476
  if (schemaFile.length === 0) {
2334
2477
  throw emptyFlag("schema");
2335
2478
  }
2336
- const error = (0, import_core13.validateSchemaFile)({ environments: [], providers: {}, schemaFile })[0];
2479
+ const error = (0, import_core14.validateSchemaFile)({ environments: [], providers: {}, schemaFile })[0];
2337
2480
  if (error !== void 0) {
2338
2481
  throw error;
2339
2482
  }
@@ -2343,7 +2486,7 @@ function planInit(root, flags = {}) {
2343
2486
  throw emptyFlag("alias");
2344
2487
  }
2345
2488
  if (!ALIAS_NAME.test(alias)) {
2346
- throw new import_core13.PenvError(
2489
+ throw new import_core14.PenvError(
2347
2490
  "INIT_ALIAS_INVALID",
2348
2491
  `\`${alias}\` is not an alias penv can write`,
2349
2492
  `An alias is \`@name\` \u2014 a tsconfig \`paths\` entry a bundler resolves \u2014 or \`#name\`, a package.json \`imports\` entry Node resolves itself. Those are the two things a module specifier can be that is not a package.`
@@ -2388,7 +2531,7 @@ function renderPlan(plan2) {
2388
2531
  rows.push([
2389
2532
  " schemaFile",
2390
2533
  plan2.decisions.schemaFile,
2391
- plan2.decisions.schemaFile === import_core13.DEFAULT_SCHEMA_FILE ? "" : `(default: ${import_core13.DEFAULT_SCHEMA_FILE})`
2534
+ plan2.decisions.schemaFile === import_core14.DEFAULT_SCHEMA_FILE ? "" : `(default: ${import_core14.DEFAULT_SCHEMA_FILE})`
2392
2535
  ]);
2393
2536
  for (const prefix of plan2.decisions.publicPrefixes) {
2394
2537
  rows.push([" publicPrefix", prefix, ""]);
@@ -2462,7 +2605,7 @@ ${providers} },
2462
2605
  }
2463
2606
  function renderConfigModule(decisions) {
2464
2607
  let body = renderEnvironments(decisions);
2465
- if (decisions.schemaFile !== import_core13.DEFAULT_SCHEMA_FILE) {
2608
+ if (decisions.schemaFile !== import_core14.DEFAULT_SCHEMA_FILE) {
2466
2609
  body += `
2467
2610
  // The module that exports the schema. It is yours \u2014 penv scaffolds it once
2468
2611
  // and never regenerates it \u2014 so this says where you keep it.
@@ -2485,7 +2628,7 @@ ${body}});
2485
2628
  `;
2486
2629
  }
2487
2630
  function renderGitignore(decisions) {
2488
- const inside = (0, import_core13.schemaInsideTree)(configOf(decisions));
2631
+ const inside = (0, import_core14.schemaInsideTree)(configOf(decisions));
2489
2632
  const listed = inside === void 0 ? "" : `${inside}, `;
2490
2633
  return `# Written by penv. Value files hold configuration values and are never
2491
2634
  # committed; only the structure, ${listed}meta, and config are.
@@ -2630,13 +2773,13 @@ ${objectIndent}${source.slice(close)}`;
2630
2773
  ${entryIndent}${member},${source.slice(open + 1)}`;
2631
2774
  }
2632
2775
  function shapeError(what, target, alias) {
2633
- return new import_core13.PenvError(
2776
+ return new import_core14.PenvError(
2634
2777
  "TSCONFIG_SHAPE",
2635
2778
  `penv cannot add the \`${alias}\` path alias to tsconfig.json: ${what}`,
2636
2779
  `Add it by hand: \`{ "compilerOptions": { "paths": { "${alias}": ["${target}"] } } }\`.`
2637
2780
  );
2638
2781
  }
2639
- function insertEnvAlias(source, target = import_core13.DEFAULT_SCHEMA_FILE, name = DEFAULT_ALIAS) {
2782
+ function insertEnvAlias(source, target = import_core14.DEFAULT_SCHEMA_FILE, name = DEFAULT_ALIAS) {
2640
2783
  const alias = `"${name}": ["${target}"]`;
2641
2784
  const root = skipTrivia(source, 0);
2642
2785
  if (source.charAt(root) !== "{") {
@@ -2824,7 +2967,7 @@ function renderInit(result2) {
2824
2967
  ];
2825
2968
  }
2826
2969
  async function askOnTty(plan2) {
2827
- const rl = (0, import_promises.createInterface)({ input: process.stdin, output: process.stdout });
2970
+ const rl = (0, import_promises2.createInterface)({ input: process.stdin, output: process.stdout });
2828
2971
  try {
2829
2972
  return await promptForDecisions(plan2, {
2830
2973
  ask: (question) => rl.question(question),
@@ -2835,7 +2978,7 @@ async function askOnTty(plan2) {
2835
2978
  rl.close();
2836
2979
  }
2837
2980
  }
2838
- var initCommand = (0, import_citty8.defineCommand)({
2981
+ var initCommand = (0, import_citty9.defineCommand)({
2839
2982
  meta: { name: "init", description: "Initialize a project (.penv/, env.ts, config, @env alias)" },
2840
2983
  args: {
2841
2984
  yes: {
@@ -2844,7 +2987,7 @@ var initCommand = (0, import_citty8.defineCommand)({
2844
2987
  },
2845
2988
  schema: {
2846
2989
  type: "string",
2847
- description: `Where the schema module goes, e.g. src/env.ts (default: ${import_core13.DEFAULT_SCHEMA_FILE})`
2990
+ description: `Where the schema module goes, e.g. src/env.ts (default: ${import_core14.DEFAULT_SCHEMA_FILE})`
2848
2991
  },
2849
2992
  alias: {
2850
2993
  type: "string",
@@ -2898,7 +3041,7 @@ function inferType(value) {
2898
3041
  }
2899
3042
  function draftFields(entries) {
2900
3043
  return entries.map((entry) => {
2901
- const key = (0, import_core14.accessPath)((0, import_core14.refFromVariable)(entry.key)).join(".");
3044
+ const key = (0, import_core15.accessPath)((0, import_core15.refFromVariable)(entry.key)).join(".");
2902
3045
  return {
2903
3046
  key: IDENTIFIER.test(key) ? key : JSON.stringify(key),
2904
3047
  type: inferType(entry.value)
@@ -2909,33 +3052,33 @@ function assertImportable(ref, variable) {
2909
3052
  if (!ref.name.includes(".")) {
2910
3053
  return;
2911
3054
  }
2912
- throw new import_core14.PenvError(
3055
+ throw new import_core15.PenvError(
2913
3056
  "IMPORT_UNPARSEABLE_NAME",
2914
3057
  `The variable ${variable} becomes the parameter \`${ref.name}\`, whose \`.\` would be read as a scope`,
2915
3058
  `Filenames are split on \`.\`. Rename ${variable} in the source file, then import it again.`
2916
3059
  );
2917
3060
  }
2918
3061
  function assertNotReserved(ref, variable, where, config) {
2919
- if ((0, import_core14.isReservedToken)(ref.name, config)) {
2920
- throw new import_core14.ReservedTokenError("parameter", variable, where);
3062
+ if ((0, import_core15.isReservedToken)(ref.name, config)) {
3063
+ throw new import_core15.ReservedTokenError("parameter", variable, where);
2921
3064
  }
2922
3065
  }
2923
3066
  function assertRoundTrips(ref, variable, config) {
2924
- if ((0, import_core14.roundTripsCleanly)(variable)) {
3067
+ if ((0, import_core15.roundTripsCleanly)(variable)) {
2925
3068
  return;
2926
3069
  }
2927
- if ((0, import_core14.variableName)(ref, config) === variable) {
3070
+ if ((0, import_core15.variableName)(ref, config) === variable) {
2928
3071
  return;
2929
3072
  }
2930
- const generated = (0, import_core14.variableName)(ref, config);
2931
- throw new import_core14.PenvError(
3073
+ const generated = (0, import_core15.variableName)(ref, config);
3074
+ throw new import_core15.PenvError(
2932
3075
  "IMPORT_LOSSY_NAME",
2933
3076
  `The variable ${variable} becomes the parameter \`${ref.name}\`, which regenerates as ${generated}`,
2934
3077
  `\`penv generate\` would write ${generated}, so anything reading \`process.env["${variable}"]\` would read \`undefined\`. Declare the name you want in the \`names\` block of penv.config.ts \u2014 \`names: { "${ref.name}": "${variable}" }\` \u2014 then import it again. Nothing was imported.`
2935
3078
  );
2936
3079
  }
2937
3080
  function collisionsIn(refs, config) {
2938
- const errors = (0, import_core14.checkNameCollisions)(refs, config);
3081
+ const errors = (0, import_core15.checkNameCollisions)(refs, config);
2939
3082
  const first = errors[0];
2940
3083
  if (first !== void 0) {
2941
3084
  throw first;
@@ -2945,7 +3088,7 @@ function assertDeclared(segment, config) {
2945
3088
  if (config.environments.includes(segment)) {
2946
3089
  return segment;
2947
3090
  }
2948
- throw new import_core14.UnknownEnvironmentError(segment, config.environments);
3091
+ throw new import_core15.UnknownEnvironmentError(segment, config.environments);
2949
3092
  }
2950
3093
  function scopeFromFilename(file, config) {
2951
3094
  const name = (0, import_node_path7.basename)(file);
@@ -2970,13 +3113,13 @@ function scopeFromFilename(file, config) {
2970
3113
  return { kind: "environment-local", environment: assertDeclared(first, config) };
2971
3114
  }
2972
3115
  if (first === LOCAL && rest.length === 2) {
2973
- throw new import_core14.FilenameGrammarError(
3116
+ throw new import_core15.FilenameGrammarError(
2974
3117
  name,
2975
3118
  "`local` precedes the environment segment",
2976
3119
  `The environment segment always precedes \`local\` \u2014 \`.env.${second}.${LOCAL}\` is the file Next.js and Vite read, and \`.env.${LOCAL}.${second}\` is not a synonym for it. Rename it to \`.env.${second}.${LOCAL}\`, then import it again. Nothing was imported.`
2977
3120
  );
2978
3121
  }
2979
- throw new import_core14.FilenameGrammarError(
3122
+ throw new import_core15.FilenameGrammarError(
2980
3123
  name,
2981
3124
  `\`${rest.join("` and `")}\` are ${rest.length} scope segments`,
2982
3125
  "A dotenv file carries exactly one scope: `.env`, `.env.<environment>`, `.env.local`, or `.env.<environment>.local`. Point `penv import` at one of those. Nothing was imported."
@@ -2991,7 +3134,7 @@ function environmentOf(scope) {
2991
3134
  case "local":
2992
3135
  return void 0;
2993
3136
  default:
2994
- return (0, import_core14.assertNever)(scope, "scope");
3137
+ return (0, import_core15.assertNever)(scope, "scope");
2995
3138
  }
2996
3139
  }
2997
3140
  function scopeWithEnvironment(scope, environment) {
@@ -3004,7 +3147,7 @@ function scopeWithEnvironment(scope, environment) {
3004
3147
  case "environment-local":
3005
3148
  return scope;
3006
3149
  default:
3007
- return (0, import_core14.assertNever)(scope, "scope");
3150
+ return (0, import_core15.assertNever)(scope, "scope");
3008
3151
  }
3009
3152
  }
3010
3153
  function explicitEnvironment(options, source, config) {
@@ -3012,7 +3155,7 @@ function explicitEnvironment(options, source, config) {
3012
3155
  if (value.length > 0) {
3013
3156
  return value;
3014
3157
  }
3015
- throw new import_core14.PenvError(
3158
+ throw new import_core15.PenvError(
3016
3159
  "IMPORT_ENV_FLAG_EMPTY",
3017
3160
  `\`--env\` for the import of ${source} names no environment`,
3018
3161
  `Pass a declared environment \u2014 ${config.environments.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.`
@@ -3022,7 +3165,7 @@ function assertEnvironmentAgrees(derived, explicit, source) {
3022
3165
  if (derived === void 0 || explicit === void 0 || derived === explicit) {
3023
3166
  return;
3024
3167
  }
3025
- throw new import_core14.PenvError(
3168
+ throw new import_core15.PenvError(
3026
3169
  "IMPORT_ENV_CONFLICT",
3027
3170
  `The file ${source} is scoped to environment ${derived}, but \`--env ${explicit}\` names ${explicit}`,
3028
3171
  `Drop \`--env\` to import ${source} as ${derived}, pass \`--env ${derived}\` to say the same thing twice, or point \`penv import\` at the file that holds ${explicit}'s values. Nothing was imported.`
@@ -3043,15 +3186,15 @@ function environmentNamed(file, explicit) {
3043
3186
  function decisionsOf(config, cwd) {
3044
3187
  return {
3045
3188
  environments: config.environments,
3046
- schemaFile: (0, import_core14.schemaFileOf)(config),
3189
+ schemaFile: (0, import_core15.schemaFileOf)(config),
3047
3190
  publicPrefixes: config.publicPrefixes ?? [],
3048
3191
  alias: detectAlias(cwd)
3049
3192
  };
3050
3193
  }
3051
3194
  function configInEffect(cwd, environment) {
3052
- const existing = (0, import_core14.findConfigFile)(cwd);
3195
+ const existing = (0, import_core15.findConfigFile)(cwd);
3053
3196
  if (existing !== void 0) {
3054
- const config = (0, import_core14.loadConfigFrom)(existing);
3197
+ const config = (0, import_core15.loadConfigFrom)(existing);
3055
3198
  return { config, decisions: decisionsOf(config, cwd) };
3056
3199
  }
3057
3200
  const planned = planInit(cwd).decisions;
@@ -3066,13 +3209,13 @@ function importDotenv(options) {
3066
3209
  const cwd = (0, import_node_path7.resolve)(options.cwd);
3067
3210
  const file = (0, import_node_path7.isAbsolute)(options.file) ? options.file : (0, import_node_path7.resolve)(cwd, options.file);
3068
3211
  if (!(0, import_node_fs4.existsSync)(file)) {
3069
- throw new import_core14.PenvError(
3212
+ throw new import_core15.PenvError(
3070
3213
  "IMPORT_FILE_MISSING",
3071
3214
  `There is no file at ${file} to import`,
3072
3215
  "Point `penv import` at an existing dotenv file, e.g. `penv import .env`."
3073
3216
  );
3074
3217
  }
3075
- const parsed = (0, import_core14.parseDotenv)((0, import_node_fs4.readFileSync)(file, "utf8"));
3218
+ const parsed = (0, import_core15.parseDotenv)((0, import_node_fs4.readFileSync)(file, "utf8"));
3076
3219
  const { config, decisions } = configInEffect(cwd, environmentNamed(file, options.environment));
3077
3220
  const source = displayPath2(cwd, file);
3078
3221
  const named = scopeFromFilename(file, config);
@@ -3080,10 +3223,10 @@ function importDotenv(options) {
3080
3223
  const explicit = options.environment === void 0 ? void 0 : explicitEnvironment(options, source, config);
3081
3224
  assertEnvironmentAgrees(derived, explicit, source);
3082
3225
  const scope = explicit === void 0 ? named : scopeWithEnvironment(named, assertDeclared(explicit, config));
3083
- const environment = (0, import_core14.lookupEnvironment)(config, explicit ?? derived);
3226
+ const environment = (0, import_core15.lookupEnvironment)(config, explicit ?? derived);
3084
3227
  const refs = [];
3085
3228
  for (const entry of parsed.entries) {
3086
- const ref = (0, import_core14.refFromVariable)(entry.key);
3229
+ const ref = (0, import_core15.refFromVariable)(entry.key);
3087
3230
  assertImportable(ref, entry.key);
3088
3231
  assertNotReserved(ref, entry.key, source, config);
3089
3232
  assertRoundTrips(ref, entry.key, config);
@@ -3179,7 +3322,7 @@ function renderImport(result2, validation) {
3179
3322
  lines.push("", "Done. .penv/ is now your source of truth.");
3180
3323
  return lines;
3181
3324
  }
3182
- var importCommand = (0, import_citty9.defineCommand)({
3325
+ var importCommand = (0, import_citty10.defineCommand)({
3183
3326
  meta: {
3184
3327
  name: "import",
3185
3328
  description: "Import an existing dotenv file; it becomes the source of truth"
@@ -3211,8 +3354,8 @@ var importCommand = (0, import_citty9.defineCommand)({
3211
3354
 
3212
3355
  // src/commands/key.ts
3213
3356
  var import_node_crypto = require("crypto");
3214
- var import_core15 = require("@penvhq/core");
3215
- var import_citty10 = require("citty");
3357
+ var import_core16 = require("@penvhq/core");
3358
+ var import_citty11 = require("citty");
3216
3359
 
3217
3360
  // src/keychain.ts
3218
3361
  var import_node_module2 = require("module");
@@ -3245,35 +3388,35 @@ function runKeyCreate(options) {
3245
3388
  const environment = targetEnvironment(project, options.environment);
3246
3389
  const declared = project.config.keys?.[environment];
3247
3390
  if (declared === void 0) {
3248
- throw new import_core15.PenvError(
3391
+ throw new import_core16.PenvError(
3249
3392
  "KEY_SOURCE_UNDECLARED",
3250
3393
  `Environment ${environment} declares no key source, so penv does not know what a key for it would be`,
3251
3394
  `Add a \`keys\` entry to penv.config.ts \u2014 e.g. \`keys: { ${environment}: { source: "env", id: "${environment}" } }\` \u2014 then run this again.`
3252
3395
  );
3253
3396
  }
3254
- const key = (0, import_node_crypto.randomBytes)(import_core15.KEY_BYTES).toString("base64");
3397
+ const key = (0, import_node_crypto.randomBytes)(import_core16.KEY_BYTES).toString("base64");
3255
3398
  if (declared.source === "keychain") {
3256
3399
  const keychain = options.keychain ?? defaultKeychain;
3257
3400
  if (options.force !== true) {
3258
3401
  let existing;
3259
3402
  try {
3260
- existing = keychain.getPassword(import_core15.KEYCHAIN_SERVICE, declared.id);
3403
+ existing = keychain.getPassword(import_core16.KEYCHAIN_SERVICE, declared.id);
3261
3404
  } catch (cause) {
3262
- throw new import_core15.PenvError(
3405
+ throw new import_core16.PenvError(
3263
3406
  "KEYCHAIN_UNAVAILABLE",
3264
3407
  `penv could not read your OS keychain to check for an existing key \`${declared.id}\``,
3265
3408
  `Unlock your keychain and run this again. Original error: ${cause instanceof Error ? cause.message : String(cause)}`
3266
3409
  );
3267
3410
  }
3268
3411
  if (existing !== null) {
3269
- throw new import_core15.PenvError(
3412
+ throw new import_core16.PenvError(
3270
3413
  "KEY_EXISTS",
3271
3414
  `Environment ${environment} already has a key \`${declared.id}\` in your OS keychain`,
3272
3415
  "Replacing it would orphan every value already sealed under it \u2014 they could never be decrypted again. Re-run with `--force` only if you are certain nothing is sealed under the current key."
3273
3416
  );
3274
3417
  }
3275
3418
  }
3276
- keychain.setPassword(import_core15.KEYCHAIN_SERVICE, declared.id, key);
3419
+ keychain.setPassword(import_core16.KEYCHAIN_SERVICE, declared.id, key);
3277
3420
  return { source: "keychain", environment, id: declared.id };
3278
3421
  }
3279
3422
  return {
@@ -3303,10 +3446,10 @@ function renderKeyCreate(result2) {
3303
3446
  "without it, and penv keeps no copy to fall back on."
3304
3447
  ];
3305
3448
  }
3306
- var keyCommand = (0, import_citty10.defineCommand)({
3449
+ var keyCommand = (0, import_citty11.defineCommand)({
3307
3450
  meta: { name: "key", description: "Work with encryption keys" },
3308
3451
  subCommands: {
3309
- create: (0, import_citty10.defineCommand)({
3452
+ create: (0, import_citty11.defineCommand)({
3310
3453
  meta: { name: "create", description: "Generate a key for an environment" },
3311
3454
  args: {
3312
3455
  env: { type: "string", description: "The environment the key is for" },
@@ -3333,8 +3476,8 @@ var keyCommand = (0, import_citty10.defineCommand)({
3333
3476
  });
3334
3477
 
3335
3478
  // src/commands/list.ts
3336
- var import_core16 = require("@penvhq/core");
3337
- var import_citty11 = require("citty");
3479
+ var import_core17 = require("@penvhq/core");
3480
+ var import_citty12 = require("citty");
3338
3481
  function scopeLabel2(scope) {
3339
3482
  switch (scope.kind) {
3340
3483
  case "environment":
@@ -3346,7 +3489,7 @@ function scopeLabel2(scope) {
3346
3489
  case "unscoped":
3347
3490
  return "default";
3348
3491
  default:
3349
- return (0, import_core16.assertNever)(scope, "scope");
3492
+ return (0, import_core17.assertNever)(scope, "scope");
3350
3493
  }
3351
3494
  }
3352
3495
  async function runList(options) {
@@ -3354,12 +3497,12 @@ async function runList(options) {
3354
3497
  const environment = targetEnvironment(project, options.environment);
3355
3498
  const keys = keySourceFor(project, environment);
3356
3499
  const parameters = [];
3357
- for (const resolution of await (0, import_core16.resolveAll)(environment, project.provider, keys)) {
3500
+ for (const resolution of await (0, import_core17.resolveAll)(environment, project.provider, keys)) {
3358
3501
  const winner = resolution.winner;
3359
3502
  const scope = winner?.file.scope;
3360
3503
  parameters.push({
3361
3504
  parameter: resolution.parameter,
3362
- variable: (0, import_core16.variableName)(resolution.ref, project.config),
3505
+ variable: (0, import_core17.variableName)(resolution.ref, project.config),
3363
3506
  scope: scope === void 0 ? "absent" : scopeLabel2(scope),
3364
3507
  location: winner?.location,
3365
3508
  encrypted: winner?.file.encrypted === true,
@@ -3374,7 +3517,7 @@ function renderList(result2) {
3374
3517
  }
3375
3518
  return columns(result2.parameters.map((entry) => [entry.parameter, entry.scope, entry.variable]));
3376
3519
  }
3377
- var listCommand = (0, import_citty11.defineCommand)({
3520
+ var listCommand = (0, import_citty12.defineCommand)({
3378
3521
  meta: { name: "list", description: "List parameters" },
3379
3522
  args: {
3380
3523
  env: { type: "string", description: "The environment to resolve against" },
@@ -3392,8 +3535,8 @@ var listCommand = (0, import_citty11.defineCommand)({
3392
3535
  });
3393
3536
 
3394
3537
  // src/commands/mv.ts
3395
- var import_core17 = require("@penvhq/core");
3396
- var import_citty12 = require("citty");
3538
+ var import_core18 = require("@penvhq/core");
3539
+ var import_citty13 = require("citty");
3397
3540
  function environmentOf2(file) {
3398
3541
  const scope = file.scope;
3399
3542
  return scope.kind === "environment" || scope.kind === "environment-local" ? scope.environment : void 0;
@@ -3408,38 +3551,39 @@ async function planFile(project, source, target, parameter) {
3408
3551
  }
3409
3552
  const environment = environmentOf2(source);
3410
3553
  if (environment === void 0) {
3411
- throw new import_core17.PenvError(
3554
+ throw new import_core18.PenvError(
3412
3555
  "SECRET_SCOPE_AMBIGUOUS",
3413
- `${PENV_DIR}/${(0, import_core17.formatValueFile)(source)} is encrypted at a scope that names no environment, so penv cannot tell which key would re-seal it`,
3556
+ `${PENV_DIR}/${(0, import_core18.formatValueFile)(source)} is encrypted at a scope that names no environment, so penv cannot tell which key would re-seal it`,
3414
3557
  "Keys are declared per environment in the `keys` block of penv.config.ts. Decrypt it with `penv decrypt`, move the parameter, then encrypt it again at its new address."
3415
3558
  );
3416
3559
  }
3417
3560
  const keys = keySourceFor(project, environment);
3418
- const opened = (0, import_core17.openValue)(source, stored, keys);
3561
+ const opened = (0, import_core18.openValue)(source, stored, keys);
3419
3562
  if (opened.kind === "failed") {
3420
- throw new import_core17.PenvError(
3563
+ throw new import_core18.PenvError(
3421
3564
  "VALUE_UNDECRYPTABLE",
3422
- `${PENV_DIR}/${(0, import_core17.formatValueFile)(source)} could not be decrypted, so penv cannot re-seal it at its new address: ${opened.failure.detail}`,
3565
+ `${PENV_DIR}/${(0, import_core18.formatValueFile)(source)} could not be decrypted, so penv cannot re-seal it at its new address: ${opened.failure.detail}`,
3423
3566
  "A sealed value is bound to the file it lives in, so moving it means opening it and sealing it again. Make the key available and run this again. Nothing has been moved."
3424
3567
  );
3425
3568
  }
3426
3569
  return {
3427
3570
  source,
3428
3571
  target,
3429
- contents: (0, import_core17.sealValue)(target, opened.value, keys, parameter, environment),
3572
+ contents: (0, import_core18.sealValue)(target, opened.value, keys, parameter, environment),
3430
3573
  resealed: true
3431
3574
  };
3432
3575
  }
3433
3576
  function filesOf(all, ref) {
3434
- const id = (0, import_core17.parameterId)(ref);
3435
- return all.filter((file) => (0, import_core17.parameterId)(file) === id);
3577
+ const id = (0, import_core18.parameterId)(ref);
3578
+ return all.filter((file) => (0, import_core18.parameterId)(file) === id);
3436
3579
  }
3437
3580
  async function runMove(options) {
3438
3581
  const project = openProject(options.cwd);
3439
3582
  const from = refFromKey(options.from, project.config);
3583
+ assertWritableKey(options.to);
3440
3584
  const to = refFromKey(options.to, project.config);
3441
- if ((0, import_core17.parameterId)(from) === (0, import_core17.parameterId)(to)) {
3442
- throw new import_core17.PenvError(
3585
+ if ((0, import_core18.parameterId)(from) === (0, import_core18.parameterId)(to)) {
3586
+ throw new import_core18.PenvError(
3443
3587
  "PARAMETER_UNCHANGED",
3444
3588
  `\`${options.from}\` and \`${options.to}\` are the same parameter`,
3445
3589
  "Name a different destination, e.g. `penv mv redis-password redis/password`."
@@ -3449,24 +3593,24 @@ async function runMove(options) {
3449
3593
  const sources = filesOf(all, from);
3450
3594
  const meta = await project.provider.readMeta(from);
3451
3595
  if (sources.length === 0 && meta === void 0) {
3452
- throw new import_core17.PenvError(
3596
+ throw new import_core18.PenvError(
3453
3597
  "PARAMETER_ABSENT",
3454
- `Parameter ${(0, import_core17.parameterId)(from)} has no value files and no meta, so there is nothing to move`,
3598
+ `Parameter ${(0, import_core18.parameterId)(from)} has no value files and no meta, so there is nothing to move`,
3455
3599
  `\`penv list\` shows every parameter penv holds.`
3456
3600
  );
3457
3601
  }
3458
3602
  const occupied2 = filesOf(all, to);
3459
3603
  if (occupied2.length > 0 || await project.provider.readMeta(to) !== void 0) {
3460
- throw new import_core17.PenvError(
3604
+ throw new import_core18.PenvError(
3461
3605
  "PARAMETER_EXISTS",
3462
- `Parameter ${(0, import_core17.parameterId)(to)} already exists, and penv will not merge two parameters into one`,
3463
- `Remove or rename ${(0, import_core17.parameterId)(to)} first. \`penv get ${options.to} --explain\` shows every file it holds.`
3606
+ `Parameter ${(0, import_core18.parameterId)(to)} already exists, and penv will not merge two parameters into one`,
3607
+ `Remove or rename ${(0, import_core18.parameterId)(to)} first. \`penv get ${options.to} --explain\` shows every file it holds.`
3464
3608
  );
3465
3609
  }
3466
3610
  const planned = [];
3467
3611
  for (const source of sources) {
3468
3612
  const target = { ...source, namespace: to.namespace, name: to.name };
3469
- const one = await planFile(project, source, target, (0, import_core17.parameterId)(to));
3613
+ const one = await planFile(project, source, target, (0, import_core18.parameterId)(to));
3470
3614
  if (one !== void 0) {
3471
3615
  planned.push(one);
3472
3616
  }
@@ -3484,18 +3628,18 @@ async function runMove(options) {
3484
3628
  await project.provider.removeMeta(from);
3485
3629
  }
3486
3630
  return {
3487
- from: (0, import_core17.parameterId)(from),
3488
- to: (0, import_core17.parameterId)(to),
3631
+ from: (0, import_core18.parameterId)(from),
3632
+ to: (0, import_core18.parameterId)(to),
3489
3633
  files: planned.map((file) => ({
3490
- from: (0, import_core17.formatValueFile)(file.source),
3491
- to: (0, import_core17.formatValueFile)(file.target),
3634
+ from: (0, import_core18.formatValueFile)(file.source),
3635
+ to: (0, import_core18.formatValueFile)(file.target),
3492
3636
  resealed: file.resealed
3493
3637
  })),
3494
3638
  // The meta's path, not the parameter's dotted id: `redis.password` is what
3495
3639
  // the schema calls it and `redis/password.json` is the file, and a report
3496
3640
  // that printed the first while moving the second names no file on disk.
3497
- meta: meta === void 0 ? void 0 : (0, import_core17.formatMetaFile)({ ...to, format: "json" }),
3498
- schema: { was: (0, import_core17.accessPath)(from).join("."), now: (0, import_core17.accessPath)(to).join(".") }
3641
+ meta: meta === void 0 ? void 0 : (0, import_core18.formatMetaFile)({ ...to, format: "json" }),
3642
+ schema: { was: (0, import_core18.accessPath)(from).join("."), now: (0, import_core18.accessPath)(to).join(".") }
3499
3643
  };
3500
3644
  }
3501
3645
  function renderMove(result2) {
@@ -3516,7 +3660,7 @@ function renderMove(result2) {
3516
3660
  );
3517
3661
  return lines;
3518
3662
  }
3519
- var mvCommand = (0, import_citty12.defineCommand)({
3663
+ var mvCommand = (0, import_citty13.defineCommand)({
3520
3664
  meta: { name: "mv", description: "Rename a parameter, every scope and its meta at once" },
3521
3665
  args: {
3522
3666
  from: {
@@ -3538,7 +3682,7 @@ var mvCommand = (0, import_citty12.defineCommand)({
3538
3682
  });
3539
3683
 
3540
3684
  // src/commands/pull.ts
3541
- var import_citty13 = require("citty");
3685
+ var import_citty14 = require("citty");
3542
3686
  async function runPull(options) {
3543
3687
  const project = openProject(options.cwd);
3544
3688
  const environment = targetEnvironment(project, options.environment);
@@ -3595,7 +3739,7 @@ function renderPull(result2) {
3595
3739
  }
3596
3740
  ]);
3597
3741
  }
3598
- var pullCommand = (0, import_citty13.defineCommand)({
3742
+ var pullCommand = (0, import_citty14.defineCommand)({
3599
3743
  meta: {
3600
3744
  name: "pull",
3601
3745
  description: "Materialise the local .penv tree from an environment's source-of-truth provider"
@@ -3615,8 +3759,8 @@ var pullCommand = (0, import_citty13.defineCommand)({
3615
3759
  });
3616
3760
 
3617
3761
  // src/commands/remove.ts
3618
- var import_core18 = require("@penvhq/core");
3619
- var import_citty14 = require("citty");
3762
+ var import_core19 = require("@penvhq/core");
3763
+ var import_citty15 = require("citty");
3620
3764
  async function runRemove(options) {
3621
3765
  const project = openProject(options.cwd);
3622
3766
  const ref = refFromKey(options.key);
@@ -3633,12 +3777,12 @@ async function runRemove(options) {
3633
3777
  continue;
3634
3778
  }
3635
3779
  await project.provider.remove(file);
3636
- removed.push((0, import_core18.formatValueFile)(file));
3780
+ removed.push((0, import_core19.formatValueFile)(file));
3637
3781
  }
3638
3782
  return {
3639
3783
  parameter: options.key,
3640
3784
  removed,
3641
- considered: files.map((file) => (0, import_core18.formatValueFile)(file))
3785
+ considered: files.map((file) => (0, import_core19.formatValueFile)(file))
3642
3786
  };
3643
3787
  }
3644
3788
  function renderRemove(result2) {
@@ -3660,7 +3804,7 @@ function renderRemove(result2) {
3660
3804
  }));
3661
3805
  return formatRows(rows);
3662
3806
  }
3663
- var removeCommand = (0, import_citty14.defineCommand)({
3807
+ var removeCommand = (0, import_citty15.defineCommand)({
3664
3808
  meta: { name: "remove", description: "Delete a parameter" },
3665
3809
  args: {
3666
3810
  key: { type: "positional", required: true, description: "The parameter, e.g. redis/password" },
@@ -3687,8 +3831,8 @@ var removeCommand = (0, import_citty14.defineCommand)({
3687
3831
  });
3688
3832
 
3689
3833
  // src/commands/rotate.ts
3690
- var import_core19 = require("@penvhq/core");
3691
- var import_citty15 = require("citty");
3834
+ var import_core20 = require("@penvhq/core");
3835
+ var import_citty16 = require("citty");
3692
3836
  function rotatingFile(ref, environment) {
3693
3837
  return {
3694
3838
  namespace: ref.namespace,
@@ -3713,7 +3857,7 @@ async function writeRotatedValue(project, provider, ref, environment, value) {
3713
3857
  }
3714
3858
  function requireNewValue(value, phase, key) {
3715
3859
  if (value === void 0) {
3716
- throw new import_core19.PenvError(
3860
+ throw new import_core20.PenvError(
3717
3861
  "ROTATION_NO_VALUE",
3718
3862
  `A ${phase} rotation of ${key} writes a new value, and none was given`,
3719
3863
  "Pass the new value as the argument \u2014 `penv rotate <key> <value>` \u2014 or pipe it in on stdin."
@@ -3722,8 +3866,8 @@ function requireNewValue(value, phase, key) {
3722
3866
  return value;
3723
3867
  }
3724
3868
  function requireRetaining(provider, environment) {
3725
- if (!(0, import_core19.retainsPrevious)(provider)) {
3726
- throw new import_core19.PenvError(
3869
+ if (!(0, import_core20.retainsPrevious)(provider)) {
3870
+ throw new import_core20.PenvError(
3727
3871
  "ROTATION_NOT_RETAINING",
3728
3872
  `A dual-valid rotation needs the previous value to stay readable during the grace window, and the \`${provider.type}\` provider for environment ${environment} does not retain it`,
3729
3873
  "Point this environment at a provider that keeps prior versions (its `readPrevious` is what penv reads during the window), or, if a momentary overlap is not required, declare the parameter `atomic-cutover` in its meta and flip it in one step."
@@ -3738,9 +3882,9 @@ async function runRotate(options) {
3738
3882
  const provider = await sourceProviderFor(project, environment);
3739
3883
  const nowIso = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3740
3884
  const before = await provider.readMeta(ref);
3741
- const { mechanism } = (0, import_core19.rotationOf)(before, environment);
3885
+ const { mechanism } = (0, import_core20.rotationOf)(before, environment);
3742
3886
  if (mechanism === void 0) {
3743
- throw new import_core19.PenvError(
3887
+ throw new import_core20.PenvError(
3744
3888
  "ROTATION_NO_MECHANISM",
3745
3889
  `Parameter ${options.key} declares no rotation mechanism for environment ${environment}, so penv does not know how to rotate it`,
3746
3890
  'Set `rotationMechanism` in the parameter\'s meta to `"dual-valid"` (a grace-window overlap) or `"atomic-cutover"` (a single flip), then run `penv rotate` again.'
@@ -3750,7 +3894,7 @@ async function runRotate(options) {
3750
3894
  const complete = options.complete === true;
3751
3895
  if (mechanism === "atomic-cutover") {
3752
3896
  if (begin || complete) {
3753
- throw new import_core19.PenvError(
3897
+ throw new import_core20.PenvError(
3754
3898
  "ROTATION_MECHANISM_MISMATCH",
3755
3899
  `Parameter ${options.key} is atomic-cutover, which flips in one step, so \`--begin\`/\`--complete\` do not apply`,
3756
3900
  "Run `penv rotate <key> <value>` with no phase flag to flip it. `--begin`/`--complete` bracket a dual-valid grace window, which atomic-cutover has none of."
@@ -3758,13 +3902,13 @@ async function runRotate(options) {
3758
3902
  }
3759
3903
  const value = requireNewValue(options.value, "cutover", options.key);
3760
3904
  await writeRotatedValue(project, provider, ref, environment, value);
3761
- const after2 = (0, import_core19.completeRotation)(before, environment, nowIso);
3905
+ const after2 = (0, import_core20.completeRotation)(before, environment, nowIso);
3762
3906
  await provider.writeMeta(ref, after2);
3763
3907
  return result(ref, environment, mechanism, "cutover", provider.type, true, after2);
3764
3908
  }
3765
3909
  const retaining = requireRetaining(provider, environment);
3766
3910
  if (begin === complete) {
3767
- throw new import_core19.PenvError(
3911
+ throw new import_core20.PenvError(
3768
3912
  "ROTATION_PHASE_REQUIRED",
3769
3913
  `A dual-valid rotation of ${options.key} needs exactly one of \`--begin\` or \`--complete\``,
3770
3914
  "`--begin` writes the new value and opens the grace window; `--complete` closes it once every reader has moved to the new value. Run them in that order, one at a time."
@@ -3773,16 +3917,16 @@ async function runRotate(options) {
3773
3917
  if (begin) {
3774
3918
  const value = requireNewValue(options.value, "begin", options.key);
3775
3919
  await writeRotatedValue(project, retaining, ref, environment, value);
3776
- const after2 = (0, import_core19.beginRotation)(before, environment, nowIso);
3920
+ const after2 = (0, import_core20.beginRotation)(before, environment, nowIso);
3777
3921
  await retaining.writeMeta(ref, after2);
3778
3922
  return result(ref, environment, mechanism, "begin", retaining.type, true, after2);
3779
3923
  }
3780
- const after = (0, import_core19.completeRotation)(before, environment, nowIso);
3924
+ const after = (0, import_core20.completeRotation)(before, environment, nowIso);
3781
3925
  await retaining.writeMeta(ref, after);
3782
3926
  return result(ref, environment, mechanism, "complete", retaining.type, false, after);
3783
3927
  }
3784
3928
  function result(ref, environment, mechanism, phase, source, wroteValue, after) {
3785
- const { state, rotatingSince, lastRotated } = (0, import_core19.rotationOf)(after, environment);
3929
+ const { state, rotatingSince, lastRotated } = (0, import_core20.rotationOf)(after, environment);
3786
3930
  return {
3787
3931
  parameter: [...ref.namespace, ref.name].join("/"),
3788
3932
  environment,
@@ -3831,7 +3975,7 @@ function renderRotate(result2) {
3831
3975
  }
3832
3976
  ]);
3833
3977
  }
3834
- var rotateCommand = (0, import_citty15.defineCommand)({
3978
+ var rotateCommand = (0, import_citty16.defineCommand)({
3835
3979
  meta: {
3836
3980
  name: "rotate",
3837
3981
  description: "Rotate a parameter's value by the mechanism its meta declares"
@@ -3869,8 +4013,8 @@ var rotateCommand = (0, import_citty15.defineCommand)({
3869
4013
  // src/commands/watch.ts
3870
4014
  var import_node_fs5 = require("fs");
3871
4015
  var import_node_path8 = require("path");
3872
- var import_core20 = require("@penvhq/core");
3873
- var import_citty16 = require("citty");
4016
+ var import_core21 = require("@penvhq/core");
4017
+ var import_citty17 = require("citty");
3874
4018
  var DEBOUNCE_MS = 100;
3875
4019
  function runWatch(options) {
3876
4020
  const project = openProject(options.cwd);
@@ -4002,8 +4146,8 @@ function runWatch(options) {
4002
4146
  }
4003
4147
  addWatcher(project.penvDir, true);
4004
4148
  addWatcher((0, import_node_path8.dirname)(project.configFile), false, configFile);
4005
- if ((0, import_core20.schemaInsideTree)(project.config) === void 0) {
4006
- const schemaFile = (0, import_node_path8.resolve)(project.root, (0, import_core20.schemaFileOf)(project.config));
4149
+ if ((0, import_core21.schemaInsideTree)(project.config) === void 0) {
4150
+ const schemaFile = (0, import_node_path8.resolve)(project.root, (0, import_core21.schemaFileOf)(project.config));
4007
4151
  addWatcher((0, import_node_path8.dirname)(schemaFile), false, (0, import_node_path8.basename)(schemaFile));
4008
4152
  }
4009
4153
  void validate();
@@ -4047,7 +4191,7 @@ function renderDrift(drift, environment) {
4047
4191
  }
4048
4192
  return lines;
4049
4193
  }
4050
- var watchCommand = (0, import_citty16.defineCommand)({
4194
+ var watchCommand = (0, import_citty17.defineCommand)({
4051
4195
  meta: {
4052
4196
  name: "watch",
4053
4197
  description: "Re-validate whenever .penv/ or penv.config.ts changes"
@@ -4086,7 +4230,7 @@ var watchCommand = (0, import_citty16.defineCommand)({
4086
4230
  });
4087
4231
 
4088
4232
  // src/index.ts
4089
- var main = (0, import_citty17.defineCommand)({
4233
+ var main = (0, import_citty18.defineCommand)({
4090
4234
  meta: {
4091
4235
  name: "penv",
4092
4236
  description: "Configuration that shares a data model with your production secret manager"
@@ -4097,6 +4241,7 @@ var main = (0, import_citty17.defineCommand)({
4097
4241
  generate: generateCommand,
4098
4242
  get: getCommand,
4099
4243
  set: setCommand,
4244
+ fill: fillCommand,
4100
4245
  mv: mvCommand,
4101
4246
  pull: pullCommand,
4102
4247
  push: pushCommand,
@@ -4112,8 +4257,8 @@ var main = (0, import_citty17.defineCommand)({
4112
4257
  }
4113
4258
  });
4114
4259
  function runMain() {
4115
- (0, import_core21.setKeychain)(defaultKeychain);
4116
- return (0, import_citty17.runMain)(main);
4260
+ (0, import_core22.setKeychain)(defaultKeychain);
4261
+ return (0, import_citty18.runMain)(main);
4117
4262
  }
4118
4263
  // Annotate the CommonJS export names for ESM import in node:
4119
4264
  0 && (module.exports = {
@@ -4123,6 +4268,7 @@ function runMain() {
4123
4268
  insertEnvAlias,
4124
4269
  main,
4125
4270
  renderDoctor,
4271
+ renderFill,
4126
4272
  renderMove,
4127
4273
  renderPull,
4128
4274
  renderPush,
@@ -4132,6 +4278,7 @@ function runMain() {
4132
4278
  runDoctor,
4133
4279
  runEncrypt,
4134
4280
  runExplain,
4281
+ runFill,
4135
4282
  runGenerate,
4136
4283
  runGet,
4137
4284
  runInit,