@penvhq/cli 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -47771,6 +47771,16 @@ function assertNever2(value2, context) {
47771
47771
  function own(record3, key) {
47772
47772
  return record3 !== void 0 && Object.hasOwn(record3, key) ? record3[key] : void 0;
47773
47773
  }
47774
+ function environmentNames(config2) {
47775
+ return Object.keys(config2.environments);
47776
+ }
47777
+ function environmentEntry(config2, environment) {
47778
+ const declared = own(config2.environments, environment);
47779
+ if (declared === void 0) {
47780
+ return void 0;
47781
+ }
47782
+ return typeof declared === "string" ? { provider: declared } : declared;
47783
+ }
47774
47784
  function retainsPrevious(provider) {
47775
47785
  return typeof provider.readPrevious === "function";
47776
47786
  }
@@ -47787,11 +47797,11 @@ var CODE_FILE_EXTENSIONS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "m
47787
47797
  function isCodeModule(filename, config2) {
47788
47798
  const segments = filename.split(".");
47789
47799
  const extension = segments[segments.length - 1];
47790
- return extension !== void 0 && CODE_FILE_EXTENSIONS.has(extension) && !config2.environments.includes(extension);
47800
+ return extension !== void 0 && CODE_FILE_EXTENSIONS.has(extension) && !environmentNames(config2).includes(extension);
47791
47801
  }
47792
47802
  var SUPPORTED_META_FORMAT = "json";
47793
47803
  function reservedTokensFor(config2) {
47794
- return [.../* @__PURE__ */ new Set([...RESERVED_TOKENS, ...config2.environments])];
47804
+ return [.../* @__PURE__ */ new Set([...RESERVED_TOKENS, ...environmentNames(config2)])];
47795
47805
  }
47796
47806
  function isReservedToken(token, config2) {
47797
47807
  return reservedTokensFor(config2).includes(token);
@@ -47831,17 +47841,19 @@ function formatMetaFile(ref) {
47831
47841
  return `${refPath(ref)}.${ref.format}`;
47832
47842
  }
47833
47843
  function declaredList(config2) {
47834
- if (config2.environments.length === 0) {
47844
+ const names = environmentNames(config2);
47845
+ if (names.length === 0) {
47835
47846
  return "no environments are declared in penv.config.ts";
47836
47847
  }
47837
- return `declared environments are ${config2.environments.map((e) => `\`${e}\``).join(", ")}`;
47848
+ return `declared environments are ${names.map((e) => `\`${e}\``).join(", ")}`;
47838
47849
  }
47839
47850
  function asEnvironment(segment, relativePath, config2) {
47840
- if (config2.environments.includes(segment)) {
47851
+ const names = environmentNames(config2);
47852
+ if (names.includes(segment)) {
47841
47853
  return segment;
47842
47854
  }
47843
47855
  if (BARE_WORD.test(segment)) {
47844
- throw new UnknownEnvironmentError(segment, config2.environments);
47856
+ throw new UnknownEnvironmentError(segment, names);
47845
47857
  }
47846
47858
  throw new FilenameGrammarError(
47847
47859
  relativePath,
@@ -47996,12 +48008,12 @@ function isLegalEnvironmentName(name) {
47996
48008
  }
47997
48009
  function validateEnvironmentNames(config2) {
47998
48010
  const errors = [];
47999
- for (const environment of config2.environments) {
48011
+ for (const environment of environmentNames(config2)) {
48000
48012
  if (RESERVED_TOKENS.includes(environment)) {
48001
48013
  errors.push(new ReservedTokenError("environment", environment, "penv.config.ts"));
48002
48014
  continue;
48003
48015
  }
48004
- if (typeof environment === "string" && !isLegalEnvironmentName(environment)) {
48016
+ if (!isLegalEnvironmentName(environment)) {
48005
48017
  errors.push(new IllegalEnvironmentNameError(environment));
48006
48018
  }
48007
48019
  }
@@ -48111,15 +48123,25 @@ function createKeychainKeySource(config2, keychain) {
48111
48123
  };
48112
48124
  }
48113
48125
  function nullKeySource(environment) {
48114
- const detail = `environment ${environment} declares no \`keys\` block in penv.config.ts, so penv was never told where its keys live`;
48126
+ const detail = `environment ${environment} declares no \`keySource\` in penv.config.ts, so penv was never told where its keys live`;
48115
48127
  return {
48116
48128
  type: "none",
48117
48129
  lookup: () => ({ kind: "unavailable", detail }),
48118
48130
  current: () => ({ kind: "unavailable", detail })
48119
48131
  };
48120
48132
  }
48133
+ function keyConfigFor(config2, environment) {
48134
+ const declared = environmentEntry(config2, environment)?.keySource;
48135
+ if (declared === void 0) {
48136
+ return void 0;
48137
+ }
48138
+ if (typeof declared === "string") {
48139
+ return { source: declared, id: environment };
48140
+ }
48141
+ return { source: declared.source, id: declared.id ?? environment };
48142
+ }
48121
48143
  function resolveKeySource(config2, environment) {
48122
- const declared = own(config2.keys, environment);
48144
+ const declared = keyConfigFor(config2, environment);
48123
48145
  if (declared === void 0) {
48124
48146
  return nullKeySource(environment);
48125
48147
  }
@@ -48132,13 +48154,13 @@ function resolveKeySource(config2, environment) {
48132
48154
  throw new PenvError(
48133
48155
  "KEY_SOURCE_UNSUPPORTED",
48134
48156
  `Environment ${environment} declares key source \`${String(declared.source)}\`, which penv cannot read`,
48135
- `\`${String(declared.source)}\` is not a key source penv knows. Declare \`source: "env"\` (exported as \`${envVarFor(declared.id)}\`) or \`source: "keychain"\`.`
48157
+ `\`${String(declared.source)}\` is not a key source penv knows. Declare \`keySource: "env"\` (exported as \`${envVarFor(declared.id)}\`) or \`keySource: "keychain"\`.`
48136
48158
  );
48137
48159
  }
48138
48160
  var NO_KEY_SOURCE = "none";
48139
48161
  function keySourceIdentifier(config2, environment) {
48140
48162
  const source = resolveKeySource(config2, environment);
48141
- const declared = own(config2.keys, environment);
48163
+ const declared = keyConfigFor(config2, environment);
48142
48164
  if (declared === void 0) {
48143
48165
  return NO_KEY_SOURCE;
48144
48166
  }
@@ -48168,60 +48190,47 @@ var KEY_ID = /^[A-Za-z0-9._-]+$/;
48168
48190
  var SOURCES = ["env", "keychain"];
48169
48191
  function validateKeys(config2, declared) {
48170
48192
  const errors = [];
48171
- const keys = config2.keys;
48172
- if (keys === void 0) {
48173
- return errors;
48174
- }
48175
- if (keys === null || typeof keys !== "object" || Array.isArray(keys)) {
48176
- errors.push(
48177
- new ConfigError(
48178
- "`keys` in penv.config.ts is not an object",
48179
- 'Declare one key source per environment, e.g. `keys: { production: { source: "env", id: "prod" } }`, or remove the block.'
48180
- )
48181
- );
48182
- return errors;
48183
- }
48184
- const entries = keys;
48185
- for (const environment of Object.keys(entries)) {
48186
- if (!declared.has(environment)) {
48187
- errors.push(
48188
- new ConfigError(
48189
- `The \`keys\` block in penv.config.ts names environment ${environment}, which is not declared`,
48190
- `Add \`${environment}\` to the \`environments\` list, or remove its \`keys\` entry. Environments are a whitelist \u2014 penv never infers one.`
48191
- )
48192
- );
48193
+ for (const environment of declared) {
48194
+ const keySource = environmentEntry(config2, environment)?.keySource;
48195
+ if (keySource === void 0) {
48193
48196
  continue;
48194
48197
  }
48195
- const entry = own(entries, environment);
48196
- if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
48198
+ if (typeof keySource === "string") {
48199
+ if (!SOURCES.includes(keySource)) {
48200
+ errors.push(unknownSource(environment, keySource));
48201
+ }
48202
+ continue;
48203
+ }
48204
+ if (keySource === null || typeof keySource !== "object" || Array.isArray(keySource)) {
48197
48205
  errors.push(
48198
48206
  new ConfigError(
48199
- `The \`keys\` entry for environment ${environment} is not a key-source object`,
48200
- `Declare it as \`${environment}: { source: "env", id: "prod" }\`.`
48207
+ `The \`keySource\` for environment ${environment} is not a key source`,
48208
+ `Declare it as \`keySource: "env"\` \u2014 the key named after the environment \u2014 or as \`keySource: { source: "env", id: "prod" }\` to name a different key.`
48201
48209
  )
48202
48210
  );
48203
48211
  continue;
48204
48212
  }
48205
- const { source, id } = entry;
48213
+ const { source, id } = keySource;
48206
48214
  if (typeof source !== "string" || !SOURCES.includes(source)) {
48207
- errors.push(
48208
- new ConfigError(
48209
- `The \`keys\` entry for environment ${environment} declares source \`${String(source)}\``,
48210
- `A key source is ${SOURCES.map((s) => `\`${s}\``).join(" or ")}.`
48211
- )
48212
- );
48215
+ errors.push(unknownSource(environment, String(source)));
48213
48216
  }
48214
- if (typeof id !== "string" || !KEY_ID.test(id)) {
48217
+ if (id !== void 0 && (typeof id !== "string" || !KEY_ID.test(id))) {
48215
48218
  errors.push(
48216
48219
  new ConfigError(
48217
- `The \`keys\` entry for environment ${environment} declares id \`${String(id)}\``,
48218
- "A key id is one or more of `A-Za-z0-9._-`. It is written into every value file sealed under it, where `:` separates the fields, so `:` cannot appear in one."
48220
+ `The \`keySource\` for environment ${environment} declares id \`${String(id)}\``,
48221
+ "A key id is one or more of `A-Za-z0-9._-`. It is written into every value file sealed under it, where `:` separates the fields, so `:` cannot appear in one. Omit `id` to use the environment's own name."
48219
48222
  )
48220
48223
  );
48221
48224
  }
48222
48225
  }
48223
48226
  return errors;
48224
48227
  }
48228
+ function unknownSource(environment, source) {
48229
+ return new ConfigError(
48230
+ `The \`keySource\` for environment ${environment} declares source \`${source}\``,
48231
+ `A key source is ${SOURCES.map((s) => `\`${s}\``).join(" or ")}.`
48232
+ );
48233
+ }
48225
48234
  var CONFIG_FILENAMES = ["penv.config.ts", "penv.config.js", "penv.config.mjs"];
48226
48235
  var jitiModule;
48227
48236
  function setJitiApi(api) {
@@ -48250,7 +48259,7 @@ function jitiFor(file2) {
48250
48259
  ...alias === void 0 ? {} : { alias }
48251
48260
  });
48252
48261
  }
48253
- var EXPORT_REMEDY = "penv reads its configuration from the default export: `export default defineConfig({ environments: [...], providers: { ... } })`.";
48262
+ var EXPORT_REMEDY = 'penv reads its configuration from the default export: `export default defineConfig({ environments: { development: "@penvhq/provider-filesystem" } })`.';
48254
48263
  function describeValue(value2) {
48255
48264
  if (value2 === null) {
48256
48265
  return "`null`";
@@ -48353,6 +48362,11 @@ function loadConfigFrom(file2) {
48353
48362
  EXPORT_REMEDY
48354
48363
  );
48355
48364
  }
48365
+ const merged = mergedEnvironmentsBlock(config2);
48366
+ const first = merged[0];
48367
+ if (first !== void 0) {
48368
+ throw first;
48369
+ }
48356
48370
  return config2;
48357
48371
  }
48358
48372
  function loadConfig(cwd = process.cwd()) {
@@ -48374,20 +48388,20 @@ var LEGACY_PROVIDER_TYPES = {
48374
48388
  github: "@penvhq/provider-github"
48375
48389
  };
48376
48390
  var PACKAGE_NAME = /^(@[a-z0-9~-][a-z0-9._~-]*\/)?[a-z0-9~-][a-z0-9._~-]*$/;
48377
- function validateProviderType(environment, type) {
48378
- const legacy = own(LEGACY_PROVIDER_TYPES, type);
48391
+ function validateProviderName(environment, provider) {
48392
+ const legacy = own(LEGACY_PROVIDER_TYPES, provider);
48379
48393
  if (legacy !== void 0) {
48380
48394
  return new PenvError(
48381
- "PROVIDER_TYPE_LEGACY",
48382
- `The provider type \`${type}\` for environment ${environment} is a short name, and provider types are package names`,
48383
- `Write \`${environment}: { type: "${legacy}" }\` and make sure the package is installed. A provider's \`type\` is the package penv imports, so the config and the dependency tree name the same thing.`
48395
+ "PROVIDER_LEGACY",
48396
+ `The provider \`${provider}\` for environment ${environment} is a short name, and a provider is a package name`,
48397
+ `Write \`${environment}: { provider: "${legacy}" }\` and make sure the package is installed. A provider is the package penv imports, so the config and the dependency tree name the same thing.`
48384
48398
  );
48385
48399
  }
48386
- if (!PACKAGE_NAME.test(type)) {
48400
+ if (!PACKAGE_NAME.test(provider)) {
48387
48401
  return new PenvError(
48388
- "PROVIDER_TYPE_INVALID",
48389
- `The provider type \`${type}\` for environment ${environment} is not a package name`,
48390
- `Name the provider's package, e.g. \`${environment}: { type: "@penvhq/provider-filesystem" }\`. penv imports the package the \`type\` names from this project's node_modules.`
48402
+ "PROVIDER_INVALID",
48403
+ `The provider \`${provider}\` for environment ${environment} is not a package name`,
48404
+ `Name the provider's package, e.g. \`${environment}: "@penvhq/provider-filesystem"\`. penv imports the package the \`provider\` names from this project's node_modules.`
48391
48405
  );
48392
48406
  }
48393
48407
  return void 0;
@@ -48406,63 +48420,76 @@ function validateDefaultEnvironment(config2, declared) {
48406
48420
  ];
48407
48421
  }
48408
48422
  if (!declared.has(value2)) {
48409
- return [new UnknownEnvironmentError(value2, config2.environments)];
48423
+ return [new UnknownEnvironmentError(value2, [...declared])];
48410
48424
  }
48411
48425
  return [];
48412
48426
  }
