@penvhq/cli 0.11.0 → 0.12.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.js CHANGED
@@ -2,11 +2,12 @@ import {
2
2
  detectPackageManager,
3
3
  engineVersion,
4
4
  installWithPackageManager,
5
+ installedPackages,
5
6
  noCommand,
6
7
  planInstall,
7
8
  renderInstallPlan,
8
9
  startChild
9
- } from "./chunk-JJY4RLDJ.js";
10
+ } from "./chunk-NHTDIZZ2.js";
10
11
 
11
12
  // src/index.ts
12
13
  import { setKeychain } from "@penvhq/core";
@@ -22,7 +23,7 @@ import {
22
23
  formatValueFile as formatValueFile2,
23
24
  isSecret as isSecret2,
24
25
  keySourceIdentifier,
25
- PenvError as PenvError7,
26
+ PenvError as PenvError6,
26
27
  parameterId as parameterId3,
27
28
  serializeArtifact,
28
29
  variableName as variableName3
@@ -453,7 +454,7 @@ function refsFrom(files) {
453
454
  }
454
455
 
455
456
  // src/ui.ts
456
- import { PenvError as PenvError4 } from "@penvhq/core";
457
+ import { isPenvErrorLike } from "@penvhq/core";
457
458
 
458
459
  // src/style.ts
459
460
  function supportsColor(stream) {
@@ -586,7 +587,7 @@ function writeError(lines) {
586
587
  }
587
588
  }
588
589
  function reportError(error) {
589
- if (error instanceof PenvError4) {
590
+ if (isPenvErrorLike(error)) {
590
591
  process.stderr.write(`${err.red(CROSS)} ${error.summary}
591
592
  `);
592
593
  if (error.remedy !== void 0) {
@@ -629,7 +630,7 @@ import {
629
630
  MissingMaterializationError,
630
631
  openSealed,
631
632
  own,
632
- PenvError as PenvError6,
633
+ PenvError as PenvError5,
633
634
  parseArtifact,
634
635
  RECORDS_PATH,
635
636
  UndecryptableValueError,
@@ -878,7 +879,7 @@ import {
878
879
  checkNameCollisions,
879
880
  jitiFor,
880
881
  NameCollisionError,
881
- PenvError as PenvError5,
882
+ PenvError as PenvError4,
882
883
  ReservedTokenError as ReservedTokenError2,
883
884
  resolveAll,
884
885
  SCHEMA_HARVEST_ENV,
@@ -1227,7 +1228,7 @@ async function checkEnvironment(project, environment) {
1227
1228
  try {
1228
1229
  files = await project.provider.list();
1229
1230
  } catch (error) {
1230
- if (!(error instanceof PenvError5)) {
1231
+ if (!(error instanceof PenvError4)) {
1231
1232
  throw error;
1232
1233
  }
1233
1234
  return {
@@ -1379,7 +1380,7 @@ function assertNotNested(host, inner) {
1379
1380
  if (outer === void 0) {
1380
1381
  return;
1381
1382
  }
1382
- throw new PenvError6(
1383
+ throw new PenvError5(
1383
1384
  "RUN_NESTED",
1384
1385
  `\`${inner}\` is starting inside \`${outer}\`, and two penv environments cannot own one process`,
1385
1386
  `Drop one of the two wrappers \u2014 the inner one is in a package.json script \u2014 then run \`${outer}\` again.`
@@ -1389,7 +1390,7 @@ function assertSource(source, inner) {
1389
1390
  if (source === "project" || source === "snapshot") {
1390
1391
  return source;
1391
1392
  }
1392
- throw new PenvError6(
1393
+ throw new PenvError5(
1393
1394
  "RUN_SOURCE_UNKNOWN",
1394
1395
  `\`--source ${source}\` names no source penv reads`,
1395
1396
  `A run reads ${SOURCES.map((name) => `\`${name}\``).join(" or ")}, and \`project\` is the default \u2014 so \`${inner}\` reads the local tree.`
@@ -1410,7 +1411,7 @@ async function assertNoPublicSecret(project, environment, refs, retry) {
1410
1411
  }
1411
1412
  const parameter = [...ref.namespace, ref.name].join("/");
1412
1413
  const prefix = prefixes.find((candidate) => variable.startsWith(candidate));
1413
- throw new PenvError6(
1414
+ throw new PenvError5(
1414
1415
  "RUN_PUBLIC_SECRET",
1415
1416
  `The secret ${parameter} maps to ${variable}, which the \`${prefix}\` prefix publishes to the browser`,
1416
1417
  `Rename the parameter, or drop \`secret\` from its meta if it is not one \u2014 then \`${retry}\`.`
@@ -1419,7 +1420,7 @@ async function assertNoPublicSecret(project, environment, refs, retry) {
1419
1420
  }
1420
1421
  function invalidConfiguration(result2, inner) {
1421
1422
  const lines = result2.issues.map((issue) => ` ${issue.subject}: ${issue.message}`).join("\n");
1422
- return new PenvError6(
1423
+ return new PenvError5(
1423
1424
  "RUN_INVALID_CONFIGURATION",
1424
1425
  `Configuration for environment ${result2.environment} is not valid, so nothing was started:
1425
1426
  ${lines}`,
@@ -1441,7 +1442,7 @@ function assertNoActiveDotenv(project) {
1441
1442
  if (first === void 0) {
1442
1443
  return;
1443
1444
  }
1444
- throw new PenvError6(
1445
+ throw new PenvError5(
1445
1446
  "RUN_DOTENV_ACTIVE",
1446
1447
  `${first.name} is active configuration again, and your framework would read it beside penv's records`,
1447
1448
  `Adopt it with \`penv init\`, or delete ${first.name} \u2014 its values belong in ${RECORDS_PATH}/.`
@@ -1489,7 +1490,7 @@ function declaredCredentials(config, root) {
1489
1490
  continue;
1490
1491
  }
1491
1492
  if (!Array.isArray(credentials) || credentials.some((name) => typeof name !== "string" || !VARIABLE.test(name))) {
1492
- throw new PenvError6(
1493
+ throw new PenvError5(
1493
1494
  "PROVIDER_CREDENTIALS_INVALID",
1494
1495
  `\`${provider.type}\` declares \`penv.credentials\`, and it is not a list of variable names`,
1495
1496
  `Its package.json should read \`"penv": { "credentials": ["ACME_TOKEN"] }\` \u2014 penv strips exactly what an extension declares, so it will not guess at a declaration it cannot read.`
@@ -1525,7 +1526,7 @@ async function prepare(project, environment, host, inner) {
1525
1526
  function snapshotPath(host) {
1526
1527
  const path = host[SNAPSHOT_VARIABLE]?.trim();
1527
1528
  if (path === void 0 || path.length === 0) {
1528
- throw new PenvError6(
1529
+ throw new PenvError5(
1529
1530
  "RUN_SNAPSHOT_UNSET",
1530
1531
  `\`--source snapshot\` reads the sealed artifact ${SNAPSHOT_VARIABLE} names, and ${SNAPSHOT_VARIABLE} is not set`,
1531
1532
  `Build one with \`${ARTIFACT_BUILD_COMMAND}\` and point ${SNAPSHOT_VARIABLE} at it.`
@@ -1537,7 +1538,7 @@ function readSnapshot(path) {
1537
1538
  try {
1538
1539
  return readFileSync3(path, "utf8");
1539
1540
  } catch {
1540
- throw new PenvError6(
1541
+ throw new PenvError5(
1541
1542
  "RUN_SNAPSHOT_MISSING",
1542
1543
  `${SNAPSHOT_VARIABLE} names ${path}, and penv cannot read a sealed artifact there`,
1543
1544
  `Point ${SNAPSHOT_VARIABLE} at the artifact your release mounted \u2014 \`${ARTIFACT_BUILD_COMMAND}\` writes one.`
@@ -1653,7 +1654,7 @@ async function runRun(options) {
1653
1654
  }
1654
1655
  if (source === "snapshot") {
1655
1656
  if (options.watch === true) {
1656
- throw new PenvError6(
1657
+ throw new PenvError5(
1657
1658
  "RUN_SNAPSHOT_WATCH",
1658
1659
  "`--watch` re-syncs the project tree, and a run from a sealed artifact has no tree to watch",
1659
1660
  "Drop `--watch` \u2014 an artifact is built once and read unchanged. Watch the project instead: `penv run --watch -- <command>`."
@@ -1797,7 +1798,7 @@ function buildCommand(environment, out2) {
1797
1798
  function targetOf(options) {
1798
1799
  const environment = options.environment?.trim();
1799
1800
  if (environment === void 0 || environment.length === 0) {
1800
- throw new PenvError7(
1801
+ throw new PenvError6(
1801
1802
  "ARTIFACT_ENV_REQUIRED",
1802
1803
  "`penv artifact build` names the environment it builds for, and `--env` was not given",
1803
1804
  `Name it: \`${buildCommand(void 0, options.out)}\`. An artifact carries one environment, and penv will not pick which.`
@@ -1808,7 +1809,7 @@ function targetOf(options) {
1808
1809
  function outputOf(options, environment) {
1809
1810
  const out2 = options.out?.trim();
1810
1811
  if (out2 === void 0 || out2.length === 0) {
1811
- throw new PenvError7(
1812
+ throw new PenvError6(
1812
1813
  "ARTIFACT_OUT_REQUIRED",
1813
1814
  "`penv artifact build` writes where it is told, and `--out` was not given",
1814
1815
  `Name the path: \`${buildCommand(environment, void 0)}\`. The artifact belongs outside the repository, so there is no default worth having.`
@@ -1826,7 +1827,7 @@ async function winnerOf(project, ref, environment) {
1826
1827
  return void 0;
1827
1828
  }
1828
1829
  function plaintextSecret(parameter, location, environment) {
1829
- return new PenvError7(
1830
+ return new PenvError6(
1830
1831
  "ARTIFACT_PLAINTEXT_SECRET",
1831
1832
  `${parameter} is a secret for environment ${environment}, and its value comes from ${location}, which is not sealed`,
1832
1833
  `Seal it with \`penv encrypt ${parameter} --env ${environment}\` \u2014 an artifact carries ciphertext or nothing.`
@@ -1840,7 +1841,7 @@ async function runArtifactBuild(options) {
1840
1841
  const { schema, issues } = await loadSchema(project, environment);
1841
1842
  if (schema === void 0) {
1842
1843
  const lines = issues.map((issue) => ` ${issue.subject}: ${issue.message}`).join("\n");
1843
- throw new PenvError7(
1844
+ throw new PenvError6(
1844
1845
  "ARTIFACT_NO_SCHEMA",
1845
1846
  `The schema did not load, so penv cannot tell what this artifact should deliver:
1846
1847
  ${lines}`,
@@ -1965,7 +1966,7 @@ import { dirname as dirname4, join as join4, resolve as resolve4 } from "path";
1965
1966
  import {
1966
1967
  CUTOVER_PATH,
1967
1968
  findConfigFile,
1968
- PenvError as PenvError8,
1969
+ PenvError as PenvError7,
1969
1970
  ROLLBACK_DOTENV_PATH,
1970
1971
  ROLLBACK_PATH
1971
1972
  } from "@penvhq/core";
@@ -2026,7 +2027,7 @@ function readCutover(root) {
2026
2027
  };
2027
2028
  }
2028
2029
  function unreadable(what) {
2029
- return new PenvError8(
2030
+ return new PenvError7(
2030
2031
  "CUTOVER_UNREADABLE",
2031
2032
  `${CUTOVER_PATH} records the last dotenv cutover, and ${what}`,
2032
2033
  `Run \`penv cleanup\` to drop that record and the rollback bundle it names.`
@@ -2067,7 +2068,7 @@ function runUndo(options) {
2067
2068
  const cutover = readCutover(root);
2068
2069
  const bundled = bundledFiles(root);
2069
2070
  if (cutover === void 0 && bundled.length === 0) {
2070
- throw new PenvError8(
2071
+ throw new PenvError7(
2071
2072
  "INIT_UNDO_NOTHING",
2072
2073
  "There is no dotenv cutover to undo in this project",
2073
2074
  "Run `penv init` to adopt your dotenv files; undo puts them back afterwards."
@@ -2081,7 +2082,7 @@ function runUndo(options) {
2081
2082
  if (occupied2.length > 0) {
2082
2083
  const listed = occupied2.join(", ");
2083
2084
  const many = occupied2.length > 1;
2084
- throw new PenvError8(
2085
+ throw new PenvError7(
2085
2086
  "INIT_UNDO_OCCUPIED",
2086
2087
  `${listed} ${many ? "exist" : "exists"} again, and restoring what penv moved aside would write over ${many ? "them" : "it"}`,
2087
2088
  `Move ${listed} out of the way, or run \`penv cleanup\` to keep ${many ? "them" : "it"} and drop the bundle. Nothing was restored.`
@@ -2174,7 +2175,7 @@ import { defineCommand as defineCommand7 } from "citty";
2174
2175
  import {
2175
2176
  checkNameCollisions as checkNameCollisions2,
2176
2177
  holdsProjection as holdsProjection3,
2177
- PenvError as PenvError9,
2178
+ PenvError as PenvError8,
2178
2179
  recordPath,
2179
2180
  requireValue,
2180
2181
  variableName as variableName4
@@ -2242,7 +2243,7 @@ async function destinationFor(project, environment, options) {
2242
2243
  const declared = project.config.providers[environment];
2243
2244
  const location = declared?.location;
2244
2245
  if (declared === void 0 || declared.type === LOCAL_TREE_TYPE) {
2245
- throw new PenvError9(
2246
+ throw new PenvError8(
2246
2247
  "NO_DESTINATION",
2247
2248
  `Environment ${environment}'s provider is the local records tree itself, so penv has nowhere to push`,
2248
2249
  `Declare a provider for it in penv.config.ts \u2014 e.g. \`${environment}: { type: "@penvhq/provider-github", location: "owner/repo" }\` \u2014 or push somewhere once with \`penv push --env ${environment} --destination <package> --location <place>\`.`
@@ -2269,7 +2270,7 @@ function plan(resolutions, config, environment, allowDecrypt) {
2269
2270
  let encrypted = false;
2270
2271
  if (winner.file.encrypted) {
2271
2272
  if (!allowDecrypt) {
2272
- throw new PenvError9(
2273
+ throw new PenvError8(
2273
2274
  "ENCRYPTED_VALUE_REFUSED",
2274
2275
  `Parameter ${resolution.parameter} for environment ${environment} resolves to the encrypted value file ${recordPath(winner.location)}, and a push sends plaintext for the destination to re-seal`,
2275
2276
  "Re-run with `--allow-decrypt` to decrypt it locally and push it, or push an environment whose values are plaintext. penv's encryption stops at the projection; the destination seals it under its own key."
@@ -2322,7 +2323,7 @@ async function ensureTargetApproved(provider, environment, options) {
2322
2323
  return false;
2323
2324
  }
2324
2325
  if (provider.ensureTarget === void 0) {
2325
- throw new PenvError9(
2326
+ throw new PenvError8(
2326
2327
  "MISSING_TARGET",
2327
2328
  `The destination has no environment \`${environment}\` to receive this push, and this provider cannot create one`,
2328
2329
  `Create the environment on the destination side, then run \`penv push --env ${environment}\` again.`
@@ -2332,7 +2333,7 @@ async function ensureTargetApproved(provider, environment, options) {
2332
2333
  `The destination has no environment \`${environment}\`. Create it?`
2333
2334
  );
2334
2335
  if (!approved) {
2335
- throw new PenvError9(
2336
+ throw new PenvError8(
2336
2337
  "MISSING_TARGET",
2337
2338
  `The destination has no environment \`${environment}\` to receive this push`,
2338
2339
  `Re-run with \`--yes\` to create it, answer \`y\` at the prompt, or create it on the destination side yourself.`
@@ -3496,7 +3497,7 @@ import {
3496
3497
  formatValueFile as formatValueFile5,
3497
3498
  isSecret as isSecret5,
3498
3499
  openValue as openValue3,
3499
- PenvError as PenvError11,
3500
+ PenvError as PenvError10,
3500
3501
  parameterId as parameterId5,
3501
3502
  recordPath as recordPath3,
3502
3503
  sealValue as sealValue2
@@ -3507,7 +3508,7 @@ import { defineCommand as defineCommand9 } from "citty";
3507
3508
  import {
3508
3509
  formatValueFile as formatValueFile4,
3509
3510
  isSecret as isSecret4,
3510
- PenvError as PenvError10,
3511
+ PenvError as PenvError9,
3511
3512
  parameterId as parameterId4,
3512
3513
  recordPath as recordPath2,
3513
3514
  sealValue
@@ -3531,7 +3532,7 @@ function targetScope(project, options, key) {
3531
3532
  return scopeFrom(options);
3532
3533
  }
3533
3534
  if (environment.trim().length === 0) {
3534
- throw new PenvError10(
3535
+ throw new PenvError9(
3535
3536
  "ENVIRONMENT_FLAG_EMPTY",
3536
3537
  `\`--env\` for parameter ${key} names no environment`,
3537
3538
  `Pass a declared environment \u2014 ${project.config.environments.map((e) => `\`${e}\``).join(", ")} \u2014 e.g. \`--env production\`, or drop \`--env\` to write the scope that has no environment.`
@@ -3544,7 +3545,7 @@ function policyEnvironment(project, options) {
3544
3545
  }
3545
3546
  function sealFor(project, file, value, parameter, environment) {
3546
3547
  if (environment === void 0) {
3547
- throw new PenvError10(
3548
+ throw new PenvError9(
3548
3549
  "SECRET_SCOPE_AMBIGUOUS",
3549
3550
  `Parameter ${parameter} is a secret, and ${recordPath2(formatValueFile4(file))} names no environment`,
3550
3551
  "Keys are declared per environment in the `keys` block of penv.config.ts, so penv cannot tell which key should seal a file that every environment reads. Write it at an environment scope \u2014 add `--env <environment>` \u2014 or drop `secret` from the parameter's meta."
@@ -3646,7 +3647,7 @@ function twins(project, key, options) {
3646
3647
  }
3647
3648
  function environmentFor(project, options, verb) {
3648
3649
  if (options.environment === void 0) {
3649
- throw new PenvError11(
3650
+ throw new PenvError10(
3650
3651
  "SECRET_SCOPE_AMBIGUOUS",
3651
3652
  `\`penv ${verb}\` names no environment, and keys are declared per environment`,
3652
3653
  "Pass `--env <environment>`. penv cannot tell which environment's key applies to a file that names none, and will not pick one for you."
@@ -3665,7 +3666,7 @@ async function runEncrypt(options) {
3665
3666
  const value = await readOne(project, plain);
3666
3667
  if (value === void 0) {
3667
3668
  const already = await readOne(project, sealed);
3668
- throw new PenvError11(
3669
+ throw new PenvError10(
3669
3670
  "PARAMETER_ABSENT",
3670
3671
  already === void 0 ? `Parameter ${parameter} has no value file at ${recordPath3(formatValueFile5(plain))}` : `Parameter ${parameter} is already encrypted at ${recordPath3(formatValueFile5(sealed))}`,
3671
3672
  already === void 0 ? `Write it first with \`penv set ${options.key} --env ${environment}\`, which seals it automatically when the parameter's meta declares it a secret.` : "Nothing to do."
@@ -3686,7 +3687,7 @@ async function runDecrypt(options) {
3686
3687
  const [plain, sealed] = twins(project, options.key, options);
3687
3688
  const parameter = parameterId5(plain);
3688
3689
  if (isSecret5(await project.provider.readMeta(plain), environment)) {
3689
- throw new PenvError11(
3690
+ throw new PenvError10(
3690
3691
  "SECRET_DECRYPT_REFUSED",
3691
3692
  `Parameter ${parameter} is declared a secret for environment ${environment}, so penv will not write it in plaintext`,
3692
3693
  "A secret with a plaintext value file is a `penv doctor` failure. Drop `secret` from the parameter's meta if it is not one, or run `penv generate --allow-decrypt` if you need the plaintext value in a `.env` artifact."
@@ -3694,7 +3695,7 @@ async function runDecrypt(options) {
3694
3695
  }
3695
3696
  const stored = await readOne(project, sealed);
3696
3697
  if (stored === void 0) {
3697
- throw new PenvError11(
3698
+ throw new PenvError10(
3698
3699
  "PARAMETER_ABSENT",
3699
3700
  `Parameter ${parameter} has no encrypted value file at ${recordPath3(formatValueFile5(sealed))}`,
3700
3701
  `Nothing to decrypt. \`penv get ${options.key} --env ${environment} --explain\` shows every file penv looked at.`
@@ -3717,7 +3718,7 @@ async function runDecrypt(options) {
3717
3718
  removed: formatValueFile5(sealed)
3718
3719
  };
3719
3720
  }
3720
- var UndecryptableAt = class extends PenvError11 {
3721
+ var UndecryptableAt = class extends PenvError10 {
3721
3722
  constructor(parameter, environment, location, detail) {
3722
3723
  super(
3723
3724
  "VALUE_UNDECRYPTABLE",
@@ -3772,7 +3773,7 @@ var decryptCommand = defineCommand9({
3772
3773
  });
3773
3774
 
3774
3775
  // src/commands/fill.ts
3775
- import { PenvError as PenvError12, recordPath as recordPath4, SCHEMA_SHAPE_FILE as SCHEMA_SHAPE_FILE2 } from "@penvhq/core";
3776
+ import { PenvError as PenvError11, recordPath as recordPath4, SCHEMA_SHAPE_FILE as SCHEMA_SHAPE_FILE2 } from "@penvhq/core";
3776
3777
  import { defineCommand as defineCommand10 } from "citty";
3777
3778
  var BLOCKING = /* @__PURE__ */ new Set(["config", "collision", "reserved"]);
3778
3779
  async function runFill(options) {
@@ -3787,7 +3788,7 @@ async function runFill(options) {
3787
3788
  const detail = blockers.map(
3788
3789
  (issue) => ` - ${issue.message}${issue.remedy === void 0 ? "" : ` (${issue.remedy})`}`
3789
3790
  ).join("\n");
3790
- throw new PenvError12(
3791
+ throw new PenvError11(
3791
3792
  "FILL_BLOCKED",
3792
3793
  `penv fill cannot run: environment ${environment} has ${blockers.length} unresolved configuration ${blockers.length === 1 ? "issue" : "issues"}:
3793
3794
  ${detail}`,
@@ -3927,7 +3928,7 @@ import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve5 }
3927
3928
  import {
3928
3929
  checkNameCollisions as checkNameCollisions3,
3929
3930
  effectiveMeta as effectiveMeta2,
3930
- PenvError as PenvError13,
3931
+ PenvError as PenvError12,
3931
3932
  recordPath as recordPath5,
3932
3933
  requireValue as requireValue2,
3933
3934
  serializeDotenv,
@@ -3952,7 +3953,7 @@ function entriesFor(project, environment, allowDecrypt) {
3952
3953
  const winner = resolution.winner;
3953
3954
  if (winner?.file.encrypted === true) {
3954
3955
  if (!allowDecrypt) {
3955
- throw new PenvError13(
3956
+ throw new PenvError12(
3956
3957
  "ENCRYPTED_VALUE_REFUSED",
3957
3958
  `Parameter ${resolution.parameter} for environment ${environment} resolves to the encrypted value file ${recordPath5(winner.location)}, and \`penv generate\` writes plaintext`,
3958
3959
  `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.`
@@ -4036,7 +4037,7 @@ var generateCommand = defineCommand11({
4036
4037
  });
4037
4038
 
4038
4039
  // src/commands/get.ts
4039
- import { PenvError as PenvError14, recordPath as recordPath6, requireValue as requireValue3, resolveParameter } from "@penvhq/core";
4040
+ import { PenvError as PenvError13, recordPath as recordPath6, requireValue as requireValue3, resolveParameter } from "@penvhq/core";
4040
4041
  import { defineCommand as defineCommand12 } from "citty";
4041
4042
  async function runGet(options) {
4042
4043
  const project = openProject(options.cwd);
@@ -4046,7 +4047,7 @@ async function runGet(options) {
4046
4047
  const resolution = await resolveParameter(ref, environment, project.provider, keys);
4047
4048
  const value = requireValue3(resolution, environment);
4048
4049
  if (value === void 0) {
4049
- throw new PenvError14(
4050
+ throw new PenvError13(
4050
4051
  "PARAMETER_ABSENT",
4051
4052
  `Parameter ${resolution.parameter} resolves to no value for environment ${environment}`,
4052
4053
  `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.`
@@ -4130,7 +4131,7 @@ import {
4130
4131
  findConfigFile as findConfigFile2,
4131
4132
  loadConfigFrom as loadConfigFrom2,
4132
4133
  lookupEnvironment,
4133
- PenvError as PenvError17,
4134
+ PenvError as PenvError16,
4134
4135
  parseDotenv as parseDotenv2,
4135
4136
  schemaFileOf as schemaFileOf4,
4136
4137
  UnknownEnvironmentError
@@ -4141,7 +4142,7 @@ import { defineCommand as defineCommand14 } from "citty";
4141
4142
  import {
4142
4143
  checkNameCollisions as checkNameCollisions4,
4143
4144
  isReservedToken as isReservedToken3,
4144
- PenvError as PenvError15,
4145
+ PenvError as PenvError14,
4145
4146
  ReservedTokenError as ReservedTokenError3,
4146
4147
  refFromVariable as refFromVariable2,
4147
4148
  roundTripsCleanly,
@@ -4151,7 +4152,7 @@ function assertImportable(ref, variable) {
4151
4152
  if (!ref.name.includes(".")) {
4152
4153
  return;
4153
4154
  }
4154
- throw new PenvError15(
4155
+ throw new PenvError14(
4155
4156
  "IMPORT_UNPARSEABLE_NAME",
4156
4157
  `The variable ${variable} becomes the parameter \`${ref.name}\`, whose \`.\` would be read as a scope`,
4157
4158
  `Filenames are split on \`.\`. Rename ${variable} in the source file, then import it again.`
@@ -4170,7 +4171,7 @@ function assertRoundTrips(ref, variable, config) {
4170
4171
  return;
4171
4172
  }
4172
4173
  const generated = variableName7(ref, config);
4173
- throw new PenvError15(
4174
+ throw new PenvError14(
4174
4175
  "IMPORT_LOSSY_NAME",
4175
4176
  `The variable ${variable} becomes the parameter \`${ref.name}\`, which regenerates as ${generated}`,
4176
4177
  `\`penv generate\` would write ${generated}, so anything reading \`process.env["${variable}"]\` would read \`undefined\`. Declare the name you want in the \`override\` block of penv.config.ts \u2014 \`override: { "${ref.name}": "${variable}" }\` \u2014 then import it again. Nothing was imported.`
@@ -4385,7 +4386,7 @@ import {
4385
4386
  isLegalEnvironmentName as isLegalEnvironmentName2,
4386
4387
  loadConfigFrom,
4387
4388
  PENV_DIR as PENV_DIR2,
4388
- PenvError as PenvError16,
4389
+ PenvError as PenvError15,
4389
4390
  parameterId as parameterId6,
4390
4391
  parseDotenv,
4391
4392
  RECORDS_PATH as RECORDS_PATH3,
@@ -4666,7 +4667,7 @@ function suggestEnvironments(root) {
4666
4667
  return [...found].sort();
4667
4668
  }
4668
4669
  function emptyFlag(flag) {
4669
- return new PenvError16(
4670
+ return new PenvError15(
4670
4671
  "INIT_FLAG_EMPTY",
4671
4672
  `\`--${flag}\` was given without a value`,
4672
4673
  flag === "schema" ? `Name the module that exports the schema, e.g. \`--schema src/env.ts\`, or drop the flag to use ${DEFAULT_SCHEMA_FILE2}.` : "Name the environment, e.g. `--env production`, or drop the flag to leave the whitelist empty and declare it in penv.config.ts."
@@ -4729,7 +4730,7 @@ function planInit(root, flags = {}) {
4729
4730
  throw emptyFlag("alias");
4730
4731
  }
4731
4732
  if (!ALIAS_NAME.test(alias)) {
4732
- throw new PenvError16(
4733
+ throw new PenvError15(
4733
4734
  "INIT_ALIAS_INVALID",
4734
4735
  `\`${alias}\` is not an alias penv can write`,
4735
4736
  `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.`
@@ -5063,7 +5064,7 @@ ${objectIndent}${source.slice(close)}`;
5063
5064
  ${entryIndent}${member},${source.slice(open + 1)}`;
5064
5065
  }
5065
5066
  function shapeError(what, target, alias) {
5066
- return new PenvError16(
5067
+ return new PenvError15(
5067
5068
  "TSCONFIG_SHAPE",
5068
5069
  `penv cannot add the \`${alias}\` path alias to tsconfig.json: ${what}`,
5069
5070
  `Add it by hand: \`{ "compilerOptions": { "paths": { "${alias}": ["${target}"] } } }\`.`
@@ -5405,7 +5406,7 @@ function planCutover(input) {
5405
5406
  assertBundleResolved(root);
5406
5407
  const selected = [...input.selected];
5407
5408
  if (selected.length === 0) {
5408
- throw new PenvError16(
5409
+ throw new PenvError15(
5409
5410
  "INIT_NOTHING_SELECTED",
5410
5411
  "No dotenv file was selected, so there is nothing to migrate",
5411
5412
  "Run `penv init` again and choose the files penv should adopt."
@@ -5417,7 +5418,7 @@ function planCutover(input) {
5417
5418
  const environments = declared === void 0 ? chosen : declared.environments;
5418
5419
  for (const environment of chosen) {
5419
5420
  if (!environments.includes(environment)) {
5420
- throw new PenvError16(
5421
+ throw new PenvError15(
5421
5422
  "INIT_ENVIRONMENT_UNDECLARED",
5422
5423
  `${CONFIG_FILE} declares ${describeList(environments)}, and adopting these files needs \`${environment}\` declared too`,
5423
5424
  `Add \`${environment}\` to \`environments\` in ${CONFIG_FILE}, with a provider for it, then run \`penv init\` again. Nothing was changed.`
@@ -5504,7 +5505,7 @@ function requireEnvironment(input) {
5504
5505
  if (environment.length > 0) {
5505
5506
  return environment;
5506
5507
  }
5507
- throw new PenvError16(
5508
+ throw new PenvError15(
5508
5509
  "INIT_ENVIRONMENT_UNNAMED",
5509
5510
  "The selected files name no environment, and penv does not invent one",
5510
5511
  `Run \`penv init --env ${DEVELOPMENT}\` to say which environment these values are for.`
@@ -5516,7 +5517,7 @@ function assertCascadeComplete(root, selected, environments) {
5516
5517
  for (const environment of environments) {
5517
5518
  for (const name of cascadeFor(environment)) {
5518
5519
  if (present.has(name) && !taken.has(name)) {
5519
- throw new PenvError16(
5520
+ throw new PenvError15(
5520
5521
  "INIT_CUTOVER_INCOMPLETE",
5521
5522
  `${name} is part of ${environment}'s cascade and was not selected, so your framework would keep reading it beside penv`,
5522
5523
  "Run `penv init` again and take every file penv listed for that environment. Nothing was changed."
@@ -5529,7 +5530,7 @@ function assertBundleResolved(root) {
5529
5530
  if (!bundleUnresolved(root)) {
5530
5531
  return;
5531
5532
  }
5532
- throw new PenvError16(
5533
+ throw new PenvError15(
5533
5534
  "INIT_BUNDLE_UNRESOLVED",
5534
5535
  `The dotenv files from the last cutover are still in ${ROLLBACK_DOTENV_PATH3}/, and penv will not migrate a second time over them`,
5535
5536
  "Run `penv init undo` to put them back, or `penv cleanup` to drop them once you are happy with the migration."
@@ -5541,7 +5542,7 @@ function selectionForYes(plan2) {
5541
5542
  (file) => file.environment !== void 0 && file.environment !== DEVELOPMENT
5542
5543
  );
5543
5544
  if (shared !== void 0 && other !== void 0) {
5544
- throw new PenvError16(
5545
+ throw new PenvError15(
5545
5546
  "INIT_YES_SHARED_FALLBACK",
5546
5547
  `${other.name} falls back to the shared ${shared.name} this cutover would move, so \`--yes\` will not decide what happens to ${other.environment ?? ""}`,
5547
5548
  "Run `penv init` without `--yes` and choose every file the cutover takes. Nothing was changed."
@@ -5587,7 +5588,7 @@ function issueLines(result2) {
5587
5588
  }
5588
5589
  var SCAFFOLD_ROLLED_BACK = "penv put the project back as it found it \u2014 your dotenv files and everything else are exactly where they were.";
5589
5590
  function invalidAfterImport(result2) {
5590
- return new PenvError16(
5591
+ return new PenvError15(
5591
5592
  "INIT_CUTOVER_INVALID",
5592
5593
  `The imported values do not satisfy the draft schema for ${result2.environment}:
5593
5594
  ${issueLines(result2)}`,
@@ -5595,7 +5596,7 @@ ${issueLines(result2)}`,
5595
5596
  );
5596
5597
  }
5597
5598
  function draftNotLoaded(result2) {
5598
- return new PenvError16(
5599
+ return new PenvError15(
5599
5600
  "INIT_DRAFT_NOT_LOADED",
5600
5601
  `penv could not load the schema it drafted for ${result2.environment}, so nothing was checked against it:
5601
5602
  ${issueLines(result2)}`,
@@ -5662,7 +5663,11 @@ function renderCutover(result2) {
5662
5663
  const glyph = step.action === "conflicted" ? WARN : step.action === "info" ? "\u2192" : CHECK;
5663
5664
  return step.note === void 0 ? { glyph, text: step.text } : { glyph, text: step.text, note: step.note };
5664
5665
  }),
5665
- ...plan2.install.packages.filter((entry) => !entry.satisfied).map((entry) => ({ glyph: CHECK, text: `Installed ${entry.name}`, note: entry.version })),
5666
+ ...installedPackages(plan2.install).map((entry) => ({
5667
+ glyph: CHECK,
5668
+ text: `Installed ${entry.name}`,
5669
+ note: entry.version
5670
+ })),
5666
5671
  {
5667
5672
  glyph: CHECK,
5668
5673
  text: `Imported ${plan2.fields.length} parameters`,
@@ -5735,7 +5740,7 @@ async function promptForSelection(plan2, io) {
5735
5740
  for (const name of names) {
5736
5741
  const file = plan2.found.find((candidate) => candidate.name === name);
5737
5742
  if (file === void 0) {
5738
- throw new PenvError16(
5743
+ throw new PenvError15(
5739
5744
  "INIT_SELECTION_UNKNOWN",
5740
5745
  `\`${name}\` is not one of the dotenv files penv found`,
5741
5746
  "Run `penv init` again and name the files exactly as they are listed, e.g. `.env.production`."
@@ -5898,7 +5903,7 @@ var initCommand = defineCommand13({
5898
5903
  });
5899
5904
  function runUndoAction(root, action) {
5900
5905
  if (action !== "undo") {
5901
- throw new PenvError16(
5906
+ throw new PenvError15(
5902
5907
  "INIT_UNKNOWN_ACTION",
5903
5908
  `\`penv init ${action}\` is not something init does`,
5904
5909
  "Run `penv init undo` to put back the dotenv files of the last cutover."
@@ -6004,7 +6009,7 @@ function explicitEnvironment(options, source, config) {
6004
6009
  if (value.length > 0) {
6005
6010
  return value;
6006
6011
  }
6007
- throw new PenvError17(
6012
+ throw new PenvError16(
6008
6013
  "IMPORT_ENV_FLAG_EMPTY",
6009
6014
  `\`--env\` for the import of ${source} names no environment`,
6010
6015
  `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.`
@@ -6014,7 +6019,7 @@ function assertEnvironmentAgrees(derived, explicit, source) {
6014
6019
  if (derived === void 0 || explicit === void 0 || derived === explicit) {
6015
6020
  return;
6016
6021
  }
6017
- throw new PenvError17(
6022
+ throw new PenvError16(
6018
6023
  "IMPORT_ENV_CONFLICT",
6019
6024
  `The file ${source} is scoped to environment ${derived}, but \`--env ${explicit}\` names ${explicit}`,
6020
6025
  `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.`
@@ -6061,7 +6066,7 @@ function importDotenv(options) {
6061
6066
  const cwd = resolve7(options.cwd);
6062
6067
  const file = isAbsolute3(options.file) ? options.file : resolve7(cwd, options.file);
6063
6068
  if (!existsSync7(file)) {
6064
- throw new PenvError17(
6069
+ throw new PenvError16(
6065
6070
  "IMPORT_FILE_MISSING",
6066
6071
  `There is no file at ${file} to import`,
6067
6072
  "Point `penv import` at an existing dotenv file, e.g. `penv import .env`."
@@ -6185,19 +6190,19 @@ var importCommand = defineCommand14({
6185
6190
 
6186
6191
  // src/commands/key.ts
6187
6192
  import { randomBytes } from "crypto";
6188
- import { KEY_BYTES, KEYCHAIN_SERVICE, PenvError as PenvError19 } from "@penvhq/core";
6193
+ import { KEY_BYTES, KEYCHAIN_SERVICE, PenvError as PenvError18 } from "@penvhq/core";
6189
6194
  import { defineCommand as defineCommand15 } from "citty";
6190
6195
 
6191
6196
  // src/keychain.ts
6192
6197
  import { createRequire as createRequire3 } from "module";
6193
- import { PenvError as PenvError18 } from "@penvhq/core";
6198
+ import { PenvError as PenvError17 } from "@penvhq/core";
6194
6199
  var KEYRING_MODULE = "@napi-rs/keyring";
6195
6200
  var nodeRequire = (id) => createRequire3(import.meta.url)(id);
6196
6201
  function firstLine2(cause) {
6197
6202
  return (cause instanceof Error ? cause.message : String(cause)).split("\n")[0] ?? "";
6198
6203
  }
6199
6204
  function keyringMissing(cause) {
6200
- return new PenvError18(
6205
+ return new PenvError17(
6201
6206
  "KEYCHAIN_BINDING_MISSING",
6202
6207
  `penv could not load ${KEYRING_MODULE}, the native binding it reads your OS keychain through`,
6203
6208
  `This penv engine was installed as a plain tarball, which carries no native modules. Install penv with \`npm install -g @penvhq/launcher\`, or declare \`source: "env"\` for this environment and export its key. Original error: ${firstLine2(cause)}`
@@ -6239,7 +6244,7 @@ function runKeyCreate(options) {
6239
6244
  const environment = targetEnvironment(project, options.environment);
6240
6245
  const declared = project.config.keys?.[environment];
6241
6246
  if (declared === void 0) {
6242
- throw new PenvError19(
6247
+ throw new PenvError18(
6243
6248
  "KEY_SOURCE_UNDECLARED",
6244
6249
  `Environment ${environment} declares no key source, so penv does not know what a key for it would be`,
6245
6250
  `Add a \`keys\` entry to penv.config.ts \u2014 e.g. \`keys: { ${environment}: { source: "env", id: "${environment}" } }\` \u2014 then run this again.`
@@ -6253,17 +6258,17 @@ function runKeyCreate(options) {
6253
6258
  try {
6254
6259
  existing = keychain.getPassword(KEYCHAIN_SERVICE, declared.id);
6255
6260
  } catch (cause) {
6256
- if (cause instanceof PenvError19) {
6261
+ if (cause instanceof PenvError18) {
6257
6262
  throw cause;
6258
6263
  }
6259
- throw new PenvError19(
6264
+ throw new PenvError18(
6260
6265
  "KEYCHAIN_UNAVAILABLE",
6261
6266
  `penv could not read your OS keychain to check for an existing key \`${declared.id}\``,
6262
6267
  `Unlock your keychain and run this again. Original error: ${cause instanceof Error ? cause.message : String(cause)}`
6263
6268
  );
6264
6269
  }
6265
6270
  if (existing !== null) {
6266
- throw new PenvError19(
6271
+ throw new PenvError18(
6267
6272
  "KEY_EXISTS",
6268
6273
  `Environment ${environment} already has a key \`${declared.id}\` in your OS keychain`,
6269
6274
  "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."
@@ -6432,7 +6437,7 @@ import {
6432
6437
  loadConfig as loadConfig2,
6433
6438
  oldLayoutEntries,
6434
6439
  PENV_DIR as PENV_DIR3,
6435
- PenvError as PenvError20,
6440
+ PenvError as PenvError19,
6436
6441
  RECORDS_PATH as RECORDS_PATH5,
6437
6442
  recordsDir as recordsDir3,
6438
6443
  renderStateGitignore as renderStateGitignore2,
@@ -6447,7 +6452,7 @@ function planMigrate(cwd) {
6447
6452
  const tree = recordsDir3(root);
6448
6453
  const collisions = collidingEntries(entries, tree);
6449
6454
  if (collisions.length > 0) {
6450
- throw new PenvError20(
6455
+ throw new PenvError19(
6451
6456
  "HALF_MIGRATED",
6452
6457
  `${describe(collisions)} in both \`${PENV_DIR3}/\` and \`${RECORDS_PATH5}/\`, and penv cannot tell which copy is current`,
6453
6458
  `Move what is left under \`${PENV_DIR3}/\` into \`${RECORDS_PATH5}/\` yourself, keeping the copy you want, then run \`penv validate\`.`
@@ -6588,7 +6593,7 @@ import {
6588
6593
  formatMetaFile,
6589
6594
  formatValueFile as formatValueFile6,
6590
6595
  openValue as openValue4,
6591
- PenvError as PenvError21,
6596
+ PenvError as PenvError20,
6592
6597
  parameterId as parameterId7,
6593
6598
  recordPath as recordPath7,
6594
6599
  sealValue as sealValue3
@@ -6608,7 +6613,7 @@ async function planFile(project, source, target, parameter) {
6608
6613
  }
6609
6614
  const environment = environmentOf2(source);
6610
6615
  if (environment === void 0) {
6611
- throw new PenvError21(
6616
+ throw new PenvError20(
6612
6617
  "SECRET_SCOPE_AMBIGUOUS",
6613
6618
  `${recordPath7(formatValueFile6(source))} is encrypted at a scope that names no environment, so penv cannot tell which key would re-seal it`,
6614
6619
  "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."
@@ -6617,7 +6622,7 @@ async function planFile(project, source, target, parameter) {
6617
6622
  const keys = keySourceFor(project, environment);
6618
6623
  const opened = openValue4(source, stored, keys);
6619
6624
  if (opened.kind === "failed") {
6620
- throw new PenvError21(
6625
+ throw new PenvError20(
6621
6626
  "VALUE_UNDECRYPTABLE",
6622
6627
  `${recordPath7(formatValueFile6(source))} could not be decrypted, so penv cannot re-seal it at its new address: ${opened.failure.detail}`,
6623
6628
  "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."
@@ -6640,7 +6645,7 @@ async function runMove(options) {
6640
6645
  assertWritableKey(options.to);
6641
6646
  const to = refFromKey(options.to, project.config);
6642
6647
  if (parameterId7(from) === parameterId7(to)) {
6643
- throw new PenvError21(
6648
+ throw new PenvError20(
6644
6649
  "PARAMETER_UNCHANGED",
6645
6650
  `\`${options.from}\` and \`${options.to}\` are the same parameter`,
6646
6651
  "Name a different destination, e.g. `penv mv redis-password redis/password`."
@@ -6650,7 +6655,7 @@ async function runMove(options) {
6650
6655
  const sources = filesOf(all, from);
6651
6656
  const meta = await project.provider.readMeta(from);
6652
6657
  if (sources.length === 0 && meta === void 0) {
6653
- throw new PenvError21(
6658
+ throw new PenvError20(
6654
6659
  "PARAMETER_ABSENT",
6655
6660
  `Parameter ${parameterId7(from)} has no value files and no meta, so there is nothing to move`,
6656
6661
  `\`penv list\` shows every parameter penv holds.`
@@ -6658,7 +6663,7 @@ async function runMove(options) {
6658
6663
  }
6659
6664
  const occupied2 = filesOf(all, to);
6660
6665
  if (occupied2.length > 0 || await project.provider.readMeta(to) !== void 0) {
6661
- throw new PenvError21(
6666
+ throw new PenvError20(
6662
6667
  "PARAMETER_EXISTS",
6663
6668
  `Parameter ${parameterId7(to)} already exists, and penv will not merge two parameters into one`,
6664
6669
  `Remove or rename ${parameterId7(to)} first. \`penv get ${options.to} --explain\` shows every file it holds.`
@@ -6817,7 +6822,7 @@ import {
6817
6822
  beginRotation,
6818
6823
  completeRotation,
6819
6824
  holdsRecords as holdsRecords2,
6820
- PenvError as PenvError22,
6825
+ PenvError as PenvError21,
6821
6826
  retainsPrevious,
6822
6827
  rotationOf as rotationOf2
6823
6828
  } from "@penvhq/core";
@@ -6846,7 +6851,7 @@ async function writeRotatedValue(project, provider, ref, environment, value) {
6846
6851
  }
6847
6852
  function requireNewValue(value, phase, key) {
6848
6853
  if (value === void 0) {
6849
- throw new PenvError22(
6854
+ throw new PenvError21(
6850
6855
  "ROTATION_NO_VALUE",
6851
6856
  `A ${phase} rotation of ${key} writes a new value, and none was given`,
6852
6857
  "Pass the new value as the argument \u2014 `penv rotate <key> <value>` \u2014 or pipe it in on stdin."
@@ -6856,7 +6861,7 @@ function requireNewValue(value, phase, key) {
6856
6861
  }
6857
6862
  function requireRetaining(provider, environment) {
6858
6863
  if (!retainsPrevious(provider)) {
6859
- throw new PenvError22(
6864
+ throw new PenvError21(
6860
6865
  "ROTATION_NOT_RETAINING",
6861
6866
  `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`,
6862
6867
  "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."
@@ -6870,7 +6875,7 @@ async function runRotate(options) {
6870
6875
  const ref = refFromKey(options.key, project.config);
6871
6876
  const source = await sourceProviderFor(project, environment);
6872
6877
  if (!holdsRecords2(source)) {
6873
- throw new PenvError22(
6878
+ throw new PenvError21(
6874
6879
  "ROTATION_NOT_RECORDS",
6875
6880
  `Environment ${environment} is backed by \`${source.type}\`, which holds a resolved projection penv cannot rotate in place`,
6876
6881
  "Rotate the parameter in the environment that holds the records (the local tree, Vault, SSM), then `penv push` the result to this destination."
@@ -6881,7 +6886,7 @@ async function runRotate(options) {
6881
6886
  const before = await provider.readMeta(ref);
6882
6887
  const { mechanism } = rotationOf2(before, environment);
6883
6888
  if (mechanism === void 0) {
6884
- throw new PenvError22(
6889
+ throw new PenvError21(
6885
6890
  "ROTATION_NO_MECHANISM",
6886
6891
  `Parameter ${options.key} declares no rotation mechanism for environment ${environment}, so penv does not know how to rotate it`,
6887
6892
  '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.'
@@ -6891,7 +6896,7 @@ async function runRotate(options) {
6891
6896
  const complete = options.complete === true;
6892
6897
  if (mechanism === "atomic-cutover") {
6893
6898
  if (begin || complete) {
6894
- throw new PenvError22(
6899
+ throw new PenvError21(
6895
6900
  "ROTATION_MECHANISM_MISMATCH",
6896
6901
  `Parameter ${options.key} is atomic-cutover, which flips in one step, so \`--begin\`/\`--complete\` do not apply`,
6897
6902
  "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."
@@ -6905,7 +6910,7 @@ async function runRotate(options) {
6905
6910
  }
6906
6911
  const retaining = requireRetaining(provider, environment);
6907
6912
  if (begin === complete) {
6908
- throw new PenvError22(
6913
+ throw new PenvError21(
6909
6914
  "ROTATION_PHASE_REQUIRED",
6910
6915
  `A dual-valid rotation of ${options.key} needs exactly one of \`--begin\` or \`--complete\``,
6911
6916
  "`--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."