@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 +107 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +107 -28
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.d.cts
CHANGED
|
@@ -310,6 +310,8 @@ interface InitOptions {
|
|
|
310
310
|
readonly cwd: string;
|
|
311
311
|
/** What to write. Omitted means the plan's defaults, as `--yes` takes them. */
|
|
312
312
|
readonly decisions?: InitDecisions;
|
|
313
|
+
/** The detected framework name, passed by the command so the seam step need not re-detect it. */
|
|
314
|
+
readonly framework?: string;
|
|
313
315
|
}
|
|
314
316
|
interface AliasEdit {
|
|
315
317
|
readonly source: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -310,6 +310,8 @@ interface InitOptions {
|
|
|
310
310
|
readonly cwd: string;
|
|
311
311
|
/** What to write. Omitted means the plan's defaults, as `--yes` takes them. */
|
|
312
312
|
readonly decisions?: InitDecisions;
|
|
313
|
+
/** The detected framework name, passed by the command so the seam step need not re-detect it. */
|
|
314
|
+
readonly framework?: string;
|
|
313
315
|
}
|
|
314
316
|
interface AliasEdit {
|
|
315
317
|
readonly source: string;
|
package/dist/index.js
CHANGED
|
@@ -2833,7 +2833,7 @@ function occupied(cwd, relative3) {
|
|
|
2833
2833
|
return existsSync(file) && !exportsSchema(file);
|
|
2834
2834
|
}
|
|
2835
2835
|
function schemaFileFor(cwd) {
|
|
2836
|
-
const dir =
|
|
2836
|
+
const dir = srcPrefix(cwd);
|
|
2837
2837
|
const preferred = `${dir}env.ts`;
|
|
2838
2838
|
if (!occupied(cwd, preferred)) {
|
|
2839
2839
|
return { file: preferred };
|
|
@@ -2844,6 +2844,9 @@ function schemaFileFor(cwd) {
|
|
|
2844
2844
|
}
|
|
2845
2845
|
return { file: DEFAULT_SCHEMA_FILE, displaced: preferred };
|
|
2846
2846
|
}
|
|
2847
|
+
function srcPrefix(cwd) {
|
|
2848
|
+
return existsSync(join(cwd, "src")) ? "src/" : "";
|
|
2849
|
+
}
|
|
2847
2850
|
function dependenciesOf(cwd) {
|
|
2848
2851
|
const manifest = manifestOf(cwd);
|
|
2849
2852
|
if (manifest === void 0) {
|
|
@@ -2990,19 +2993,25 @@ function bun({ alias }) {
|
|
|
2990
2993
|
kind: "scaffold",
|
|
2991
2994
|
file: ".penv/preload.ts",
|
|
2992
2995
|
content: `// Bun evaluates this file before your entry point, because it is listed under
|
|
2993
|
-
// \`preload\` in bunfig.toml
|
|
2994
|
-
//
|
|
2996
|
+
// \`preload\` in bunfig.toml. Running it injects your config into process.env
|
|
2997
|
+
// before any library reads it.
|
|
2995
2998
|
import "${alias}";
|
|
2996
2999
|
`,
|
|
2997
3000
|
ifPresent: `Keep .penv/preload.ts as \`import "${alias}";\`.`,
|
|
2998
|
-
notes: [
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3001
|
+
notes: [`\`${alias}\` must be a tsconfig \`paths\` alias for Bun to resolve it.`],
|
|
3002
|
+
// The preload file is inert until bunfig.toml points at it — so penv writes
|
|
3003
|
+
// that too, both for the runtime and for `bun test`. Only when there is no
|
|
3004
|
+
// bunfig to overwrite; an existing one is the user's, and penv prints instead.
|
|
3005
|
+
also: {
|
|
3006
|
+
file: "bunfig.toml",
|
|
3007
|
+
content: 'preload = ["./.penv/preload.ts"]\n\n[test]\npreload = ["./.penv/preload.ts"]\n',
|
|
3008
|
+
ifPresent: 'Add "./.penv/preload.ts" to the `preload` array in bunfig.toml \u2014 and mirror it under `[test]` so `bun test` injects too.'
|
|
3009
|
+
}
|
|
3002
3010
|
};
|
|
3003
3011
|
}
|
|
3004
|
-
function node({ alias,
|
|
3005
|
-
const
|
|
3012
|
+
function node({ alias, schemaFile }) {
|
|
3013
|
+
const builtSchema = `./${schemaFile.replace(/\.ts$/, ".js")}`;
|
|
3014
|
+
const runtimeSpecifier = alias.startsWith("#") ? alias : builtSchema;
|
|
3006
3015
|
return {
|
|
3007
3016
|
kind: "instruct",
|
|
3008
3017
|
instruction: `Plain Node / Express / Fastify have no framework hook that runs before your
|
|
@@ -3022,10 +3031,17 @@ import before it. If you have no server.ts yet, create it from TanStack's server
|
|
|
3022
3031
|
template and put that import first.`
|
|
3023
3032
|
};
|
|
3024
3033
|
}
|
|
3025
|
-
function astro(
|
|
3034
|
+
function astro({ alias }) {
|
|
3026
3035
|
return {
|
|
3027
3036
|
kind: "instruct",
|
|
3028
|
-
instruction:
|
|
3037
|
+
instruction: `Astro has no single pre-app hook. Add a small integration to astro.config that
|
|
3038
|
+
injects the import into every SSR page, then cover the routes it cannot reach:
|
|
3039
|
+
\u2022 In astro.config integrations, add:
|
|
3040
|
+
{ name: "penv", hooks: { "astro:config:setup": ({ injectScript }) =>
|
|
3041
|
+
injectScript("page-ssr", 'import "${alias}";') } }
|
|
3042
|
+
\u2022 Also add \`import "${alias}";\` as the first line of src/middleware.ts and of any
|
|
3043
|
+
endpoint route (src/pages/*.ts) that imports a library reading process.env.
|
|
3044
|
+
Injection only applies under a server (on-demand) adapter, not a static build.`
|
|
3029
3045
|
};
|
|
3030
3046
|
}
|
|
3031
3047
|
function viteSpa(_context) {
|
|
@@ -3226,17 +3242,18 @@ async function promptForDecisions(plan2, io) {
|
|
|
3226
3242
|
);
|
|
3227
3243
|
io.write("");
|
|
3228
3244
|
}
|
|
3229
|
-
const inject = seamKindFor(plan2) === "none" ? false : await askInject(io);
|
|
3230
3245
|
const proceed = (await io.ask(prompt("Proceed?", "Y/n"))).trim().toLowerCase();
|
|
3231
3246
|
if (proceed.length > 0 && proceed !== "y" && proceed !== "yes") {
|
|
3232
3247
|
return void 0;
|
|
3233
3248
|
}
|
|
3249
|
+
const inject = seamKindFor(plan2) === "none" ? false : await askInject(io);
|
|
3234
3250
|
return { ...plan2.decisions, environments, inject };
|
|
3235
3251
|
}
|
|
3236
3252
|
function seamKindFor(plan2) {
|
|
3237
3253
|
return seamFor(plan2.detected?.name, {
|
|
3238
3254
|
alias: plan2.decisions.alias,
|
|
3239
|
-
srcDir: plan2.decisions.schemaFile.startsWith("src/") ? "src/" : ""
|
|
3255
|
+
srcDir: plan2.decisions.schemaFile.startsWith("src/") ? "src/" : "",
|
|
3256
|
+
schemaFile: plan2.decisions.schemaFile
|
|
3240
3257
|
}).kind;
|
|
3241
3258
|
}
|
|
3242
3259
|
async function askInject(io) {
|
|
@@ -3631,15 +3648,48 @@ function writeGitignore(root, decisions = DEFAULT_DECISIONS) {
|
|
|
3631
3648
|
text: `${existing === void 0 ? "Created" : "Updated"} ${relative3}`
|
|
3632
3649
|
};
|
|
3633
3650
|
}
|
|
3634
|
-
|
|
3635
|
-
|
|
3651
|
+
var INJECT_MIN_VERSION = "0.6.0";
|
|
3652
|
+
function outdatedRuntimeWarning(root) {
|
|
3653
|
+
const version = installedPenvVersion(root);
|
|
3654
|
+
if (version === void 0 || !isBelow(version, INJECT_MIN_VERSION)) {
|
|
3655
|
+
return void 0;
|
|
3656
|
+
}
|
|
3657
|
+
return `Injection needs @penvhq/penv ${INJECT_MIN_VERSION}+ \u2014 this project has ${version}, whose \`load\` ignores \`{ inject: true }\`. Upgrade, or process.env stays empty.`;
|
|
3658
|
+
}
|
|
3659
|
+
function installedPenvVersion(root) {
|
|
3660
|
+
const file = join2(root, "node_modules", "@penvhq", "penv", "package.json");
|
|
3661
|
+
if (!existsSync2(file)) {
|
|
3662
|
+
return void 0;
|
|
3663
|
+
}
|
|
3664
|
+
try {
|
|
3665
|
+
const version = JSON.parse(readFileSync2(file, "utf8")).version;
|
|
3666
|
+
return typeof version === "string" ? version : void 0;
|
|
3667
|
+
} catch {
|
|
3668
|
+
return void 0;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
function releaseTriple(version) {
|
|
3672
|
+
const parts = (version.split("-")[0] ?? "").split(".");
|
|
3673
|
+
const at = (i) => Number.parseInt(parts[i] ?? "0", 10) || 0;
|
|
3674
|
+
return [at(0), at(1), at(2)];
|
|
3675
|
+
}
|
|
3676
|
+
function isBelow(a, b) {
|
|
3677
|
+
const [am, an, ap] = releaseTriple(a);
|
|
3678
|
+
const [bm, bn, bp] = releaseTriple(b);
|
|
3679
|
+
if (am !== bm) return am < bm;
|
|
3680
|
+
if (an !== bn) return an < bn;
|
|
3681
|
+
return ap < bp;
|
|
3636
3682
|
}
|
|
3637
|
-
function writeSeam(root, decisions = DEFAULT_DECISIONS) {
|
|
3683
|
+
function writeSeam(root, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
|
|
3638
3684
|
if (!decisions.inject) {
|
|
3639
3685
|
return void 0;
|
|
3640
3686
|
}
|
|
3641
|
-
const
|
|
3642
|
-
|
|
3687
|
+
const seam = seamFor(framework, {
|
|
3688
|
+
alias: decisions.alias,
|
|
3689
|
+
srcDir: srcPrefix(root),
|
|
3690
|
+
schemaFile: decisions.schemaFile
|
|
3691
|
+
});
|
|
3692
|
+
const outdated = outdatedRuntimeWarning(root);
|
|
3643
3693
|
if (seam.kind === "none") {
|
|
3644
3694
|
return { target: "seam", action: "info", text: "No injection seam needed", note: seam.reason };
|
|
3645
3695
|
}
|
|
@@ -3648,30 +3698,51 @@ function writeSeam(root, decisions = DEFAULT_DECISIONS) {
|
|
|
3648
3698
|
target: "seam",
|
|
3649
3699
|
action: "info",
|
|
3650
3700
|
text: "Place the injection seam",
|
|
3651
|
-
note: seam.instruction
|
|
3701
|
+
note: withWarning(seam.instruction, outdated)
|
|
3652
3702
|
};
|
|
3653
3703
|
}
|
|
3704
|
+
const alsoNote = writeAlso(root, seam.also);
|
|
3654
3705
|
const file = join2(root, ...seam.file.split("/"));
|
|
3655
|
-
const
|
|
3656
|
-
|
|
3706
|
+
const baseNotes = [...seam.notes, ...alsoNote === void 0 ? [] : [alsoNote]];
|
|
3707
|
+
const notes = baseNotes.length === 0 ? "" : `
|
|
3708
|
+
${baseNotes.map((n) => ` ${n}`).join("\n")}`;
|
|
3657
3709
|
if (existsSync2(file)) {
|
|
3658
3710
|
return {
|
|
3659
3711
|
target: "seam",
|
|
3660
3712
|
action: "info",
|
|
3661
3713
|
text: `Add the injection seam to ${seam.file}`,
|
|
3662
|
-
note: `${seam.ifPresent}${notes}
|
|
3714
|
+
note: withWarning(`${seam.ifPresent}${notes}`, outdated)
|
|
3663
3715
|
};
|
|
3664
3716
|
}
|
|
3665
3717
|
mkdirSync(dirname3(file), { recursive: true });
|
|
3666
3718
|
writeFileSync2(file, seam.content, "utf8");
|
|
3667
3719
|
return {
|
|
3668
3720
|
target: "seam",
|
|
3669
|
-
action: "created",
|
|
3721
|
+
action: outdated === void 0 ? "created" : "info",
|
|
3670
3722
|
text: `Wrote ${seam.file} (runs the injection before your app)`,
|
|
3671
|
-
...notes === "" ? {} : { note: notes.trimStart() }
|
|
3723
|
+
...notes === "" && outdated === void 0 ? {} : { note: withWarning(notes.trimStart(), outdated) }
|
|
3672
3724
|
};
|
|
3673
3725
|
}
|
|
3674
|
-
function
|
|
3726
|
+
function writeAlso(root, also) {
|
|
3727
|
+
if (also === void 0) {
|
|
3728
|
+
return void 0;
|
|
3729
|
+
}
|
|
3730
|
+
const file = join2(root, ...also.file.split("/"));
|
|
3731
|
+
if (existsSync2(file)) {
|
|
3732
|
+
return also.ifPresent;
|
|
3733
|
+
}
|
|
3734
|
+
mkdirSync(dirname3(file), { recursive: true });
|
|
3735
|
+
writeFileSync2(file, also.content, "utf8");
|
|
3736
|
+
return `Wrote ${also.file} to register it.`;
|
|
3737
|
+
}
|
|
3738
|
+
function withWarning(note, warning) {
|
|
3739
|
+
if (warning === void 0) {
|
|
3740
|
+
return note;
|
|
3741
|
+
}
|
|
3742
|
+
return note === "" ? warning : `${note}
|
|
3743
|
+
\u26A0 ${warning}`;
|
|
3744
|
+
}
|
|
3745
|
+
function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
|
|
3675
3746
|
const steps = [
|
|
3676
3747
|
ensurePenvDir(root),
|
|
3677
3748
|
writeSchemaFile(root, fields, draft, decisions),
|
|
@@ -3679,13 +3750,13 @@ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS) {
|
|
|
3679
3750
|
writeTsconfigAlias(root, decisions),
|
|
3680
3751
|
writeGitignore(root, decisions)
|
|
3681
3752
|
];
|
|
3682
|
-
const seam = writeSeam(root, decisions);
|
|
3753
|
+
const seam = writeSeam(root, decisions, framework);
|
|
3683
3754
|
return seam === void 0 ? steps : [...steps, seam];
|
|
3684
3755
|
}
|
|
3685
3756
|
function runInit(options) {
|
|
3686
3757
|
const root = resolve4(options.cwd);
|
|
3687
3758
|
const decisions = options.decisions ?? planInit(root).decisions;
|
|
3688
|
-
return { root, decisions, steps: scaffold(root, [], false, decisions) };
|
|
3759
|
+
return { root, decisions, steps: scaffold(root, [], false, decisions, options.framework) };
|
|
3689
3760
|
}
|
|
3690
3761
|
function renderInit(result2) {
|
|
3691
3762
|
const steps = result2.steps.map((step) => {
|
|
@@ -3752,7 +3823,15 @@ var initCommand = defineCommand9({
|
|
|
3752
3823
|
if (!asked) {
|
|
3753
3824
|
write([...plan2.notes, ""]);
|
|
3754
3825
|
}
|
|
3755
|
-
write(
|
|
3826
|
+
write(
|
|
3827
|
+
renderInit(
|
|
3828
|
+
runInit({
|
|
3829
|
+
cwd: root,
|
|
3830
|
+
decisions,
|
|
3831
|
+
...plan2.detected && { framework: plan2.detected.name }
|
|
3832
|
+
})
|
|
3833
|
+
)
|
|
3834
|
+
);
|
|
3756
3835
|
});
|
|
3757
3836
|
}
|
|
3758
3837
|
});
|