48413
- function legacySinksBlock(config2) {
48414
- const sinks = config2.sinks;
48415
- if (sinks === void 0) {
48427
+ function mergedEnvironmentsBlock(config2) {
48428
+ const raw = config2;
48429
+ const carried = [];
48430
+ if (Array.isArray(config2.environments)) {
48431
+ carried.push("`environments` as a list");
48432
+ }
48433
+ if (raw.providers !== void 0) {
48434
+ carried.push("a `providers` block");
48435
+ }
48436
+ if (raw.keys !== void 0) {
48437
+ carried.push("a `keys` block");
48438
+ }
48439
+ if (carried.length === 0) {
48416
48440
  return [];
48417
48441
  }
48418
48442
  return [
48419
48443
  new PenvError(
48420
- "CONFIG_SINKS_REMOVED",
48421
- "`sinks` in penv.config.ts is no longer a config key \u2014 everything is a provider now",
48422
- 'Move each entry into `providers` as that environment\'s provider: `sinks: { production: { type: "github", repo: "acme/api" } }` becomes `providers: { production: { type: "@penvhq/provider-github", location: "acme/api" } }`, with `npm i -D @penvhq/provider-github`. `penv push` and `penv pull` work against it directly.'
48444
+ "CONFIG_ENVIRONMENTS_MERGED",
48445
+ `penv.config.ts declares ${carried.join(" and ")}, and one \`environments\` record now holds all three`,
48446
+ 'Give each environment one entry, in its provider\'s own words: `type` becomes `provider`, `location` becomes the field that provider declares (`project` for vercel, `path` for ssm and vault), `targets` becomes a single `target` defaulting to the environment\'s name, and `keys.<env>` becomes that entry\'s `keySource`. So `environments: ["production"], providers: { production: { type: "@penvhq/provider-vercel", location: "penv-cloud", targets: { production: "production" } } }, keys: { production: { source: "env", id: "production" } }` becomes `environments: { production: { provider: "@penvhq/provider-vercel", project: "penv-cloud", keySource: "env" } }`. A key id that is not the environment\'s own name has to stay in the object form \u2014 `keySource: { source: "env", id: "prod" }` \u2014 because the shorthand names the key after the environment, and a renamed key is a key the sealed values do not carry. An environment whose provider needs no fields is just the package name: `development: "@penvhq/provider-filesystem"`.'
48423
48447
  )
48424
48448
  ];
48425
48449
  }
48450
+ function providerMissing(environment) {
48451
+ return new PenvError(
48452
+ "PROVIDER_MISSING",
48453
+ `The entry for environment ${environment} declares no \`provider\``,
48454
+ `Name the package of the backend that holds this environment's values, e.g. \`${environment}: { provider: "@penvhq/provider-filesystem" }\`. If it still says \`type\`, that is the field \`provider\` replaced.`
48455
+ );
48456
+ }
48426
48457
  function validateConfig(config2) {
48427
48458
  const errors = [];
48459
+ const merged = mergedEnvironmentsBlock(config2);
48460
+ if (merged.length > 0) {
48461
+ return merged;
48462
+ }
48428
48463
  const environments = config2.environments;
48429
- if (!Array.isArray(environments)) {
48464
+ if (environments === null || typeof environments !== "object") {
48430
48465
  errors.push(
48431
48466
  new PenvError(
48432
48467
  "CONFIG_ENVIRONMENTS_INVALID",
48433
- "`environments` in penv.config.ts is not an array",
48434
- 'Declare the whitelist as an array of names, e.g. `environments: ["development", "production"]`.'
48468
+ "`environments` in penv.config.ts is not an object",
48469
+ 'Declare one entry per environment, e.g. `environments: { development: "@penvhq/provider-filesystem" }`.'
48435
48470
  )
48436
48471
  );
48437
48472
  return errors;
48438
48473
  }
48439
- if (environments.length === 0) {
48474
+ const entries = environments;
48475
+ const names = Object.keys(entries);
48476
+ if (names.length === 0) {
48440
48477
  errors.push(
48441
48478
  new PenvError(
48442
48479
  "CONFIG_ENVIRONMENTS_EMPTY",
48443
48480
  "`environments` in penv.config.ts is empty, so no environment can ever be loaded",
48444
- 'Declare at least one environment, e.g. `environments: ["development", "production"]`. Environments are a whitelist \u2014 penv never infers one from a folder or a filename.'
48481
+ 'Declare at least one, e.g. `environments: { development: "@penvhq/provider-filesystem" }`. Environments are a whitelist \u2014 penv never infers one from a folder or a filename.'
48445
48482
  )
48446
48483
  );
48447
48484
  }
48448
48485
  const declared = /* @__PURE__ */ new Set();
48449
- for (const environment of environments) {
48450
- if (typeof environment !== "string" || environment.trim().length === 0) {
48486
+ for (const environment of names) {
48487
+ if (environment.trim().length === 0) {
48451
48488
  errors.push(
48452
48489
  new PenvError(
48453
48490
  "CONFIG_ENVIRONMENT_INVALID",
48454
- `The environment \`${String(environment)}\` in penv.config.ts is not a non-empty name`,
48455
- 'Every entry in `environments` is a non-empty string, e.g. `"production"`.'
48456
- )
48457
- );
48458
- continue;
48459
- }
48460
- if (declared.has(environment)) {
48461
- errors.push(
48462
- new PenvError(
48463
- "CONFIG_ENVIRONMENT_DUPLICATE",
48464
- `The environment \`${environment}\` is declared twice in penv.config.ts`,
48465
- "Each environment is declared once. Remove the duplicate."
48491
+ `The environment \`${environment}\` in penv.config.ts is not a non-empty name`,
48492
+ 'Every key in `environments` is a non-empty name, e.g. `"production"`.'
48466
48493
  )
48467
48494
  );
48468
48495
  continue;
@@ -48471,63 +48498,36 @@ function validateConfig(config2) {
48471
48498
  }
48472
48499
  errors.push(...validateEnvironmentNames(config2));
48473
48500
  errors.push(...validateDefaultEnvironment(config2, declared));
48474
- const providers = config2.providers;
48475
- if (providers === null || typeof providers !== "object" || Array.isArray(providers)) {
48476
- errors.push(
48477
- new PenvError(
48478
- "CONFIG_PROVIDERS_INVALID",
48479
- "`providers` in penv.config.ts is not an object",
48480
- 'Declare one provider per environment, e.g. `providers: { production: { type: "@penvhq/provider-filesystem" } }`.'
48481
- )
48482
- );
48483
- return errors;
48484
- }
48485
- const providerEntries = providers;
48486
48501
  for (const environment of declared) {
48487
- const provider = own(providerEntries, environment);
48488
- if (provider === void 0) {
48489
- errors.push(
48490
- new PenvError(
48491
- "PROVIDER_MISSING",
48492
- `Environment ${environment} has no entry in \`providers\` in penv.config.ts`,
48493
- `Add \`${environment}: { type: "@penvhq/provider-filesystem" }\` to the \`providers\` block, or remove \`${environment}\` from \`environments\`.`
48494
- )
48495
- );
48502
+ const entry = own(entries, environment);
48503
+ if (typeof entry === "string") {
48504
+ const nameError2 = validateProviderName(environment, entry);
48505
+ if (nameError2 !== void 0) {
48506
+ errors.push(nameError2);
48507
+ }
48496
48508
  continue;
48497
48509
  }
48498
- if (provider === null || typeof provider !== "object" || Array.isArray(provider)) {
48510
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
48499
48511
  errors.push(
48500
48512
  new PenvError(
48501
- "PROVIDER_INVALID",
48502
- `The provider for environment ${environment} is ${describeValue(provider)}, not a provider object`,
48503
- `Declare it as \`${environment}: { type: "@penvhq/provider-filesystem" }\`, adding a \`location\` when the backend needs one.`
48513
+ "ENVIRONMENT_ENTRY_INVALID",
48514
+ `The entry for environment ${environment} is ${describeValue(entry)}, not a provider package name or an entry object`,
48515
+ `Declare it as \`${environment}: "@penvhq/provider-filesystem"\`, or as \`${environment}: { provider: "@penvhq/provider-vercel", project: "acme-web" }\` when the provider needs fields.`
48504
48516
  )
48505
48517
  );
48506
48518
  continue;
48507
48519
  }
48508
- const type = provider.type;
48509
- if (typeof type !== "string" || type.trim().length === 0) {
48510
- errors.push(
48511
- new PenvError(
48512
- "PROVIDER_TYPE_MISSING",
48513
- `The provider for environment ${environment} declares no \`type\``,
48514
- `Name the package of the backend that holds this environment's values, e.g. \`${environment}: { type: "@penvhq/provider-filesystem" }\`.`
48515
- )
48516
- );
48520
+ const provider = entry.provider;
48521
+ if (typeof provider !== "string" || provider.trim().length === 0) {
48522
+ errors.push(providerMissing(environment));
48517
48523
  continue;
48518
48524
  }
48519
- const typeError = validateProviderType(environment, type);
48520
- if (typeError !== void 0) {
48521
- errors.push(typeError);
48522
- }
48523
- }
48524
- for (const environment of Object.keys(providerEntries)) {
48525
- if (!declared.has(environment)) {
48526
- errors.push(new UnknownEnvironmentError(environment, environments));
48525
+ const nameError = validateProviderName(environment, provider);
48526
+ if (nameError !== void 0) {
48527
+ errors.push(nameError);
48527
48528
  }
48528
48529
  }
48529
48530
  errors.push(...validateKeys(config2, declared));
48530
- errors.push(...legacySinksBlock(config2));
48531
48531
  errors.push(...validateSchemaFile(config2));
48532
48532
  errors.push(...validatePublicPrefixes(config2));
48533
48533
  errors.push(...legacyNamesBlock(config2));
@@ -48596,8 +48596,9 @@ function legacyNamesBlock(config2) {
48596
48596
  ];
48597
48597
  }
48598
48598
  function assertEnvironment(environment, config2) {
48599
- if (!config2.environments.includes(environment)) {
48600
- throw new UnknownEnvironmentError(environment, config2.environments);
48599
+ const declared = environmentNames(config2);
48600
+ if (!declared.includes(environment)) {
48601
+ throw new UnknownEnvironmentError(environment, declared);
48601
48602
  }
48602
48603
  if (!isLegalEnvironmentName(environment)) {
48603
48604
  throw new IllegalEnvironmentNameError(environment);
@@ -48627,7 +48628,7 @@ function resolveEnvironment(config2, explicit) {
48627
48628
  if (environment === void 0) {
48628
48629
  throw new ConfigError(
48629
48630
  "No environment is set, so penv cannot tell which environment to load",
48630
- `Pass \`--env <environment>\` \u2014 one of ${quoteList(config2.environments)} \u2014 or declare \`defaultEnvironment\` in penv.config.ts so every command has one.`
48631
+ `Pass \`--env <environment>\` \u2014 one of ${quoteList(environmentNames(config2))} \u2014 or declare \`defaultEnvironment\` in penv.config.ts so every command has one.`
48631
48632
  );
48632
48633
  }
48633
48634
  return environment;
@@ -48681,7 +48682,7 @@ var ALGORITHM = "aes-256-gcm";
48681
48682
  function remedyFor(failure, location) {
48682
48683
  switch (failure.reason) {
48683
48684
  case "key-source-unavailable":
48684
- return `penv could not consult a key source: ${failure.detail}. Make the key available where this environment expects it \u2014 if the source is your OS keychain, unlock it; if no key source is declared, add a \`keys\` block to penv.config.ts.`;
48685
+ return `penv could not consult a key source: ${failure.detail}. Make the key available where this environment expects it \u2014 if the source is your OS keychain, unlock it; if no key source is declared, add a \`keySource\` to this environment's entry in penv.config.ts.`;
48685
48686
  case "key-absent":
48686
48687
  return `The key that seals ${location} is not there: ${failure.detail}. Export the key, or re-seal the value under a key you hold with \`penv encrypt\`.`;
48687
48688
  case "malformed-envelope":
@@ -48717,7 +48718,7 @@ var KeyUnavailableError = class extends PenvError {
48717
48718
  super(
48718
48719
  "KEY_UNAVAILABLE",
48719
48720
  `Parameter ${parameter} is a secret for environment ${environment}, and penv has no key to seal it with: ${detail}`,
48720
- "penv will not write a secret in plaintext, and will not invent a key. Declare the key source in the `keys` block of penv.config.ts and make the key available, then run the command again."
48721
+ "penv will not write a secret in plaintext, and will not invent a key. Declare this environment's `keySource` in penv.config.ts and make the key available, then run the command again."
48721
48722
  );
48722
48723
  this.parameter = parameter;
48723
48724
  this.environment = environment;
@@ -50369,7 +50370,7 @@ var import_node_path7 = require("path");
50369
50370
  // ../runtime/dist/index.js
50370
50371
  init_cjs_shims();
50371
50372
 
50372
- // ../runtime/dist/chunk-SLMVNKRU.js
50373
+ // ../runtime/dist/chunk-FPKLY3QL.js
50373
50374
  init_cjs_shims();
50374
50375
 
50375
50376
  // ../providers/filesystem/dist/index.js
@@ -50521,11 +50522,11 @@ function createFilesystemProvider(options) {
50521
50522
  return new FilesystemProvider(options);
50522
50523
  }
50523
50524
 
50524
- // ../runtime/dist/chunk-SLMVNKRU.js
50525
+ // ../runtime/dist/chunk-FPKLY3QL.js
50525
50526
  var LOCAL_TREE_TYPE = "@penvhq/provider-filesystem";
50526
50527
  function hasRemoteSource(config2, environment) {
50527
- const declared = own(config2.providers, environment);
50528
- return declared !== void 0 && declared.type !== LOCAL_TREE_TYPE;
50528
+ const declared = environmentEntry(config2, environment);
50529
+ return declared !== void 0 && declared.provider !== LOCAL_TREE_TYPE;
50529
50530
  }
50530
50531
 
50531
50532
  // ../runtime/dist/index.js
@@ -50719,11 +50720,15 @@ function penvOwnVariables(host) {
50719
50720
  }
50720
50721
  function strippedVariables(host, config2, credentials) {
50721
50722
  const names = penvOwnVariables(host);
50722
- for (const provider of Object.values(config2.providers)) {
50723
- for (const name of own(PROVIDER_CREDENTIALS, provider.type) ?? []) {
50723
+ for (const environment of environmentNames(config2)) {
50724
+ const provider = environmentEntry(config2, environment)?.provider;
50725
+ if (provider === void 0) {
50726
+ continue;
50727
+ }
50728
+ for (const name of own(PROVIDER_CREDENTIALS, provider) ?? []) {
50724
50729
  names.add(name);
50725
50730
  }
50726
- for (const name of own(credentials, provider.type) ?? []) {
50731
+ for (const name of own(credentials, provider) ?? []) {
50727
50732
  names.add(name);
50728
50733
  }
50729
50734
  }
@@ -51319,22 +51324,10 @@ var import_node_path4 = require("path");
51319
51324
  // src/env-flags.ts
51320
51325
  init_cjs_shims();
51321
51326
  var BASE_RESERVED = ["help", "version", "h", "v", "_", "--"];
51322
- var COMMAND_FLAGS = [
51323
- ...BASE_RESERVED,
51324
- "env",
51325
- "out",
51326
- "allow-decrypt",
51327
- "destination",
51328
- "dest",
51329
- "d",
51330
- "location",
51331
- "l",
51332
- "yes",
51333
- "y"
51334
- ];
51327
+ var COMMAND_FLAGS = [...BASE_RESERVED, "env", "out", "allow-decrypt", "yes", "y"];
51335
51328
  function shadowedEnvironments(config2) {
51336
51329
  const flags = new Set(COMMAND_FLAGS);
51337
- return config2.environments.filter((environment) => flags.has(environment));
51330
+ return environmentNames(config2).filter((environment) => flags.has(environment));
51338
51331
  }
51339
51332
  function shorthandCandidates(args, declared) {
51340
51333
  const reserved = /* @__PURE__ */ new Set([...BASE_RESERVED, ...declared]);
@@ -51347,13 +51340,14 @@ function environmentFromShorthand(config2, candidates, explicit) {
51347
51340
  if (candidates.length === 0) {
51348
51341
  return void 0;
51349
51342
  }
51350
- const hits = candidates.filter((candidate) => config2.environments.includes(candidate));
51351
- const strangers = candidates.filter((candidate) => !config2.environments.includes(candidate));
51343
+ const declared = environmentNames(config2);
51344
+ const hits = candidates.filter((candidate) => declared.includes(candidate));
51345
+ const strangers = candidates.filter((candidate) => !declared.includes(candidate));
51352
51346
  if (strangers.length > 0) {
51353
51347
  throw new PenvError(
51354
51348
  "UNKNOWN_FLAG",
51355
51349
  `${quoteList2(strangers)} ${strangers.length === 1 ? "is not a flag" : "are not flags"} this command takes, and ${strangers.length === 1 ? "names" : "name"} no declared environment`,
51356
- `Declared environments work as bare flags: ${quoteList2(config2.environments)}. Anything else needs \`--env <name>\`.`
51350
+ `Declared environments work as bare flags: ${quoteList2(declared)}. Anything else needs \`--env <name>\`.`
51357
51351
  );
51358
51352
  }
51359
51353
  if (hits.length > 1) {
@@ -51560,14 +51554,18 @@ async function loadPluginProvider(type, context) {
51560
51554
  function assertProvidersRegistered(config2, projectRoot, options) {
51561
51555
  const local = localExtensions(projectRoot);
51562
51556
  const ci = options?.ci ?? isCi(process.env.CI);
51563
- for (const [environment, provider] of Object.entries(config2.providers)) {
51564
- if (isProviderRegistered(provider.type)) {
51557
+ for (const environment of environmentNames(config2)) {
51558
+ const provider = environmentEntry(config2, environment)?.provider;
51559
+ if (typeof provider !== "string" || provider.trim().length === 0) {
51560
+ throw providerMissing(environment);
51561
+ }
51562
+ if (isProviderRegistered(provider)) {
51565
51563
  continue;
51566
51564
  }
51567
- if (ci && local.includes(provider.type)) {
51568
- throw localExtensionInCi(provider.type, environment);
51565
+ if (ci && local.includes(provider)) {
51566
+ throw localExtensionInCi(provider, environment);
51569
51567
  }
51570
- resolveExtension(provider.type, projectRoot, environment, local);
51568
+ resolveExtension(provider, projectRoot, environment, local);
51571
51569
  }
51572
51570
  }
51573
51571
  function resolveExtension(type, projectRoot, environment, local = localExtensions(projectRoot)) {
@@ -51725,11 +51723,11 @@ function schemaShapeFileOf(project) {
51725
51723
  return selfContainedSchemaModule(project.root, schemaFile) ?? SCHEMA_SHAPE_FILE;
51726
51724
  }
51727
51725
  async function sourceProviderFor(project, environment) {
51728
- const providerConfig = project.config.providers[environment];
51726
+ const providerConfig = environmentEntry(project.config, environment);
51729
51727
  if (providerConfig === void 0) {
51730
51728
  return createProvider(LOCAL_TREE_TYPE2, { root: project.root, config: project.config });
51731
51729
  }
51732
- return createSourceProvider(providerConfig.type, {
51730
+ return createSourceProvider(providerConfig.provider, {
51733
51731
  root: project.root,
51734
51732
  config: project.config,
51735
51733
  providerConfig,
@@ -51741,7 +51739,7 @@ function targetEnvironment(project, explicit, shorthand) {
51741
51739
  return resolveEnvironment(project.config, explicit ?? fromFlags);
51742
51740
  }
51743
51741
  var KEY_SEPARATOR = /[./\\]/;
51744
- var NO_ENVIRONMENTS = { environments: [], providers: {} };
51742
+ var NO_ENVIRONMENTS = { environments: {} };
51745
51743
  function refFromKey(key, config2) {
51746
51744
  const segments = key.split(KEY_SEPARATOR).filter((segment) => segment.length > 0);
51747
51745
  const name = segments[segments.length - 1];
@@ -52064,8 +52062,9 @@ function discoverDotenvFiles(root) {
52064
52062
  return files.sort((a, b) => order(a) < order(b) ? -1 : order(a) > order(b) ? 1 : 0);
52065
52063
  }
52066
52064
  function activeDotenvFiles(root, config2) {
52065
+ const declared = environmentNames(config2);
52067
52066
  return discoverDotenvFiles(root).filter(
52068
- (file2) => file2.environment === void 0 || config2.environments.includes(file2.environment)
52067
+ (file2) => file2.environment === void 0 || declared.includes(file2.environment)
52069
52068
  );
52070
52069
  }
52071
52070
  function environmentsDeclaredBy(files) {
@@ -52804,11 +52803,12 @@ function packageManifest(specifier, root) {
52804
52803
  }
52805
52804
  function declaredCredentials(config2, root) {
52806
52805
  const declared = {};
52807
- for (const provider of Object.values(config2.providers)) {
52808
- if (own(declared, provider.type) !== void 0) {
52806
+ for (const environment of environmentNames(config2)) {
52807
+ const provider = environmentEntry(config2, environment)?.provider;
52808
+ if (provider === void 0 || own(declared, provider) !== void 0) {
52809
52809
  continue;
52810
52810
  }
52811
- const penv = packageManifest(provider.type, root)?.penv;
52811
+ const penv = packageManifest(provider, root)?.penv;
52812
52812
  const credentials = isPlainObject5(penv) ? penv.credentials : void 0;
52813
52813
  if (credentials === void 0) {
52814
52814
  continue;
@@ -52816,11 +52816,11 @@ function declaredCredentials(config2, root) {
52816
52816
  if (!Array.isArray(credentials) || credentials.some((name) => typeof name !== "string" || !VARIABLE2.test(name))) {
52817
52817
  throw new PenvError(
52818
52818
  "PROVIDER_CREDENTIALS_INVALID",
52819
- `\`${provider.type}\` declares \`penv.credentials\`, and it is not a list of variable names`,
52819
+ `\`${provider}\` declares \`penv.credentials\`, and it is not a list of variable names`,
52820
52820
  `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.`
52821
52821
  );
52822
52822
  }
52823
- declared[provider.type] = credentials;
52823
+ declared[provider] = credentials;
52824
52824
  }
52825
52825
  return declared;
52826
52826
  }
@@ -53510,38 +53510,23 @@ function lineReader(input = process.stdin, output = process.stdout) {
53510
53510
  // src/commands/push.ts
53511
53511
  var LAST_PUSHED_KEY = "lastPushedAt";
53512
53512
  async function destinationFor(project, environment, options) {
53513
- if (options.destination !== void 0) {
53514
- const providerConfig = {
53515
- type: options.destination,
53516
- ...options.location === void 0 ? {} : { location: options.location }
53517
- };
53518
- const provider2 = options.provider ?? await createSourceProvider(options.destination, {
53519
- root: project.root,
53520
- config: project.config,
53521
- providerConfig,
53522
- environment
53523
- });
53524
- return { provider: provider2, location: options.location };
53525
- }
53526
- const declared = project.config.providers[environment];
53527
- const location = declared?.location;
53528
- if (declared === void 0 || declared.type === LOCAL_TREE_TYPE2) {
53513
+ const declared = environmentEntry(project.config, environment);
53514
+ if (declared === void 0 || declared.provider === LOCAL_TREE_TYPE2) {
53529
53515
  throw new PenvError(
53530
53516
  "NO_DESTINATION",
53531
53517
  `Environment ${environment}'s provider is the local records tree itself, so penv has nowhere to push`,
53532
- `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>\`.`
53518
+ `Declare the backend that holds it in penv.config.ts \u2014 e.g. \`${environment}: { provider: "@penvhq/provider-github", repository: "owner/repo" }\` \u2014 and run this again. A push only ever goes where the config says this environment lives.`
53533
53519
  );
53534
53520
  }
53535
53521
  if (options.provider !== void 0) {
53536
- return { provider: options.provider, location };
53522
+ return options.provider;
53537
53523
  }
53538
- const provider = await createSourceProvider(declared.type, {
53524
+ return createSourceProvider(declared.provider, {
53539
53525
  root: project.root,
53540
53526
  config: project.config,
53541
53527
  providerConfig: declared,
53542
53528
  environment
53543
53529
  });
53544
- return { provider, location };
53545
53530
  }
53546
53531
  function plan(resolutions, config2, environment, allowDecrypt) {
53547
53532
  const outbound = [];
@@ -53625,7 +53610,7 @@ async function ensureTargetApproved(provider, environment, options) {
53625
53610
  await provider.ensureTarget(environment);
53626
53611
  return true;
53627
53612
  }
53628
- async function pushProjection(project, provider, environment, location, options) {
53613
+ async function pushProjection(project, provider, environment, options) {
53629
53614
  const tree = localTree(project);
53630
53615
  const keys = keySourceFor(project, environment);
53631
53616
  const resolutions = resolveAllSync(tree, environment, keys, true);
@@ -53656,7 +53641,6 @@ async function pushProjection(project, provider, environment, location, options)
53656
53641
  environment,
53657
53642
  destination: provider.type,
53658
53643
  mode: "projection",
53659
- location,
53660
53644
  pushed: outbound.length,
53661
53645
  meta: 0,
53662
53646
  repositorySecrets,
@@ -53672,7 +53656,7 @@ function crossesToRecords(file2, environment) {
53672
53656
  }
53673
53657
  return scope.kind === "environment" && scope.environment === environment;
53674
53658
  }
53675
- async function pushRecords(project, provider, environment, location) {
53659
+ async function pushRecords(project, provider, environment) {
53676
53660
  const tree = localTree(project);
53677
53661
  const files = tree.listSync().filter((file2) => crossesToRecords(file2, environment));
53678
53662
  let pushed = 0;
@@ -53698,7 +53682,6 @@ async function pushRecords(project, provider, environment, location) {
53698
53682
  environment,
53699
53683
  destination: provider.type,
53700
53684
  mode: "records",
53701
- location,
53702
53685
  pushed,
53703
53686
  meta: meta3,
53704
53687
  repositorySecrets: 0,
@@ -53710,11 +53693,11 @@ async function pushRecords(project, provider, environment, location) {
53710
53693
  async function runPush(options) {
53711
53694
  const project = openProject(options.cwd);
53712
53695
  const environment = targetEnvironment(project, options.environment, options.envFlags);
53713
- const { provider, location } = await destinationFor(project, environment, options);
53696
+ const provider = await destinationFor(project, environment, options);
53714
53697
  if (holdsProjection(provider)) {
53715
- return pushProjection(project, provider, environment, location, options);
53698
+ return pushProjection(project, provider, environment, options);
53716
53699
  }
53717
- return pushRecords(project, provider, environment, location);
53700
+ return pushRecords(project, provider, environment);
53718
53701
  }
53719
53702
  function renderPush(result2) {
53720
53703
  if (result2.pushed === 0 && result2.meta === 0) {
@@ -53726,7 +53709,7 @@ function renderPush(result2) {
53726
53709
  }
53727
53710
  ]);
53728
53711
  }
53729
- const target = `${result2.destination} for environment ${result2.environment}${result2.location === void 0 ? "" : ` (${result2.location})`}`;
53712
+ const target = `${result2.destination} for environment ${result2.environment}`;
53730
53713
  if (result2.mode === "records") {
53731
53714
  return formatRows([
53732
53715
  {
@@ -53775,6 +53758,19 @@ function renderPush(result2) {
53775
53758
  }
53776
53759
  return formatRows(rows);
53777
53760
  }
53761
+ var REDIRECT_FLAGS = ["destination", "dest", "d", "location", "l"];
53762
+ function assertNoRedirect(args) {
53763
+ const given = REDIRECT_FLAGS.filter((flag) => args[flag] !== void 0);
53764
+ const named = given[0];
53765
+ if (named === void 0) {
53766
+ return;
53767
+ }
53768
+ throw new PenvError(
53769
+ "REMOVED_FLAG",
53770
+ `\`--${named}\` is not a flag \`penv push\` takes`,
53771
+ "A push only ever goes where the config says this environment lives. Declare the destination in `environments.<env>` \u2014 the provider package, addressed in that provider's own words \u2014 and push with `--env <name>`."
53772
+ );
53773
+ }
53778
53774
  var pushCommand = defineCommand({
53779
53775
  meta: {
53780
53776
  name: "push",
@@ -53786,16 +53782,6 @@ var pushCommand = defineCommand({
53786
53782
  type: "boolean",
53787
53783
  description: "Decrypt sealed values locally and push them as plaintext for the destination to re-seal"
53788
53784
  },
53789
- destination: {
53790
- type: "string",
53791
- alias: ["dest", "d"],
53792
- description: "Push once to this provider package instead of the declared one"
53793
- },
53794
- location: {
53795
- type: "string",
53796
- alias: "l",
53797
- description: "The destination-side place, when --destination needs one"
53798
- },
53799
53785
  yes: {
53800
53786
  type: "boolean",
53801
53787
  alias: "y",
@@ -53804,25 +53790,13 @@ var pushCommand = defineCommand({
53804
53790
  },
53805
53791
  run({ args }) {
53806
53792
  return guard(async () => {
53793
+ assertNoRedirect(args);
53807
53794
  const result2 = await runPush({
53808
53795
  cwd: process.cwd(),
53809
53796
  ...args.env === void 0 ? {} : { environment: args.env },
53810
53797
  ...args["allow-decrypt"] === void 0 ? {} : { allowDecrypt: args["allow-decrypt"] },
53811
- ...args.destination === void 0 ? {} : { destination: args.destination },
53812
- ...args.location === void 0 ? {} : { location: args.location },
53813
53798
  ...args.yes === void 0 ? {} : { yes: args.yes },
53814
- envFlags: shorthandCandidates(args, [
53815
- "env",
53816
- "allow-decrypt",
53817
- "allowDecrypt",
53818
- "destination",
53819
- "dest",
53820
- "d",
53821
- "location",
53822
- "l",
53823
- "yes",
53824
- "y"
53825
- ])
53799
+ envFlags: shorthandCandidates(args, ["env", "allow-decrypt", "allowDecrypt", "yes", "y"])
53826
53800
  });
53827
53801
  write(renderPush(result2));
53828
53802
  });
@@ -53921,7 +53895,7 @@ async function runDoctor(options) {
53921
53895
  check: "provider",
53922
53896
  severity: "pass",
53923
53897
  label: "Provider",
53924
- subject: project.config.providers[environment]?.type ?? project.provider.type
53898
+ subject: environmentEntry(project.config, environment)?.provider ?? project.provider.type
53925
53899
  });
53926
53900
  return {
53927
53901
  environment,
@@ -54191,8 +54165,8 @@ async function projectionFindings(project, environment, override) {
54191
54165
  if (override !== void 0) {
54192
54166
  projection = override;
54193
54167
  } else {
54194
- const declared = project.config.providers[environment];
54195
- if (declared === void 0 || declared.type === LOCAL_TREE_TYPE2) {
54168
+ const declared = environmentEntry(project.config, environment);
54169
+ if (declared === void 0 || declared.provider === LOCAL_TREE_TYPE2) {
54196
54170
  return [];
54197
54171
  }
54198
54172
  let source;
@@ -54204,7 +54178,7 @@ async function projectionFindings(project, environment, override) {
54204
54178
  check: "projection-unreachable",
54205
54179
  severity: "unknown",
54206
54180
  label: "Destination",
54207
- subject: `could not build the ${declared.type} provider for ${environment}`,
54181
+ subject: `could not build the ${declared.provider} provider for ${environment}`,
54208
54182
  detail: errorDetail(error51)
54209
54183
  }
54210
54184
  ];
@@ -54477,8 +54451,8 @@ function refPathOf(ref) {
54477
54451
  return [...ref.namespace, ref.name].join("/");
54478
54452
  }
54479
54453
  async function rotationSubjects(project, environment, local, override) {
54480
- const providerConfig = project.config.providers[environment];
54481
- if (override === void 0 && (providerConfig === void 0 || providerConfig.type === LOCAL_TREE_TYPE2)) {
54454
+ const providerConfig = environmentEntry(project.config, environment);
54455
+ if (override === void 0 && (providerConfig === void 0 || providerConfig.provider === LOCAL_TREE_TYPE2)) {
54482
54456
  return { kind: "read", subjects: local };
54483
54457
  }
54484
54458
  const source = override ?? await sourceProviderFor(project, environment);
@@ -54500,7 +54474,7 @@ async function rotationSubjects(project, environment, local, override) {
54500
54474
  check: "rotation-overdue",
54501
54475
  severity: "unknown",
54502
54476
  label: "Rotation",
54503
- subject: `could not reach the ${providerConfig?.type ?? source.type} source of truth for ${environment}`,
54477
+ subject: `could not reach the ${providerConfig?.provider ?? source.type} source of truth for ${environment}`,
54504
54478
  detail: errorDetail(error51)
54505
54479
  }
54506
54480
  };
@@ -54621,8 +54595,8 @@ async function readRelevant(provider, environment) {
54621
54595
  return entries;
54622
54596
  }
54623
54597
  async function providerDriftFindings(project, environment, override) {
54624
- const providerConfig = project.config.providers[environment];
54625
- if (providerConfig === void 0 || providerConfig.type === LOCAL_TREE_TYPE2) {
54598
+ const providerConfig = environmentEntry(project.config, environment);
54599
+ if (providerConfig === void 0 || providerConfig.provider === LOCAL_TREE_TYPE2) {
54626
54600
  return [
54627
54601
  {
54628
54602
  check: "provider-value-drift",
@@ -54647,7 +54621,7 @@ async function providerDriftFindings(project, environment, override) {
54647
54621
  check: "provider-value-drift",
54648
54622
  severity: "unknown",
54649
54623
  label: "Provider values",
54650
- subject: `could not reach the ${providerConfig.type} provider for ${environment}`,
54624
+ subject: `could not reach the ${providerConfig.provider} provider for ${environment}`,
54651
54625
  detail: errorDetail(error51)
54652
54626
  }
54653
54627
  ];
@@ -54666,7 +54640,7 @@ async function providerDriftFindings(project, environment, override) {
54666
54640
  severity: "unknown",
54667
54641
  label: "Provider value unreadable",
54668
54642
  subject: formatValueFile(here.file),
54669
- detail: `the local value is sealed and did not open, so it cannot be compared against the ${providerConfig.type} source of truth`
54643
+ detail: `the local value is sealed and did not open, so it cannot be compared against the ${providerConfig.provider} source of truth`
54670
54644
  });
54671
54645
  continue;
54672
54646
  }
@@ -54676,7 +54650,7 @@ async function providerDriftFindings(project, environment, override) {
54676
54650
  severity: "failure",
54677
54651
  label: "Provider value drift",
54678
54652
  subject: formatValueFile(here.file),
54679
- detail: `the local tree and the ${providerConfig.type} source of truth hold different values`
54653
+ detail: `the local tree and the ${providerConfig.provider} source of truth hold different values`
54680
54654
  });
54681
54655
  }
54682
54656
  continue;
@@ -54690,7 +54664,7 @@ async function providerDriftFindings(project, environment, override) {
54690
54664
  severity: "warning",
54691
54665
  label: here !== void 0 ? "Only in the local tree" : "Only in the source",
54692
54666
  subject: formatValueFile(present.file),
54693
- detail: here !== void 0 ? `present locally, absent from the ${providerConfig.type} source of truth` : `present in the ${providerConfig.type} source of truth, absent from the local tree`
54667
+ detail: here !== void 0 ? `present locally, absent from the ${providerConfig.provider} source of truth` : `present in the ${providerConfig.provider} source of truth, absent from the local tree`
54694
54668
  });
54695
54669
  }
54696
54670
  if (findings.length > 0) {
@@ -54701,7 +54675,7 @@ async function providerDriftFindings(project, environment, override) {
54701
54675
  check: "provider-value-drift",
54702
54676
  severity: "pass",
54703
54677
  label: "Provider values",
54704
- subject: `every value matches the ${providerConfig.type} source of truth for ${environment}`
54678
+ subject: `every value matches the ${providerConfig.provider} source of truth for ${environment}`
54705
54679
  }
54706
54680
  ];
54707
54681
  }
@@ -54801,7 +54775,7 @@ function targetScope(project, options, key) {
54801
54775
  throw new PenvError(
54802
54776
  "ENVIRONMENT_FLAG_EMPTY",
54803
54777
  `\`--env\` for parameter ${key} names no environment`,
54804
- `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.`
54778
+ `Pass a declared environment \u2014 ${environmentNames(project.config).map((e) => `\`${e}\``).join(", ")} \u2014 e.g. \`--env production\`, or drop \`--env\` to write the scope that has no environment.`
54805
54779
  );
54806
54780
  }
54807
54781
  return scopeFrom({ ...options, environment: targetEnvironment(project, environment) });
@@ -54814,7 +54788,7 @@ function sealFor(project, file2, value2, parameter, environment) {
54814
54788
  throw new PenvError(
54815
54789
  "SECRET_SCOPE_AMBIGUOUS",
54816
54790
  `Parameter ${parameter} is a secret, and ${recordPath(formatValueFile(file2))} names no environment`,
54817
- "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."
54791
+ "A key is declared by its environment's `keySource` in penv.config.ts, so penv cannot tell which key should seal a file that every environment reads. Write it at an environment scope \u2014 add `--env <environment>` \u2014 or drop `secret` from the parameter's meta."
54818
54792
  );
54819
54793
  }
54820
54794
  return sealValue(file2, value2, keySourceFor(project, environment), parameter, environment);
@@ -55892,8 +55866,14 @@ function environmentsFromFlag(flag) {
55892
55866
  }
55893
55867
  return [...new Set(names)];
55894
55868
  }
55869
+ function environmentsRecord(names) {
55870
+ return Object.fromEntries(names.map((environment) => [environment, LOCAL_PROVIDER]));
55871
+ }
55895
55872
  function configOf(decisions) {
55896
- return { environments: decisions.environments, providers: {}, schemaFile: decisions.schemaFile };
55873
+ return {
55874
+ environments: environmentsRecord(decisions.environments),
55875
+ schemaFile: decisions.schemaFile
55876
+ };
55897
55877
  }
55898
55878
  function declaredIn2(root) {
55899
55879
  const file2 = (0, import_node_path13.join)(root, CONFIG_FILE);
@@ -55925,7 +55905,7 @@ function planInit(root, flags = {}) {
55925
55905
  if (schemaFile.length === 0) {
55926
55906
  throw emptyFlag("schema");
55927
55907
  }
55928
- const error51 = validateSchemaFile({ environments: [], providers: {}, schemaFile })[0];
55908
+ const error51 = validateSchemaFile({ environments: {}, schemaFile })[0];
55929
55909
  if (error51 !== void 0) {
55930
55910
  throw error51;
55931
55911
  }
@@ -55948,7 +55928,7 @@ function planInit(root, flags = {}) {
55948
55928
  }
55949
55929
  const suggestedEnvironments = suggestEnvironments(root);
55950
55930
  const safeDefault = flags.yes === true && declared === void 0;
55951
- const environments = flags.environments ?? declared?.environments ?? (safeDefault ? [DEVELOPMENT] : []);
55931
+ const environments = flags.environments ?? (declared === void 0 ? void 0 : environmentNames(declared)) ?? (safeDefault ? [DEVELOPMENT] : []);
55952
55932
  const defaultEnvironment = declared?.defaultEnvironment ?? (safeDefault ? DEVELOPMENT : void 0);
55953
55933
  if (safeDefault) {
55954
55934
  notes.push(
@@ -56081,27 +56061,27 @@ export { schema };
56081
56061
 
56082
56062
  ` + loadComment + loadCall;
56083
56063
  }
56064
+ function entryKey(name) {
56065
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
56066
+ }
56084
56067
  function renderEnvironments(decisions) {
56085
- const shared = " // Environments are a whitelist. A filename segment is an environment only if\n // it is declared here \u2014 penv never infers one from a folder or a filename.\n";
56068
+ const shared = " // Environments are a whitelist, and each entry is that environment's whole\n // declaration: which provider holds it, in that provider's own words, and\n // which key seals it. A segment is an environment only if it is named here.\n";
56086
56069
  if (decisions.environments.length === 0) {
56087
56070
  return `${shared} // It starts empty because which environments you deploy is not something penv
56088
56071
  // can read off your codebase, and an environment you do not have is worse
56089
- // than one you have not declared yet. Name yours, and give each a provider:
56090
- // environments: ["development", "production"],
56091
- // providers: { development: { type: "@penvhq/provider-filesystem" }, production: { type: "@penvhq/provider-vault" } },
56092
- environments: [],
56093
-
56094
- providers: {},
56072
+ // than one you have not declared yet. Name yours:
56073
+ // environments: {
56074
+ // development: "@penvhq/provider-filesystem",
56075
+ // production: { provider: "@penvhq/provider-vault", path: "penv", keySource: "env" },
56076
+ // },
56077
+ environments: {},
56095
56078
  `;
56096
56079
  }
56097
- const names = decisions.environments.map((name) => JSON.stringify(name)).join(", ");
56098
- const providers = decisions.environments.map((name) => ` ${JSON.stringify(name)}: { type: "@penvhq/provider-filesystem" },
56080
+ const entries = decisions.environments.map((name) => ` ${entryKey(name)}: ${JSON.stringify(LOCAL_PROVIDER)},
56099
56081
  `).join("");
56100
- return `${shared} environments: [${names}],
56101
-
56102
- // One entry per environment: where that environment's values are read from.
56103
- providers: {
56104
- ${providers} },
56082
+ return `${shared} // A provider that needs no fields is just its package name.
56083
+ environments: {
56084
+ ${entries} },
56105
56085
  `;
56106
56086
  }
56107
56087
  function renderConfigModule(decisions) {
@@ -56620,13 +56600,13 @@ function planCutover(input) {
56620
56600
  const declared = declaredIn2(root);
56621
56601
  const named = environmentsDeclaredBy(selected);
56622
56602
  const chosen = named.length > 0 ? named : [requireEnvironment(input)];
56623
- const environments = declared === void 0 ? chosen : declared.environments;
56603
+ const environments = declared === void 0 ? chosen : environmentNames(declared);
56624
56604
  for (const environment of chosen) {
56625
56605
  if (!environments.includes(environment)) {
56626
56606
  throw new PenvError(
56627
56607
  "INIT_ENVIRONMENT_UNDECLARED",
56628
56608
  `${CONFIG_FILE} declares ${describeList(environments)}, and adopting these files needs \`${environment}\` declared too`,
56629
- `Add \`${environment}\` to \`environments\` in ${CONFIG_FILE}, with a provider for it, then run \`penv init\` again. Nothing was changed.`
56609
+ `Add \`${environment}: "${LOCAL_PROVIDER}"\` to \`environments\` in ${CONFIG_FILE}, or name the backend that holds it, then run \`penv init\` again. Nothing was changed.`
56630
56610
  );
56631
56611
  }
56632
56612
  }
@@ -56693,10 +56673,7 @@ function distinct(refs) {
56693
56673
  }
56694
56674
  function configFor(decisions) {
56695
56675
  return {
56696
- environments: decisions.environments,
56697
- providers: Object.fromEntries(
56698
- decisions.environments.map((environment) => [environment, { type: LOCAL_PROVIDER }])
56699
- ),
56676
+ environments: environmentsRecord(decisions.environments),
56700
56677
  schemaFile: decisions.schemaFile,
56701
56678
  ...decisions.defaultEnvironment === void 0 ? {} : { defaultEnvironment: decisions.defaultEnvironment },
56702
56679
  ...decisions.publicPrefixes.length === 0 ? {} : { publicPrefixes: [...decisions.publicPrefixes] }
@@ -57036,7 +57013,8 @@ function offeredEnvironment(root) {
57036
57013
  if (declared === void 0) {
57037
57014
  return DEVELOPMENT;
57038
57015
  }
57039
- return declared.defaultEnvironment ?? (declared.environments.includes(DEVELOPMENT) ? DEVELOPMENT : declared.environments[0] ?? DEVELOPMENT);
57016
+ const names = environmentNames(declared);
57017
+ return declared.defaultEnvironment ?? (names.includes(DEVELOPMENT) ? DEVELOPMENT : names[0] ?? DEVELOPMENT);
57040
57018
  }
57041
57019
  async function cutoverWithYes(root, base, adoption) {
57042
57020
  const selected = selectionForYes(adoption);
@@ -57144,10 +57122,11 @@ var BACKUP_SUFFIX = ".backup";
57144
57122
  var DOTENV_SEGMENT = "env";
57145
57123
  var LOCAL3 = "local";
57146
57124
  function assertDeclared(segment, config2) {
57147
- if (config2.environments.includes(segment)) {
57125
+ const declared = environmentNames(config2);
57126
+ if (declared.includes(segment)) {
57148
57127
  return segment;
57149
57128
  }
57150
- throw new UnknownEnvironmentError(segment, config2.environments);
57129
+ throw new UnknownEnvironmentError(segment, declared);
57151
57130
  }
57152
57131
  function scopeFromFilename(file2, config2) {
57153
57132
  const name = (0, import_node_path14.basename)(file2);
@@ -57217,7 +57196,7 @@ function explicitEnvironment(options, source, config2) {
57217
57196
  throw new PenvError(
57218
57197
  "IMPORT_ENV_FLAG_EMPTY",
57219
57198
  `\`--env\` for the import of ${source} names no environment`,
57220
- `Pass a declared environment \u2014 ${config2.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.`
57199
+ `Pass a declared environment \u2014 ${environmentNames(config2).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.`
57221
57200
  );
57222
57201
  }
57223
57202
  function assertEnvironmentAgrees(derived, explicit, source) {
@@ -57244,7 +57223,7 @@ function environmentNamed(file2, explicit) {
57244
57223
  }
57245
57224
  function decisionsOf(config2, cwd) {
57246
57225
  return {
57247
- environments: config2.environments,
57226
+ environments: environmentNames(config2),
57248
57227
  // `import` re-scaffolds env.ts for an existing project; injection is an init
57249
57228
  // choice, so it is not turned on here.
57250
57229
  inject: false,
@@ -57300,7 +57279,7 @@ function importDotenv(options) {
57300
57279
  backup,
57301
57280
  scope,
57302
57281
  environment,
57303
- environments: config2.environments,
57282
+ environments: environmentNames(config2),
57304
57283
  variables: parsed.entries.length,
57305
57284
  orphanComments: parsed.orphanComments,
57306
57285
  steps
@@ -57446,12 +57425,12 @@ function envVarFor2(id) {
57446
57425
  function runKeyCreate(options) {
57447
57426
  const project = openProject(options.cwd);
57448
57427
  const environment = targetEnvironment(project, options.environment);
57449
- const declared = project.config.keys?.[environment];
57428
+ const declared = keyConfigFor(project.config, environment);
57450
57429
  if (declared === void 0) {
57451
57430
  throw new PenvError(
57452
57431
  "KEY_SOURCE_UNDECLARED",
57453
57432
  `Environment ${environment} declares no key source, so penv does not know what a key for it would be`,
57454
- `Add a \`keys\` entry to penv.config.ts \u2014 e.g. \`keys: { ${environment}: { source: "env", id: "${environment}" } }\` \u2014 then run this again.`
57433
+ `Give its entry in penv.config.ts a \`keySource\` \u2014 e.g. \`${environment}: { provider: "@penvhq/provider-filesystem", keySource: "env" }\` \u2014 then run this again. The key is named after the environment unless the object form names another.`
57455
57434
  );
57456
57435
  }
57457
57436
  const key = (0, import_node_crypto.randomBytes)(KEY_BYTES).toString("base64");
@@ -57784,7 +57763,7 @@ async function planFile(project, source, target, parameter) {
57784
57763
  throw new PenvError(
57785
57764
  "SECRET_SCOPE_AMBIGUOUS",
57786
57765
  `${recordPath(formatValueFile(source))} is encrypted at a scope that names no environment, so penv cannot tell which key would re-seal it`,
57787
- "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."
57766
+ "A key is declared by its environment's `keySource` in penv.config.ts. Decrypt it with `penv decrypt`, move the parameter, then encrypt it again at its new address."
57788
57767
  );
57789
57768
  }
57790
57769
  const keys = keySourceFor(project, environment);