@penvhq/cli 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -47428,6 +47428,13 @@ var PenvError = class extends Error {
47428
47428
  ${REMEDY_ARROW} ${this.remedy}`;
47429
47429
  }
47430
47430
  };
47431
+ function isPenvErrorLike(value2) {
47432
+ if (!(value2 instanceof Error)) {
47433
+ return false;
47434
+ }
47435
+ const candidate = value2;
47436
+ return typeof candidate.code === "string" && typeof candidate.summary === "string" && (candidate.remedy === void 0 || typeof candidate.remedy === "string");
47437
+ }
47431
47438
  var FilenameGrammarError = class extends PenvError {
47432
47439
  name = "FilenameGrammarError";
47433
47440
  filename;
@@ -49280,7 +49287,7 @@ function scanForbidden(value2, path) {
49280
49287
  function sectionRemedy(path) {
49281
49288
  const [section, name] = path;
49282
49289
  if (section === "engine") {
49283
- return `Restore it with \`git checkout ${MANIFEST_PATH2}\`, or write the exact version and the \`integrity\` npm published for that ${ENGINE_PACKAGE} release. penv runs the bytes this pin names, so it will not guess one.`;
49290
+ return `Restore it with \`git checkout ${MANIFEST_PATH2}\`, or run \`penv upgrade <version>\` to write the pin again \u2014 it takes the \`integrity\` npm published for that ${ENGINE_PACKAGE} release rather than asking you for it.`;
49284
49291
  }
