@penvhq/cli 0.11.0 → 0.13.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;
@@ -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}
@@ -50919,6 +50926,7 @@ function escapeArgument(argument, doubleEscape) {
50919
50926
  // src/install.ts
50920
50927
  var RUNTIME_PACKAGE = "@penvhq/penv";
50921
50928
  var SCHEMA_PACKAGE = "zod";
50929
+ var TYPES_PACKAGE = "@penvhq/core";
50922
50930
  var LOCKFILES = [
50923
50931
  ["pnpm", "pnpm-lock.yaml"],
50924
50932
  ["yarn", "yarn.lock"],
@@ -50927,11 +50935,25 @@ var LOCKFILES = [
50927
50935
  ["npm", "package-lock.json"]
50928
50936
  ];
50929
50937
  var ADD = {
50930
- pnpm: ["pnpm", "add", "--save-exact"],
50931
- npm: ["npm", "install", "--save-exact"],
50932
- yarn: ["yarn", "add", "--exact"],
50933
- bun: ["bun", "add", "--exact"]
50938
+ pnpm: ["pnpm", "add"],
50939
+ npm: ["npm", "install"],
50940
+ yarn: ["yarn", "add"],
50941
+ bun: ["bun", "add"]
50942
+ };
50943
+ var EXACT = {
50944
+ pnpm: "--save-exact",
50945
+ npm: "--save-exact",
50946
+ yarn: "--exact",
50947
+ bun: "--exact"
50934
50948
  };
50949
+ var DEV = {
50950
+ pnpm: "-D",
50951
+ npm: "--save-dev",
50952
+ yarn: "--dev",
50953
+ bun: "--dev"
50954
+ };
50955
+ var WORKSPACE_ROOT_FLAG = "-w";
50956
+ var PNPM_WORKSPACE = "pnpm-workspace.yaml";
50935
50957
  function engineVersion() {
50936
50958
  const version2 = ownManifest()?.version;
50937
50959
  if (typeof version2 === "string" && version2.length > 0) {
@@ -50994,100 +51016,288 @@ function manifestOf(root) {
50994
51016
  return void 0;
50995
51017
  }
50996
51018
  }
50997
- function declaredVersion(root, name) {
50998
- const manifest = manifestOf(root);
51019
+ function declaredIn(dir, name) {
51020
+ const manifest = manifestOf(dir);
50999
51021
  for (const field of ["dependencies", "devDependencies"]) {
51000
51022
  const block = manifest?.[field];
51001
51023
  if (block !== null && typeof block === "object" && !Array.isArray(block)) {
51002
51024
  const version2 = block[name];
51003
51025
  if (typeof version2 === "string") {
51004
- return version2;
51026
+ return { version: version2, dev: field === "devDependencies" };
51005
51027
  }
51006
51028
  }
51007
51029
  }
51008
51030
  return void 0;
51009
51031
  }
51032
+ function isPnpmWorkspaceRoot(root) {
51033
+ 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"));
51034
+ }
51035
+ function workspaceGlobs(root) {
51036
+ let text;
51037
+ try {
51038
+ text = (0, import_node_fs2.readFileSync)((0, import_node_path2.join)(root, PNPM_WORKSPACE), "utf8");
51039
+ } catch {
51040
+ return [];
51041
+ }
51042
+ const unquote = (raw) => raw.replace(/^['"]|['"]$/g, "").trim();
51043
+ const globs = [];
51044
+ let inside = false;
51045
+ for (const line of text.split(/\r?\n/)) {
51046
+ const flow = /^packages:\s*\[(.*)\]\s*$/.exec(line);
51047
+ if (flow?.[1] !== void 0) {
51048
+ return flow[1].split(",").map(unquote).filter((glob) => glob !== "");
51049
+ }
51050
+ if (/^packages:\s*$/.test(line)) {
51051
+ inside = true;
51052
+ continue;
51053
+ }
51054
+ if (!inside) {
51055
+ continue;
51056
+ }
51057
+ const item = /^\s+-\s*(.+?)\s*$/.exec(line);
51058
+ if (item?.[1] !== void 0) {
51059
+ globs.push(unquote(item[1]));
51060
+ continue;
51061
+ }
51062
+ if (line.trim() !== "" && !line.trimStart().startsWith("#")) {
51063
+ break;
51064
+ }
51065
+ }
51066
+ return globs;
51067
+ }
51068
+ function directoriesIn(dir) {
51069
+ try {
51070
+ 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));
51071
+ } catch {
51072
+ return [];
51073
+ }
51074
+ }
51075
+ function isDirectory(path) {
51076
+ try {
51077
+ return (0, import_node_fs2.statSync)(path).isDirectory();
51078
+ } catch {
51079
+ return false;
51080
+ }
51081
+ }
51082
+ function expandGlob(root, glob) {
51083
+ let dirs = [root];
51084
+ for (const segment of glob.split("/").filter((part) => part !== "" && part !== ".")) {
51085
+ const next = [];
51086
+ for (const dir of dirs) {
51087
+ if (segment === "**") {
51088
+ const stack = [dir];
51089
+ while (stack.length > 0) {
51090
+ const current = stack.pop();
51091
+ next.push(current);
51092
+ stack.push(...directoriesIn(current));
51093
+ }
51094
+ continue;
51095
+ }
51096
+ if (segment.includes("*")) {
51097
+ const pattern = new RegExp(
51098
+ `^${segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")}$`
51099
+ );
51100
+ next.push(
51101
+ ...directoriesIn(dir).filter((child) => pattern.test(child.slice(dir.length + 1)))
51102
+ );
51103
+ continue;
51104
+ }
51105
+ const candidate = (0, import_node_path2.join)(dir, segment);
51106
+ if (isDirectory(candidate)) {
51107
+ next.push(candidate);
51108
+ }
51109
+ }
51110
+ dirs = next;
51111
+ }
51112
+ return dirs;
51113
+ }
51114
+ function workspaceMembers(root, name) {
51115
+ if (!isPnpmWorkspaceRoot(root)) {
51116
+ return [];
51117
+ }
51118
+ const globs = workspaceGlobs(root);
51119
+ const excluded = globs.filter((glob) => glob.startsWith("!")).flatMap((glob) => expandGlob(root, glob.slice(1)));
51120
+ const found = /* @__PURE__ */ new Set();
51121
+ for (const glob of globs.filter((entry) => !entry.startsWith("!"))) {
51122
+ for (const dir of expandGlob(root, glob)) {
51123
+ if (dir !== root && !excluded.includes(dir) && declaredIn(dir, name) !== void 0) {
51124
+ found.add(dir);
51125
+ }
51126
+ }
51127
+ }
51128
+ return [...found].sort();
51129
+ }
51130
+ function manifestPathOf(root, dir) {
51131
+ const within = (0, import_node_path2.relative)(root, dir).split(import_node_path2.sep).filter((part) => part !== "");
51132
+ return [...within, "package.json"].join("/");
51133
+ }
51134
+ function addCommand(manager, options, specs) {
51135
+ const [bin, verb] = ADD[manager];
51136
+ return [
51137
+ bin,
51138
+ ...options.filter === void 0 ? [] : ["--filter", options.filter],
51139
+ verb,
51140
+ ...options.workspaceRoot ? [WORKSPACE_ROOT_FLAG] : [],
51141
+ EXACT[manager],
51142
+ ...options.dev ? [DEV[manager]] : [],
51143
+ ...specs
51144
+ ];
51145
+ }
51146
+ function stepFor(manager, manifest, packages, options) {
51147
+ const pending = packages.filter((entry) => !entry.satisfied);
51148
+ const specs = (pending.length === 0 ? packages : pending).map(
51149
+ (entry) => `${entry.name}@${entry.version}`
51150
+ );
51151
+ return {
51152
+ manifest,
51153
+ packages,
51154
+ command: addCommand(manager, options, specs),
51155
+ dev: options.dev,
51156
+ satisfied: pending.length === 0
51157
+ };
51158
+ }
51010
51159
  function planInstall(root, version2 = engineVersion()) {
51011
51160
  const manager = detectPackageManager(root);
51012
51161
  const lockfile = LOCKFILES.find(
51013
51162
  ([name, file2]) => name === manager && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, file2))
51014
51163
  )?.[1];
51015
- const runtimeDeclared = declaredVersion(root, RUNTIME_PACKAGE);
51016
- const zodDeclared = declaredVersion(root, SCHEMA_PACKAGE);
51164
+ const workspaceRoot = manager === "pnpm" && isPnpmWorkspaceRoot(root);
51165
+ const runtime = declaredIn(root, RUNTIME_PACKAGE);
51166
+ const zod = declaredIn(root, SCHEMA_PACKAGE);
51167
+ const types = declaredIn(root, TYPES_PACKAGE);
51017
51168
  const packages = [
51018
51169
  {
51019
51170
  name: RUNTIME_PACKAGE,
51020
51171
  version: version2,
51021
- ...runtimeDeclared === void 0 ? {} : { declared: runtimeDeclared },
51022
- satisfied: runtimeDeclared === version2
51172
+ ...runtime === void 0 ? {} : { declared: runtime.version },
51173
+ satisfied: runtime?.version === version2
51023
51174
  },
51024
51175
  {
51025
51176
  name: SCHEMA_PACKAGE,
51026
51177
  version: schemaPackageVersion(),
51027
- ...zodDeclared === void 0 ? {} : { declared: zodDeclared },
51178
+ ...zod === void 0 ? {} : { declared: zod.version },
51028
51179
  // Any declared zod counts: which zod a project uses is the project's
51029
51180
  // decision, and penv is here to make sure there is one, not to move it.
51030
- satisfied: zodDeclared !== void 0
51181
+ satisfied: zod !== void 0
51031
51182
  }
51032
51183
  ];
51184
+ const typesPackage = {
51185
+ name: TYPES_PACKAGE,
51186
+ version: version2,
51187
+ ...types === void 0 ? {} : { declared: types.version },
51188
+ // Any declared version counts, for zod's reason: the augmentation binds on
51189
+ // the module resolving, not on which release of it a project pinned.
51190
+ satisfied: types !== void 0
51191
+ };
51033
51192
  const pending = packages.filter((entry) => !entry.satisfied);
51034
- const specs = (pending.length === 0 ? packages : pending).map(
51035
- (entry) => `${entry.name}@${entry.version}`
51036
- );
51193
+ const steps = [
51194
+ stepFor(manager, "package.json", packages, {
51195
+ workspaceRoot,
51196
+ dev: runtime?.dev === true && pending.every((entry) => entry.name === RUNTIME_PACKAGE) && pending.length > 0
51197
+ }),
51198
+ stepFor(manager, "package.json", [typesPackage], { workspaceRoot, dev: true })
51199
+ ];
51200
+ for (const dir of workspaceMembers(root, RUNTIME_PACKAGE)) {
51201
+ const declared = declaredIn(dir, RUNTIME_PACKAGE);
51202
+ steps.push(
51203
+ stepFor(
51204
+ manager,
51205
+ manifestPathOf(root, dir),
51206
+ [
51207
+ {
51208
+ name: RUNTIME_PACKAGE,
51209
+ version: version2,
51210
+ declared: declared.version,
51211
+ satisfied: declared.version === version2
51212
+ }
51213
+ ],
51214
+ {
51215
+ filter: `./${(0, import_node_path2.relative)(root, dir).split(import_node_path2.sep).join("/")}`,
51216
+ workspaceRoot: false,
51217
+ dev: declared.dev
51218
+ }
51219
+ )
51220
+ );
51221
+ }
51037
51222
  return {
51038
51223
  root,
51039
51224
  manager,
51040
- packages,
51041
- command: [...ADD[manager], ...specs],
51225
+ steps,
51042
51226
  ...lockfile === void 0 ? {} : { lockfile },
51043
- satisfied: pending.length === 0
51227
+ satisfied: steps.every((step) => step.satisfied)
51044
51228
  };
51045
51229
  }
51046
51230
  function describe4(entry) {
51047
51231
  return `${entry.name} ${entry.version}`;
51048
51232
  }
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);
51233
+ function installedPackages(plan2) {
51234
+ const pending = plan2.steps.flatMap((step) => step.packages).filter((entry) => !entry.satisfied);
51235
+ return [...new Map(pending.map((entry) => [entry.name, entry])).values()];
51236
+ }
51237
+ function plannedPackages(plan2) {
51238
+ const all = plan2.steps.flatMap((step) => step.packages);
51239
+ return [...new Map(all.map((entry) => [entry.name, entry])).values()];
51240
+ }
51241
+ function series(values) {
51242
+ return values.length < 2 ? values[0] ?? "" : `${values.slice(0, -1).join(", ")} and ${values.at(-1)}`;
51243
+ }
51244
+ function renderStep(step) {
51245
+ const pending = step.packages.filter((entry) => !entry.satisfied);
51056
51246
  const added = pending.filter((entry) => entry.declared === void 0);
51057
51247
  const replaced = pending.filter((entry) => entry.declared !== void 0);
51248
+ const block = step.dev ? "devDependencies" : "dependencies";
51058
51249
  return [
51059
- "package.json",
51250
+ step.manifest,
51060
51251
  ...added.length === 0 ? [] : [
51061
- ' + "dependencies": {',
51252
+ ` + "${block}": {`,
51062
51253
  ...added.map((entry) => ` + "${entry.name}": "${entry.version}"`),
51063
51254
  " + }"
51064
51255
  ],
51065
51256
  ...replaced.flatMap((entry) => [
51066
51257
  ` - "${entry.name}": "${entry.declared}"`,
51067
51258
  ` + "${entry.name}": "${entry.version}"`
51068
- ]),
51069
- ...plan2.lockfile === void 0 ? [] : [plan2.lockfile, ...pending.map((entry) => ` + ${entry.name}@${entry.version}`)],
51259
+ ])
51260
+ ];
51261
+ }
51262
+ function renderInstallPlan(plan2) {
51263
+ if (plan2.satisfied) {
51264
+ return [
51265
+ `package.json already has ${series(plannedPackages(plan2).map(describe4))} \u2014 nothing to install.`
51266
+ ];
51267
+ }
51268
+ const pending = plan2.steps.filter((step) => !step.satisfied);
51269
+ const [first, ...rest] = pending.map((step) => step.command.join(" "));
51270
+ const landing = installedPackages(plan2).map((entry) => ` + ${entry.name}@${entry.version}`);
51271
+ return [
51272
+ ...pending.flatMap(renderStep),
51273
+ ...plan2.lockfile === void 0 ? [] : [plan2.lockfile, ...landing],
51070
51274
  "",
51071
- `Run with: ${plan2.command.join(" ")}`
51275
+ `Run with: ${first ?? ""}`,
51276
+ ...rest.map((command) => ` then ${command}`)
51072
51277
  ];
51073
51278
  }
