@penvhq/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2803,7 +2803,7 @@ function occupied(cwd, relative3) {
2803
2803
  return (0, import_node_fs2.existsSync)(file) && !exportsSchema(file);
2804
2804
  }
2805
2805
  function schemaFileFor(cwd) {
2806
- const dir = (0, import_node_fs2.existsSync)((0, import_node_path5.join)(cwd, "src")) ? "src/" : "";
2806
+ const dir = srcPrefix(cwd);
2807
2807
  const preferred = `${dir}env.ts`;
2808
2808
  if (!occupied(cwd, preferred)) {
2809
2809
  return { file: preferred };
@@ -2814,6 +2814,9 @@ function schemaFileFor(cwd) {
2814
2814
  }
2815
2815
  return { file: import_core14.DEFAULT_SCHEMA_FILE, displaced: preferred };
2816
2816
  }
2817
+ function srcPrefix(cwd) {
2818
+ return (0, import_node_fs2.existsSync)((0, import_node_path5.join)(cwd, "src")) ? "src/" : "";
2819
+ }
2817
2820
  function dependenciesOf(cwd) {
2818
2821
  const manifest = manifestOf(cwd);
2819
2822
  if (manifest === void 0) {
@@ -2951,19 +2954,25 @@ function bun({ alias }) {
2951
2954
  kind: "scaffold",
2952
2955
  file: ".penv/preload.ts",
2953
2956
  content: `// Bun evaluates this file before your entry point, because it is listed under
2954
- // \`preload\` in bunfig.toml (see the note penv printed). Running it injects your
2955
- // config into process.env before any library reads it.
2957
+ // \`preload\` in bunfig.toml. Running it injects your config into process.env
2958
+ // before any library reads it.
2956
2959
  import "${alias}";
2957
2960
  `,
2958
2961
  ifPresent: `Keep .penv/preload.ts as \`import "${alias}";\`.`,
2959
- notes: [
2960
- 'Register it in bunfig.toml so Bun runs it: `preload = ["./.penv/preload.ts"]` (and the same under `[test]`).',
2961
- `\`${alias}\` must be a tsconfig \`paths\` alias for Bun to resolve it.`
2962
- ]
2962
+ notes: [`\`${alias}\` must be a tsconfig \`paths\` alias for Bun to resolve it.`],
2963
+ // The preload file is inert until bunfig.toml points at it — so penv writes
2964
+ // that too, both for the runtime and for `bun test`. Only when there is no
2965
+ // bunfig to overwrite; an existing one is the user's, and penv prints instead.
2966
+ also: {
2967
+ file: "bunfig.toml",
2968
+ content: 'preload = ["./.penv/preload.ts"]\n\n[test]\npreload = ["./.penv/preload.ts"]\n',
2969
+ ifPresent: 'Add "./.penv/preload.ts" to the `preload` array in bunfig.toml \u2014 and mirror it under `[test]` so `bun test` injects too.'
2970
+ }
2963
2971
  };
2964
2972
  }
2965
- function node({ alias, srcDir }) {
2966
- const runtimeSpecifier = alias.startsWith("#") ? alias : `./${srcDir}.penv/env.js`;
2973
+ function node({ alias, schemaFile }) {
2974
+ const builtSchema = `./${schemaFile.replace(/\.ts$/, ".js")}`;
2975
+ const runtimeSpecifier = alias.startsWith("#") ? alias : builtSchema;
2967
2976
  return {
2968
2977
  kind: "instruct",
2969
2978
  instruction: `Plain Node / Express / Fastify have no framework hook that runs before your
@@ -2983,10 +2992,17 @@ import before it. If you have no server.ts yet, create it from TanStack's server
2983
2992
  template and put that import first.`
2984
2993
  };
2985
2994
  }
2986
- function astro(_context) {
2995
+ function astro({ alias }) {
2987
2996
  return {
2988
2997
  kind: "instruct",
2989
- instruction: 'Astro has no single pre-app hook. Add a small integration to astro.config that\ninjects the import into every SSR page, then cover the routes it cannot reach:\n \u2022 In astro.config integrations, add:\n { name: "penv", hooks: { "astro:config:setup": ({ injectScript }) =>\n injectScript("page-ssr", \'import "@env";\') } }\n \u2022 Also add `import "@env";` as the first line of src/middleware.ts and of any\n endpoint route (src/pages/*.ts) that imports a library reading process.env.\nInjection only applies under a server (on-demand) adapter, not a static build.'
2998
+ instruction: `Astro has no single pre-app hook. Add a small integration to astro.config that
2999
+ injects the import into every SSR page, then cover the routes it cannot reach:
3000
+ \u2022 In astro.config integrations, add:
3001
+ { name: "penv", hooks: { "astro:config:setup": ({ injectScript }) =>
3002
+ injectScript("page-ssr", 'import "${alias}";') } }
3003
+ \u2022 Also add \`import "${alias}";\` as the first line of src/middleware.ts and of any
3004
+ endpoint route (src/pages/*.ts) that imports a library reading process.env.
3005
+ Injection only applies under a server (on-demand) adapter, not a static build.`
2990
3006
  };
2991
3007
  }
2992
3008
  function viteSpa(_context) {
@@ -3187,17 +3203,18 @@ async function promptForDecisions(plan2, io) {
3187
3203
  );
3188
3204
  io.write("");
3189
3205
  }
3190
- const inject = seamKindFor(plan2) === "none" ? false : await askInject(io);
3191
3206
  const proceed = (await io.ask(prompt("Proceed?", "Y/n"))).trim().toLowerCase();
3192
3207
  if (proceed.length > 0 && proceed !== "y" && proceed !== "yes") {
3193
3208
  return void 0;
3194
3209
  }
3210
+ const inject = seamKindFor(plan2) === "none" ? false : await askInject(io);
3195
3211
  return { ...plan2.decisions, environments, inject };
3196
3212
  }
3197
3213
  function seamKindFor(plan2) {
3198
3214
  return seamFor(plan2.detected?.name, {
3199
3215
  alias: plan2.decisions.alias,
3200
- srcDir: plan2.decisions.schemaFile.startsWith("src/") ? "src/" : ""
3216
+ srcDir: plan2.decisions.schemaFile.startsWith("src/") ? "src/" : "",
3217
+ schemaFile: plan2.decisions.schemaFile
3201
3218
  }).kind;
3202
3219
  }
3203
3220
  async function askInject(io) {
@@ -3592,15 +3609,48 @@ function writeGitignore(root, decisions = DEFAULT_DECISIONS) {
3592
3609
  text: `${existing === void 0 ? "Created" : "Updated"} ${relative3}`
3593
3610
  };
3594
3611
  }
3595
- function srcDirOf(root) {
3596
- return (0, import_node_fs3.existsSync)((0, import_node_path6.join)(root, "src")) ? "src/" : "";
3612
+ var INJECT_MIN_VERSION = "0.6.0";
3613
+ function outdatedRuntimeWarning(root) {
3614
+ const version = installedPenvVersion(root);
3615
+ if (version === void 0 || !isBelow(version, INJECT_MIN_VERSION)) {
3616
+ return void 0;
3617
+ }
3618
+ return `Injection needs @penvhq/penv ${INJECT_MIN_VERSION}+ \u2014 this project has ${version}, whose \`load\` ignores \`{ inject: true }\`. Upgrade, or process.env stays empty.`;
3619
+ }
3620
+ function installedPenvVersion(root) {
3621
+ const file = (0, import_node_path6.join)(root, "node_modules", "@penvhq", "penv", "package.json");
3622
+ if (!(0, import_node_fs3.existsSync)(file)) {
3623
+ return void 0;
3624
+ }
3625
+ try {
3626
+ const version = JSON.parse((0, import_node_fs3.readFileSync)(file, "utf8")).version;
3627
+ return typeof version === "string" ? version : void 0;
3628
+ } catch {
3629
+ return void 0;
3630
+ }
3631
+ }
3632
+ function releaseTriple(version) {
3633
+ const parts = (version.split("-")[0] ?? "").split(".");
3634
+ const at = (i) => Number.parseInt(parts[i] ?? "0", 10) || 0;
3635
+ return [at(0), at(1), at(2)];
3636
+ }
3637
+ function isBelow(a, b) {
3638
+ const [am, an, ap] = releaseTriple(a);
3639
+ const [bm, bn, bp] = releaseTriple(b);
3640
+ if (am !== bm) return am < bm;
3641
+ if (an !== bn) return an < bn;
3642
+ return ap < bp;
3597
3643
  }
3598
- function writeSeam(root, decisions = DEFAULT_DECISIONS) {
3644
+ function writeSeam(root, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
3599
3645
  if (!decisions.inject) {
3600
3646
  return void 0;
3601
3647
  }
3602
- const framework = detectFramework(root)?.name;
3603
- const seam = seamFor(framework, { alias: decisions.alias, srcDir: srcDirOf(root) });
3648
+ const seam = seamFor(framework, {
3649
+ alias: decisions.alias,
3650
+ srcDir: srcPrefix(root),
3651
+ schemaFile: decisions.schemaFile
3652
+ });
3653
+ const outdated = outdatedRuntimeWarning(root);
3604
3654
  if (seam.kind === "none") {
3605
3655
  return { target: "seam", action: "info", text: "No injection seam needed", note: seam.reason };
3606
3656
  }
@@ -3609,30 +3659,51 @@ function writeSeam(root, decisions = DEFAULT_DECISIONS) {
3609
3659
  target: "seam",
3610
3660
  action: "info",
3611
3661
  text: "Place the injection seam",
3612
- note: seam.instruction
3662
+ note: withWarning(seam.instruction, outdated)
3613
3663
  };
3614
3664
  }
3665
+ const alsoNote = writeAlso(root, seam.also);
3615
3666
  const file = (0, import_node_path6.join)(root, ...seam.file.split("/"));
3616
- const notes = seam.notes.length === 0 ? "" : `
3617
- ${seam.notes.map((n) => ` ${n}`).join("\n")}`;
3667
+ const baseNotes = [...seam.notes, ...alsoNote === void 0 ? [] : [alsoNote]];
3668
+ const notes = baseNotes.length === 0 ? "" : `
3669
+ ${baseNotes.map((n) => ` ${n}`).join("\n")}`;
3618
3670
  if ((0, import_node_fs3.existsSync)(file)) {
3619
3671
  return {
3620
3672
  target: "seam",
3621
3673
  action: "info",
3622
3674
  text: `Add the injection seam to ${seam.file}`,
3623
- note: `${seam.ifPresent}${notes}`
3675
+ note: withWarning(`${seam.ifPresent}${notes}`, outdated)
3624
3676
  };
3625
3677
  }
3626
3678
  (0, import_node_fs3.mkdirSync)((0, import_node_path6.dirname)(file), { recursive: true });
3627
3679
  (0, import_node_fs3.writeFileSync)(file, seam.content, "utf8");
3628
3680
  return {
3629
3681
  target: "seam",
3630
- action: "created",
3682
+ action: outdated === void 0 ? "created" : "info",
3631
3683
  text: `Wrote ${seam.file} (runs the injection before your app)`,
3632
- ...notes === "" ? {} : { note: notes.trimStart() }
3684
+ ...notes === "" && outdated === void 0 ? {} : { note: withWarning(notes.trimStart(), outdated) }
3633
3685
  };
3634
3686
  }
3635
- function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS) {
3687
+ function writeAlso(root, also) {
3688
+ if (also === void 0) {
3689
+ return void 0;
3690
+ }
3691
+ const file = (0, import_node_path6.join)(root, ...also.file.split("/"));
3692
+ if ((0, import_node_fs3.existsSync)(file)) {
3693
+ return also.ifPresent;
3694
+ }
3695
+ (0, import_node_fs3.mkdirSync)((0, import_node_path6.dirname)(file), { recursive: true });
3696
+ (0, import_node_fs3.writeFileSync)(file, also.content, "utf8");
3697
+ return `Wrote ${also.file} to register it.`;
3698
+ }
3699
+ function withWarning(note, warning) {
3700
+ if (warning === void 0) {
3701
+ return note;
3702
+ }
3703
+ return note === "" ? warning : `${note}
3704
+ \u26A0 ${warning}`;
3705
+ }
3706
+ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
3636
3707
  const steps = [
3637
3708
  ensurePenvDir(root),
3638
3709
  writeSchemaFile(root, fields, draft, decisions),
@@ -3640,13 +3711,13 @@ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS) {
3640
3711
  writeTsconfigAlias(root, decisions),
3641
3712
  writeGitignore(root, decisions)
3642
3713
  ];
3643
- const seam = writeSeam(root, decisions);
3714
+ const seam = writeSeam(root, decisions, framework);
3644
3715
  return seam === void 0 ? steps : [...steps, seam];
3645
3716
  }
3646
3717
  function runInit(options) {
3647
3718
  const root = (0, import_node_path6.resolve)(options.cwd);
3648
3719
  const decisions = options.decisions ?? planInit(root).decisions;
3649
- return { root, decisions, steps: scaffold(root, [], false, decisions) };
3720
+ return { root, decisions, steps: scaffold(root, [], false, decisions, options.framework) };
3650
3721
  }
3651
3722
  function renderInit(result2) {
3652
3723
  const steps = result2.steps.map((step) => {
@@ -3713,7 +3784,15 @@ var initCommand = (0, import_citty9.defineCommand)({
3713
3784
  if (!asked) {
3714
3785
  write([...plan2.notes, ""]);
3715
3786
  }
3716
- write(renderInit(runInit({ cwd: root, decisions })));
3787
+ write(
3788
+ renderInit(
3789
+ runInit({
3790
+ cwd: root,
3791
+ decisions,
3792
+ ...plan2.detected && { framework: plan2.detected.name }
3793
+ })
3794
+ )
3795
+ );
3717
3796
  });
3718
3797
  }
3719
3798
  });