49285
49292
  if (section === "extensions" && typeof name === "string") {
49286
49293
  return `Run \`penv add ${name}\` to rewrite that entry.`;
@@ -50427,10 +50434,10 @@ var FilesystemProvider = class {
50427
50434
  return files;
50428
50435
  }
50429
50436
  readMetaSync(ref) {
50430
- const relative5 = formatMetaFile({ ...ref, format: META_FORMAT });
50431
- const contents = this.#readFile(this.#pathOf(relative5));
50437
+ const relative6 = formatMetaFile({ ...ref, format: META_FORMAT });
50438
+ const contents = this.#readFile(this.#pathOf(relative6));
50432
50439
  if (contents === void 0) return void 0;
50433
- return parseMeta(contents, relative5);
50440
+ return parseMeta(contents, relative6);
50434
50441
  }
50435
50442
  writeSync(file2, value2) {
50436
50443
  this.#writeFile(formatValueFile(file2), `${value2}
@@ -50927,11 +50934,25 @@ var LOCKFILES = [
50927
50934
  ["npm", "package-lock.json"]
50928
50935
  ];
50929
50936
  var ADD = {
50930
- pnpm: ["pnpm", "add", "--save-exact"],
50931
- npm: ["npm", "install", "--save-exact"],
50932
- yarn: ["yarn", "add", "--exact"],
50933
- bun: ["bun", "add", "--exact"]
50937
+ pnpm: ["pnpm", "add"],
50938
+ npm: ["npm", "install"],
50939
+ yarn: ["yarn", "add"],
50940
+ bun: ["bun", "add"]
50941
+ };
50942
+ var EXACT = {
50943
+ pnpm: "--save-exact",
50944
+ npm: "--save-exact",
50945
+ yarn: "--exact",
50946
+ bun: "--exact"
50934
50947
  };
50948
+ var DEV = {
50949
+ pnpm: "-D",
50950
+ npm: "--save-dev",
50951
+ yarn: "--dev",
50952
+ bun: "--dev"
50953
+ };
50954
+ var WORKSPACE_ROOT_FLAG = "-w";
50955
+ var PNPM_WORKSPACE = "pnpm-workspace.yaml";
50935
50956
  function engineVersion() {
50936
50957
  const version2 = ownManifest()?.version;
50937
50958
  if (typeof version2 === "string" && version2.length > 0) {
@@ -50994,69 +51015,219 @@ function manifestOf(root) {
50994
51015
  return void 0;
50995
51016
  }
50996
51017
  }
50997
- function declaredVersion(root, name) {
50998
- const manifest = manifestOf(root);
51018
+ function declaredIn(dir, name) {
51019
+ const manifest = manifestOf(dir);
50999
51020
  for (const field of ["dependencies", "devDependencies"]) {
51000
51021
  const block = manifest?.[field];
51001
51022
  if (block !== null && typeof block === "object" && !Array.isArray(block)) {
51002
51023
  const version2 = block[name];
51003
51024
  if (typeof version2 === "string") {
51004
- return version2;
51025
+ return { version: version2, dev: field === "devDependencies" };
51005
51026
  }
51006
51027
  }
51007
51028
  }
51008
51029
  return void 0;
51009
51030
  }
51031
+ function isPnpmWorkspaceRoot(root) {
51032
+ return (0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, PNPM_WORKSPACE)) && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, "package.json"));
51033
+ }
51034
+ function workspaceGlobs(root) {
51035
+ let text;
51036
+ try {
51037
+ text = (0, import_node_fs2.readFileSync)((0, import_node_path2.join)(root, PNPM_WORKSPACE), "utf8");
51038
+ } catch {
51039
+ return [];
51040
+ }
51041
+ const unquote = (raw) => raw.replace(/^['"]|['"]$/g, "").trim();
51042
+ const globs = [];
51043
+ let inside = false;
51044
+ for (const line of text.split(/\r?\n/)) {
51045
+ const flow = /^packages:\s*\[(.*)\]\s*$/.exec(line);
51046
+ if (flow?.[1] !== void 0) {
51047
+ return flow[1].split(",").map(unquote).filter((glob) => glob !== "");
51048
+ }
51049
+ if (/^packages:\s*$/.test(line)) {
51050
+ inside = true;
51051
+ continue;
51052
+ }
51053
+ if (!inside) {
51054
+ continue;
51055
+ }
51056
+ const item = /^\s+-\s*(.+?)\s*$/.exec(line);
51057
+ if (item?.[1] !== void 0) {
51058
+ globs.push(unquote(item[1]));
51059
+ continue;
51060
+ }
51061
+ if (line.trim() !== "" && !line.trimStart().startsWith("#")) {
51062
+ break;
51063
+ }
51064
+ }
51065
+ return globs;
51066
+ }
51067
+ function directoriesIn(dir) {
51068
+ try {
51069
+ return (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "node_modules").map((entry) => (0, import_node_path2.join)(dir, entry.name));
51070
+ } catch {
51071
+ return [];
51072
+ }
51073
+ }
51074
+ function isDirectory(path) {
51075
+ try {
51076
+ return (0, import_node_fs2.statSync)(path).isDirectory();
51077
+ } catch {
51078
+ return false;
51079
+ }
51080
+ }
51081
+ function expandGlob(root, glob) {
51082
+ let dirs = [root];
51083
+ for (const segment of glob.split("/").filter((part) => part !== "" && part !== ".")) {
51084
+ const next = [];
51085
+ for (const dir of dirs) {
51086
+ if (segment === "**") {
51087
+ const stack = [dir];
51088
+ while (stack.length > 0) {
51089
+ const current = stack.pop();
51090
+ next.push(current);
51091
+ stack.push(...directoriesIn(current));
51092
+ }
51093
+ continue;
51094
+ }
51095
+ if (segment.includes("*")) {
51096
+ const pattern = new RegExp(
51097
+ `^${segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")}$`
51098
+ );
51099
+ next.push(
51100
+ ...directoriesIn(dir).filter((child) => pattern.test(child.slice(dir.length + 1)))
51101
+ );
51102
+ continue;
51103
+ }
51104
+ const candidate = (0, import_node_path2.join)(dir, segment);
51105
+ if (isDirectory(candidate)) {
51106
+ next.push(candidate);
51107
+ }
51108
+ }
51109
+ dirs = next;
51110
+ }
51111
+ return dirs;
51112
+ }
51113
+ function workspaceMembers(root, name) {
51114
+ if (!isPnpmWorkspaceRoot(root)) {
51115
+ return [];
51116
+ }
51117
+ const globs = workspaceGlobs(root);
51118
+ const excluded = globs.filter((glob) => glob.startsWith("!")).flatMap((glob) => expandGlob(root, glob.slice(1)));
51119
+ const found = /* @__PURE__ */ new Set();
51120
+ for (const glob of globs.filter((entry) => !entry.startsWith("!"))) {
51121
+ for (const dir of expandGlob(root, glob)) {
51122
+ if (dir !== root && !excluded.includes(dir) && declaredIn(dir, name) !== void 0) {
51123
+ found.add(dir);
51124
+ }
51125
+ }
51126
+ }
51127
+ return [...found].sort();
51128
+ }
51129
+ function manifestPathOf(root, dir) {
51130
+ const within = (0, import_node_path2.relative)(root, dir).split(import_node_path2.sep).filter((part) => part !== "");
51131
+ return [...within, "package.json"].join("/");
51132
+ }
51133
+ function addCommand(manager, options, specs) {
51134
+ const [bin, verb] = ADD[manager];
51135
+ return [
51136
+ bin,
51137
+ ...options.filter === void 0 ? [] : ["--filter", options.filter],
51138
+ verb,
51139
+ ...options.workspaceRoot ? [WORKSPACE_ROOT_FLAG] : [],
51140
+ EXACT[manager],
51141
+ ...options.dev ? [DEV[manager]] : [],
51142
+ ...specs
51143
+ ];
51144
+ }
51145
+ function stepFor(manager, manifest, packages, options) {
51146
+ const pending = packages.filter((entry) => !entry.satisfied);
51147
+ const specs = (pending.length === 0 ? packages : pending).map(
51148
+ (entry) => `${entry.name}@${entry.version}`
51149
+ );
51150
+ return {
51151
+ manifest,
51152
+ packages,
51153
+ command: addCommand(manager, options, specs),
51154
+ satisfied: pending.length === 0
51155
+ };
51156
+ }
51010
51157
  function planInstall(root, version2 = engineVersion()) {
51011
51158
  const manager = detectPackageManager(root);
51012
51159
  const lockfile = LOCKFILES.find(
51013
51160
  ([name, file2]) => name === manager && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, file2))
51014
51161
  )?.[1];
51015
- const runtimeDeclared = declaredVersion(root, RUNTIME_PACKAGE);
51016
- const zodDeclared = declaredVersion(root, SCHEMA_PACKAGE);
51162
+ const workspaceRoot = manager === "pnpm" && isPnpmWorkspaceRoot(root);
51163
+ const runtime = declaredIn(root, RUNTIME_PACKAGE);
51164
+ const zod = declaredIn(root, SCHEMA_PACKAGE);
51017
51165
  const packages = [
51018
51166
  {
51019
51167
  name: RUNTIME_PACKAGE,
51020
51168
  version: version2,
51021
- ...runtimeDeclared === void 0 ? {} : { declared: runtimeDeclared },
51022
- satisfied: runtimeDeclared === version2
51169
+ ...runtime === void 0 ? {} : { declared: runtime.version },
51170
+ satisfied: runtime?.version === version2
51023
51171
  },
51024
51172
  {
51025
51173
  name: SCHEMA_PACKAGE,
51026
51174
  version: schemaPackageVersion(),
51027
- ...zodDeclared === void 0 ? {} : { declared: zodDeclared },
51175
+ ...zod === void 0 ? {} : { declared: zod.version },
51028
51176
  // Any declared zod counts: which zod a project uses is the project's
51029
51177
  // decision, and penv is here to make sure there is one, not to move it.
51030
- satisfied: zodDeclared !== void 0
51178
+ satisfied: zod !== void 0
51031
51179
  }
51032
51180
  ];
51033
51181
  const pending = packages.filter((entry) => !entry.satisfied);
51034
- const specs = (pending.length === 0 ? packages : pending).map(
51035
- (entry) => `${entry.name}@${entry.version}`
51036
- );
51182
+ const steps = [
51183
+ stepFor(manager, "package.json", packages, {
51184
+ workspaceRoot,
51185
+ dev: runtime?.dev === true && pending.every((entry) => entry.name === RUNTIME_PACKAGE) && pending.length > 0
51186
+ })
51187
+ ];
51188
+ for (const dir of workspaceMembers(root, RUNTIME_PACKAGE)) {
51189
+ const declared = declaredIn(dir, RUNTIME_PACKAGE);
51190
+ steps.push(
51191
+ stepFor(
51192
+ manager,
51193
+ manifestPathOf(root, dir),
51194
+ [
51195
+ {
51196
+ name: RUNTIME_PACKAGE,
51197
+ version: version2,
51198
+ declared: declared.version,
51199
+ satisfied: declared.version === version2
51200
+ }
51201
+ ],
51202
+ {
51203
+ filter: `./${(0, import_node_path2.relative)(root, dir).split(import_node_path2.sep).join("/")}`,
51204
+ workspaceRoot: false,
51205
+ dev: declared.dev
51206
+ }
51207
+ )
51208
+ );
51209
+ }
51037
51210
  return {
51038
51211
  root,
51039
51212
  manager,
51040
- packages,
51041
- command: [...ADD[manager], ...specs],
51213
+ steps,
51042
51214
  ...lockfile === void 0 ? {} : { lockfile },
51043
- satisfied: pending.length === 0
51215
+ satisfied: steps.every((step) => step.satisfied)
51044
51216
  };
51045
51217
  }
51046
51218
  function describe4(entry) {
51047
51219
  return `${entry.name} ${entry.version}`;
51048
51220
  }
51049
- function renderInstallPlan(plan2) {
51050
- if (plan2.satisfied) {
51051
- return [
51052
- `package.json already has ${plan2.packages.map(describe4).join(" and ")} \u2014 nothing to install.`
51053
- ];
51054
- }
51055
- const pending = plan2.packages.filter((entry) => !entry.satisfied);
51221
+ function installedPackages(plan2) {
51222
+ const pending = plan2.steps.flatMap((step) => step.packages).filter((entry) => !entry.satisfied);
51223
+ return [...new Map(pending.map((entry) => [entry.name, entry])).values()];
51224
+ }
51225
+ function renderStep(step) {
51226
+ const pending = step.packages.filter((entry) => !entry.satisfied);
51056
51227
  const added = pending.filter((entry) => entry.declared === void 0);
51057
51228
  const replaced = pending.filter((entry) => entry.declared !== void 0);
51058
51229
  return [
51059
- "package.json",
51230
+ step.manifest,
51060
51231
  ...added.length === 0 ? [] : [
51061
51232
  ' + "dependencies": {',
51062
51233
  ...added.map((entry) => ` + "${entry.name}": "${entry.version}"`),
@@ -51065,29 +51236,49 @@ function renderInstallPlan(plan2) {
51065
51236
  ...replaced.flatMap((entry) => [
51066
51237
  ` - "${entry.name}": "${entry.declared}"`,
51067
51238
  ` + "${entry.name}": "${entry.version}"`
51068
- ]),
51069
- ...plan2.lockfile === void 0 ? [] : [plan2.lockfile, ...pending.map((entry) => ` + ${entry.name}@${entry.version}`)],
51239
+ ])
51240
+ ];
51241
+ }
51242
+ function renderInstallPlan(plan2) {
51243
+ if (plan2.satisfied) {
51244
+ const packages = plan2.steps[0]?.packages ?? [];
51245
+ return [
51246
+ `package.json already has ${packages.map(describe4).join(" and ")} \u2014 nothing to install.`
51247
+ ];
51248
+ }
51249
+ const pending = plan2.steps.filter((step) => !step.satisfied);
51250
+ const [first, ...rest] = pending.map((step) => step.command.join(" "));
51251
+ const landing = installedPackages(plan2).map((entry) => ` + ${entry.name}@${entry.version}`);
51252
+ return [
51253
+ ...pending.flatMap(renderStep),
51254
+ ...plan2.lockfile === void 0 ? [] : [plan2.lockfile, ...landing],
51070
51255
  "",
51071
- `Run with: ${plan2.command.join(" ")}`
51256
+ `Run with: ${first ?? ""}`,
51257
+ ...rest.map((command) => ` then ${command}`)
51072
51258
  ];
51073
51259
  }
51074
51260
  var installWithPackageManager = async (plan2) => {
51075
- const child = startChild({
51076
- command: plan2.command,
51077
- env: process.env,
51078
- cwd: plan2.root,
51079
- purpose: `install ${plan2.packages.map(describe4).join(" and ")}`
51080
- });
51081
- const ended = await child.ended;
51082
- if (ended.exitCode !== 0 || ended.signal !== null) {
51083
- throw installFailed(plan2);
51261
+ for (const step of plan2.steps) {
51262
+ if (step.satisfied) {
51263
+ continue;
51264
+ }
51265
+ const child = startChild({
51266
+ command: step.command,
51267
+ env: process.env,
51268
+ cwd: plan2.root,
51269
+ purpose: `install ${step.packages.map(describe4).join(" and ")} in ${step.manifest}`
51270
+ });
51271
+ const ended = await child.ended;
51272
+ if (ended.exitCode !== 0 || ended.signal !== null) {
51273
+ throw installFailed(plan2, step);
51274
+ }
51084
51275
  }
51085
51276
  };
51086
- function installFailed(plan2) {
51277
+ function installFailed(plan2, step) {
51087
51278
  return new PenvError(
51088
51279
  "INIT_INSTALL_FAILED",
51089
- `${plan2.command.join(" ")} did not finish, so penv migrated nothing`,
51090
- `Run \`${plan2.command.join(" ")}\` yourself, then start this command again. Your dotenv files are exactly where they were.`
51280
+ `${step.command.join(" ")} did not finish, so penv migrated nothing`,
51281
+ `Read what ${plan2.manager} printed above \u2014 it names what it refused. Fix that and run this command again; your dotenv files are exactly where they were.`
51091
51282
  );
51092
51283
  }
51093
51284
 
@@ -51733,7 +51924,7 @@ function writeError(lines) {
51733
51924
  }
51734
51925
  }
51735
51926
  function reportError(error51) {
51736
- if (error51 instanceof PenvError) {
51927
+ if (isPenvErrorLike(error51)) {
51737
51928
  process.stderr.write(`${err.red(CROSS)} ${error51.summary}
51738
51929
  `);
51739
51930
  if (error51.remedy !== void 0) {
@@ -55251,8 +55442,8 @@ function exportsSchema(file2) {
55251
55442
  /export\s*\{[^}]*\bschema\b[^}]*\}/.test(source)
55252
55443
  );
55253
55444
  }
55254
- function occupied(cwd, relative5) {
55255
- const file2 = (0, import_node_path11.join)(cwd, ...relative5.split("/"));
55445
+ function occupied(cwd, relative6) {
55446
+ const file2 = (0, import_node_path11.join)(cwd, ...relative6.split("/"));
55256
55447
  return (0, import_node_fs11.existsSync)(file2) && !exportsSchema(file2);
55257
55448
  }
55258
55449
  function schemaFileFor(cwd) {
@@ -55675,7 +55866,7 @@ function environmentsFromFlag(flag) {
55675
55866
  function configOf(decisions) {
55676
55867
  return { environments: decisions.environments, providers: {}, schemaFile: decisions.schemaFile };
55677
55868
  }
55678
- function declaredIn(root) {
55869
+ function declaredIn2(root) {
55679
55870
  const file2 = (0, import_node_path13.join)(root, CONFIG_FILE);
55680
55871
  if (!(0, import_node_fs13.existsSync)(file2)) {
55681
55872
  return void 0;
@@ -55683,7 +55874,7 @@ function declaredIn(root) {
55683
55874
  return loadConfigFrom(file2);
55684
55875
  }
55685
55876
  function planInit(root, flags = {}) {
55686
- const declared = declaredIn(root);
55877
+ const declared = declaredIn2(root);
55687
55878
  const detected = detectFramework(root);
55688
55879
  const notes = [];
55689
55880
  if (declared !== void 0) {
@@ -56350,7 +56541,7 @@ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS, framework
56350
56541
  return seam === void 0 ? steps : [...steps, seam];
56351
56542
  }
56352
56543
  function scaffoldPaths(root, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
56353
- const fileAt = (relative5) => (0, import_node_path13.join)(root, ...relative5.split("/"));
56544
+ const fileAt = (relative6) => (0, import_node_path13.join)(root, ...relative6.split("/"));
56354
56545
  const seam = decisions.inject ? seamFor(framework, {
56355
56546
  alias: decisions.alias,
56356
56547
  srcDir: srcPrefix(root),
@@ -56397,7 +56588,7 @@ function planCutover(input) {
56397
56588
  "Run `penv init` again and choose the files penv should adopt."
56398
56589
  );
56399
56590
  }
56400
- const declared = declaredIn(root);
56591
+ const declared = declaredIn2(root);
56401
56592
  const named = environmentsDeclaredBy(selected);
56402
56593
  const chosen = named.length > 0 ? named : [requireEnvironment(input)];
56403
56594
  const environments = declared === void 0 ? chosen : declared.environments;
@@ -56648,7 +56839,11 @@ function renderCutover(result2) {
56648
56839
  const glyph = step.action === "conflicted" ? WARN : step.action === "info" ? "\u2192" : CHECK;
56649
56840
  return step.note === void 0 ? { glyph, text: step.text } : { glyph, text: step.text, note: step.note };
56650
56841
  }),
56651
- ...plan2.install.packages.filter((entry) => !entry.satisfied).map((entry) => ({ glyph: CHECK, text: `Installed ${entry.name}`, note: entry.version })),
56842
+ ...installedPackages(plan2.install).map((entry) => ({
56843
+ glyph: CHECK,
56844
+ text: `Installed ${entry.name}`,
56845
+ note: entry.version
56846
+ })),
56652
56847
  {
56653
56848
  glyph: CHECK,
56654
56849
  text: `Imported ${plan2.fields.length} parameters`,
@@ -56808,7 +57003,7 @@ async function cutoverInteractively(root, base, adoption) {
56808
57003
  write(renderCutover(await applyCutover({ ...plan2, decisions: { ...plan2.decisions, inject: inject2 } })));
56809
57004
  }
56810
57005
  function offeredEnvironment(root) {
56811
- const declared = declaredIn(root);
57006
+ const declared = declaredIn2(root);
56812
57007
  if (declared === void 0) {
56813
57008
  return DEVELOPMENT;
56814
57009
  }