@penvhq/cli 0.1.0 → 0.2.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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as citty from 'citty';
2
- import { Sink, ParameterRef, Scope } from '@penvhq/core';
2
+ import { Sink, Provider, ParameterRef, Scope, RotationMechanism, RotationState } from '@penvhq/core';
3
3
 
4
4
  /**
5
5
  * A check reports one of four verdicts. `unknown` — a check that ran but could
@@ -8,7 +8,7 @@ import { Sink, ParameterRef, Scope } from '@penvhq/core';
8
8
  * and a write-only sink makes most of what doctor can say the second kind.
9
9
  */
10
10
  type DoctorSeverity = "pass" | "warning" | "failure" | "unknown";
11
- type DoctorCheck = "schema" | "missing" | "declared" | "weak" | "unused" | "unscoped-fallback" | "plaintext-secret" | "public-secret" | "encryption" | "provider" | "sink-unreachable" | "sink-name-drift" | "sink-manual-edit" | "sink-value-drift";
11
+ type DoctorCheck = "schema" | "missing" | "declared" | "weak" | "unused" | "unscoped-fallback" | "plaintext-secret" | "public-secret" | "encryption" | "rotation-overdue" | "rotation-stuck" | "provider-value-drift" | "provider" | "sink-unreachable" | "sink-name-drift" | "sink-manual-edit" | "sink-value-drift";
12
12
  interface DoctorFinding {
13
13
  readonly check: DoctorCheck;
14
14
  readonly severity: DoctorSeverity;
@@ -29,6 +29,17 @@ interface DoctorOptions {
29
29
  readonly environment?: string;
30
30
  /** Injected in tests: the sink to check against. Defaults to the one the config declares. */
31
31
  readonly sink?: Sink;
32
+ /**
33
+ * Injected in tests: the source-of-truth provider to compare the local tree
34
+ * against. Defaults to the one the config declares (`sourceProviderFor`).
35
+ * Mirrors `sink`, for the same reason — the drift checks stay driveable without
36
+ * a live backend.
37
+ */
38
+ readonly source?: Provider;
39
+ /** Injected in tests: the wall-clock reading the rotation clocks are read against. Defaults to now. */
40
+ readonly now?: string;
41
+ /** Injected in tests: how long a `dual-valid` window may stay open before it reads as stuck. Defaults to 24h. */
42
+ readonly stuckThresholdMs?: number;
32
43
  }
33
44
  declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
34
45
  declare function renderDoctor(report: DoctorReport): string[];
@@ -53,10 +64,8 @@ interface SetResult {
53
64
  /**
54
65
  * Writes one value file, sealing it when meta says the parameter is a secret.
55
66
  *
56
- * There is no `--encrypt` flag, deliberately. A flag would make the command line
57
- * the authority on what is secret, and meta is (invariant 14) — the `.enc` marker
58
- * is validated *against* the policy, so a marker chosen at the keyboard would
59
- * invert the direction the check runs in. The policy decides; `set` obeys.
67
+ * The scope is chosen from the flags, then the seal-and-twin write is the shared
68
+ * {@link sealAwareWrite}, against the local tree — the store `set` always edits.
60
69
  */
61
70
  declare function runSet(options: SetOptions): Promise<SetResult>;
62
71
 
@@ -437,6 +446,47 @@ interface MoveResult {
437
446
  declare function runMove(options: MoveOptions): Promise<MoveResult>;
438
447
  declare function renderMove(result: MoveResult): string[];
439
448
 
449
+ /**
450
+ * `penv pull` — materialise the local `.penv` tree from an environment's
451
+ * source-of-truth provider. It is the inverse of the deploy-time injection most
452
+ * stacks already have: instead of reading the tree to feed a backend, it reads
453
+ * the backend to feed the tree.
454
+ *
455
+ * It only means anything when the environment declares a real backend
456
+ * (`vault`, `mock`): those hold the truth somewhere penv does not edit in place,
457
+ * and pulling copies it down so every other command — which reads the local tree
458
+ * — sees it. An environment with no separate `providers` entry has the local
459
+ * tree *as* its source of truth, so a pull would be the tree copying onto
460
+ * itself; that degenerate case is reported as nothing to do, never a self-copy.
461
+ *
462
+ * Values cross verbatim. They are opaque envelope strings the source holds and
463
+ * penv does not open here — a sealed value stays sealed, byte-for-byte, so the
464
+ * key that opens it never has to be present to pull it.
465
+ */
466
+ interface PullOptions {
467
+ readonly cwd: string;
468
+ readonly environment?: string;
469
+ }
470
+ interface PullResult {
471
+ readonly environment: string;
472
+ /** The source provider's type — `filesystem` when the environment declares no separate backend. */
473
+ readonly source: string;
474
+ /**
475
+ * True when the source *is* the local tree, so there was nothing to pull. The
476
+ * caller distinguishes "pulled nothing because the backend was empty" from
477
+ * "there is no backend to pull from" — opposite situations.
478
+ */
479
+ readonly localSource: boolean;
480
+ /** Value files written into the local tree. */
481
+ readonly values: number;
482
+ /** Meta files written into the local tree. */
483
+ readonly meta: number;
484
+ /** Distinct parameters the pull touched, at any scope. */
485
+ readonly refs: number;
486
+ }
487
+ declare function runPull(options: PullOptions): Promise<PullResult>;
488
+ declare function renderPull(result: PullResult): string[];
489
+
440
490
  /** The per-environment meta field recording penv's last push, compared against the destination's `updatedAt`. */
441
491
  declare const LAST_PUSHED_KEY = "lastPushedAt";
442
492
  interface PushOptions {
@@ -475,6 +525,44 @@ interface RemoveResult {
475
525
  }
476
526
  declare function runRemove(options: RemoveOptions): Promise<RemoveResult>;
477
527
 
528
+ interface RotateOptions {
529
+ readonly cwd: string;
530
+ readonly key: string;
531
+ readonly environment?: string;
532
+ /** Open a `dual-valid` window: write the new value while the old is still retained. */
533
+ readonly begin?: boolean;
534
+ /** Close a `dual-valid` window: return to `active`, stamp the completion. */
535
+ readonly complete?: boolean;
536
+ /**
537
+ * The new value. A `begin` and an `atomic-cutover` flip write it; a `complete`
538
+ * does not touch the value at all, so it needs none. Injected in tests; on the
539
+ * CLI it is the positional argument or stdin, the same source `set` reads.
540
+ */
541
+ readonly value?: string;
542
+ /** Injected in tests: the wall-clock reading recorded in meta. Defaults to now. */
543
+ readonly now?: string;
544
+ }
545
+ /** The single step a run performed — the three the two mechanisms decompose into. */
546
+ type RotatePhase = "begin" | "complete" | "cutover";
547
+ interface RotateResult {
548
+ readonly parameter: string;
549
+ readonly environment: string;
550
+ readonly mechanism: RotationMechanism;
551
+ readonly phase: RotatePhase;
552
+ /** The source provider's type — where the value and its meta were written. */
553
+ readonly source: string;
554
+ /** True when this run wrote a new value. `begin` and `cutover` do; `complete` does not. */
555
+ readonly wroteValue: boolean;
556
+ /** The rotation state after this run — `rotating` after a begin, `active` otherwise. */
557
+ readonly state: RotationState;
558
+ /** When the current window opened, ISO. Set only after a `begin`, else `null`. */
559
+ readonly rotatingSince: string | null;
560
+ /** When a rotation last completed, ISO. Set after a `complete` or a `cutover`. */
561
+ readonly lastRotated: string | null;
562
+ }
563
+ declare function runRotate(options: RotateOptions): Promise<RotateResult>;
564
+ declare function renderRotate(result: RotateResult): string[];
565
+
478
566
  interface WatchOptions {
479
567
  readonly cwd: string;
480
568
  readonly environment?: string;
@@ -523,4 +611,4 @@ declare function renderWatch(result: ValidateResult): string[];
523
611
  declare const main: citty.CommandDef<citty.ArgsDef>;
524
612
  declare function runMain(): Promise<void>;
525
613
 
526
- export { type DoctorCheck, type DoctorFinding, type DoctorReport, type DoctorSeverity, type GenerateResult, type GetExplanation, type ImportReport, type InitResult, type InitStep, LAST_PUSHED_KEY, type ListResult, type MoveResult, type PushOptions, type PushResult, type RemoveResult, type ResealResult, type SetResult, type ValidateIssue, type ValidateResult, type WatchHandle, type WatchOptions, generateDotenv, importDotenv, insertEnvAlias, main, renderDoctor, renderMove, renderPush, renderWatch, runDecrypt, runDoctor, runEncrypt, runExplain, runGenerate, runGet, runInit, runList, runMain, runMove, runPush, runRemove, runSet, runValidate, runWatch };
614
+ export { type DoctorCheck, type DoctorFinding, type DoctorReport, type DoctorSeverity, type GenerateResult, type GetExplanation, type ImportReport, type InitResult, type InitStep, LAST_PUSHED_KEY, type ListResult, type MoveResult, type PullOptions, type PullResult, type PushOptions, type PushResult, type RemoveResult, type ResealResult, type RotateOptions, type RotatePhase, type RotateResult, type SetResult, type ValidateIssue, type ValidateResult, type WatchHandle, type WatchOptions, generateDotenv, importDotenv, insertEnvAlias, main, renderDoctor, renderMove, renderPull, renderPush, renderRotate, renderWatch, runDecrypt, runDoctor, runEncrypt, runExplain, runGenerate, runGet, runInit, runList, runMain, runMove, runPull, runPush, runRemove, runRotate, runSet, runValidate, runWatch };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as citty from 'citty';
2
- import { Sink, ParameterRef, Scope } from '@penvhq/core';
2
+ import { Sink, Provider, ParameterRef, Scope, RotationMechanism, RotationState } from '@penvhq/core';
3
3
 
4
4
  /**
5
5
  * A check reports one of four verdicts. `unknown` — a check that ran but could
@@ -8,7 +8,7 @@ import { Sink, ParameterRef, Scope } from '@penvhq/core';
8
8
  * and a write-only sink makes most of what doctor can say the second kind.
9
9
  */
10
10
  type DoctorSeverity = "pass" | "warning" | "failure" | "unknown";
11
- type DoctorCheck = "schema" | "missing" | "declared" | "weak" | "unused" | "unscoped-fallback" | "plaintext-secret" | "public-secret" | "encryption" | "provider" | "sink-unreachable" | "sink-name-drift" | "sink-manual-edit" | "sink-value-drift";
11
+ type DoctorCheck = "schema" | "missing" | "declared" | "weak" | "unused" | "unscoped-fallback" | "plaintext-secret" | "public-secret" | "encryption" | "rotation-overdue" | "rotation-stuck" | "provider-value-drift" | "provider" | "sink-unreachable" | "sink-name-drift" | "sink-manual-edit" | "sink-value-drift";
12
12
  interface DoctorFinding {
13
13
  readonly check: DoctorCheck;
14
14
  readonly severity: DoctorSeverity;
@@ -29,6 +29,17 @@ interface DoctorOptions {
29
29
  readonly environment?: string;
30
30
  /** Injected in tests: the sink to check against. Defaults to the one the config declares. */
31
31
  readonly sink?: Sink;
32
+ /**
33
+ * Injected in tests: the source-of-truth provider to compare the local tree
34
+ * against. Defaults to the one the config declares (`sourceProviderFor`).
35
+ * Mirrors `sink`, for the same reason — the drift checks stay driveable without
36
+ * a live backend.
37
+ */
38
+ readonly source?: Provider;
39
+ /** Injected in tests: the wall-clock reading the rotation clocks are read against. Defaults to now. */
40
+ readonly now?: string;
41
+ /** Injected in tests: how long a `dual-valid` window may stay open before it reads as stuck. Defaults to 24h. */
42
+ readonly stuckThresholdMs?: number;
32
43
  }
33
44
  declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
34
45
  declare function renderDoctor(report: DoctorReport): string[];
@@ -53,10 +64,8 @@ interface SetResult {
53
64
  /**
54
65
  * Writes one value file, sealing it when meta says the parameter is a secret.
55
66
  *
56
- * There is no `--encrypt` flag, deliberately. A flag would make the command line
57
- * the authority on what is secret, and meta is (invariant 14) — the `.enc` marker
58
- * is validated *against* the policy, so a marker chosen at the keyboard would
59
- * invert the direction the check runs in. The policy decides; `set` obeys.
67
+ * The scope is chosen from the flags, then the seal-and-twin write is the shared
68
+ * {@link sealAwareWrite}, against the local tree — the store `set` always edits.
60
69
  */
61
70
  declare function runSet(options: SetOptions): Promise<SetResult>;
62
71
 
@@ -437,6 +446,47 @@ interface MoveResult {
437
446
  declare function runMove(options: MoveOptions): Promise<MoveResult>;
438
447
  declare function renderMove(result: MoveResult): string[];
439
448
 
449
+ /**
450
+ * `penv pull` — materialise the local `.penv` tree from an environment's
451
+ * source-of-truth provider. It is the inverse of the deploy-time injection most
452
+ * stacks already have: instead of reading the tree to feed a backend, it reads
453
+ * the backend to feed the tree.
454
+ *
455
+ * It only means anything when the environment declares a real backend
456
+ * (`vault`, `mock`): those hold the truth somewhere penv does not edit in place,
457
+ * and pulling copies it down so every other command — which reads the local tree
458
+ * — sees it. An environment with no separate `providers` entry has the local
459
+ * tree *as* its source of truth, so a pull would be the tree copying onto
460
+ * itself; that degenerate case is reported as nothing to do, never a self-copy.
461
+ *
462
+ * Values cross verbatim. They are opaque envelope strings the source holds and
463
+ * penv does not open here — a sealed value stays sealed, byte-for-byte, so the
464
+ * key that opens it never has to be present to pull it.
465
+ */
466
+ interface PullOptions {
467
+ readonly cwd: string;
468
+ readonly environment?: string;
469
+ }
470
+ interface PullResult {
471
+ readonly environment: string;
472
+ /** The source provider's type — `filesystem` when the environment declares no separate backend. */
473
+ readonly source: string;
474
+ /**
475
+ * True when the source *is* the local tree, so there was nothing to pull. The
476
+ * caller distinguishes "pulled nothing because the backend was empty" from
477
+ * "there is no backend to pull from" — opposite situations.
478
+ */
479
+ readonly localSource: boolean;
480
+ /** Value files written into the local tree. */
481
+ readonly values: number;
482
+ /** Meta files written into the local tree. */
483
+ readonly meta: number;
484
+ /** Distinct parameters the pull touched, at any scope. */
485
+ readonly refs: number;
486
+ }
487
+ declare function runPull(options: PullOptions): Promise<PullResult>;
488
+ declare function renderPull(result: PullResult): string[];
489
+
440
490
  /** The per-environment meta field recording penv's last push, compared against the destination's `updatedAt`. */
441
491
  declare const LAST_PUSHED_KEY = "lastPushedAt";
442
492
  interface PushOptions {
@@ -475,6 +525,44 @@ interface RemoveResult {
475
525
  }
476
526
  declare function runRemove(options: RemoveOptions): Promise<RemoveResult>;
477
527
 
528
+ interface RotateOptions {
529
+ readonly cwd: string;
530
+ readonly key: string;
531
+ readonly environment?: string;
532
+ /** Open a `dual-valid` window: write the new value while the old is still retained. */
533
+ readonly begin?: boolean;
534
+ /** Close a `dual-valid` window: return to `active`, stamp the completion. */
535
+ readonly complete?: boolean;
536
+ /**
537
+ * The new value. A `begin` and an `atomic-cutover` flip write it; a `complete`
538
+ * does not touch the value at all, so it needs none. Injected in tests; on the
539
+ * CLI it is the positional argument or stdin, the same source `set` reads.
540
+ */
541
+ readonly value?: string;
542
+ /** Injected in tests: the wall-clock reading recorded in meta. Defaults to now. */
543
+ readonly now?: string;
544
+ }
545
+ /** The single step a run performed — the three the two mechanisms decompose into. */
546
+ type RotatePhase = "begin" | "complete" | "cutover";
547
+ interface RotateResult {
548
+ readonly parameter: string;
549
+ readonly environment: string;
550
+ readonly mechanism: RotationMechanism;
551
+ readonly phase: RotatePhase;
552
+ /** The source provider's type — where the value and its meta were written. */
553
+ readonly source: string;
554
+ /** True when this run wrote a new value. `begin` and `cutover` do; `complete` does not. */
555
+ readonly wroteValue: boolean;
556
+ /** The rotation state after this run — `rotating` after a begin, `active` otherwise. */
557
+ readonly state: RotationState;
558
+ /** When the current window opened, ISO. Set only after a `begin`, else `null`. */
559
+ readonly rotatingSince: string | null;
560
+ /** When a rotation last completed, ISO. Set after a `complete` or a `cutover`. */
561
+ readonly lastRotated: string | null;
562
+ }
563
+ declare function runRotate(options: RotateOptions): Promise<RotateResult>;
564
+ declare function renderRotate(result: RotateResult): string[];
565
+
478
566
  interface WatchOptions {
479
567
  readonly cwd: string;
480
568
  readonly environment?: string;
@@ -523,4 +611,4 @@ declare function renderWatch(result: ValidateResult): string[];
523
611
  declare const main: citty.CommandDef<citty.ArgsDef>;
524
612
  declare function runMain(): Promise<void>;
525
613
 
526
- export { type DoctorCheck, type DoctorFinding, type DoctorReport, type DoctorSeverity, type GenerateResult, type GetExplanation, type ImportReport, type InitResult, type InitStep, LAST_PUSHED_KEY, type ListResult, type MoveResult, type PushOptions, type PushResult, type RemoveResult, type ResealResult, type SetResult, type ValidateIssue, type ValidateResult, type WatchHandle, type WatchOptions, generateDotenv, importDotenv, insertEnvAlias, main, renderDoctor, renderMove, renderPush, renderWatch, runDecrypt, runDoctor, runEncrypt, runExplain, runGenerate, runGet, runInit, runList, runMain, runMove, runPush, runRemove, runSet, runValidate, runWatch };
614
+ export { type DoctorCheck, type DoctorFinding, type DoctorReport, type DoctorSeverity, type GenerateResult, type GetExplanation, type ImportReport, type InitResult, type InitStep, LAST_PUSHED_KEY, type ListResult, type MoveResult, type PullOptions, type PullResult, type PushOptions, type PushResult, type RemoveResult, type ResealResult, type RotateOptions, type RotatePhase, type RotateResult, type SetResult, type ValidateIssue, type ValidateResult, type WatchHandle, type WatchOptions, generateDotenv, importDotenv, insertEnvAlias, main, renderDoctor, renderMove, renderPull, renderPush, renderRotate, renderWatch, runDecrypt, runDoctor, runEncrypt, runExplain, runGenerate, runGet, runInit, runList, runMain, runMove, runPull, runPush, runRemove, runRotate, runSet, runValidate, runWatch };