@penvhq/cli 0.9.4 → 0.10.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
@@ -325,8 +325,8 @@ function installFailed(plan2) {
325
325
  }
326
326
 
327
327
  // src/project.ts
328
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
329
- import { dirname, join as join3 } from "path";
328
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
329
+ import { dirname, join as join4 } from "path";
330
330
  import {
331
331
  assertMigrated,
332
332
  candidatesFor,
@@ -406,10 +406,24 @@ function environmentFromShorthand(config, candidates, explicit) {
406
406
  }
407
407
 
408
408
  // src/registry.ts
409
+ import { readFileSync as readFileSync2 } from "fs";
409
410
  import { createRequire } from "module";
410
- import { resolve } from "path";
411
+ import { join as join3, resolve } from "path";
411
412
  import { pathToFileURL } from "url";
412
- import { holdsProjection, PENV_DIR, PenvError as PenvError4, recordsDir } from "@penvhq/core";
413
+ import {
414
+ holdsProjection,
415
+ LOCAL_EXTENSIONS_PATH,
416
+ localExtensionsFile,
417
+ MANIFEST_PATH,
418
+ PENV_DIR,
419
+ PenvError as PenvError4,
420
+ packageDir,
421
+ packageEntry,
422
+ parseLocalExtensions,
423
+ parseManifest,
424
+ penvHome,
425
+ recordsDir
426
+ } from "@penvhq/core";
413
427
  import { createFilesystemProvider } from "@penvhq/provider-filesystem";
414
428
  import { createMockProvider } from "@penvhq/provider-mock";
415
429
  var PLUGIN_FACTORY_EXPORT = "penvProviderFactory";
@@ -452,19 +466,12 @@ async function createSourceProvider(type, context) {
452
466
  return loadPluginProvider(type, context);
453
467
  }
454
468
  async function loadPluginProvider(type, context) {
455
- const resolved = resolvePlugin(type, context.root);
456
- if (resolved === void 0) {
457
- throw unknownProvider(type, context.environment);
458
- }
469
+ const path = resolveExtension(type, context.root, context.environment);
459
470
  let mod;
460
471
  try {
461
- mod = await importPlugin(resolved);
462
- } catch {
463
- throw new PenvError4(
464
- "PROVIDER_PLUGIN_LOAD",
465
- `The provider package \`${type}\` failed to load`,
466
- "It resolved but threw while importing. Check it builds and its dependencies are installed."
467
- );
472
+ mod = await importPlugin(path);
473
+ } catch (cause) {
474
+ throw providerLoadFailed(type, path, cause);
468
475
  }
469
476
  const factory = mod[PLUGIN_FACTORY_EXPORT];
470
477
  if (typeof factory !== "function") {
@@ -478,15 +485,71 @@ async function loadPluginProvider(type, context) {
478
485
  assertSatisfiesContract(provider, type);
479
486
  return provider;
480
487
  }
481
- function assertProvidersRegistered(config, projectRoot) {
488
+ function assertProvidersRegistered(config, projectRoot, options) {
489
+ const local = localExtensions(projectRoot);
490
+ const ci = options?.ci ?? isCi(process.env.CI);
482
491
  for (const [environment, provider] of Object.entries(config.providers)) {
483
492
  if (isProviderRegistered(provider.type)) {
484
493
  continue;
485
494
  }
486
- if (resolvePlugin(provider.type, projectRoot) === void 0) {
487
- throw unknownProvider(provider.type, environment);
495
+ if (ci && local.includes(provider.type)) {
496
+ throw localExtensionInCi(provider.type, environment);
488
497
  }
498
+ resolveExtension(provider.type, projectRoot, environment, local);
499
+ }
500
+ }
501
+ function resolveExtension(type, projectRoot, environment, local = localExtensions(projectRoot)) {
502
+ const fromProject = resolvePlugin(type, projectRoot);
503
+ if (local.includes(type)) {
504
+ if (fromProject === void 0) {
505
+ throw localExtensionUnresolved(type, projectRoot, environment);
506
+ }
507
+ return fromProject;
508
+ }
509
+ if (fromProject !== void 0) {
510
+ return fromProject;
511
+ }
512
+ const version = pinnedVersion(type, projectRoot);
513
+ if (version === void 0) {
514
+ throw unknownProvider(type, environment);
515
+ }
516
+ const stored = storedExtension(type, version);
517
+ if (stored === void 0) {
518
+ throw extensionNotInstalled(type, version, environment);
519
+ }
520
+ return stored;
521
+ }
522
+ function pinnedVersion(type, projectRoot) {
523
+ let manifest;
524
+ try {
525
+ manifest = parseManifest(readFileSync2(join3(projectRoot, ...MANIFEST_PATH.split("/")), "utf8"));
526
+ } catch {
527
+ return void 0;
528
+ }
529
+ return manifest.extensions[type]?.version;
530
+ }
531
+ function storedExtension(type, version) {
532
+ const entry = packageEntry(packageDir(penvHome(process.env), "extensions", type, version));
533
+ return entry?.file;
534
+ }
535
+ function localExtensions(projectRoot) {
536
+ let text;
537
+ try {
538
+ text = readFileSync2(localExtensionsFile(projectRoot), "utf8");
539
+ } catch {
540
+ return [];
489
541
  }
542
+ return parseLocalExtensions(text);
543
+ }
544
+ function isCi(value) {
545
+ return value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
546
+ }
547
+ function localExtensionInCi(type, environment) {
548
+ return new PenvError4(
549
+ "LOCAL_EXTENSION_IN_CI",
550
+ `The provider \`${type}\` for environment ${environment} is a local extension, and this is CI`,
551
+ `${LOCAL_EXTENSIONS_PATH} records it as a package this project develops, so nothing pins the bytes CI would run. Publish it and run \`penv add ${type}\` to pin a release.`
552
+ );
490
553
  }
491
554
  function resolvePlugin(specifier, fromDir) {
492
555
  try {
@@ -519,13 +582,37 @@ function assertSatisfiesContract(provider, specifier) {
519
582
  }
520
583
  }
521
584
  }
585
+ function where(environment) {
586
+ return environment === void 0 ? "" : ` for environment ${environment}`;
587
+ }
522
588
  function unknownProvider(type, environment) {
523
589
  const preinstalled = [...REGISTRY.keys()].map((name) => `\`${name}\``).join(", ");
524
- const where = environment === void 0 ? "" : ` for environment ${environment}`;
525
590
  return new PenvError4(
526
591
  "UNKNOWN_PROVIDER",
527
- `The provider \`${type}\`${where} in penv.config.ts is not installed in this project`,
528
- `Install it with \`npm i ${type}\` \u2014 a provider's \`type\` is the package penv imports. The CLI ships ${preinstalled} pre-installed.`
592
+ `The provider \`${type}\`${where(environment)} in penv.config.ts is nowhere penv looks for it`,
593
+ `Run \`penv add ${type}\` to pin a release, or \`penv add --local ${type}\` if this repository is the one that builds it. penv looks in ${LOCAL_EXTENSIONS_PATH}, this project's node_modules, and then $PENV_HOME at the version ${MANIFEST_PATH} pins. The CLI ships ${preinstalled} pre-installed.`
594
+ );
595
+ }
596
+ function extensionNotInstalled(type, version, environment) {
597
+ return new PenvError4(
598
+ "EXTENSION_NOT_INSTALLED",
599
+ `${MANIFEST_PATH} pins \`${type}\` ${version}${where(environment)}, and it is not installed in ${penvHome(process.env)}`,
600
+ "Run `penv install` \u2014 it downloads and verifies every version the manifest pins, extensions included."
601
+ );
602
+ }
603
+ function localExtensionUnresolved(type, projectRoot, environment) {
604
+ return new PenvError4(
605
+ "LOCAL_EXTENSION_UNRESOLVED",
606
+ `${LOCAL_EXTENSIONS_PATH} records \`${type}\`${where(environment)} as a package this project develops, and it does not resolve from ${projectRoot}`,
607
+ `Add it as a dependency of the root (a workspace link is one) and run \`pnpm install\`, or drop the name from ${LOCAL_EXTENSIONS_PATH} if this project no longer builds it.`
608
+ );
609
+ }
610
+ function providerLoadFailed(type, path, cause) {
611
+ const detail = cause instanceof Error ? cause.message : String(cause);
612
+ return new PenvError4(
613
+ "PROVIDER_PLUGIN_LOAD",
614
+ `The provider package \`${type}\` failed to load from ${path}: ${detail}`,
615
+ "penv imports that file exactly as it stands, with no transform, so it has to be built JavaScript with its dependencies installed."
529
616
  );
530
617
  }
531
618
 
@@ -554,11 +641,11 @@ function localTree(project) {
554
641
  return project.provider;
555
642
  }
556
643
  function selfContainedSchemaModule(root, schemaFile) {
557
- const file = join3(root, ...schemaFile.split("/"));
644
+ const file = join4(root, ...schemaFile.split("/"));
558
645
  if (!existsSync3(file)) {
559
646
  return void 0;
560
647
  }
561
- const source = readFileSync2(file, "utf8");
648
+ const source = readFileSync3(file, "utf8");
562
649
  return source.includes("PenvSchemaShape") || source.includes("z.object") ? schemaFile : void 0;
563
650
  }
564
651
  function schemaShapeFileOf(project) {
@@ -794,10 +881,7 @@ function writeError(lines) {
794
881
  }
795
882
  function reportError(error) {
796
883
  if (error instanceof PenvError6) {
797
- const suffix = error.remedy === void 0 ? void 0 : `
798
- ${error.remedy}`;
799
- const message = suffix !== void 0 && error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
800
- process.stderr.write(`${err.red(CROSS)} ${message}
884
+ process.stderr.write(`${err.red(CROSS)} ${error.summary}
801
885
  `);
802
886
  if (error.remedy !== void 0) {
803
887
  process.stderr.write(` ${err.cyan("\u2192")} ${error.remedy}
@@ -827,9 +911,9 @@ async function guard(run) {
827
911
  }
828
912
 
829
913
  // src/commands/run.ts
830
- import { existsSync as existsSync4, readFileSync as readFileSync3, watch } from "fs";
914
+ import { existsSync as existsSync4, readFileSync as readFileSync4, watch } from "fs";
831
915
  import { createRequire as createRequire2 } from "module";
832
- import { basename, dirname as dirname2, join as join4, resolve as resolve2 } from "path";
916
+ import { basename, dirname as dirname2, join as join5, resolve as resolve2 } from "path";
833
917
  import {
834
918
  ARTIFACT_BUILD_COMMAND,
835
919
  assertArtifactFor,
@@ -1669,10 +1753,10 @@ function packageManifest(specifier, root) {
1669
1753
  return void 0;
1670
1754
  }
1671
1755
  for (; ; ) {
1672
- const file = join4(directory, "package.json");
1756
+ const file = join5(directory, "package.json");
1673
1757
  if (existsSync4(file)) {
1674
1758
  try {
1675
- const parsed = JSON.parse(readFileSync3(file, "utf8"));
1759
+ const parsed = JSON.parse(readFileSync4(file, "utf8"));
1676
1760
  if (isPlainObject(parsed) && parsed.name === specifier) {
1677
1761
  return parsed;
1678
1762
  }
@@ -1745,7 +1829,7 @@ function snapshotPath(host) {
1745
1829
  }
1746
1830
  function readSnapshot(path) {
1747
1831
  try {
1748
- return readFileSync3(path, "utf8");
1832
+ return readFileSync4(path, "utf8");
1749
1833
  } catch {
1750
1834
  throw new PenvError8(
1751
1835
  "RUN_SNAPSHOT_MISSING",
@@ -2166,12 +2250,12 @@ import {
2166
2250
  existsSync as existsSync5,
2167
2251
  mkdirSync as mkdirSync2,
2168
2252
  readdirSync as readdirSync2,
2169
- readFileSync as readFileSync4,
2253
+ readFileSync as readFileSync5,
2170
2254
  renameSync,
2171
2255
  rmSync,
2172
2256
  writeFileSync as writeFileSync2
2173
2257
  } from "fs";
2174
- import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
2258
+ import { dirname as dirname4, join as join6, resolve as resolve4 } from "path";
2175
2259
  import {
2176
2260
  CUTOVER_PATH,
2177
2261
  findConfigFile,
@@ -2181,7 +2265,7 @@ import {
2181
2265
  } from "@penvhq/core";
2182
2266
  var CUTOVER_FORMAT = 1;
2183
2267
  function fileFor(root, relativePosix) {
2184
- return join5(root, ...relativePosix.split("/"));
2268
+ return join6(root, ...relativePosix.split("/"));
2185
2269
  }
2186
2270
  function bundleDir(root) {
2187
2271
  return fileFor(root, ROLLBACK_DOTENV_PATH);
@@ -2210,7 +2294,7 @@ function readCutover(root) {
2210
2294
  }
2211
2295
  let parsed;
2212
2296
  try {
2213
- parsed = JSON.parse(readFileSync4(file, "utf8"));
2297
+ parsed = JSON.parse(readFileSync5(file, "utf8"));
2214
2298
  } catch {
2215
2299
  throw unreadable("it is not JSON");
2216
2300
  }
@@ -2268,7 +2352,7 @@ function bundleDotenvFiles(root, files, environments, now = /* @__PURE__ */ new
2268
2352
  const bundle = bundleDir(root);
2269
2353
  mkdirSync2(bundle, { recursive: true });
2270
2354
  for (const name of files) {
2271
- renameSync(join5(root, name), join5(bundle, name));
2355
+ renameSync(join6(root, name), join6(bundle, name));
2272
2356
  }
2273
2357
  return cutover;
2274
2358
  }
@@ -2287,7 +2371,7 @@ function runUndo(options) {
2287
2371
  const names = [...recorded, ...bundled.filter((name) => !recorded.includes(name))];
2288
2372
  const bundle = bundleDir(root);
2289
2373
  const held = new Set(bundled);
2290
- const occupied2 = names.filter((name) => held.has(name) && existsSync5(join5(root, name)));
2374
+ const occupied2 = names.filter((name) => held.has(name) && existsSync5(join6(root, name)));
2291
2375
  if (occupied2.length > 0) {
2292
2376
  const listed = occupied2.join(", ");
2293
2377
  const many = occupied2.length > 1;
@@ -2302,9 +2386,9 @@ function runUndo(options) {
2302
2386
  const missing = [];
2303
2387
  for (const name of names) {
2304
2388
  if (held.has(name)) {
2305
- renameSync(join5(bundle, name), join5(root, name));
2389
+ renameSync(join6(bundle, name), join6(root, name));
2306
2390
  restored.push(name);
2307
- } else if (existsSync5(join5(root, name))) {
2391
+ } else if (existsSync5(join6(root, name))) {
2308
2392
  alreadyBack.push(name);
2309
2393
  } else {
2310
2394
  missing.push(name);
@@ -2357,8 +2441,8 @@ var cleanupCommand = defineCommand5({
2357
2441
  });
2358
2442
 
2359
2443
  // src/commands/doctor.ts
2360
- import { readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
2361
- import { join as join6, relative as relative2 } from "path";
2444
+ import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
2445
+ import { join as join7, relative as relative2 } from "path";
2362
2446
  import {
2363
2447
  ARTIFACT_BUILD_COMMAND as ARTIFACT_BUILD_COMMAND2,
2364
2448
  accessPath as accessPath3,
@@ -2372,6 +2456,7 @@ import {
2372
2456
  isSecret as isSecret3,
2373
2457
  isStuck,
2374
2458
  openValue as openValue2,
2459
+ RECORDS_PATH as RECORDS_PATH2,
2375
2460
  resolveAll as resolveAll2,
2376
2461
  rotationOf,
2377
2462
  tryParseDuration,
@@ -2827,6 +2912,7 @@ async function runDoctor(options) {
2827
2912
  findings.push(...unusedFindings(drift));
2828
2913
  }
2829
2914
  findings.push(...fallbackFindings(subjects, environment));
2915
+ findings.push(...secrecyFindings(subjects, environment));
2830
2916
  findings.push(...plaintextSecretFindings(subjects, environment));
2831
2917
  findings.push(...publicSecretFindings(subjects, environment, project.config));
2832
2918
  findings.push(...encryptionFindings(subjects, environment));
@@ -2839,6 +2925,7 @@ async function runDoctor(options) {
2839
2925
  }
2840
2926
  findings.push(...await providerDriftFindings(project, environment, options.source));
2841
2927
  findings.push(...await projectionFindings(project, environment, options.projection));
2928
+ findings.push(...localExtensionFindings(project));
2842
2929
  findings.push(...artifactFindings(project));
2843
2930
  findings.push({
2844
2931
  check: "provider",
@@ -2976,6 +3063,31 @@ function fallbackFindings(subjects, environment) {
2976
3063
  }
2977
3064
  ];
2978
3065
  }
3066
+ function secrecyFindings(subjects, environment) {
3067
+ const undeclared = subjects.filter(
3068
+ ({ meta }) => effectiveMeta(meta, environment).secret === void 0
3069
+ );
3070
+ if (undeclared.length === 0) {
3071
+ return [
3072
+ {
3073
+ check: "secrecy-undeclared",
3074
+ severity: "pass",
3075
+ label: "Secrecy policy",
3076
+ subject: subjects.length === 0 ? `no parameter resolves for ${environment}` : `every parameter declares whether it is secret for ${environment}`
3077
+ }
3078
+ ];
3079
+ }
3080
+ return [
3081
+ {
3082
+ check: "secrecy-undeclared",
3083
+ severity: "unknown",
3084
+ label: "Secrecy policy",
3085
+ subject: `${undeclared.length} of ${subjects.length} declare neither way for ${environment}`,
3086
+ detail: "penv was never told which of these values must be encrypted",
3087
+ remedy: `declare \`"secret": true\` or \`"secret": false\` in a parameter's meta file, ${RECORDS_PATH2}/<name>.json`
3088
+ }
3089
+ ];
3090
+ }
2979
3091
  function plaintextSecretFindings(subjects, environment) {
2980
3092
  const secrets = subjects.filter(({ meta }) => isSecret3(meta, environment));
2981
3093
  const findings = [];
@@ -3284,7 +3396,7 @@ function scannableFiles(directory, out2) {
3284
3396
  return;
3285
3397
  }
3286
3398
  for (const entry of entries) {
3287
- const path = join6(directory, entry.name);
3399
+ const path = join7(directory, entry.name);
3288
3400
  if (entry.isDirectory()) {
3289
3401
  if (!UNSCANNED_DIRS.has(entry.name)) {
3290
3402
  scannableFiles(path, out2);
@@ -3296,6 +3408,29 @@ function scannableFiles(directory, out2) {
3296
3408
  }
3297
3409
  }
3298
3410
  }
3411
+ function localExtensionFindings(project) {
3412
+ const names = localExtensions(project.root);
3413
+ if (names.length === 0) {
3414
+ return [
3415
+ {
3416
+ check: "local-extension",
3417
+ severity: "pass",
3418
+ label: "Extensions",
3419
+ subject: "every extension this project names is pinned"
3420
+ }
3421
+ ];
3422
+ }
3423
+ return [
3424
+ {
3425
+ check: "local-extension",
3426
+ severity: "unknown",
3427
+ label: "Extensions",
3428
+ subject: names.join(", "),
3429
+ detail: "resolved from this project's node_modules \u2014 no release, no pinned bytes",
3430
+ remedy: `publish it and run \`penv add ${names[0]}\` when this stops being a development path`
3431
+ }
3432
+ ];
3433
+ }
3299
3434
  function artifactFindings(project) {
3300
3435
  const files = [];
3301
3436
  scannableFiles(project.root, files);
@@ -3306,7 +3441,7 @@ function artifactFindings(project) {
3306
3441
  if (statSync2(file).size > ARTIFACT_SCAN_LIMIT) {
3307
3442
  continue;
3308
3443
  }
3309
- text = readFileSync5(file, "utf8");
3444
+ text = readFileSync6(file, "utf8");
3310
3445
  } catch {
3311
3446
  continue;
3312
3447
  }
@@ -4281,7 +4416,7 @@ var getCommand = defineCommand12({
4281
4416
  });
4282
4417
 
4283
4418
  // src/commands/import.ts
4284
- import { copyFileSync, existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
4419
+ import { copyFileSync, existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
4285
4420
  import { basename as basename2, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve7 } from "path";
4286
4421
  import {
4287
4422
  assertNever as assertNever2,
@@ -4316,9 +4451,9 @@ function assertImportable(ref, variable) {
4316
4451
  `Filenames are split on \`.\`. Rename ${variable} in the source file, then import it again.`
4317
4452
  );
4318
4453
  }
4319
- function assertNotReserved(ref, variable, where, config) {
4454
+ function assertNotReserved(ref, variable, where2, config) {
4320
4455
  if (isReservedToken3(ref.name, config)) {
4321
- throw new ReservedTokenError3("parameter", variable, where);
4456
+ throw new ReservedTokenError3("parameter", variable, where2);
4322
4457
  }
4323
4458
  }
4324
4459
  function assertRoundTrips(ref, variable, config) {
@@ -4335,12 +4470,12 @@ function assertRoundTrips(ref, variable, config) {
4335
4470
  `\`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.`
4336
4471
  );
4337
4472
  }
4338
- function refsForEntries(entries, where, config) {
4473
+ function refsForEntries(entries, where2, config) {
4339
4474
  const refs = [];
4340
4475
  for (const entry of entries) {
4341
4476
  const ref = refFromVariable2(entry.key);
4342
4477
  assertImportable(ref, entry.key);
4343
- assertNotReserved(ref, entry.key, where, config);
4478
+ assertNotReserved(ref, entry.key, where2, config);
4344
4479
  assertRoundTrips(ref, entry.key, config);
4345
4480
  refs.push(ref);
4346
4481
  }
@@ -4370,8 +4505,8 @@ function writeEntries(tree, entries, refs, scope) {
4370
4505
  }
4371
4506
 
4372
4507
  // src/detect.ts
4373
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
4374
- import { join as join7 } from "path";
4508
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
4509
+ import { join as join8 } from "path";
4375
4510
  import { DEFAULT_SCHEMA_FILE } from "@penvhq/core";
4376
4511
  var SIGNATURES = [
4377
4512
  { name: "Next.js", packages: ["next"], publicPrefixes: ["NEXT_PUBLIC_"] },
@@ -4388,7 +4523,7 @@ var SIGNATURES = [
4388
4523
  function exportsSchema(file) {
4389
4524
  let source;
4390
4525
  try {
4391
- source = readFileSync6(file, "utf8");
4526
+ source = readFileSync7(file, "utf8");
4392
4527
  } catch {
4393
4528
  return false;
4394
4529
  }
@@ -4399,7 +4534,7 @@ function exportsSchema(file) {
4399
4534
  );
4400
4535
  }
4401
4536
  function occupied(cwd, relative5) {
4402
- const file = join7(cwd, ...relative5.split("/"));
4537
+ const file = join8(cwd, ...relative5.split("/"));
4403
4538
  return existsSync6(file) && !exportsSchema(file);
4404
4539
  }
4405
4540
  function schemaFileFor(cwd) {
@@ -4415,7 +4550,7 @@ function schemaFileFor(cwd) {
4415
4550
  return { file: DEFAULT_SCHEMA_FILE, displaced: preferred };
4416
4551
  }
4417
4552
  function srcPrefix(cwd) {
4418
- return existsSync6(join7(cwd, "src")) ? "src/" : "";
4553
+ return existsSync6(join8(cwd, "src")) ? "src/" : "";
4419
4554
  }
4420
4555
  function dependenciesOf(cwd) {
4421
4556
  const manifest = manifestOf2(cwd);
@@ -4434,13 +4569,13 @@ function dependenciesOf(cwd) {
4434
4569
  return names;
4435
4570
  }
4436
4571
  function manifestOf2(cwd) {
4437
- const file = join7(cwd, "package.json");
4572
+ const file = join8(cwd, "package.json");
4438
4573
  if (!existsSync6(file)) {
4439
4574
  return void 0;
4440
4575
  }
4441
4576
  let manifest;
4442
4577
  try {
4443
- manifest = JSON.parse(readFileSync6(file, "utf8"));
4578
+ manifest = JSON.parse(readFileSync7(file, "utf8"));
4444
4579
  } catch {
4445
4580
  return void 0;
4446
4581
  }
@@ -4456,7 +4591,7 @@ function detectFramework(cwd) {
4456
4591
  return detectedFrom(cwd, signature.name, signature.publicPrefixes);
4457
4592
  }
4458
4593
  }
4459
- if (existsSync6(join7(cwd, "bunfig.toml")) || dependencies.has("bun-types")) {
4594
+ if (existsSync6(join8(cwd, "bunfig.toml")) || dependencies.has("bun-types")) {
4460
4595
  return detectedFrom(cwd, "Bun", []);
4461
4596
  }
4462
4597
  return void 0;
@@ -4535,8 +4670,8 @@ function draftFieldsAcross(sources, environments) {
4535
4670
  }
4536
4671
 
4537
4672
  // src/commands/init.ts
4538
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
4539
- import { dirname as dirname6, join as join9, resolve as resolve6 } from "path";
4673
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
4674
+ import { dirname as dirname6, join as join10, resolve as resolve6 } from "path";
4540
4675
  import { createInterface as createInterface2 } from "readline/promises";
4541
4676
  import {
4542
4677
  CUTOVER_PATH as CUTOVER_PATH3,
@@ -4547,7 +4682,7 @@ import {
4547
4682
  PenvError as PenvError18,
4548
4683
  parameterId as parameterId6,
4549
4684
  parseDotenv,
4550
- RECORDS_PATH as RECORDS_PATH2,
4685
+ RECORDS_PATH as RECORDS_PATH3,
4551
4686
  RESERVED_TOKENS as RESERVED_TOKENS2,
4552
4687
  ROLLBACK_DOTENV_PATH as ROLLBACK_DOTENV_PATH3,
4553
4688
  renderStateGitignore,
@@ -4564,13 +4699,13 @@ import {
4564
4699
  existsSync as existsSync7,
4565
4700
  mkdirSync as mkdirSync3,
4566
4701
  readdirSync as readdirSync4,
4567
- readFileSync as readFileSync7,
4702
+ readFileSync as readFileSync8,
4568
4703
  rmdirSync,
4569
4704
  statSync as statSync3,
4570
4705
  unlinkSync,
4571
4706
  writeFileSync as writeFileSync4
4572
4707
  } from "fs";
4573
- import { dirname as dirname5, join as join8 } from "path";
4708
+ import { dirname as dirname5, join as join9 } from "path";
4574
4709
  function record(path, files, dirs) {
4575
4710
  let stats;
4576
4711
  try {
@@ -4581,12 +4716,12 @@ function record(path, files, dirs) {
4581
4716
  if (stats.isDirectory()) {
4582
4717
  dirs.add(path);
4583
4718
  for (const entry of readdirSync4(path)) {
4584
- record(join8(path, entry), files, dirs);
4719
+ record(join9(path, entry), files, dirs);
4585
4720
  }
4586
4721
  return;
4587
4722
  }
4588
4723
  if (stats.isFile()) {
4589
- files.set(path, readFileSync7(path));
4724
+ files.set(path, readFileSync8(path));
4590
4725
  }
4591
4726
  }
4592
4727
  function captureScaffold(root, paths) {
@@ -4631,7 +4766,7 @@ function removeAdded(path, undo) {
4631
4766
  return;
4632
4767
  }
4633
4768
  for (const entry of readdirSync4(path)) {
4634
- removeAdded(join8(path, entry), undo);
4769
+ removeAdded(join9(path, entry), undo);
4635
4770
  }
4636
4771
  if (!undo.dirs.has(path) && readdirSync4(path).length === 0) {
4637
4772
  rmdirSync(path);
@@ -4849,7 +4984,7 @@ function configOf(decisions) {
4849
4984
  return { environments: decisions.environments, providers: {}, schemaFile: decisions.schemaFile };
4850
4985
  }
4851
4986
  function declaredIn(root) {
4852
- const file = join9(root, CONFIG_FILE);
4987
+ const file = join10(root, CONFIG_FILE);
4853
4988
  if (!existsSync8(file)) {
4854
4989
  return void 0;
4855
4990
  }
@@ -5300,7 +5435,7 @@ function ensurePenvDir(root) {
5300
5435
  return { target: "penv-dir", action: "created", text: `Created ${PENV_DIR2}/` };
5301
5436
  }
5302
5437
  function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS) {
5303
- const file = join9(root, SCHEMA_SHAPE_FILE3);
5438
+ const file = join10(root, SCHEMA_SHAPE_FILE3);
5304
5439
  if (existsSync8(file)) {
5305
5440
  return {
5306
5441
  target: "schema",
@@ -5327,7 +5462,7 @@ function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS
5327
5462
  };
5328
5463
  }
5329
5464
  function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
5330
- const file = join9(root, ...decisions.schemaFile.split("/"));
5465
+ const file = join10(root, ...decisions.schemaFile.split("/"));
5331
5466
  if (existsSync8(file)) {
5332
5467
  return {
5333
5468
  target: "env",
@@ -5346,7 +5481,7 @@ function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
5346
5481
  };
5347
5482
  }
5348
5483
  function writeConfigFile(root, decisions = DEFAULT_DECISIONS) {
5349
- const file = join9(root, CONFIG_FILE);
5484
+ const file = join10(root, CONFIG_FILE);
5350
5485
  if (existsSync8(file)) {
5351
5486
  return { target: "config", action: "kept", text: `Kept ${CONFIG_FILE}` };
5352
5487
  }
@@ -5356,8 +5491,8 @@ function writeConfigFile(root, decisions = DEFAULT_DECISIONS) {
5356
5491
  function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
5357
5492
  const alias = decisions.alias;
5358
5493
  const imports = alias.startsWith(IMPORTS_PREFIX);
5359
- const file = join9(root, imports ? PACKAGE_FILE : TSCONFIG_FILE);
5360
- const where = imports ? PACKAGE_FILE : TSCONFIG_FILE;
5494
+ const file = join10(root, imports ? PACKAGE_FILE : TSCONFIG_FILE);
5495
+ const where2 = imports ? PACKAGE_FILE : TSCONFIG_FILE;
5361
5496
  if (!existsSync8(file)) {
5362
5497
  if (imports) {
5363
5498
  return {
@@ -5374,13 +5509,13 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
5374
5509
  text: `Created ${TSCONFIG_FILE} with the ${alias} path alias`
5375
5510
  };
5376
5511
  }
5377
- const source = readFileSync8(file, "utf8");
5512
+ const source = readFileSync9(file, "utf8");
5378
5513
  const edit = imports ? insertImportsAlias(source, decisions.schemaFile, alias) : insertEnvAlias(source, decisions.schemaFile, alias);
5379
5514
  if (edit.conflict !== void 0) {
5380
5515
  return {
5381
5516
  target: "tsconfig",
5382
5517
  action: "conflicted",
5383
- text: `${where} already maps ${alias} to ${edit.conflict}`,
5518
+ text: `${where2} already maps ${alias} to ${edit.conflict}`,
5384
5519
  note: `(left alone \u2014 \`import { env } from "${alias}"\` will not reach ${decisions.schemaFile})`
5385
5520
  };
5386
5521
  }
@@ -5388,20 +5523,20 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
5388
5523
  return {
5389
5524
  target: "tsconfig",
5390
5525
  action: "kept",
5391
- text: `Kept the ${alias} alias in ${where}`
5526
+ text: `Kept the ${alias} alias in ${where2}`
5392
5527
  };
5393
5528
  }
5394
5529
  writeFileSync5(file, edit.source, "utf8");
5395
5530
  return {
5396
5531
  target: "tsconfig",
5397
5532
  action: "updated",
5398
- text: `Added ${alias} alias to ${where}`
5533
+ text: `Added ${alias} alias to ${where2}`
5399
5534
  };
5400
5535
  }
5401
5536
  function writeGitignore(root, decisions = DEFAULT_DECISIONS) {
5402
- const file = join9(root, ...STATE_GITIGNORE_PATH.split("/"));
5537
+ const file = join10(root, ...STATE_GITIGNORE_PATH.split("/"));
5403
5538
  const wanted = renderStateGitignore(configOf(decisions));
5404
- const existing = existsSync8(file) ? readFileSync8(file, "utf8") : void 0;
5539
+ const existing = existsSync8(file) ? readFileSync9(file, "utf8") : void 0;
5405
5540
  if (existing === wanted) {
5406
5541
  return { target: "gitignore", action: "kept", text: `Kept ${STATE_GITIGNORE_PATH}` };
5407
5542
  }
@@ -5422,12 +5557,12 @@ function outdatedRuntimeWarning(root) {
5422
5557
  return `Injection needs @penvhq/penv ${INJECT_MIN_VERSION}+ \u2014 this project has ${version}, whose \`load\` ignores \`{ inject: true }\`. Upgrade, or process.env stays empty.`;
5423
5558
  }
5424
5559
  function installedPenvVersion(root) {
5425
- const file = join9(root, "node_modules", "@penvhq", "penv", "package.json");
5560
+ const file = join10(root, "node_modules", "@penvhq", "penv", "package.json");
5426
5561
  if (!existsSync8(file)) {
5427
5562
  return void 0;
5428
5563
  }
5429
5564
  try {
5430
- const version = JSON.parse(readFileSync8(file, "utf8")).version;
5565
+ const version = JSON.parse(readFileSync9(file, "utf8")).version;
5431
5566
  return typeof version === "string" ? version : void 0;
5432
5567
  } catch {
5433
5568
  return void 0;
@@ -5467,7 +5602,7 @@ function writeSeam(root, decisions = DEFAULT_DECISIONS, framework = detectFramew
5467
5602
  };
5468
5603
  }
5469
5604
  const alsoNote = writeAlso(root, seam.also);
5470
- const file = join9(root, ...seam.file.split("/"));
5605
+ const file = join10(root, ...seam.file.split("/"));
5471
5606
  const baseNotes = [...seam.notes, ...alsoNote === void 0 ? [] : [alsoNote]];
5472
5607
  const notes = baseNotes.length === 0 ? "" : `
5473
5608
  ${baseNotes.map((n) => ` ${n}`).join("\n")}`;
@@ -5492,7 +5627,7 @@ function writeAlso(root, also) {
5492
5627
  if (also === void 0) {
5493
5628
  return void 0;
5494
5629
  }
5495
- const file = join9(root, ...also.file.split("/"));
5630
+ const file = join10(root, ...also.file.split("/"));
5496
5631
  if (existsSync8(file)) {
5497
5632
  return also.ifPresent;
5498
5633
  }
@@ -5523,7 +5658,7 @@ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS, framework
5523
5658
  return seam === void 0 ? steps : [...steps, seam];
5524
5659
  }
5525
5660
  function scaffoldPaths(root, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
5526
- const fileAt = (relative5) => join9(root, ...relative5.split("/"));
5661
+ const fileAt = (relative5) => join10(root, ...relative5.split("/"));
5527
5662
  const seam = decisions.inject ? seamFor(framework, {
5528
5663
  alias: decisions.alias,
5529
5664
  srcDir: srcPrefix(root),
@@ -5601,7 +5736,7 @@ function planCutover(input) {
5601
5736
  const diagnostics = [];
5602
5737
  let variables = 0;
5603
5738
  for (const file of selected) {
5604
- const parsed = parseDotenv(readFileSync8(join9(root, file.name), "utf8"));
5739
+ const parsed = parseDotenv(readFileSync9(join10(root, file.name), "utf8"));
5605
5740
  const fileRefs = refsForEntries(parsed.entries, file.name, config);
5606
5741
  adopted.push({ file, entries: parsed.entries, refs: fileRefs, scope: scopeOf(file) });
5607
5742
  refs.push(...fileRefs);
@@ -5789,7 +5924,7 @@ function renderCutoverPlan(plan2) {
5789
5924
  ]);
5790
5925
  rows.push([
5791
5926
  ` ${out.dim("values")}`,
5792
- `${RECORDS_PATH2}/`,
5927
+ `${RECORDS_PATH3}/`,
5793
5928
  out.dim(`\u2190 from ${plan2.variables} variables`)
5794
5929
  ]);
5795
5930
  rows.push([
@@ -5825,7 +5960,7 @@ function renderCutover(result2) {
5825
5960
  {
5826
5961
  glyph: CHECK,
5827
5962
  text: `Imported ${plan2.fields.length} parameters`,
5828
- note: `into ${RECORDS_PATH2}/`
5963
+ note: `into ${RECORDS_PATH3}/`
5829
5964
  },
5830
5965
  { glyph: CHECK, text: `Validated ${result2.validated.join(", ")}` },
5831
5966
  {
@@ -5845,12 +5980,12 @@ function dailyCommand(root) {
5845
5980
  return `penv run -- ${detectPackageManager(root)} ${devScript(root)}`;
5846
5981
  }
5847
5982
  function devScript(root) {
5848
- const file = join9(root, PACKAGE_FILE);
5983
+ const file = join10(root, PACKAGE_FILE);
5849
5984
  if (!existsSync8(file)) {
5850
5985
  return "dev";
5851
5986
  }
5852
5987
  try {
5853
- const scripts = JSON.parse(readFileSync8(file, "utf8")).scripts;
5988
+ const scripts = JSON.parse(readFileSync9(file, "utf8")).scripts;
5854
5989
  if (scripts !== null && typeof scripts === "object" && !Array.isArray(scripts)) {
5855
5990
  const named = Object.keys(scripts);
5856
5991
  return ["dev", "start"].find((script) => named.includes(script)) ?? named[0] ?? "dev";
@@ -6084,7 +6219,7 @@ function runUndoAction(root, action) {
6084
6219
  ]),
6085
6220
  "",
6086
6221
  result2.missing.length === 0 ? `${out.green(CHECK)} ${out.bold("Undone.")} Your dotenv files are back exactly as they were, and ${CUTOVER_PATH3} is gone.` : `${out.yellow(WARN)} ${out.bold("Undone as far as penv could.")} Everything still in the bundle is back, and ${CUTOVER_PATH3} is gone.`,
6087
- `penv's records are still in ${RECORDS_PATH2}/ \u2014 nothing penv scaffolded is yours to lose.`
6222
+ `penv's records are still in ${RECORDS_PATH3}/ \u2014 nothing penv scaffolded is yours to lose.`
6088
6223
  ]);
6089
6224
  }
6090
6225
 
@@ -6226,7 +6361,7 @@ function importDotenv(options) {
6226
6361
  "Point `penv import` at an existing dotenv file, e.g. `penv import .env`."
6227
6362
  );
6228
6363
  }
6229
- const parsed = parseDotenv2(readFileSync9(file, "utf8"));
6364
+ const parsed = parseDotenv2(readFileSync10(file, "utf8"));
6230
6365
  const { config, decisions } = configInEffect(cwd, environmentNamed(file, options.environment));
6231
6366
  const source = displayPath3(cwd, file);
6232
6367
  const named = scopeFromFilename(file, config);
@@ -6497,7 +6632,7 @@ var keyCommand = defineCommand15({
6497
6632
  });
6498
6633
 
6499
6634
  // src/commands/list.ts
6500
- import { assertNever as assertNever3, RECORDS_PATH as RECORDS_PATH3, resolveAll as resolveAll3, variableName as variableName8 } from "@penvhq/core";
6635
+ import { assertNever as assertNever3, RECORDS_PATH as RECORDS_PATH4, resolveAll as resolveAll3, variableName as variableName8 } from "@penvhq/core";
6501
6636
  import { defineCommand as defineCommand16 } from "citty";
6502
6637
  function scopeLabel2(scope) {
6503
6638
  switch (scope.kind) {
@@ -6541,7 +6676,7 @@ function paintScope(entry) {
6541
6676
  function renderList(result2) {
6542
6677
  if (result2.parameters.length === 0) {
6543
6678
  return [
6544
- `No parameters in ${RECORDS_PATH3}/ for environment ${result2.environment}.`,
6679
+ `No parameters in ${RECORDS_PATH4}/ for environment ${result2.environment}.`,
6545
6680
  tip(`penv set <key> --env ${result2.environment}`)
6546
6681
  ];
6547
6682
  }
@@ -6580,19 +6715,19 @@ import {
6580
6715
  existsSync as existsSync10,
6581
6716
  mkdirSync as mkdirSync5,
6582
6717
  readdirSync as readdirSync6,
6583
- readFileSync as readFileSync10,
6718
+ readFileSync as readFileSync11,
6584
6719
  renameSync as renameSync2,
6585
6720
  rmSync as rmSync2,
6586
6721
  writeFileSync as writeFileSync6
6587
6722
  } from "fs";
6588
- import { dirname as dirname7, join as join10 } from "path";
6723
+ import { dirname as dirname7, join as join11 } from "path";
6589
6724
  import { createInterface as createInterface3 } from "readline/promises";
6590
6725
  import {
6591
6726
  loadConfig as loadConfig2,
6592
6727
  oldLayoutEntries,
6593
6728
  PENV_DIR as PENV_DIR3,
6594
6729
  PenvError as PenvError22,
6595
- RECORDS_PATH as RECORDS_PATH4,
6730
+ RECORDS_PATH as RECORDS_PATH5,
6596
6731
  recordsDir as recordsDir3,
6597
6732
  renderStateGitignore as renderStateGitignore2,
6598
6733
  STATE_GITIGNORE_PATH as STATE_GITIGNORE_PATH2
@@ -6608,29 +6743,29 @@ function planMigrate(cwd) {
6608
6743
  if (collisions.length > 0) {
6609
6744
  throw new PenvError22(
6610
6745
  "HALF_MIGRATED",
6611
- `${describe2(collisions)} in both \`${PENV_DIR3}/\` and \`${RECORDS_PATH4}/\`, and penv cannot tell which copy is current`,
6612
- `Move what is left under \`${PENV_DIR3}/\` into \`${RECORDS_PATH4}/\` yourself, keeping the copy you want, then run \`penv validate\`.`
6746
+ `${describe2(collisions)} in both \`${PENV_DIR3}/\` and \`${RECORDS_PATH5}/\`, and penv cannot tell which copy is current`,
6747
+ `Move what is left under \`${PENV_DIR3}/\` into \`${RECORDS_PATH5}/\` yourself, keeping the copy you want, then run \`penv validate\`.`
6613
6748
  );
6614
6749
  }
6615
6750
  const creates = [];
6616
6751
  if (entries.length > 0 && !existsSync10(tree)) {
6617
- creates.push(`${RECORDS_PATH4}/`);
6752
+ creates.push(`${RECORDS_PATH5}/`);
6618
6753
  }
6619
- if (readIfPresent(join10(root, ...STATE_GITIGNORE_PATH2.split("/"))) !== renderStateGitignore2(config)) {
6754
+ if (readIfPresent(join11(root, ...STATE_GITIGNORE_PATH2.split("/"))) !== renderStateGitignore2(config)) {
6620
6755
  creates.push(STATE_GITIGNORE_PATH2);
6621
6756
  }
6622
6757
  return {
6623
6758
  root,
6624
6759
  moves: entries.map((entry) => ({
6625
6760
  from: `${PENV_DIR3}/${entry}`,
6626
- to: `${RECORDS_PATH4}/${entry}`
6761
+ to: `${RECORDS_PATH5}/${entry}`
6627
6762
  })),
6628
6763
  creates,
6629
- removes: existsSync10(join10(root, ...OLD_GITIGNORE.split("/"))) ? [OLD_GITIGNORE] : []
6764
+ removes: existsSync10(join11(root, ...OLD_GITIGNORE.split("/"))) ? [OLD_GITIGNORE] : []
6630
6765
  };
6631
6766
  }
6632
6767
  function readIfPresent(file) {
6633
- return existsSync10(file) ? readFileSync10(file, "utf8") : void 0;
6768
+ return existsSync10(file) ? readFileSync11(file, "utf8") : void 0;
6634
6769
  }
6635
6770
  function collidingEntries(entries, tree) {
6636
6771
  let held;
@@ -6656,16 +6791,16 @@ function applyMigrate(plan2) {
6656
6791
  if (plan2.moves.length > 0) {
6657
6792
  mkdirSync5(recordsDir3(plan2.root), { recursive: true });
6658
6793
  for (const move of plan2.moves) {
6659
- renameSync2(join10(plan2.root, ...move.from.split("/")), join10(plan2.root, ...move.to.split("/")));
6794
+ renameSync2(join11(plan2.root, ...move.from.split("/")), join11(plan2.root, ...move.to.split("/")));
6660
6795
  }
6661
6796
  }
6662
6797
  if (plan2.creates.includes(STATE_GITIGNORE_PATH2)) {
6663
- const ignore = join10(plan2.root, ...STATE_GITIGNORE_PATH2.split("/"));
6798
+ const ignore = join11(plan2.root, ...STATE_GITIGNORE_PATH2.split("/"));
6664
6799
  mkdirSync5(dirname7(ignore), { recursive: true });
6665
6800
  writeFileSync6(ignore, renderStateGitignore2(config), "utf8");
6666
6801
  }
6667
6802
  for (const removed of plan2.removes) {
6668
- rmSync2(join10(plan2.root, ...removed.split("/")), { force: true });
6803
+ rmSync2(join11(plan2.root, ...removed.split("/")), { force: true });
6669
6804
  }
6670
6805
  return { ...plan2, status: "migrated" };
6671
6806
  }
@@ -6678,7 +6813,7 @@ function runMigrate(options) {
6678
6813
  }
6679
6814
  function renderMigrate(result2) {
6680
6815
  if (result2.status === "current") {
6681
- return [`${out.green(CHECK)} Already on ${RECORDS_PATH4}/ \u2014 nothing to migrate.`];
6816
+ return [`${out.green(CHECK)} Already on ${RECORDS_PATH5}/ \u2014 nothing to migrate.`];
6682
6817
  }
6683
6818
  const done = result2.status === "migrated";
6684
6819
  const glyph = done ? CHECK : "\u2192";
@@ -6719,7 +6854,7 @@ async function approveOnTty() {
6719
6854
  var migrateCommand = defineCommand17({
6720
6855
  meta: {
6721
6856
  name: "migrate",
6722
- description: `Move a project written under an earlier layout to ${RECORDS_PATH4}/`
6857
+ description: `Move a project written under an earlier layout to ${RECORDS_PATH5}/`
6723
6858
  },
6724
6859
  args: {
6725
6860
  yes: { type: "boolean", description: "Apply the previewed move without asking" }