51074
51279
  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);
51280
+ for (const step of plan2.steps) {
51281
+ if (step.satisfied) {
51282
+ continue;
51283
+ }
51284
+ const child = startChild({
51285
+ command: step.command,
51286
+ env: process.env,
51287
+ cwd: plan2.root,
51288
+ purpose: `install ${step.packages.map(describe4).join(" and ")} in ${step.manifest}`
51289
+ });
51290
+ const ended = await child.ended;
51291
+ if (ended.exitCode !== 0 || ended.signal !== null) {
51292
+ throw installFailed(plan2, step);
51293
+ }
51084
51294
  }
51085
51295
  };
51086
- function installFailed(plan2) {
51296
+ function installFailed(plan2, step) {
51087
51297
  return new PenvError(
51088
51298
  "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.`
51299
+ `${step.command.join(" ")} did not finish, so penv migrated nothing`,
51300
+ `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
51301
  );
51092
51302
  }
51093
51303
 
@@ -51733,7 +51943,7 @@ function writeError(lines) {
51733
51943
  }
51734
51944
  }
51735
51945
  function reportError(error51) {
51736
- if (error51 instanceof PenvError) {
51946
+ if (isPenvErrorLike(error51)) {
51737
51947
  process.stderr.write(`${err.red(CROSS)} ${error51.summary}
51738
51948
  `);
51739
51949
  if (error51.remedy !== void 0) {
@@ -55251,8 +55461,8 @@ function exportsSchema(file2) {
55251
55461
  /export\s*\{[^}]*\bschema\b[^}]*\}/.test(source)
55252
55462
  );
55253
55463
  }
55254
- function occupied(cwd, relative5) {
55255
- const file2 = (0, import_node_path11.join)(cwd, ...relative5.split("/"));
55464
+ function occupied(cwd, relative6) {
55465
+ const file2 = (0, import_node_path11.join)(cwd, ...relative6.split("/"));
55256
55466
  return (0, import_node_fs11.existsSync)(file2) && !exportsSchema(file2);
55257
55467
  }
55258
55468
  function schemaFileFor(cwd) {
@@ -55675,7 +55885,7 @@ function environmentsFromFlag(flag) {
55675
55885
  function configOf(decisions) {
55676
55886
  return { environments: decisions.environments, providers: {}, schemaFile: decisions.schemaFile };
55677
55887
  }
55678
- function declaredIn(root) {
55888
+ function declaredIn2(root) {
55679
55889
  const file2 = (0, import_node_path13.join)(root, CONFIG_FILE);
55680
55890
  if (!(0, import_node_fs13.existsSync)(file2)) {
55681
55891
  return void 0;
@@ -55683,7 +55893,7 @@ function declaredIn(root) {
55683
55893
  return loadConfigFrom(file2);
55684
55894
  }
55685
55895
  function planInit(root, flags = {}) {
55686
- const declared = declaredIn(root);
55896
+ const declared = declaredIn2(root);
55687
55897
  const detected = detectFramework(root);
55688
55898
  const notes = [];
55689
55899
  if (declared !== void 0) {
@@ -56350,7 +56560,7 @@ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS, framework
56350
56560
  return seam === void 0 ? steps : [...steps, seam];
56351
56561
  }
56352
56562
  function scaffoldPaths(root, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
56353
- const fileAt = (relative5) => (0, import_node_path13.join)(root, ...relative5.split("/"));
56563
+ const fileAt = (relative6) => (0, import_node_path13.join)(root, ...relative6.split("/"));
56354
56564
  const seam = decisions.inject ? seamFor(framework, {
56355
56565
  alias: decisions.alias,
56356
56566
  srcDir: srcPrefix(root),
@@ -56397,7 +56607,7 @@ function planCutover(input) {
56397
56607
  "Run `penv init` again and choose the files penv should adopt."
56398
56608
  );
56399
56609
  }
56400
- const declared = declaredIn(root);
56610
+ const declared = declaredIn2(root);
56401
56611
  const named = environmentsDeclaredBy(selected);
56402
56612
  const chosen = named.length > 0 ? named : [requireEnvironment(input)];
56403
56613
  const environments = declared === void 0 ? chosen : declared.environments;
@@ -56648,7 +56858,11 @@ function renderCutover(result2) {
56648
56858
  const glyph = step.action === "conflicted" ? WARN : step.action === "info" ? "\u2192" : CHECK;
56649
56859
  return step.note === void 0 ? { glyph, text: step.text } : { glyph, text: step.text, note: step.note };
56650
56860
  }),
56651
- ...plan2.install.packages.filter((entry) => !entry.satisfied).map((entry) => ({ glyph: CHECK, text: `Installed ${entry.name}`, note: entry.version })),
56861
+ ...installedPackages(plan2.install).map((entry) => ({
56862
+ glyph: CHECK,
56863
+ text: `Installed ${entry.name}`,
56864
+ note: entry.version
56865
+ })),
56652
56866
  {
56653
56867
  glyph: CHECK,
56654
56868
  text: `Imported ${plan2.fields.length} parameters`,
@@ -56808,7 +57022,7 @@ async function cutoverInteractively(root, base, adoption) {
56808
57022
  write(renderCutover(await applyCutover({ ...plan2, decisions: { ...plan2.decisions, inject: inject2 } })));
56809
57023
  }
56810
57024
  function offeredEnvironment(root) {
56811
- const declared = declaredIn(root);
57025
+ const declared = declaredIn2(root);
56812
57026
  if (declared === void 0) {
56813
57027
  return DEVELOPMENT;
56814
57028
  }