@penvhq/cli 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -69,7 +69,7 @@ var import_core35 = require("@penvhq/core");
69
69
  var import_citty22 = require("citty");
70
70
 
71
71
  // src/commands/artifact.ts
72
- var import_node_fs6 = require("fs");
72
+ var import_node_fs7 = require("fs");
73
73
  var import_node_path7 = require("path");
74
74
  var import_core12 = require("@penvhq/core");
75
75
  var import_runtime2 = require("@penvhq/runtime");
@@ -114,7 +114,7 @@ var startChild = (invocation) => {
114
114
  const ended = new Promise((resolve9, reject) => {
115
115
  child.on("error", (cause) => {
116
116
  release();
117
- reject(cannotStart(executable, cause));
117
+ reject(cannotStart(executable, cause, invocation.purpose));
118
118
  });
119
119
  child.on("exit", (code, signal) => {
120
120
  release();
@@ -135,8 +135,15 @@ function noCommand() {
135
135
  "Put the command after `--`, e.g. `penv run -- pnpm dev`."
136
136
  );
137
137
  }
138
- function cannotStart(executable, cause) {
138
+ function cannotStart(executable, cause, purpose) {
139
139
  const detail = cause instanceof Error ? cause.message : String(cause);
140
+ if (purpose !== void 0) {
141
+ return new import_core.PenvError(
142
+ "PENV_COMMAND_NOT_STARTED",
143
+ `penv could not start \`${executable}\` to ${purpose}: ${detail}`,
144
+ `Check that \`${executable}\` runs on its own \u2014 penv starts it the way your shell does, so it has to be on PATH. Nothing was changed.`
145
+ );
146
+ }
140
147
  return new import_core.PenvError(
141
148
  "RUN_COMMAND_NOT_STARTED",
142
149
  `\`${executable}\` could not be started: ${detail}`,
@@ -165,12 +172,15 @@ function cmdCommandLine(resolved, args) {
165
172
  " "
166
173
  );
167
174
  }
168
- function extensions(env) {
175
+ function extensions(env, platform) {
176
+ if (platform !== "win32") {
177
+ return [""];
178
+ }
169
179
  const declared = env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
170
- return ["", ...declared.split(";").filter((extension) => extension.length > 0)];
180
+ return [...declared.split(";").filter((extension) => extension.length > 0), ""];
171
181
  }
172
- function findExecutable(executable, env) {
173
- const candidates = extensions(env);
182
+ function findExecutable(executable, env, platform = process.platform) {
183
+ const candidates = extensions(env, platform);
174
184
  const isFile = (path2) => (0, import_node_fs.existsSync)(path2) && (0, import_node_fs.statSync)(path2).isFile();
175
185
  if (executable.includes("/") || executable.includes("\\") || (0, import_node_path.isAbsolute)(executable)) {
176
186
  return candidates.map((extension) => executable + extension).find(isFile);
@@ -197,6 +207,7 @@ function escapeArgument(argument, doubleEscape) {
197
207
  // src/install.ts
198
208
  var import_meta = {};
199
209
  var RUNTIME_PACKAGE = "@penvhq/penv";
210
+ var SCHEMA_PACKAGE = "zod";
200
211
  var LOCKFILES = [
201
212
  ["pnpm", "pnpm-lock.yaml"],
202
213
  ["yarn", "yarn.lock"],
@@ -211,13 +222,9 @@ var ADD = {
211
222
  bun: ["bun", "add", "--exact"]
212
223
  };
213
224
  function engineVersion() {
214
- const manifest = new URL("../package.json", import_meta.url);
215
- try {
216
- const version = JSON.parse((0, import_node_fs2.readFileSync)(manifest, "utf8")).version;
217
- if (typeof version === "string" && version.length > 0) {
218
- return version;
219
- }
220
- } catch {
225
+ const version = ownManifest()?.version;
226
+ if (typeof version === "string" && version.length > 0) {
227
+ return version;
221
228
  }
222
229
  throw new import_core2.PenvError(
223
230
  "ENGINE_VERSION_UNREADABLE",
@@ -225,6 +232,29 @@ function engineVersion() {
225
232
  `Reinstall penv, then run \`penv init\` again.`
226
233
  );
227
234
  }
235
+ function schemaPackageVersion() {
236
+ const peers = ownManifest()?.peerDependencies;
237
+ const declared = peers !== null && typeof peers === "object" && !Array.isArray(peers) ? peers[SCHEMA_PACKAGE] : void 0;
238
+ const floor = typeof declared === "string" ? declared.replace(/^[\^~>=\s]+/, "").trim() : "";
239
+ if (floor.length > 0) {
240
+ return floor;
241
+ }
242
+ throw new import_core2.PenvError(
243
+ "ENGINE_PEER_UNREADABLE",
244
+ `penv could not read its own \`${SCHEMA_PACKAGE}\` peer range, so it cannot say which ${SCHEMA_PACKAGE} this project needs`,
245
+ `Reinstall penv, then run \`penv init\` again.`
246
+ );
247
+ }
248
+ function ownManifest() {
249
+ try {
250
+ const parsed = JSON.parse(
251
+ (0, import_node_fs2.readFileSync)(new URL("../package.json", import_meta.url), "utf8")
252
+ );
253
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
254
+ } catch {
255
+ return void 0;
256
+ }
257
+ }
228
258
  function detectPackageManager(root) {
229
259
  for (const [manager, lockfile] of LOCKFILES) {
230
260
  if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, lockfile))) {
@@ -253,12 +283,12 @@ function manifestOf(root) {
253
283
  return void 0;
254
284
  }
255
285
  }
256
- function declaredVersion(root) {
286
+ function declaredVersion(root, name) {
257
287
  const manifest = manifestOf(root);
258
288
  for (const field of ["dependencies", "devDependencies"]) {
259
289
  const block = manifest?.[field];
260
290
  if (block !== null && typeof block === "object" && !Array.isArray(block)) {
261
- const version = block[RUNTIME_PACKAGE];
291
+ const version = block[name];
262
292
  if (typeof version === "string") {
263
293
  return version;
264
294
  }
@@ -271,27 +301,61 @@ function planInstall(root, version = engineVersion()) {
271
301
  const lockfile = LOCKFILES.find(
272
302
  ([name, file]) => name === manager && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, file))
273
303
  )?.[1];
274
- const declared = declaredVersion(root);
304
+ const runtimeDeclared = declaredVersion(root, RUNTIME_PACKAGE);
305
+ const zodDeclared = declaredVersion(root, SCHEMA_PACKAGE);
306
+ const packages = [
307
+ {
308
+ name: RUNTIME_PACKAGE,
309
+ version,
310
+ ...runtimeDeclared === void 0 ? {} : { declared: runtimeDeclared },
311
+ satisfied: runtimeDeclared === version
312
+ },
313
+ {
314
+ name: SCHEMA_PACKAGE,
315
+ version: schemaPackageVersion(),
316
+ ...zodDeclared === void 0 ? {} : { declared: zodDeclared },
317
+ // Any declared zod counts: which zod a project uses is the project's
318
+ // decision, and penv is here to make sure there is one, not to move it.
319
+ satisfied: zodDeclared !== void 0
320
+ }
321
+ ];
322
+ const pending = packages.filter((entry) => !entry.satisfied);
323
+ const specs = (pending.length === 0 ? packages : pending).map(
324
+ (entry) => `${entry.name}@${entry.version}`
325
+ );
275
326
  return {
276
327
  root,
277
328
  manager,
278
- package: RUNTIME_PACKAGE,
279
- version,
280
- command: [...ADD[manager], `${RUNTIME_PACKAGE}@${version}`],
329
+ packages,
330
+ command: [...ADD[manager], ...specs],
281
331
  ...lockfile === void 0 ? {} : { lockfile },
282
- ...declared === void 0 ? {} : { declared },
283
- satisfied: declared === version
332
+ satisfied: pending.length === 0
284
333
  };
285
334
  }
335
+ function describe(entry) {
336
+ return `${entry.name} ${entry.version}`;
337
+ }
286
338
  function renderInstallPlan(plan2) {
287
339
  if (plan2.satisfied) {
288
- return [`package.json already pins ${plan2.package} ${plan2.version} \u2014 nothing to install.`];
340
+ return [
341
+ `package.json already has ${plan2.packages.map(describe).join(" and ")} \u2014 nothing to install.`
342
+ ];
289
343
  }
290
- const line = `"${plan2.package}": "${plan2.version}"`;
344
+ const pending = plan2.packages.filter((entry) => !entry.satisfied);
345
+ const added = pending.filter((entry) => entry.declared === void 0);
346
+ const replaced = pending.filter((entry) => entry.declared !== void 0);
291
347
  return [
292
348
  "package.json",
293
- ...plan2.declared === void 0 ? [' + "dependencies": {', ` + ${line}`, " + }"] : [` - "${plan2.package}": "${plan2.declared}"`, ` + ${line}`],
294
- ...plan2.lockfile === void 0 ? [] : [plan2.lockfile, ` + ${plan2.package}@${plan2.version}`],
349
+ ...added.length === 0 ? [] : [
350
+ ' + "dependencies": {',
351
+ ...added.map((entry) => ` + "${entry.name}": "${entry.version}"`),
352
+ " + }"
353
+ ],
354
+ ...replaced.flatMap((entry) => [
355
+ ` - "${entry.name}": "${entry.declared}"`,
356
+ ` + "${entry.name}": "${entry.version}"`
357
+ ]),
358
+ ...plan2.lockfile === void 0 ? [] : [plan2.lockfile, ...pending.map((entry) => ` + ${entry.name}@${entry.version}`)],
295
359
  "",
296
360
  `Run with: ${plan2.command.join(" ")}`
297
361
  ];
@@ -300,7 +364,8 @@ var installWithPackageManager = async (plan2) => {
300
364
  const child = startChild({
301
365
  command: plan2.command,
302
366
  env: process.env,
303
- cwd: plan2.root
367
+ cwd: plan2.root,
368
+ purpose: `install ${plan2.packages.map(describe).join(" and ")}`
304
369
  });
305
370
  const ended = await child.ended;
306
371
  if (ended.exitCode !== 0 || ended.signal !== null) {
@@ -316,7 +381,7 @@ function installFailed(plan2) {
316
381
  }
317
382
 
318
383
  // src/project.ts
319
- var import_node_fs3 = require("fs");
384
+ var import_node_fs4 = require("fs");
320
385
  var import_node_path4 = require("path");
321
386
  var import_core5 = require("@penvhq/core");
322
387
  var import_provider_filesystem2 = require("@penvhq/provider-filesystem");
@@ -380,6 +445,7 @@ function environmentFromShorthand(config, candidates, explicit) {
380
445
  }
381
446
 
382
447
  // src/registry.ts
448
+ var import_node_fs3 = require("fs");
383
449
  var import_node_module = require("module");
384
450
  var import_node_path3 = require("path");
385
451
  var import_node_url = require("url");
@@ -452,16 +518,40 @@ async function loadPluginProvider(type, context) {
452
518
  assertSatisfiesContract(provider, type);
453
519
  return provider;
454
520
  }
455
- function assertProvidersRegistered(config, projectRoot) {
521
+ function assertProvidersRegistered(config, projectRoot, options) {
522
+ const local = localExtensions(projectRoot);
523
+ const ci = options?.ci ?? isCi(process.env.CI);
456
524
  for (const [environment, provider] of Object.entries(config.providers)) {
457
525
  if (isProviderRegistered(provider.type)) {
458
526
  continue;
459
527
  }
528
+ if (ci && local.includes(provider.type)) {
529
+ throw localExtensionInCi(provider.type, environment);
530
+ }
460
531
  if (resolvePlugin(provider.type, projectRoot) === void 0) {
461
532
  throw unknownProvider(provider.type, environment);
462
533
  }
463
534
  }
464
535
  }
536
+ function localExtensions(projectRoot) {
537
+ let text;
538
+ try {
539
+ text = (0, import_node_fs3.readFileSync)((0, import_core4.localExtensionsFile)(projectRoot), "utf8");
540
+ } catch {
541
+ return [];
542
+ }
543
+ return (0, import_core4.parseLocalExtensions)(text);
544
+ }
545
+ function isCi(value) {
546
+ return value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
547
+ }
548
+ function localExtensionInCi(type, environment) {
549
+ return new import_core4.PenvError(
550
+ "LOCAL_EXTENSION_IN_CI",
551
+ `The provider \`${type}\` for environment ${environment} is a local extension, and this is CI`,
552
+ `${import_core4.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.`
553
+ );
554
+ }
465
555
  function resolvePlugin(specifier, fromDir) {
466
556
  try {
467
557
  const require2 = (0, import_node_module.createRequire)((0, import_node_path3.resolve)(fromDir, "noop.js"));
@@ -499,7 +589,7 @@ function unknownProvider(type, environment) {
499
589
  return new import_core4.PenvError(
500
590
  "UNKNOWN_PROVIDER",
501
591
  `The provider \`${type}\`${where} in penv.config.ts is not installed in this project`,
502
- `Install it with \`npm i ${type}\` \u2014 a provider's \`type\` is the package penv imports. The CLI ships ${preinstalled} pre-installed.`
592
+ `A provider's \`type\` is the package penv imports, so it has to resolve from this project: install it with \`npm i ${type}\`, or \u2014 if this repository is the one that builds it \u2014 run \`penv add --local ${type}\` once it is a dependency of the root. The CLI ships ${preinstalled} pre-installed.`
503
593
  );
504
594
  }
505
595
 
@@ -529,10 +619,10 @@ function localTree(project) {
529
619
  }
530
620
  function selfContainedSchemaModule(root, schemaFile) {
531
621
  const file = (0, import_node_path4.join)(root, ...schemaFile.split("/"));
532
- if (!(0, import_node_fs3.existsSync)(file)) {
622
+ if (!(0, import_node_fs4.existsSync)(file)) {
533
623
  return void 0;
534
624
  }
535
- const source = (0, import_node_fs3.readFileSync)(file, "utf8");
625
+ const source = (0, import_node_fs4.readFileSync)(file, "utf8");
536
626
  return source.includes("PenvSchemaShape") || source.includes("z.object") ? schemaFile : void 0;
537
627
  }
538
628
  function schemaShapeFileOf(project) {
@@ -768,10 +858,7 @@ function writeError(lines) {
768
858
  }
769
859
  function reportError(error) {
770
860
  if (error instanceof import_core6.PenvError) {
771
- const suffix = error.remedy === void 0 ? void 0 : `
772
- ${error.remedy}`;
773
- const message = suffix !== void 0 && error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
774
- process.stderr.write(`${err.red(CROSS)} ${message}
861
+ process.stderr.write(`${err.red(CROSS)} ${error.summary}
775
862
  `);
776
863
  if (error.remedy !== void 0) {
777
864
  process.stderr.write(` ${err.cyan("\u2192")} ${error.remedy}
@@ -801,7 +888,7 @@ async function guard(run) {
801
888
  }
802
889
 
803
890
  // src/commands/run.ts
804
- var import_node_fs5 = require("fs");
891
+ var import_node_fs6 = require("fs");
805
892
  var import_node_module2 = require("module");
806
893
  var import_node_path6 = require("path");
807
894
  var import_core11 = require("@penvhq/core");
@@ -809,7 +896,7 @@ var import_runtime = require("@penvhq/runtime");
809
896
  var import_citty3 = require("citty");
810
897
 
811
898
  // src/dotenv-files.ts
812
- var import_node_fs4 = require("fs");
899
+ var import_node_fs5 = require("fs");
813
900
  var import_core7 = require("@penvhq/core");
814
901
  var SHARED = ".env";
815
902
  var LOCAL = "local";
@@ -869,7 +956,7 @@ function order(file) {
869
956
  function discoverDotenvFiles(root) {
870
957
  let entries;
871
958
  try {
872
- entries = (0, import_node_fs4.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
959
+ entries = (0, import_node_fs5.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name);
873
960
  } catch {
874
961
  return [];
875
962
  }
@@ -1606,9 +1693,9 @@ function packageManifest(specifier, root) {
1606
1693
  }
1607
1694
  for (; ; ) {
1608
1695
  const file = (0, import_node_path6.join)(directory, "package.json");
1609
- if ((0, import_node_fs5.existsSync)(file)) {
1696
+ if ((0, import_node_fs6.existsSync)(file)) {
1610
1697
  try {
1611
- const parsed = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
1698
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(file, "utf8"));
1612
1699
  if (isPlainObject(parsed) && parsed.name === specifier) {
1613
1700
  return parsed;
1614
1701
  }
@@ -1681,7 +1768,7 @@ function snapshotPath(host) {
1681
1768
  }
1682
1769
  function readSnapshot(path) {
1683
1770
  try {
1684
- return (0, import_node_fs5.readFileSync)(path, "utf8");
1771
+ return (0, import_node_fs6.readFileSync)(path, "utf8");
1685
1772
  } catch {
1686
1773
  throw new import_core11.PenvError(
1687
1774
  "RUN_SNAPSHOT_MISSING",
@@ -1760,11 +1847,11 @@ function watchProject(project, onChange) {
1760
1847
  timer = setTimeout(onChange, DEBOUNCE_MS);
1761
1848
  };
1762
1849
  const add = (target, recursive, only) => {
1763
- if (!(0, import_node_fs5.existsSync)(target)) {
1850
+ if (!(0, import_node_fs6.existsSync)(target)) {
1764
1851
  return;
1765
1852
  }
1766
1853
  try {
1767
- const watcher = (0, import_node_fs5.watch)(target, { recursive }, (_event, filename) => {
1854
+ const watcher = (0, import_node_fs6.watch)(target, { recursive }, (_event, filename) => {
1768
1855
  if (only !== void 0 && (filename === null || (0, import_node_path6.basename)(filename) !== only)) {
1769
1856
  return;
1770
1857
  }
@@ -2027,8 +2114,8 @@ ${lines}`,
2027
2114
  keySource: (0, import_core12.keySourceIdentifier)(project.config, environment),
2028
2115
  values
2029
2116
  };
2030
- (0, import_node_fs6.mkdirSync)((0, import_node_path7.dirname)(file), { recursive: true });
2031
- (0, import_node_fs6.writeFileSync)(file, (0, import_core12.serializeArtifact)(artifact), "utf8");
2117
+ (0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(file), { recursive: true });
2118
+ (0, import_node_fs7.writeFileSync)(file, (0, import_core12.serializeArtifact)(artifact), "utf8");
2032
2119
  const inside = (0, import_node_path7.relative)(project.root, file);
2033
2120
  return {
2034
2121
  file,
@@ -2098,7 +2185,7 @@ var import_core14 = require("@penvhq/core");
2098
2185
  var import_citty5 = require("citty");
2099
2186
 
2100
2187
  // src/cutover.ts
2101
- var import_node_fs7 = require("fs");
2188
+ var import_node_fs8 = require("fs");
2102
2189
  var import_node_path8 = require("path");
2103
2190
  var import_core13 = require("@penvhq/core");
2104
2191
  var CUTOVER_FORMAT = 1;
@@ -2117,7 +2204,7 @@ function cutoverRoot(cwd) {
2117
2204
  }
2118
2205
  function bundledFiles(root) {
2119
2206
  try {
2120
- return (0, import_node_fs7.readdirSync)(bundleDir(root)).sort();
2207
+ return (0, import_node_fs8.readdirSync)(bundleDir(root)).sort();
2121
2208
  } catch {
2122
2209
  return [];
2123
2210
  }
@@ -2127,32 +2214,32 @@ function bundleUnresolved(root) {
2127
2214
  }
2128
2215
  function readCutover(root) {
2129
2216
  const file = cutoverFile(root);
2130
- if (!(0, import_node_fs7.existsSync)(file)) {
2217
+ if (!(0, import_node_fs8.existsSync)(file)) {
2131
2218
  return void 0;
2132
2219
  }
2133
2220
  let parsed;
2134
2221
  try {
2135
- parsed = JSON.parse((0, import_node_fs7.readFileSync)(file, "utf8"));
2222
+ parsed = JSON.parse((0, import_node_fs8.readFileSync)(file, "utf8"));
2136
2223
  } catch {
2137
2224
  throw unreadable("it is not JSON");
2138
2225
  }
2139
2226
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2140
2227
  throw unreadable("it is not an object");
2141
2228
  }
2142
- const record = parsed;
2143
- if (record.format !== CUTOVER_FORMAT) {
2229
+ const record2 = parsed;
2230
+ if (record2.format !== CUTOVER_FORMAT) {
2144
2231
  throw unreadable(
2145
- `it is format ${JSON.stringify(record.format)}, and penv reads format ${CUTOVER_FORMAT}`
2232
+ `it is format ${JSON.stringify(record2.format)}, and penv reads format ${CUTOVER_FORMAT}`
2146
2233
  );
2147
2234
  }
2148
- const files = record.files;
2235
+ const files = record2.files;
2149
2236
  if (!Array.isArray(files) || files.some((name) => typeof name !== "string")) {
2150
2237
  throw unreadable("it lists no filenames");
2151
2238
  }
2152
- const environments = Array.isArray(record.environments) ? record.environments.filter((name) => typeof name === "string") : [];
2239
+ const environments = Array.isArray(record2.environments) ? record2.environments.filter((name) => typeof name === "string") : [];
2153
2240
  return {
2154
2241
  format: CUTOVER_FORMAT,
2155
- movedAt: typeof record.movedAt === "string" ? record.movedAt : "",
2242
+ movedAt: typeof record2.movedAt === "string" ? record2.movedAt : "",
2156
2243
  files,
2157
2244
  environments
2158
2245
  };
@@ -2185,12 +2272,12 @@ function bundleDotenvFiles(root, files, environments, now = /* @__PURE__ */ new
2185
2272
  environments: [...environments]
2186
2273
  };
2187
2274
  const file = cutoverFile(root);
2188
- (0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(file), { recursive: true });
2189
- (0, import_node_fs7.writeFileSync)(file, serializeCutover(cutover), "utf8");
2275
+ (0, import_node_fs8.mkdirSync)((0, import_node_path8.dirname)(file), { recursive: true });
2276
+ (0, import_node_fs8.writeFileSync)(file, serializeCutover(cutover), "utf8");
2190
2277
  const bundle = bundleDir(root);
2191
- (0, import_node_fs7.mkdirSync)(bundle, { recursive: true });
2278
+ (0, import_node_fs8.mkdirSync)(bundle, { recursive: true });
2192
2279
  for (const name of files) {
2193
- (0, import_node_fs7.renameSync)((0, import_node_path8.join)(root, name), (0, import_node_path8.join)(bundle, name));
2280
+ (0, import_node_fs8.renameSync)((0, import_node_path8.join)(root, name), (0, import_node_path8.join)(bundle, name));
2194
2281
  }
2195
2282
  return cutover;
2196
2283
  }
@@ -2209,7 +2296,7 @@ function runUndo(options) {
2209
2296
  const names = [...recorded, ...bundled.filter((name) => !recorded.includes(name))];
2210
2297
  const bundle = bundleDir(root);
2211
2298
  const held = new Set(bundled);
2212
- const occupied2 = names.filter((name) => held.has(name) && (0, import_node_fs7.existsSync)((0, import_node_path8.join)(root, name)));
2299
+ const occupied2 = names.filter((name) => held.has(name) && (0, import_node_fs8.existsSync)((0, import_node_path8.join)(root, name)));
2213
2300
  if (occupied2.length > 0) {
2214
2301
  const listed = occupied2.join(", ");
2215
2302
  const many = occupied2.length > 1;
@@ -2224,9 +2311,9 @@ function runUndo(options) {
2224
2311
  const missing = [];
2225
2312
  for (const name of names) {
2226
2313
  if (held.has(name)) {
2227
- (0, import_node_fs7.renameSync)((0, import_node_path8.join)(bundle, name), (0, import_node_path8.join)(root, name));
2314
+ (0, import_node_fs8.renameSync)((0, import_node_path8.join)(bundle, name), (0, import_node_path8.join)(root, name));
2228
2315
  restored.push(name);
2229
- } else if ((0, import_node_fs7.existsSync)((0, import_node_path8.join)(root, name))) {
2316
+ } else if ((0, import_node_fs8.existsSync)((0, import_node_path8.join)(root, name))) {
2230
2317
  alreadyBack.push(name);
2231
2318
  } else {
2232
2319
  missing.push(name);
@@ -2238,13 +2325,13 @@ function runUndo(options) {
2238
2325
  function runCleanup(options) {
2239
2326
  const root = cutoverRoot(options.cwd);
2240
2327
  const held = bundledFiles(root);
2241
- const cleaned = held.length > 0 || (0, import_node_fs7.existsSync)(cutoverFile(root)) || (0, import_node_fs7.existsSync)(fileFor(root, import_core13.ROLLBACK_PATH));
2328
+ const cleaned = held.length > 0 || (0, import_node_fs8.existsSync)(cutoverFile(root)) || (0, import_node_fs8.existsSync)(fileFor(root, import_core13.ROLLBACK_PATH));
2242
2329
  removeBundle(root);
2243
2330
  return { root, removed: held, cleaned };
2244
2331
  }
2245
2332
  function removeBundle(root) {
2246
- (0, import_node_fs7.rmSync)(fileFor(root, import_core13.ROLLBACK_PATH), { recursive: true, force: true });
2247
- (0, import_node_fs7.rmSync)(cutoverFile(root), { force: true });
2333
+ (0, import_node_fs8.rmSync)(fileFor(root, import_core13.ROLLBACK_PATH), { recursive: true, force: true });
2334
+ (0, import_node_fs8.rmSync)(cutoverFile(root), { force: true });
2248
2335
  }
2249
2336
 
2250
2337
  // src/commands/cleanup.ts
@@ -2279,7 +2366,7 @@ var cleanupCommand = (0, import_citty5.defineCommand)({
2279
2366
  });
2280
2367
 
2281
2368
  // src/commands/doctor.ts
2282
- var import_node_fs8 = require("fs");
2369
+ var import_node_fs9 = require("fs");
2283
2370
  var import_node_path9 = require("path");
2284
2371
  var import_core16 = require("@penvhq/core");
2285
2372
  var import_citty7 = require("citty");
@@ -2725,6 +2812,7 @@ async function runDoctor(options) {
2725
2812
  findings.push(...unusedFindings(drift));
2726
2813
  }
2727
2814
  findings.push(...fallbackFindings(subjects, environment));
2815
+ findings.push(...secrecyFindings(subjects, environment));
2728
2816
  findings.push(...plaintextSecretFindings(subjects, environment));
2729
2817
  findings.push(...publicSecretFindings(subjects, environment, project.config));
2730
2818
  findings.push(...encryptionFindings(subjects, environment));
@@ -2737,6 +2825,7 @@ async function runDoctor(options) {
2737
2825
  }
2738
2826
  findings.push(...await providerDriftFindings(project, environment, options.source));
2739
2827
  findings.push(...await projectionFindings(project, environment, options.projection));
2828
+ findings.push(...localExtensionFindings(project));
2740
2829
  findings.push(...artifactFindings(project));
2741
2830
  findings.push({
2742
2831
  check: "provider",
@@ -2874,6 +2963,31 @@ function fallbackFindings(subjects, environment) {
2874
2963
  }
2875
2964
  ];
2876
2965
  }
2966
+ function secrecyFindings(subjects, environment) {
2967
+ const undeclared = subjects.filter(
2968
+ ({ meta }) => (0, import_core16.effectiveMeta)(meta, environment).secret === void 0
2969
+ );
2970
+ if (undeclared.length === 0) {
2971
+ return [
2972
+ {
2973
+ check: "secrecy-undeclared",
2974
+ severity: "pass",
2975
+ label: "Secrecy policy",
2976
+ subject: subjects.length === 0 ? `no parameter resolves for ${environment}` : `every parameter declares whether it is secret for ${environment}`
2977
+ }
2978
+ ];
2979
+ }
2980
+ return [
2981
+ {
2982
+ check: "secrecy-undeclared",
2983
+ severity: "unknown",
2984
+ label: "Secrecy policy",
2985
+ subject: `${undeclared.length} of ${subjects.length} declare neither way for ${environment}`,
2986
+ detail: "penv was never told which of these values must be encrypted",
2987
+ remedy: `declare \`"secret": true\` or \`"secret": false\` in a parameter's meta file, ${import_core16.RECORDS_PATH}/<name>.json`
2988
+ }
2989
+ ];
2990
+ }
2877
2991
  function plaintextSecretFindings(subjects, environment) {
2878
2992
  const secrets = subjects.filter(({ meta }) => (0, import_core16.isSecret)(meta, environment));
2879
2993
  const findings = [];
@@ -3177,7 +3291,7 @@ function looksLikeArtifact(text) {
3177
3291
  function scannableFiles(directory, out2) {
3178
3292
  let entries;
3179
3293
  try {
3180
- entries = (0, import_node_fs8.readdirSync)(directory, { withFileTypes: true });
3294
+ entries = (0, import_node_fs9.readdirSync)(directory, { withFileTypes: true });
3181
3295
  } catch {
3182
3296
  return;
3183
3297
  }
@@ -3194,6 +3308,29 @@ function scannableFiles(directory, out2) {
3194
3308
  }
3195
3309
  }
3196
3310
  }
3311
+ function localExtensionFindings(project) {
3312
+ const names = localExtensions(project.root);
3313
+ if (names.length === 0) {
3314
+ return [
3315
+ {
3316
+ check: "local-extension",
3317
+ severity: "pass",
3318
+ label: "Extensions",
3319
+ subject: "every extension this project names is pinned"
3320
+ }
3321
+ ];
3322
+ }
3323
+ return [
3324
+ {
3325
+ check: "local-extension",
3326
+ severity: "unknown",
3327
+ label: "Extensions",
3328
+ subject: names.join(", "),
3329
+ detail: "resolved from this project's node_modules \u2014 no release, no pinned bytes",
3330
+ remedy: `publish it and run \`penv add ${names[0]}\` when this stops being a development path`
3331
+ }
3332
+ ];
3333
+ }
3197
3334
  function artifactFindings(project) {
3198
3335
  const files = [];
3199
3336
  scannableFiles(project.root, files);
@@ -3201,10 +3338,10 @@ function artifactFindings(project) {
3201
3338
  for (const file of files) {
3202
3339
  let text;
3203
3340
  try {
3204
- if ((0, import_node_fs8.statSync)(file).size > ARTIFACT_SCAN_LIMIT) {
3341
+ if ((0, import_node_fs9.statSync)(file).size > ARTIFACT_SCAN_LIMIT) {
3205
3342
  continue;
3206
3343
  }
3207
- text = (0, import_node_fs8.readFileSync)(file, "utf8");
3344
+ text = (0, import_node_fs9.readFileSync)(file, "utf8");
3208
3345
  } catch {
3209
3346
  continue;
3210
3347
  }
@@ -3964,7 +4101,7 @@ var fillCommand = (0, import_citty10.defineCommand)({
3964
4101
  });
3965
4102
 
3966
4103
  // src/commands/generate.ts
3967
- var import_node_fs9 = require("fs");
4104
+ var import_node_fs10 = require("fs");
3968
4105
  var import_node_path10 = require("path");
3969
4106
  var import_core20 = require("@penvhq/core");
3970
4107
  var import_citty11 = require("citty");
@@ -4018,7 +4155,7 @@ function runGenerate(options) {
4018
4155
  const environment = targetEnvironment(project, options.environment, options.envFlags);
4019
4156
  const { entries, decrypted } = entriesFor(project, environment, options.allowDecrypt === true);
4020
4157
  const file = options.out === void 0 ? (0, import_node_path10.resolve)(project.root, DEFAULT_OUTPUT) : (0, import_node_path10.isAbsolute)(options.out) ? options.out : (0, import_node_path10.resolve)(options.cwd, options.out);
4021
- (0, import_node_fs9.writeFileSync)(file, (0, import_core20.serializeDotenv)(entries), "utf8");
4158
+ (0, import_node_fs10.writeFileSync)(file, (0, import_core20.serializeDotenv)(entries), "utf8");
4022
4159
  return { file, environment, entries: entries.length, decrypted };
4023
4160
  }
4024
4161
  function displayPath2(cwd, file) {
@@ -4156,8 +4293,8 @@ var getCommand = (0, import_citty12.defineCommand)({
4156
4293
  });
4157
4294
 
4158
4295
  // src/commands/import.ts
4159
- var import_node_fs12 = require("fs");
4160
- var import_node_path13 = require("path");
4296
+ var import_node_fs14 = require("fs");
4297
+ var import_node_path14 = require("path");
4161
4298
  var import_core26 = require("@penvhq/core");
4162
4299
  var import_citty14 = require("citty");
4163
4300
 
@@ -4227,7 +4364,7 @@ function writeEntries(tree, entries, refs, scope) {
4227
4364
  }
4228
4365
 
4229
4366
  // src/detect.ts
4230
- var import_node_fs10 = require("fs");
4367
+ var import_node_fs11 = require("fs");
4231
4368
  var import_node_path11 = require("path");
4232
4369
  var import_core23 = require("@penvhq/core");
4233
4370
  var SIGNATURES = [
@@ -4245,7 +4382,7 @@ var SIGNATURES = [
4245
4382
  function exportsSchema(file) {
4246
4383
  let source;
4247
4384
  try {
4248
- source = (0, import_node_fs10.readFileSync)(file, "utf8");
4385
+ source = (0, import_node_fs11.readFileSync)(file, "utf8");
4249
4386
  } catch {
4250
4387
  return false;
4251
4388
  }
@@ -4257,7 +4394,7 @@ function exportsSchema(file) {
4257
4394
  }
4258
4395
  function occupied(cwd, relative5) {
4259
4396
  const file = (0, import_node_path11.join)(cwd, ...relative5.split("/"));
4260
- return (0, import_node_fs10.existsSync)(file) && !exportsSchema(file);
4397
+ return (0, import_node_fs11.existsSync)(file) && !exportsSchema(file);
4261
4398
  }
4262
4399
  function schemaFileFor(cwd) {
4263
4400
  const dir = srcPrefix(cwd);
@@ -4272,7 +4409,7 @@ function schemaFileFor(cwd) {
4272
4409
  return { file: import_core23.DEFAULT_SCHEMA_FILE, displaced: preferred };
4273
4410
  }
4274
4411
  function srcPrefix(cwd) {
4275
- return (0, import_node_fs10.existsSync)((0, import_node_path11.join)(cwd, "src")) ? "src/" : "";
4412
+ return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(cwd, "src")) ? "src/" : "";
4276
4413
  }
4277
4414
  function dependenciesOf(cwd) {
4278
4415
  const manifest = manifestOf2(cwd);
@@ -4292,12 +4429,12 @@ function dependenciesOf(cwd) {
4292
4429
  }
4293
4430
  function manifestOf2(cwd) {
4294
4431
  const file = (0, import_node_path11.join)(cwd, "package.json");
4295
- if (!(0, import_node_fs10.existsSync)(file)) {
4432
+ if (!(0, import_node_fs11.existsSync)(file)) {
4296
4433
  return void 0;
4297
4434
  }
4298
4435
  let manifest;
4299
4436
  try {
4300
- manifest = JSON.parse((0, import_node_fs10.readFileSync)(file, "utf8"));
4437
+ manifest = JSON.parse((0, import_node_fs11.readFileSync)(file, "utf8"));
4301
4438
  } catch {
4302
4439
  return void 0;
4303
4440
  }
@@ -4313,7 +4450,7 @@ function detectFramework(cwd) {
4313
4450
  return detectedFrom(cwd, signature.name, signature.publicPrefixes);
4314
4451
  }
4315
4452
  }
4316
- if ((0, import_node_fs10.existsSync)((0, import_node_path11.join)(cwd, "bunfig.toml")) || dependencies.has("bun-types")) {
4453
+ if ((0, import_node_fs11.existsSync)((0, import_node_path11.join)(cwd, "bunfig.toml")) || dependencies.has("bun-types")) {
4317
4454
  return detectedFrom(cwd, "Bun", []);
4318
4455
  }
4319
4456
  return void 0;
@@ -4392,12 +4529,94 @@ function draftFieldsAcross(sources, environments) {
4392
4529
  }
4393
4530
 
4394
4531
  // src/commands/init.ts
4395
- var import_node_fs11 = require("fs");
4396
- var import_node_path12 = require("path");
4532
+ var import_node_fs13 = require("fs");
4533
+ var import_node_path13 = require("path");
4397
4534
  var import_promises = require("readline/promises");
4398
4535
  var import_core25 = require("@penvhq/core");
4399
4536
  var import_citty13 = require("citty");
4400
4537
 
4538
+ // src/scaffold-undo.ts
4539
+ var import_node_fs12 = require("fs");
4540
+ var import_node_path12 = require("path");
4541
+ function record(path, files, dirs) {
4542
+ let stats;
4543
+ try {
4544
+ stats = (0, import_node_fs12.statSync)(path);
4545
+ } catch {
4546
+ return;
4547
+ }
4548
+ if (stats.isDirectory()) {
4549
+ dirs.add(path);
4550
+ for (const entry of (0, import_node_fs12.readdirSync)(path)) {
4551
+ record((0, import_node_path12.join)(path, entry), files, dirs);
4552
+ }
4553
+ return;
4554
+ }
4555
+ if (stats.isFile()) {
4556
+ files.set(path, (0, import_node_fs12.readFileSync)(path));
4557
+ }
4558
+ }
4559
+ function captureScaffold(root, paths) {
4560
+ const files = /* @__PURE__ */ new Map();
4561
+ const dirs = /* @__PURE__ */ new Set();
4562
+ for (const path of paths) {
4563
+ for (let dir = (0, import_node_path12.dirname)(path); dir.startsWith(root) && dir !== root; dir = (0, import_node_path12.dirname)(dir)) {
4564
+ if ((0, import_node_fs12.existsSync)(dir)) {
4565
+ dirs.add(dir);
4566
+ }
4567
+ }
4568
+ record(path, files, dirs);
4569
+ }
4570
+ return { root, paths: [...paths], files, dirs };
4571
+ }
4572
+ function restoreScaffold(undo) {
4573
+ for (const path of undo.paths) {
4574
+ removeAdded(path, undo);
4575
+ }
4576
+ for (const path of undo.paths) {
4577
+ pruneAncestors(path, undo);
4578
+ }
4579
+ for (const [file, contents] of undo.files) {
4580
+ (0, import_node_fs12.mkdirSync)((0, import_node_path12.dirname)(file), { recursive: true });
4581
+ (0, import_node_fs12.writeFileSync)(file, contents);
4582
+ }
4583
+ }
4584
+ function removeAdded(path, undo) {
4585
+ let stats;
4586
+ try {
4587
+ stats = (0, import_node_fs12.statSync)(path);
4588
+ } catch {
4589
+ return;
4590
+ }
4591
+ if (stats.isFile()) {
4592
+ if (!undo.files.has(path)) {
4593
+ (0, import_node_fs12.unlinkSync)(path);
4594
+ }
4595
+ return;
4596
+ }
4597
+ if (!stats.isDirectory()) {
4598
+ return;
4599
+ }
4600
+ for (const entry of (0, import_node_fs12.readdirSync)(path)) {
4601
+ removeAdded((0, import_node_path12.join)(path, entry), undo);
4602
+ }
4603
+ if (!undo.dirs.has(path) && (0, import_node_fs12.readdirSync)(path).length === 0) {
4604
+ (0, import_node_fs12.rmdirSync)(path);
4605
+ }
4606
+ }
4607
+ function pruneAncestors(path, undo) {
4608
+ for (let dir = (0, import_node_path12.dirname)(path); dir.startsWith(undo.root) && dir !== undo.root; dir = (0, import_node_path12.dirname)(dir)) {
4609
+ if (undo.dirs.has(dir)) {
4610
+ return;
4611
+ }
4612
+ try {
4613
+ (0, import_node_fs12.rmdirSync)(dir);
4614
+ } catch {
4615
+ return;
4616
+ }
4617
+ }
4618
+ }
4619
+
4401
4620
  // src/seams.ts
4402
4621
  function nextjs({ alias, srcDir }) {
4403
4622
  return {
@@ -4553,7 +4772,7 @@ var NOT_ENVIRONMENTS2 = [...import_core25.RESERVED_TOKENS, "example", "sample",
4553
4772
  function suggestEnvironments(root) {
4554
4773
  let entries;
4555
4774
  try {
4556
- entries = (0, import_node_fs11.readdirSync)(root);
4775
+ entries = (0, import_node_fs13.readdirSync)(root);
4557
4776
  } catch {
4558
4777
  return [];
4559
4778
  }
@@ -4597,8 +4816,8 @@ function configOf(decisions) {
4597
4816
  return { environments: decisions.environments, providers: {}, schemaFile: decisions.schemaFile };
4598
4817
  }
4599
4818
  function declaredIn(root) {
4600
- const file = (0, import_node_path12.join)(root, CONFIG_FILE);
4601
- if (!(0, import_node_fs11.existsSync)(file)) {
4819
+ const file = (0, import_node_path13.join)(root, CONFIG_FILE);
4820
+ if (!(0, import_node_fs13.existsSync)(file)) {
4602
4821
  return void 0;
4603
4822
  }
4604
4823
  return (0, import_core25.loadConfigFrom)(file);
@@ -5040,16 +5259,16 @@ function renderTsconfig(target, alias) {
5040
5259
  `;
5041
5260
  }
5042
5261
  function ensurePenvDir(root) {
5043
- const dir = (0, import_node_path12.resolve)(root, import_core25.PENV_DIR);
5044
- if ((0, import_node_fs11.existsSync)(dir)) {
5262
+ const dir = (0, import_node_path13.resolve)(root, import_core25.PENV_DIR);
5263
+ if ((0, import_node_fs13.existsSync)(dir)) {
5045
5264
  return { target: "penv-dir", action: "kept", text: `Found ${import_core25.PENV_DIR}/` };
5046
5265
  }
5047
- (0, import_node_fs11.mkdirSync)(dir, { recursive: true });
5266
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
5048
5267
  return { target: "penv-dir", action: "created", text: `Created ${import_core25.PENV_DIR}/` };
5049
5268
  }
5050
5269
  function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS) {
5051
- const file = (0, import_node_path12.join)(root, import_core25.SCHEMA_SHAPE_FILE);
5052
- if ((0, import_node_fs11.existsSync)(file)) {
5270
+ const file = (0, import_node_path13.join)(root, import_core25.SCHEMA_SHAPE_FILE);
5271
+ if ((0, import_node_fs13.existsSync)(file)) {
5053
5272
  return {
5054
5273
  target: "schema",
5055
5274
  action: "kept",
@@ -5066,7 +5285,7 @@ function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS
5066
5285
  note: `(the shape lives there, from before the ${import_core25.SCHEMA_SHAPE_FILE} split \u2014 penv did not add a second one. To adopt the split: move the \`z.object\` (and any \`declare module\` block) into ${import_core25.SCHEMA_SHAPE_FILE}, leaving ${oldLayout} importing \`schema\` from it and calling \`load\`.)`
5067
5286
  };
5068
5287
  }
5069
- (0, import_node_fs11.writeFileSync)(file, renderSchemaShapeModule(fields, draft), "utf8");
5288
+ (0, import_node_fs13.writeFileSync)(file, renderSchemaShapeModule(fields, draft), "utf8");
5070
5289
  return {
5071
5290
  target: "schema",
5072
5291
  action: "created",
@@ -5075,8 +5294,8 @@ function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS
5075
5294
  };
5076
5295
  }
5077
5296
  function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
5078
- const file = (0, import_node_path12.join)(root, ...decisions.schemaFile.split("/"));
5079
- if ((0, import_node_fs11.existsSync)(file)) {
5297
+ const file = (0, import_node_path13.join)(root, ...decisions.schemaFile.split("/"));
5298
+ if ((0, import_node_fs13.existsSync)(file)) {
5080
5299
  return {
5081
5300
  target: "env",
5082
5301
  action: "kept",
@@ -5084,8 +5303,8 @@ function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
5084
5303
  note: "(yours \u2014 penv never regenerates it)"
5085
5304
  };
5086
5305
  }
5087
- (0, import_node_fs11.mkdirSync)((0, import_node_path12.dirname)(file), { recursive: true });
5088
- (0, import_node_fs11.writeFileSync)(file, renderEnvModule(decisions.schemaFile, decisions.inject), "utf8");
5306
+ (0, import_node_fs13.mkdirSync)((0, import_node_path13.dirname)(file), { recursive: true });
5307
+ (0, import_node_fs13.writeFileSync)(file, renderEnvModule(decisions.schemaFile, decisions.inject), "utf8");
5089
5308
  return {
5090
5309
  target: "env",
5091
5310
  action: "created",
@@ -5094,19 +5313,19 @@ function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
5094
5313
  };
5095
5314
  }
5096
5315
  function writeConfigFile(root, decisions = DEFAULT_DECISIONS) {
5097
- const file = (0, import_node_path12.join)(root, CONFIG_FILE);
5098
- if ((0, import_node_fs11.existsSync)(file)) {
5316
+ const file = (0, import_node_path13.join)(root, CONFIG_FILE);
5317
+ if ((0, import_node_fs13.existsSync)(file)) {
5099
5318
  return { target: "config", action: "kept", text: `Kept ${CONFIG_FILE}` };
5100
5319
  }
5101
- (0, import_node_fs11.writeFileSync)(file, renderConfigModule(decisions), "utf8");
5320
+ (0, import_node_fs13.writeFileSync)(file, renderConfigModule(decisions), "utf8");
5102
5321
  return { target: "config", action: "created", text: `Generated ${CONFIG_FILE}` };
5103
5322
  }
5104
5323
  function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
5105
5324
  const alias = decisions.alias;
5106
5325
  const imports = alias.startsWith(IMPORTS_PREFIX);
5107
- const file = (0, import_node_path12.join)(root, imports ? PACKAGE_FILE : TSCONFIG_FILE);
5326
+ const file = (0, import_node_path13.join)(root, imports ? PACKAGE_FILE : TSCONFIG_FILE);
5108
5327
  const where = imports ? PACKAGE_FILE : TSCONFIG_FILE;
5109
- if (!(0, import_node_fs11.existsSync)(file)) {
5328
+ if (!(0, import_node_fs13.existsSync)(file)) {
5110
5329
  if (imports) {
5111
5330
  return {
5112
5331
  target: "tsconfig",
@@ -5115,14 +5334,14 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
5115
5334
  note: `(run \`npm init\` first, or use \`--alias @env\` to alias through ${TSCONFIG_FILE})`
5116
5335
  };
5117
5336
  }
5118
- (0, import_node_fs11.writeFileSync)(file, renderTsconfig(decisions.schemaFile, alias), "utf8");
5337
+ (0, import_node_fs13.writeFileSync)(file, renderTsconfig(decisions.schemaFile, alias), "utf8");
5119
5338
  return {
5120
5339
  target: "tsconfig",
5121
5340
  action: "created",
5122
5341
  text: `Created ${TSCONFIG_FILE} with the ${alias} path alias`
5123
5342
  };
5124
5343
  }
5125
- const source = (0, import_node_fs11.readFileSync)(file, "utf8");
5344
+ const source = (0, import_node_fs13.readFileSync)(file, "utf8");
5126
5345
  const edit = imports ? insertImportsAlias(source, decisions.schemaFile, alias) : insertEnvAlias(source, decisions.schemaFile, alias);
5127
5346
  if (edit.conflict !== void 0) {
5128
5347
  return {
@@ -5139,7 +5358,7 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
5139
5358
  text: `Kept the ${alias} alias in ${where}`
5140
5359
  };
5141
5360
  }
5142
- (0, import_node_fs11.writeFileSync)(file, edit.source, "utf8");
5361
+ (0, import_node_fs13.writeFileSync)(file, edit.source, "utf8");
5143
5362
  return {
5144
5363
  target: "tsconfig",
5145
5364
  action: "updated",
@@ -5147,14 +5366,14 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
5147
5366
  };
5148
5367
  }
5149
5368
  function writeGitignore(root, decisions = DEFAULT_DECISIONS) {
5150
- const file = (0, import_node_path12.join)(root, ...import_core25.STATE_GITIGNORE_PATH.split("/"));
5369
+ const file = (0, import_node_path13.join)(root, ...import_core25.STATE_GITIGNORE_PATH.split("/"));
5151
5370
  const wanted = (0, import_core25.renderStateGitignore)(configOf(decisions));
5152
- const existing = (0, import_node_fs11.existsSync)(file) ? (0, import_node_fs11.readFileSync)(file, "utf8") : void 0;
5371
+ const existing = (0, import_node_fs13.existsSync)(file) ? (0, import_node_fs13.readFileSync)(file, "utf8") : void 0;
5153
5372
  if (existing === wanted) {
5154
5373
  return { target: "gitignore", action: "kept", text: `Kept ${import_core25.STATE_GITIGNORE_PATH}` };
5155
5374
  }
5156
- (0, import_node_fs11.mkdirSync)((0, import_node_path12.dirname)(file), { recursive: true });
5157
- (0, import_node_fs11.writeFileSync)(file, wanted, "utf8");
5375
+ (0, import_node_fs13.mkdirSync)((0, import_node_path13.dirname)(file), { recursive: true });
5376
+ (0, import_node_fs13.writeFileSync)(file, wanted, "utf8");
5158
5377
  return {
5159
5378
  target: "gitignore",
5160
5379
  action: existing === void 0 ? "created" : "updated",
@@ -5170,12 +5389,12 @@ function outdatedRuntimeWarning(root) {
5170
5389
  return `Injection needs @penvhq/penv ${INJECT_MIN_VERSION}+ \u2014 this project has ${version}, whose \`load\` ignores \`{ inject: true }\`. Upgrade, or process.env stays empty.`;
5171
5390
  }
5172
5391
  function installedPenvVersion(root) {
5173
- const file = (0, import_node_path12.join)(root, "node_modules", "@penvhq", "penv", "package.json");
5174
- if (!(0, import_node_fs11.existsSync)(file)) {
5392
+ const file = (0, import_node_path13.join)(root, "node_modules", "@penvhq", "penv", "package.json");
5393
+ if (!(0, import_node_fs13.existsSync)(file)) {
5175
5394
  return void 0;
5176
5395
  }
5177
5396
  try {
5178
- const version = JSON.parse((0, import_node_fs11.readFileSync)(file, "utf8")).version;
5397
+ const version = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8")).version;
5179
5398
  return typeof version === "string" ? version : void 0;
5180
5399
  } catch {
5181
5400
  return void 0;
@@ -5215,11 +5434,11 @@ function writeSeam(root, decisions = DEFAULT_DECISIONS, framework = detectFramew
5215
5434
  };
5216
5435
  }
5217
5436
  const alsoNote = writeAlso(root, seam.also);
5218
- const file = (0, import_node_path12.join)(root, ...seam.file.split("/"));
5437
+ const file = (0, import_node_path13.join)(root, ...seam.file.split("/"));
5219
5438
  const baseNotes = [...seam.notes, ...alsoNote === void 0 ? [] : [alsoNote]];
5220
5439
  const notes = baseNotes.length === 0 ? "" : `
5221
5440
  ${baseNotes.map((n) => ` ${n}`).join("\n")}`;
5222
- if ((0, import_node_fs11.existsSync)(file)) {
5441
+ if ((0, import_node_fs13.existsSync)(file)) {
5223
5442
  return {
5224
5443
  target: "seam",
5225
5444
  action: "info",
@@ -5227,8 +5446,8 @@ ${baseNotes.map((n) => ` ${n}`).join("\n")}`;
5227
5446
  note: withWarning(`${seam.ifPresent}${notes}`, outdated)
5228
5447
  };
5229
5448
  }
5230
- (0, import_node_fs11.mkdirSync)((0, import_node_path12.dirname)(file), { recursive: true });
5231
- (0, import_node_fs11.writeFileSync)(file, seam.content, "utf8");
5449
+ (0, import_node_fs13.mkdirSync)((0, import_node_path13.dirname)(file), { recursive: true });
5450
+ (0, import_node_fs13.writeFileSync)(file, seam.content, "utf8");
5232
5451
  return {
5233
5452
  target: "seam",
5234
5453
  action: outdated === void 0 ? "created" : "info",
@@ -5240,12 +5459,12 @@ function writeAlso(root, also) {
5240
5459
  if (also === void 0) {
5241
5460
  return void 0;
5242
5461
  }
5243
- const file = (0, import_node_path12.join)(root, ...also.file.split("/"));
5244
- if ((0, import_node_fs11.existsSync)(file)) {
5462
+ const file = (0, import_node_path13.join)(root, ...also.file.split("/"));
5463
+ if ((0, import_node_fs13.existsSync)(file)) {
5245
5464
  return also.ifPresent;
5246
5465
  }
5247
- (0, import_node_fs11.mkdirSync)((0, import_node_path12.dirname)(file), { recursive: true });
5248
- (0, import_node_fs11.writeFileSync)(file, also.content, "utf8");
5466
+ (0, import_node_fs13.mkdirSync)((0, import_node_path13.dirname)(file), { recursive: true });
5467
+ (0, import_node_fs13.writeFileSync)(file, also.content, "utf8");
5249
5468
  return `Wrote ${also.file} to register it.`;
5250
5469
  }
5251
5470
  function withWarning(note, warning) {
@@ -5270,6 +5489,23 @@ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS, framework
5270
5489
  const seam = writeSeam(root, decisions, framework);
5271
5490
  return seam === void 0 ? steps : [...steps, seam];
5272
5491
  }
5492
+ function scaffoldPaths(root, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
5493
+ const fileAt = (relative5) => (0, import_node_path13.join)(root, ...relative5.split("/"));
5494
+ const seam = decisions.inject ? seamFor(framework, {
5495
+ alias: decisions.alias,
5496
+ srcDir: srcPrefix(root),
5497
+ schemaFile: decisions.schemaFile
5498
+ }) : void 0;
5499
+ return [
5500
+ fileAt(import_core25.PENV_DIR),
5501
+ fileAt(import_core25.SCHEMA_SHAPE_FILE),
5502
+ fileAt(decisions.schemaFile),
5503
+ fileAt(CONFIG_FILE),
5504
+ fileAt(TSCONFIG_FILE),
5505
+ fileAt(PACKAGE_FILE),
5506
+ ...seam?.kind === "scaffold" ? [fileAt(seam.file), ...seam.also === void 0 ? [] : [fileAt(seam.also.file)]] : []
5507
+ ];
5508
+ }
5273
5509
  var DEVELOPMENT = "development";
5274
5510
  var LOCAL_PROVIDER = "@penvhq/provider-filesystem";
5275
5511
  function planAdoption(root) {
@@ -5332,7 +5568,7 @@ function planCutover(input) {
5332
5568
  const diagnostics = [];
5333
5569
  let variables = 0;
5334
5570
  for (const file of selected) {
5335
- const parsed = (0, import_core25.parseDotenv)((0, import_node_fs11.readFileSync)((0, import_node_path12.join)(root, file.name), "utf8"));
5571
+ const parsed = (0, import_core25.parseDotenv)((0, import_node_fs13.readFileSync)((0, import_node_path13.join)(root, file.name), "utf8"));
5336
5572
  const fileRefs = refsForEntries(parsed.entries, file.name, config);
5337
5573
  adopted.push({ file, entries: parsed.entries, refs: fileRefs, scope: scopeOf(file) });
5338
5574
  refs.push(...fileRefs);
@@ -5443,17 +5679,27 @@ async function applyCutover(plan2, options = {}) {
5443
5679
  if (!plan2.install.satisfied) {
5444
5680
  await (options.install ?? installWithPackageManager)(plan2.install);
5445
5681
  }
5446
- const steps = scaffold(plan2.root, plan2.fields, true, plan2.decisions, plan2.framework);
5447
- const project = openProject(plan2.root);
5448
- const tree = localTree(project);
5449
- for (const adopted of plan2.adopted) {
5450
- writeEntries(tree, adopted.entries, adopted.refs, adopted.scope);
5451
- }
5452
- for (const environment of plan2.adopting) {
5453
- const check = await checkEnvironment(project, environment);
5454
- if (!check.result.ok) {
5455
- throw invalidAfterImport(check.result);
5682
+ const undo = captureScaffold(plan2.root, scaffoldPaths(plan2.root, plan2.decisions, plan2.framework));
5683
+ let steps;
5684
+ try {
5685
+ steps = scaffold(plan2.root, plan2.fields, true, plan2.decisions, plan2.framework);
5686
+ const project = openProject(plan2.root);
5687
+ const tree = localTree(project);
5688
+ for (const adopted of plan2.adopted) {
5689
+ writeEntries(tree, adopted.entries, adopted.refs, adopted.scope);
5690
+ }
5691
+ for (const environment of plan2.adopting) {
5692
+ const check = await checkEnvironment(project, environment);
5693
+ if (check.schema === void 0) {
5694
+ throw draftNotLoaded(check.result);
5695
+ }
5696
+ if (!check.result.ok) {
5697
+ throw invalidAfterImport(check.result);
5698
+ }
5456
5699
  }
5700
+ } catch (error) {
5701
+ restoreScaffold(undo);
5702
+ throw error;
5457
5703
  }
5458
5704
  const cutover = bundleDotenvFiles(
5459
5705
  plan2.root,
@@ -5462,13 +5708,24 @@ async function applyCutover(plan2, options = {}) {
5462
5708
  );
5463
5709
  return { plan: plan2, steps, moved: cutover.files, validated: plan2.adopting };
5464
5710
  }
5711
+ function issueLines(result2) {
5712
+ return result2.issues.map((issue) => ` ${issue.subject}: ${issue.message}`).join("\n");
5713
+ }
5714
+ var SCAFFOLD_ROLLED_BACK = "penv put the project back as it found it \u2014 your dotenv files and everything else are exactly where they were.";
5465
5715
  function invalidAfterImport(result2) {
5466
- const lines = result2.issues.map((issue) => ` ${issue.subject}: ${issue.message}`).join("\n");
5467
5716
  return new import_core25.PenvError(
5468
5717
  "INIT_CUTOVER_INVALID",
5469
- `The imported values do not satisfy the draft schema for ${result2.environment}, so your dotenv files were left where they are:
5470
- ${lines}`,
5471
- `Correct ${import_core25.SCHEMA_SHAPE_FILE} or the values above, then run \`penv init\` again.`
5718
+ `The imported values do not satisfy the draft schema for ${result2.environment}:
5719
+ ${issueLines(result2)}`,
5720
+ `Correct the values above, then run \`penv init\` again. ${SCAFFOLD_ROLLED_BACK}`
5721
+ );
5722
+ }
5723
+ function draftNotLoaded(result2) {
5724
+ return new import_core25.PenvError(
5725
+ "INIT_DRAFT_NOT_LOADED",
5726
+ `penv could not load the schema it drafted for ${result2.environment}, so nothing was checked against it:
5727
+ ${issueLines(result2)}`,
5728
+ `Fix the error above, then run \`penv init\` again. ${SCAFFOLD_ROLLED_BACK}`
5472
5729
  );
5473
5730
  }
5474
5731
  function renderSelection(plan2, selected = plan2.preselected) {
@@ -5531,7 +5788,7 @@ function renderCutover(result2) {
5531
5788
  const glyph = step.action === "conflicted" ? WARN : step.action === "info" ? "\u2192" : CHECK;
5532
5789
  return step.note === void 0 ? { glyph, text: step.text } : { glyph, text: step.text, note: step.note };
5533
5790
  }),
5534
- ...plan2.install.satisfied ? [] : [{ glyph: CHECK, text: `Installed ${plan2.install.package}`, note: plan2.install.version }],
5791
+ ...plan2.install.packages.filter((entry) => !entry.satisfied).map((entry) => ({ glyph: CHECK, text: `Installed ${entry.name}`, note: entry.version })),
5535
5792
  {
5536
5793
  glyph: CHECK,
5537
5794
  text: `Imported ${plan2.fields.length} parameters`,
@@ -5555,12 +5812,12 @@ function dailyCommand(root) {
5555
5812
  return `penv run -- ${detectPackageManager(root)} ${devScript(root)}`;
5556
5813
  }
5557
5814
  function devScript(root) {
5558
- const file = (0, import_node_path12.join)(root, PACKAGE_FILE);
5559
- if (!(0, import_node_fs11.existsSync)(file)) {
5815
+ const file = (0, import_node_path13.join)(root, PACKAGE_FILE);
5816
+ if (!(0, import_node_fs13.existsSync)(file)) {
5560
5817
  return "dev";
5561
5818
  }
5562
5819
  try {
5563
- const scripts = JSON.parse((0, import_node_fs11.readFileSync)(file, "utf8")).scripts;
5820
+ const scripts = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8")).scripts;
5564
5821
  if (scripts !== null && typeof scripts === "object" && !Array.isArray(scripts)) {
5565
5822
  const named = Object.keys(scripts);
5566
5823
  return ["dev", "start"].find((script) => named.includes(script)) ?? named[0] ?? "dev";
@@ -5570,7 +5827,7 @@ function devScript(root) {
5570
5827
  return "dev";
5571
5828
  }
5572
5829
  function runInit(options) {
5573
- const root = (0, import_node_path12.resolve)(options.cwd);
5830
+ const root = (0, import_node_path13.resolve)(options.cwd);
5574
5831
  const decisions = options.decisions ?? planInit(root).decisions;
5575
5832
  return { root, decisions, steps: scaffold(root, [], false, decisions, options.framework) };
5576
5833
  }
@@ -5733,7 +5990,7 @@ var initCommand = (0, import_citty13.defineCommand)({
5733
5990
  },
5734
5991
  run({ args }) {
5735
5992
  return guard(async () => {
5736
- const root = (0, import_node_path12.resolve)(process.cwd());
5993
+ const root = (0, import_node_path13.resolve)(process.cwd());
5737
5994
  if (args.action !== void 0) {
5738
5995
  runUndoAction(root, String(args.action));
5739
5996
  return;
@@ -5809,7 +6066,7 @@ function assertDeclared(segment, config) {
5809
6066
  throw new import_core26.UnknownEnvironmentError(segment, config.environments);
5810
6067
  }
5811
6068
  function scopeFromFilename(file, config) {
5812
- const name = (0, import_node_path13.basename)(file);
6069
+ const name = (0, import_node_path14.basename)(file);
5813
6070
  const segments = name.split(".");
5814
6071
  const start = segments.indexOf(DOTENV_SEGMENT);
5815
6072
  if (start === -1) {
@@ -5893,7 +6150,7 @@ function environmentNamed(file, explicit) {
5893
6150
  if (explicit !== void 0 && explicit.trim().length > 0) {
5894
6151
  return explicit.trim();
5895
6152
  }
5896
- const segments = (0, import_node_path13.basename)(file).split(".");
6153
+ const segments = (0, import_node_path14.basename)(file).split(".");
5897
6154
  const start = segments.indexOf(DOTENV_SEGMENT);
5898
6155
  if (start === -1) {
5899
6156
  return void 0;
@@ -5927,16 +6184,16 @@ function configInEffect(cwd, environment) {
5927
6184
  return { config: openProject(cwd).config, decisions };
5928
6185
  }
5929
6186
  function importDotenv(options) {
5930
- const cwd = (0, import_node_path13.resolve)(options.cwd);
5931
- const file = (0, import_node_path13.isAbsolute)(options.file) ? options.file : (0, import_node_path13.resolve)(cwd, options.file);
5932
- if (!(0, import_node_fs12.existsSync)(file)) {
6187
+ const cwd = (0, import_node_path14.resolve)(options.cwd);
6188
+ const file = (0, import_node_path14.isAbsolute)(options.file) ? options.file : (0, import_node_path14.resolve)(cwd, options.file);
6189
+ if (!(0, import_node_fs14.existsSync)(file)) {
5933
6190
  throw new import_core26.PenvError(
5934
6191
  "IMPORT_FILE_MISSING",
5935
6192
  `There is no file at ${file} to import`,
5936
6193
  "Point `penv import` at an existing dotenv file, e.g. `penv import .env`."
5937
6194
  );
5938
6195
  }
5939
- const parsed = (0, import_core26.parseDotenv)((0, import_node_fs12.readFileSync)(file, "utf8"));
6196
+ const parsed = (0, import_core26.parseDotenv)((0, import_node_fs14.readFileSync)(file, "utf8"));
5940
6197
  const { config, decisions } = configInEffect(cwd, environmentNamed(file, options.environment));
5941
6198
  const source = displayPath3(cwd, file);
5942
6199
  const named = scopeFromFilename(file, config);
@@ -5952,7 +6209,7 @@ function importDotenv(options) {
5952
6209
  const tree = localTree(project);
5953
6210
  writeEntries(tree, parsed.entries, refs, scope);
5954
6211
  const backup = `${file}${BACKUP_SUFFIX}`;
5955
- (0, import_node_fs12.copyFileSync)(file, backup);
6212
+ (0, import_node_fs14.copyFileSync)(file, backup);
5956
6213
  return {
5957
6214
  root: project.root,
5958
6215
  file,
@@ -5966,7 +6223,7 @@ function importDotenv(options) {
5966
6223
  };
5967
6224
  }
5968
6225
  function displayPath3(root, file) {
5969
- const rel = (0, import_node_path13.relative)(root, file);
6226
+ const rel = (0, import_node_path14.relative)(root, file);
5970
6227
  return rel === "" || rel.startsWith("..") ? file : rel.split("\\").join("/");
5971
6228
  }
5972
6229
  function keptSchemaStep(step, variables) {
@@ -6287,30 +6544,30 @@ var listCommand = (0, import_citty16.defineCommand)({
6287
6544
  });
6288
6545
 
6289
6546
  // src/commands/migrate.ts
6290
- var import_node_fs13 = require("fs");
6291
- var import_node_path14 = require("path");
6547
+ var import_node_fs15 = require("fs");
6548
+ var import_node_path15 = require("path");
6292
6549
  var import_promises2 = require("readline/promises");
6293
6550
  var import_core30 = require("@penvhq/core");
6294
6551
  var import_citty17 = require("citty");
6295
6552
  var OLD_GITIGNORE = `${import_core30.PENV_DIR}/.gitignore`;
6296
6553
  function planMigrate(cwd) {
6297
6554
  const { config, file } = (0, import_core30.loadConfig)(cwd);
6298
- const root = (0, import_node_path14.dirname)(file);
6555
+ const root = (0, import_node_path15.dirname)(file);
6299
6556
  const entries = (0, import_core30.oldLayoutEntries)(root, config);
6300
6557
  const tree = (0, import_core30.recordsDir)(root);
6301
6558
  const collisions = collidingEntries(entries, tree);
6302
6559
  if (collisions.length > 0) {
6303
6560
  throw new import_core30.PenvError(
6304
6561
  "HALF_MIGRATED",
6305
- `${describe(collisions)} in both \`${import_core30.PENV_DIR}/\` and \`${import_core30.RECORDS_PATH}/\`, and penv cannot tell which copy is current`,
6562
+ `${describe2(collisions)} in both \`${import_core30.PENV_DIR}/\` and \`${import_core30.RECORDS_PATH}/\`, and penv cannot tell which copy is current`,
6306
6563
  `Move what is left under \`${import_core30.PENV_DIR}/\` into \`${import_core30.RECORDS_PATH}/\` yourself, keeping the copy you want, then run \`penv validate\`.`
6307
6564
  );
6308
6565
  }
6309
6566
  const creates = [];
6310
- if (entries.length > 0 && !(0, import_node_fs13.existsSync)(tree)) {
6567
+ if (entries.length > 0 && !(0, import_node_fs15.existsSync)(tree)) {
6311
6568
  creates.push(`${import_core30.RECORDS_PATH}/`);
6312
6569
  }
6313
- if (readIfPresent((0, import_node_path14.join)(root, ...import_core30.STATE_GITIGNORE_PATH.split("/"))) !== (0, import_core30.renderStateGitignore)(config)) {
6570
+ if (readIfPresent((0, import_node_path15.join)(root, ...import_core30.STATE_GITIGNORE_PATH.split("/"))) !== (0, import_core30.renderStateGitignore)(config)) {
6314
6571
  creates.push(import_core30.STATE_GITIGNORE_PATH);
6315
6572
  }
6316
6573
  return {
@@ -6320,23 +6577,23 @@ function planMigrate(cwd) {
6320
6577
  to: `${import_core30.RECORDS_PATH}/${entry}`
6321
6578
  })),
6322
6579
  creates,
6323
- removes: (0, import_node_fs13.existsSync)((0, import_node_path14.join)(root, ...OLD_GITIGNORE.split("/"))) ? [OLD_GITIGNORE] : []
6580
+ removes: (0, import_node_fs15.existsSync)((0, import_node_path15.join)(root, ...OLD_GITIGNORE.split("/"))) ? [OLD_GITIGNORE] : []
6324
6581
  };
6325
6582
  }
6326
6583
  function readIfPresent(file) {
6327
- return (0, import_node_fs13.existsSync)(file) ? (0, import_node_fs13.readFileSync)(file, "utf8") : void 0;
6584
+ return (0, import_node_fs15.existsSync)(file) ? (0, import_node_fs15.readFileSync)(file, "utf8") : void 0;
6328
6585
  }
6329
6586
  function collidingEntries(entries, tree) {
6330
6587
  let held;
6331
6588
  try {
6332
- held = (0, import_node_fs13.readdirSync)(tree);
6589
+ held = (0, import_node_fs15.readdirSync)(tree);
6333
6590
  } catch {
6334
6591
  return [];
6335
6592
  }
6336
6593
  const taken = new Set(held.map((name) => name.toLowerCase()));
6337
6594
  return entries.filter((entry) => taken.has(entry.toLowerCase())).sort();
6338
6595
  }
6339
- function describe(names) {
6596
+ function describe2(names) {
6340
6597
  return names.length === 1 ? `\`${names[0]}\` is` : `${names.map((name) => `\`${name}\``).join(", ")} are`;
6341
6598
  }
6342
6599
  function isNoop(plan2) {
@@ -6348,18 +6605,18 @@ function applyMigrate(plan2) {
6348
6605
  }
6349
6606
  const { config } = (0, import_core30.loadConfig)(plan2.root);
6350
6607
  if (plan2.moves.length > 0) {
6351
- (0, import_node_fs13.mkdirSync)((0, import_core30.recordsDir)(plan2.root), { recursive: true });
6608
+ (0, import_node_fs15.mkdirSync)((0, import_core30.recordsDir)(plan2.root), { recursive: true });
6352
6609
  for (const move of plan2.moves) {
6353
- (0, import_node_fs13.renameSync)((0, import_node_path14.join)(plan2.root, ...move.from.split("/")), (0, import_node_path14.join)(plan2.root, ...move.to.split("/")));
6610
+ (0, import_node_fs15.renameSync)((0, import_node_path15.join)(plan2.root, ...move.from.split("/")), (0, import_node_path15.join)(plan2.root, ...move.to.split("/")));
6354
6611
  }
6355
6612
  }
6356
6613
  if (plan2.creates.includes(import_core30.STATE_GITIGNORE_PATH)) {
6357
- const ignore = (0, import_node_path14.join)(plan2.root, ...import_core30.STATE_GITIGNORE_PATH.split("/"));
6358
- (0, import_node_fs13.mkdirSync)((0, import_node_path14.dirname)(ignore), { recursive: true });
6359
- (0, import_node_fs13.writeFileSync)(ignore, (0, import_core30.renderStateGitignore)(config), "utf8");
6614
+ const ignore = (0, import_node_path15.join)(plan2.root, ...import_core30.STATE_GITIGNORE_PATH.split("/"));
6615
+ (0, import_node_fs15.mkdirSync)((0, import_node_path15.dirname)(ignore), { recursive: true });
6616
+ (0, import_node_fs15.writeFileSync)(ignore, (0, import_core30.renderStateGitignore)(config), "utf8");
6360
6617
  }
6361
6618
  for (const removed of plan2.removes) {
6362
- (0, import_node_fs13.rmSync)((0, import_node_path14.join)(plan2.root, ...removed.split("/")), { force: true });
6619
+ (0, import_node_fs15.rmSync)((0, import_node_path15.join)(plan2.root, ...removed.split("/")), { force: true });
6363
6620
  }
6364
6621
  return { ...plan2, status: "migrated" };
6365
6622
  }
@@ -6845,14 +7102,14 @@ var rotateCommand = (0, import_citty20.defineCommand)({
6845
7102
  });
6846
7103
 
6847
7104
  // src/commands/watch.ts
6848
- var import_node_fs14 = require("fs");
6849
- var import_node_path15 = require("path");
7105
+ var import_node_fs16 = require("fs");
7106
+ var import_node_path16 = require("path");
6850
7107
  var import_core34 = require("@penvhq/core");
6851
7108
  var import_citty21 = require("citty");
6852
7109
  var DEBOUNCE_MS2 = 100;
6853
7110
  function runWatch(options) {
6854
7111
  const project = openProject(options.cwd);
6855
- const configFile = (0, import_node_path15.basename)(project.configFile);
7112
+ const configFile = (0, import_node_path16.basename)(project.configFile);
6856
7113
  const debounceMs = options.debounceMs ?? DEBOUNCE_MS2;
6857
7114
  const watchers = /* @__PURE__ */ new Set();
6858
7115
  let timer;
@@ -6905,22 +7162,22 @@ function runWatch(options) {
6905
7162
  watcher.close();
6906
7163
  }
6907
7164
  function armRecovery(target, recursive, only) {
6908
- const parent = (0, import_node_path15.dirname)(target);
6909
- const name = (0, import_node_path15.basename)(target);
7165
+ const parent = (0, import_node_path16.dirname)(target);
7166
+ const name = (0, import_node_path16.basename)(target);
6910
7167
  let recovery;
6911
7168
  try {
6912
- recovery = (0, import_node_fs14.watch)(parent, { recursive: false }, (_event, filename) => {
7169
+ recovery = (0, import_node_fs16.watch)(parent, { recursive: false }, (_event, filename) => {
6913
7170
  if (closed || recovery === void 0) {
6914
7171
  return;
6915
7172
  }
6916
- if (!(0, import_node_fs14.existsSync)(parent)) {
7173
+ if (!(0, import_node_fs16.existsSync)(parent)) {
6917
7174
  stop2(recovery);
6918
7175
  return;
6919
7176
  }
6920
- if (filename !== null && (0, import_node_path15.basename)(filename) !== name) {
7177
+ if (filename !== null && (0, import_node_path16.basename)(filename) !== name) {
6921
7178
  return;
6922
7179
  }
6923
- if (!(0, import_node_fs14.existsSync)(target)) {
7180
+ if (!(0, import_node_fs16.existsSync)(target)) {
6924
7181
  return;
6925
7182
  }
6926
7183
  stop2(recovery);
@@ -6940,11 +7197,11 @@ function runWatch(options) {
6940
7197
  }
6941
7198
  function addWatcher(target, recursive, only) {
6942
7199
  let watcher;
6943
- const listen = (useRecursive) => (0, import_node_fs14.watch)(target, { recursive: useRecursive }, (_event, filename) => {
7200
+ const listen = (useRecursive) => (0, import_node_fs16.watch)(target, { recursive: useRecursive }, (_event, filename) => {
6944
7201
  if (closed) {
6945
7202
  return;
6946
7203
  }
6947
- if (!(0, import_node_fs14.existsSync)(target)) {
7204
+ if (!(0, import_node_fs16.existsSync)(target)) {
6948
7205
  if (watcher !== void 0) {
6949
7206
  stop2(watcher);
6950
7207
  }
@@ -6952,7 +7209,7 @@ function runWatch(options) {
6952
7209
  schedule();
6953
7210
  return;
6954
7211
  }
6955
- if (only !== void 0 && (filename === null || (0, import_node_path15.basename)(filename) !== only)) {
7212
+ if (only !== void 0 && (filename === null || (0, import_node_path16.basename)(filename) !== only)) {
6956
7213
  return;
6957
7214
  }
6958
7215
  schedule();
@@ -6979,11 +7236,11 @@ function runWatch(options) {
6979
7236
  watchers.add(watcher);
6980
7237
  }
6981
7238
  addWatcher(project.recordsDir, true);
6982
- addWatcher((0, import_node_path15.dirname)(project.configFile), false, configFile);
7239
+ addWatcher((0, import_node_path16.dirname)(project.configFile), false, configFile);
6983
7240
  addWatcher(project.root, false, import_core34.SCHEMA_SHAPE_FILE);
6984
7241
  if ((0, import_core34.schemaInsideTree)(project.config) === void 0) {
6985
- const schemaFile = (0, import_node_path15.resolve)(project.root, (0, import_core34.schemaFileOf)(project.config));
6986
- addWatcher((0, import_node_path15.dirname)(schemaFile), false, (0, import_node_path15.basename)(schemaFile));
7242
+ const schemaFile = (0, import_node_path16.resolve)(project.root, (0, import_core34.schemaFileOf)(project.config));
7243
+ addWatcher((0, import_node_path16.dirname)(schemaFile), false, (0, import_node_path16.basename)(schemaFile));
6987
7244
  }
6988
7245
  void validate();
6989
7246
  return {