@penvhq/cli 0.2.0 → 0.3.1

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