@penvhq/cli 0.16.1 → 0.16.2

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.d.cts CHANGED
@@ -123,10 +123,22 @@ interface ScopeOptions {
123
123
  readonly environment?: string;
124
124
  readonly local?: boolean;
125
125
  }
126
+ /** What `set` knows when it has to ask for the value: which file, and whether to hide the typing. */
127
+ interface SetPrompt {
128
+ readonly parameter: string;
129
+ /** Whether meta says this is a secret, so the wrapper mutes the echo. */
130
+ readonly secret: boolean;
131
+ }
126
132
  interface SetOptions extends ScopeOptions {
127
133
  readonly cwd: string;
128
134
  readonly key: string;
129
- readonly value: string;
135
+ /** The value, when the command line carried one. */
136
+ readonly value?: string;
137
+ /**
138
+ * How the value is obtained when the command line carried none. `undefined`
139
+ * or an empty answer is a refusal: an empty secret is never what was meant.
140
+ */
141
+ readonly ask?: (prompt: SetPrompt) => Promise<string | undefined>;
130
142
  }
131
143
  interface SetResult {
132
144
  readonly parameter: string;
@@ -183,10 +195,7 @@ interface FillPrompt {
183
195
  /** The value file's key, kebab and slash-separated — the name the user need never derive. */
184
196
  readonly parameter: string;
185
197
  readonly environment: string;
186
- /**
187
- * Whether meta says this is a secret. Carried so a wrapper can mute the echo;
188
- * v1 does not, and the drift carries no meta, so this is `false` today.
189
- */
198
+ /** Whether meta says this is a secret, so the wrapper mutes the echo. */
190
199
  readonly secret: boolean;
191
200
  /**
192
201
  * Whether the schema excuses absence — `.optional()`, `.default()`. An answer
package/dist/index.d.ts CHANGED
@@ -123,10 +123,22 @@ interface ScopeOptions {
123
123
  readonly environment?: string;
124
124
  readonly local?: boolean;
125
125
  }
126
+ /** What `set` knows when it has to ask for the value: which file, and whether to hide the typing. */
127
+ interface SetPrompt {
128
+ readonly parameter: string;
129
+ /** Whether meta says this is a secret, so the wrapper mutes the echo. */
130
+ readonly secret: boolean;
131
+ }
126
132
  interface SetOptions extends ScopeOptions {
127
133
  readonly cwd: string;
128
134
  readonly key: string;
129
- readonly value: string;
135
+ /** The value, when the command line carried one. */
136
+ readonly value?: string;
137
+ /**
138
+ * How the value is obtained when the command line carried none. `undefined`
139
+ * or an empty answer is a refusal: an empty secret is never what was meant.
140
+ */
141
+ readonly ask?: (prompt: SetPrompt) => Promise<string | undefined>;
130
142
  }
131
143
  interface SetResult {
132
144
  readonly parameter: string;
@@ -183,10 +195,7 @@ interface FillPrompt {
183
195
  /** The value file's key, kebab and slash-separated — the name the user need never derive. */
184
196
  readonly parameter: string;
185
197
  readonly environment: string;
186
- /**
187
- * Whether meta says this is a secret. Carried so a wrapper can mute the echo;
188
- * v1 does not, and the drift carries no meta, so this is `false` today.
189
- */
198
+ /** Whether meta says this is a secret, so the wrapper mutes the echo. */
190
199
  readonly secret: boolean;
191
200
  /**
192
201
  * Whether the schema excuses absence — `.optional()`, `.default()`. An answer
package/dist/index.js CHANGED
@@ -2187,8 +2187,21 @@ import { defineCommand as defineCommand6 } from "citty";
2187
2187
 
2188
2188
  // src/input.ts
2189
2189
  import { createInterface } from "readline";
2190
+ import { Writable } from "stream";
2191
+ function isTerminal(output) {
2192
+ return output.isTTY === true;
2193
+ }
2190
2194
  function lineReader(input = process.stdin, output = process.stdout) {
2191
- const rl = createInterface({ input, output });
2195
+ let muted = false;
2196
+ const sink = new Writable({
2197
+ write(chunk, _encoding, callback) {
2198
+ if (!muted) {
2199
+ output.write(chunk);
2200
+ }
2201
+ callback();
2202
+ }
2203
+ });
2204
+ const rl = createInterface({ input, output: sink, terminal: isTerminal(output) });
2192
2205
  const buffered = [];
2193
2206
  const waiting = [];
2194
2207
  let closed = false;
@@ -2207,7 +2220,7 @@ function lineReader(input = process.stdin, output = process.stdout) {
2207
2220
  }
2208
2221
  });
2209
2222
  return {
2210
- ask(question) {
2223
+ ask(question, options) {
2211
2224
  const early = buffered.shift();
2212
2225
  if (early !== void 0) {
2213
2226
  return Promise.resolve(early);
@@ -2215,6 +2228,19 @@ function lineReader(input = process.stdin, output = process.stdout) {
2215
2228
  if (closed) {
2216
2229
  return Promise.resolve(void 0);
2217
2230
  }
2231
+ if (options?.secret === true) {
2232
+ output.write(question);
2233
+ muted = true;
2234
+ rl.setPrompt("");
2235
+ rl.prompt();
2236
+ return new Promise((resolve9) => {
2237
+ waiting.push((line) => {
2238
+ muted = false;
2239
+ output.write("\n");
2240
+ resolve9(line);
2241
+ });
2242
+ });
2243
+ }
2218
2244
  rl.setPrompt(question);
2219
2245
  rl.prompt();
2220
2246
  return new Promise((resolve9) => {
@@ -3551,16 +3577,30 @@ async function runSet(options) {
3551
3577
  const ref = refFromKey(options.key, project.config);
3552
3578
  const scope = targetScope(project, options, options.key);
3553
3579
  const environment = policyEnvironment(project, options);
3580
+ const value = options.value ?? await askForValue(project, ref, environment, options);
3554
3581
  const { encrypted, location } = await sealAwareWrite({
3555
3582
  project,
3556
3583
  provider: project.provider,
3557
3584
  ref,
3558
3585
  scope,
3559
- value: options.value,
3586
+ value,
3560
3587
  environment
3561
3588
  });
3562
3589
  return { parameter: options.key, location, encrypted };
3563
3590
  }
3591
+ async function askForValue(project, ref, environment, options) {
3592
+ const parameter = parameterId4(ref);
3593
+ const remedy = `Pass it on the command line \u2014 \`penv set ${options.key} <value>\` \u2014 pipe it in, or answer the prompt.`;
3594
+ if (options.ask === void 0) {
3595
+ throw new PenvError9("VALUE_MISSING", `No value was given for parameter ${parameter}`, remedy);
3596
+ }
3597
+ const secret = isSecret4(await project.provider.readMeta(ref), environment);
3598
+ const answer = await options.ask({ parameter, secret });
3599
+ if (answer === void 0 || answer.length === 0) {
3600
+ throw new PenvError9("VALUE_MISSING", `No value was entered for parameter ${parameter}`, remedy);
3601
+ }
3602
+ return answer;
3603
+ }
3564
3604
  function renderSet(result2) {
3565
3605
  return formatRows([
3566
3606
  {
@@ -3598,18 +3638,29 @@ var setCommand = defineCommand8({
3598
3638
  },
3599
3639
  run({ args }) {
3600
3640
  return guard(async () => {
3601
- const value = args.value ?? await readStdin();
3602
- write(
3603
- renderSet(
3604
- await runSet({
3605
- cwd: process.cwd(),
3606
- key: args.key,
3607
- value,
3608
- ...args.env === void 0 ? {} : { environment: args.env },
3609
- ...args.local === void 0 ? {} : { local: args.local }
3610
- })
3611
- )
3612
- );
3641
+ const base = {
3642
+ cwd: process.cwd(),
3643
+ key: args.key,
3644
+ ...args.env === void 0 ? {} : { environment: args.env },
3645
+ ...args.local === void 0 ? {} : { local: args.local }
3646
+ };
3647
+ if (args.value !== void 0) {
3648
+ write(renderSet(await runSet({ ...base, value: args.value })));
3649
+ return;
3650
+ }
3651
+ if (process.stdin.isTTY !== true) {
3652
+ write(renderSet(await runSet({ ...base, value: await readStdin() })));
3653
+ return;
3654
+ }
3655
+ const reader = lineReader();
3656
+ try {
3657
+ const ask = ({ parameter, secret }) => reader.ask(prompt(parameter, secret ? "secret, typing is hidden" : void 0), {
3658
+ secret
3659
+ });
3660
+ write(renderSet(await runSet({ ...base, ask })));
3661
+ } finally {
3662
+ reader.close();
3663
+ }
3613
3664
  });
3614
3665
  }
3615
3666
  });
@@ -3751,7 +3802,7 @@ var decryptCommand = defineCommand9({
3751
3802
  });
3752
3803
 
3753
3804
  // src/commands/fill.ts
3754
- import { PenvError as PenvError11, recordPath as recordPath4, SCHEMA_SHAPE_FILE as SCHEMA_SHAPE_FILE2 } from "@penvhq/core";
3805
+ import { isSecret as isSecret6, PenvError as PenvError11, recordPath as recordPath4, SCHEMA_SHAPE_FILE as SCHEMA_SHAPE_FILE2 } from "@penvhq/core";
3755
3806
  import { defineCommand as defineCommand10 } from "citty";
3756
3807
  var BLOCKING = /* @__PURE__ */ new Set(["config", "collision", "reserved"]);
3757
3808
  async function runFill(options) {
@@ -3773,6 +3824,8 @@ ${detail}`,
3773
3824
  `Fix these \u2014 \`penv validate\` reports them \u2014 then run \`penv fill\`. If you have not written a schema yet, declare the required parameters in ${SCHEMA_SHAPE_FILE2}.`
3774
3825
  );
3775
3826
  }
3827
+ const project = openProject(options.cwd);
3828
+ const secretFor = async (ref) => isSecret6(await project.provider.readMeta(ref), environment);
3776
3829
  const written = [];
3777
3830
  const skipped2 = [];
3778
3831
  const kept = [];
@@ -3787,7 +3840,7 @@ ${detail}`,
3787
3840
  const value = await options.ask({
3788
3841
  parameter: key,
3789
3842
  environment,
3790
- secret: false,
3843
+ secret: await secretFor(ref),
3791
3844
  optional: false
3792
3845
  });
3793
3846
  if (value === void 0 || value === "") {
@@ -3811,7 +3864,7 @@ ${detail}`,
3811
3864
  const value = await options.ask({
3812
3865
  parameter: key,
3813
3866
  environment,
3814
- secret: false,
3867
+ secret: await secretFor(ref),
3815
3868
  optional: true,
3816
3869
  ...item.defaultValue === void 0 ? {} : { defaultValue: item.defaultValue }
3817
3870
  });
@@ -3881,7 +3934,7 @@ var fillCommand = defineCommand10({
3881
3934
  run({ args }) {
3882
3935
  return guard(async () => {
3883
3936
  const reader = lineReader();
3884
- const ask = (prompt2) => reader.ask(prompt(prompt2.parameter, contextFor(prompt2)));
3937
+ const ask = (prompt2) => reader.ask(prompt(prompt2.parameter, contextFor(prompt2)), { secret: prompt2.secret });
3885
3938
  try {
3886
3939
  write(
3887
3940
  renderFill(