@penvhq/cli 0.16.2 → 1.0.0-alpha.3
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/README.md +23 -0
- package/bin/penv.js +34 -0
- package/package.json +18 -46
- package/dist/bin.cjs +0 -58470
- package/dist/bin.cjs.map +0 -1
- package/dist/chunk-5UQEMWMW.js +0 -552
- package/dist/chunk-5UQEMWMW.js.map +0 -1
- package/dist/index.cjs +0 -7704
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1249
- package/dist/index.d.ts +0 -1249
- package/dist/index.js +0 -7356
- package/dist/index.js.map +0 -1
- package/dist/install.cjs +0 -587
- package/dist/install.cjs.map +0 -1
- package/dist/install.d.cts +0 -175
- package/dist/install.d.ts +0 -175
- package/dist/install.js +0 -31
- package/dist/install.js.map +0 -1
package/dist/index.d.ts
DELETED
|
@@ -1,1249 +0,0 @@
|
|
|
1
|
-
import * as citty from 'citty';
|
|
2
|
-
import { ProjectionProvider, Provider, PenvConfig, DotenvEntry, ParameterRef, Scope, DotenvDiagnostic, Resolution, AnyProvider, RotationMechanism, RotationState } from '@penvhq/core';
|
|
3
|
-
import { InstallPlan, InstallRuntime } from './install.js';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* `penv artifact build` — the sealed deployment artifact CI hands a release.
|
|
8
|
-
*
|
|
9
|
-
* It is the third step of the sequence PRD §7 names: pull the target
|
|
10
|
-
* environment, validate it, build the artifact, mount it. Each step is a command
|
|
11
|
-
* with one job, and this one's is narrow on purpose — it does not re-reach a
|
|
12
|
-
* verdict `penv validate` already reaches, because two implementations of "is
|
|
13
|
-
* this configuration good" would eventually let a release be built that CI had
|
|
14
|
-
* already rejected.
|
|
15
|
-
*
|
|
16
|
-
* Two properties are the whole design:
|
|
17
|
-
*
|
|
18
|
-
* **It never decrypts.** A sealed value is copied ciphertext-and-address into
|
|
19
|
-
* the artifact exactly as the record holds it, so building needs no key at all —
|
|
20
|
-
* CI can produce a production artifact without ever being able to read one. The
|
|
21
|
-
* AAD is still the value file's full name, so the ciphertext stays bound to the
|
|
22
|
-
* scope it was sealed at (invariant 17).
|
|
23
|
-
*
|
|
24
|
-
* **It refuses to guess the environment.** `--env` is named explicitly and never
|
|
25
|
-
* defaulted, `--out` likewise. An artifact built for "whatever the default was"
|
|
26
|
-
* is a release that ships the wrong environment's credentials, and the default
|
|
27
|
-
* that made it is one config edit nobody reviewed.
|
|
28
|
-
*/
|
|
29
|
-
interface ArtifactBuildOptions {
|
|
30
|
-
readonly cwd: string;
|
|
31
|
-
/** Named explicitly. There is no default, and none is invented. */
|
|
32
|
-
readonly environment?: string;
|
|
33
|
-
/** Named explicitly. Where the release will mount it from. */
|
|
34
|
-
readonly out?: string;
|
|
35
|
-
}
|
|
36
|
-
interface ArtifactBuildResult {
|
|
37
|
-
readonly file: string;
|
|
38
|
-
readonly environment: string;
|
|
39
|
-
readonly engineVersion: string;
|
|
40
|
-
readonly keySource: string;
|
|
41
|
-
/** Delivery mappings with a value. */
|
|
42
|
-
readonly values: number;
|
|
43
|
-
/** How many of those travelled as ciphertext. */
|
|
44
|
-
readonly sealed: number;
|
|
45
|
-
/** Declared mappings the environment has no non-local winner for. */
|
|
46
|
-
readonly absent: number;
|
|
47
|
-
/** True when the artifact was written inside the project — a `doctor` finding. */
|
|
48
|
-
readonly insideRepo: boolean;
|
|
49
|
-
}
|
|
50
|
-
declare function runArtifactBuild(options: ArtifactBuildOptions): Promise<ArtifactBuildResult>;
|
|
51
|
-
declare function renderArtifactBuild(result: ArtifactBuildResult, cwd: string): string[];
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* A check reports one of four verdicts. `unknown` — a check that ran but could
|
|
55
|
-
* not reach a verdict — is never rendered as a pass: "I looked and found nothing
|
|
56
|
-
* wrong" and "I could not look" are opposite situations with opposite remedies,
|
|
57
|
-
* and a value-withholding destination makes most of what doctor can say the
|
|
58
|
-
* second kind.
|
|
59
|
-
*/
|
|
60
|
-
type DoctorSeverity = "pass" | "warning" | "failure" | "unknown";
|
|
61
|
-
type DoctorCheck = "schema" | "missing" | "declared" | "weak" | "unused" | "unscoped-fallback" | "secrecy-undeclared" | "plaintext-secret" | "public-secret" | "encryption" | "rotation-overdue" | "rotation-stuck" | "provider-value-drift" | "provider" | "projection-unreachable" | "projection-name-drift" | "projection-manual-edit" | "projection-value-drift" | "environment-flag-shadow" | "local-extension" | "artifact-in-tree";
|
|
62
|
-
interface DoctorFinding {
|
|
63
|
-
readonly check: DoctorCheck;
|
|
64
|
-
readonly severity: DoctorSeverity;
|
|
65
|
-
readonly label: string;
|
|
66
|
-
readonly subject?: string;
|
|
67
|
-
readonly detail?: string;
|
|
68
|
-
/** A line the reader can act on — the `penv set` to paste, where there is one. */
|
|
69
|
-
readonly remedy?: string;
|
|
70
|
-
}
|
|
71
|
-
interface DoctorReport {
|
|
72
|
-
readonly environment: string;
|
|
73
|
-
readonly findings: readonly DoctorFinding[];
|
|
74
|
-
/** False when any finding is a failure. Warnings and unknowns do not fail the run. */
|
|
75
|
-
readonly ok: boolean;
|
|
76
|
-
}
|
|
77
|
-
interface DoctorOptions {
|
|
78
|
-
readonly cwd: string;
|
|
79
|
-
readonly environment?: string;
|
|
80
|
-
/** Bare flags the command did not declare — environment shorthands, judged against the whitelist. */
|
|
81
|
-
readonly envFlags?: readonly string[];
|
|
82
|
-
/** Injected in tests: the projection-holding destination to check against. Defaults to the one the config declares. */
|
|
83
|
-
readonly projection?: ProjectionProvider;
|
|
84
|
-
/**
|
|
85
|
-
* Injected in tests: the source-of-truth provider to compare the local tree
|
|
86
|
-
* against. Defaults to the one the config declares (`sourceProviderFor`).
|
|
87
|
-
* Mirrors `projection`, for the same reason — the drift checks stay driveable
|
|
88
|
-
* without a live backend.
|
|
89
|
-
*/
|
|
90
|
-
readonly source?: Provider;
|
|
91
|
-
/** Injected in tests: the wall-clock reading the rotation clocks are read against. Defaults to now. */
|
|
92
|
-
readonly now?: string;
|
|
93
|
-
/** Injected in tests: how long a `dual-valid` window may stay open before it reads as stuck. Defaults to 24h. */
|
|
94
|
-
readonly stuckThresholdMs?: number;
|
|
95
|
-
}
|
|
96
|
-
declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
|
|
97
|
-
declare function renderDoctor(report: DoctorReport): string[];
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Opening a penv project from a working directory, and the pieces every command
|
|
101
|
-
* needs once it is open: the config, the environment to act on, the provider
|
|
102
|
-
* rooted at the records tree, and the parameter a CLI key names.
|
|
103
|
-
*/
|
|
104
|
-
|
|
105
|
-
interface Project {
|
|
106
|
-
/** The directory holding `penv.config.ts`. */
|
|
107
|
-
readonly root: string;
|
|
108
|
-
readonly configFile: string;
|
|
109
|
-
readonly config: PenvConfig;
|
|
110
|
-
/** The parameter tree, absolute — `.penv/state/records/`. */
|
|
111
|
-
readonly recordsDir: string;
|
|
112
|
-
/**
|
|
113
|
-
* The project's provider, as the contract — never the concrete
|
|
114
|
-
* implementation. Shared commands speak the async interface and nothing more;
|
|
115
|
-
* the sync twins a command genuinely needs are reached through `localTree`,
|
|
116
|
-
* which is the one place the filesystem-only surface is named.
|
|
117
|
-
*/
|
|
118
|
-
readonly provider: Provider;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface ScopeOptions {
|
|
122
|
-
/** The environment scope. Combined with `local`, the environment-scoped override. */
|
|
123
|
-
readonly environment?: string;
|
|
124
|
-
readonly local?: boolean;
|
|
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
|
-
}
|
|
132
|
-
interface SetOptions extends ScopeOptions {
|
|
133
|
-
readonly cwd: string;
|
|
134
|
-
readonly key: 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>;
|
|
142
|
-
}
|
|
143
|
-
interface SetResult {
|
|
144
|
-
readonly parameter: string;
|
|
145
|
-
/** The value file written, relative to the records tree. */
|
|
146
|
-
readonly location: string;
|
|
147
|
-
/** Whether meta's policy sealed it. Reported, so the marker is never a surprise. */
|
|
148
|
-
readonly encrypted: boolean;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Writes one value file, sealing it when meta says the parameter is a secret.
|
|
152
|
-
*
|
|
153
|
-
* The scope is chosen from the flags, then the seal-and-twin write is the shared
|
|
154
|
-
* {@link sealAwareWrite}, against the local tree — the store `set` always edits.
|
|
155
|
-
*/
|
|
156
|
-
declare function runSet(options: SetOptions): Promise<SetResult>;
|
|
157
|
-
|
|
158
|
-
interface ResealOptions extends ScopeOptions {
|
|
159
|
-
readonly cwd: string;
|
|
160
|
-
readonly key: string;
|
|
161
|
-
}
|
|
162
|
-
interface ResealResult {
|
|
163
|
-
readonly parameter: string;
|
|
164
|
-
/** The file that now holds the value. */
|
|
165
|
-
readonly location: string;
|
|
166
|
-
/** The file that no longer exists, because its twin replaced it. */
|
|
167
|
-
readonly removed: string;
|
|
168
|
-
}
|
|
169
|
-
declare function runEncrypt(options: ResealOptions): Promise<ResealResult>;
|
|
170
|
-
declare function runDecrypt(options: ResealOptions): Promise<ResealResult>;
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* `penv fill` — walk the schema's required-but-missing parameters and ask for
|
|
174
|
-
* each one, deriving the value file's name so the user never has to.
|
|
175
|
-
*
|
|
176
|
-
* The schema-first flow writes `.penv/env.ts` before any value exists, and there
|
|
177
|
-
* the user hits a translation they should not have to make: `databaseUrl` in the
|
|
178
|
-
* schema is `database-url` on disk, and typing the wrong one writes a file the
|
|
179
|
-
* schema still cannot see. `fill` reads the same declared drift `validate`
|
|
180
|
-
* computes, and for each missing parameter asks for a value and writes it through
|
|
181
|
-
* the one writer — `runSet` — deriving the kebab filename from the schema key.
|
|
182
|
-
*
|
|
183
|
-
* A value is never invented: a blank answer skips the parameter, because the
|
|
184
|
-
* silent value reaching runtime is the failure penv exists to delete, and a
|
|
185
|
-
* placeholder written here is exactly that value by a friendlier route.
|
|
186
|
-
*
|
|
187
|
-
* Optional parameters — `.optional()`, `.default()` — are asked too, after the
|
|
188
|
-
* required gaps, tagged so the reader knows an answer is an override and Enter
|
|
189
|
-
* keeps what the schema declared. Skipping them silently was the old behavior,
|
|
190
|
-
* and it hid a real choice: a schema default reaching runtime is legal, but the
|
|
191
|
-
* user who never heard the question never chose it.
|
|
192
|
-
*/
|
|
193
|
-
/** One question `fill` puts to the user: which parameter, in which environment. */
|
|
194
|
-
interface FillPrompt {
|
|
195
|
-
/** The value file's key, kebab and slash-separated — the name the user need never derive. */
|
|
196
|
-
readonly parameter: string;
|
|
197
|
-
readonly environment: string;
|
|
198
|
-
/** Whether meta says this is a secret, so the wrapper mutes the echo. */
|
|
199
|
-
readonly secret: boolean;
|
|
200
|
-
/**
|
|
201
|
-
* Whether the schema excuses absence — `.optional()`, `.default()`. An answer
|
|
202
|
-
* writes an override; a blank one leaves the schema's own behavior in place,
|
|
203
|
-
* which is a kept default rather than a lingering gap.
|
|
204
|
-
*/
|
|
205
|
-
readonly optional: boolean;
|
|
206
|
-
/** What the schema falls back to, rendered for display, when it declares one penv can read. */
|
|
207
|
-
readonly defaultValue?: string;
|
|
208
|
-
readonly description?: string;
|
|
209
|
-
}
|
|
210
|
-
interface FillOptions {
|
|
211
|
-
readonly cwd: string;
|
|
212
|
-
readonly environment?: string;
|
|
213
|
-
/** Bare flags the command did not declare — environment shorthands, judged against the whitelist. */
|
|
214
|
-
readonly envFlags?: readonly string[];
|
|
215
|
-
/**
|
|
216
|
-
* How a value is obtained for one prompt. `undefined` or an empty answer skips
|
|
217
|
-
* the parameter — the readline half lives only in the wrapper, so `runFill`
|
|
218
|
-
* stays pure and unit-testable.
|
|
219
|
-
*/
|
|
220
|
-
readonly ask: (prompt: FillPrompt) => Promise<string | undefined>;
|
|
221
|
-
}
|
|
222
|
-
interface FillResult {
|
|
223
|
-
readonly environment: string;
|
|
224
|
-
/** The value files written, one per answered prompt. */
|
|
225
|
-
readonly written: ReadonlyArray<{
|
|
226
|
-
readonly parameter: string;
|
|
227
|
-
/** The value file written, relative to the records tree. */
|
|
228
|
-
readonly location: string;
|
|
229
|
-
readonly encrypted: boolean;
|
|
230
|
-
}>;
|
|
231
|
-
/** The parameters a blank answer left for later — never written as an empty value. */
|
|
232
|
-
readonly skipped: readonly string[];
|
|
233
|
-
/**
|
|
234
|
-
* The optional parameters a blank answer left to the schema. Not `skipped`:
|
|
235
|
-
* a skipped parameter is still a gap, and one of these is a decision — the
|
|
236
|
-
* schema's default (or declared absence) is the value, on purpose.
|
|
237
|
-
*/
|
|
238
|
-
readonly kept: readonly string[];
|
|
239
|
-
/**
|
|
240
|
-
* The declared keys no filename reaches (`apiURL`, a reserved token). `fill`
|
|
241
|
-
* cannot ask for a value it could never write, so it carries the rename remedy
|
|
242
|
-
* out rather than prompting for a file that would error.
|
|
243
|
-
*/
|
|
244
|
-
readonly unreachable: ReadonlyArray<{
|
|
245
|
-
readonly subject: string;
|
|
246
|
-
readonly remedy: string;
|
|
247
|
-
}>;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Asks for every declared-but-missing parameter, and writes the ones answered.
|
|
251
|
-
*
|
|
252
|
-
* The drift is `validate`'s, not a second reading of the schema: `runValidate`
|
|
253
|
-
* already computes exactly the required-but-absent set, so `fill` and `validate`
|
|
254
|
-
* can never disagree about what is missing. The writing is `runSet`'s, so a
|
|
255
|
-
* filled secret is sealed exactly as a `set` one is — `fill` owns neither the
|
|
256
|
-
* resolution nor the write, only the prompting between them.
|
|
257
|
-
*/
|
|
258
|
-
declare function runFill(options: FillOptions): Promise<FillResult>;
|
|
259
|
-
declare function renderFill(result: FillResult): string[];
|
|
260
|
-
|
|
261
|
-
interface GenerateOptions {
|
|
262
|
-
readonly cwd: string;
|
|
263
|
-
readonly environment?: string;
|
|
264
|
-
/** Bare flags the command did not declare — environment shorthands, judged against the whitelist. */
|
|
265
|
-
readonly envFlags?: readonly string[];
|
|
266
|
-
/** Where to write, absolute or relative to `cwd`. Defaults to `.env` at the project root. */
|
|
267
|
-
readonly out?: string;
|
|
268
|
-
/** Permits sealed values to be written into the artifact as plaintext. */
|
|
269
|
-
readonly allowDecrypt?: boolean;
|
|
270
|
-
}
|
|
271
|
-
interface GenerateResult {
|
|
272
|
-
readonly file: string;
|
|
273
|
-
readonly environment: string;
|
|
274
|
-
readonly entries: number;
|
|
275
|
-
/** How many of them were sealed and are now plaintext in the artifact. */
|
|
276
|
-
readonly decrypted: number;
|
|
277
|
-
}
|
|
278
|
-
/** The `.env` text for one environment — what `penv generate` writes. */
|
|
279
|
-
declare function generateDotenv(options: Omit<GenerateOptions, "out">): string;
|
|
280
|
-
declare function runGenerate(options: GenerateOptions): GenerateResult;
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* `penv get <key>` — read a parameter, or explain which file wins and why.
|
|
284
|
-
*
|
|
285
|
-
* Fallback is never silent, and neither is precedence: `--explain` prints every
|
|
286
|
-
* candidate in the order the cascade considered them, so a value quietly coming
|
|
287
|
-
* from a shared default has nowhere to hide.
|
|
288
|
-
*/
|
|
289
|
-
interface GetOptions {
|
|
290
|
-
readonly cwd: string;
|
|
291
|
-
readonly key: string;
|
|
292
|
-
readonly environment?: string;
|
|
293
|
-
}
|
|
294
|
-
interface GetExplanation {
|
|
295
|
-
readonly parameter: string;
|
|
296
|
-
readonly environment: string;
|
|
297
|
-
/** `undefined` when no candidate was present. */
|
|
298
|
-
readonly location: string | undefined;
|
|
299
|
-
/**
|
|
300
|
-
* Why the winning file did not open, when it is `.enc` and did not.
|
|
301
|
-
*
|
|
302
|
-
* A winner that cannot be decrypted is not a skipped candidate — it won, and
|
|
303
|
-
* the cascade is over. Reporting it as a skip would say a lower scope should
|
|
304
|
-
* have been reached, which is the scope-widening answer the cascade refuses.
|
|
305
|
-
*/
|
|
306
|
-
readonly undecryptable?: string;
|
|
307
|
-
readonly candidates: readonly GetCandidate[];
|
|
308
|
-
}
|
|
309
|
-
interface GetCandidate {
|
|
310
|
-
readonly location: string;
|
|
311
|
-
readonly present: boolean;
|
|
312
|
-
readonly wins: boolean;
|
|
313
|
-
/** Why a present candidate did not win, or why it was never considered. */
|
|
314
|
-
readonly skipped: string | undefined;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* The value, or a named error.
|
|
318
|
-
*
|
|
319
|
-
* `requireValue` answers first, so a winner that exists but did not decrypt is
|
|
320
|
-
* reported as undecryptable rather than as absent. Only a genuine absence — no
|
|
321
|
-
* candidate at any scope — reaches the refusal below, which is what keeps `penv
|
|
322
|
-
* set` from being offered as the fix for a secret the user still has.
|
|
323
|
-
*/
|
|
324
|
-
declare function runGet(options: GetOptions): Promise<string>;
|
|
325
|
-
/**
|
|
326
|
-
* Which file wins, and why — never a value, so this must not be stopped by the
|
|
327
|
-
* winner being unreadable. Core describes an `.enc` winner rather than refusing
|
|
328
|
-
* it, so `--explain` is the same walk every other command does.
|
|
329
|
-
*/
|
|
330
|
-
declare function runExplain(options: GetOptions): Promise<GetExplanation>;
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* What the codebase already says about itself.
|
|
334
|
-
*
|
|
335
|
-
* `penv init` asks a human to confirm a plan, and a plan the human has to fill
|
|
336
|
-
* in from scratch is an interrogation. So penv reads the two facts it can
|
|
337
|
-
* observe — the framework in `package.json`, and whether a `src/` directory
|
|
338
|
-
* exists — and offers them as a suggestion.
|
|
339
|
-
*
|
|
340
|
-
* The line this module does not cross: a framework is an identity, never a
|
|
341
|
-
* config key. Nothing here is written to `penv.config.ts` as `framework: "next"`
|
|
342
|
-
* — the answers become concrete decisions (`schemaFile`, `publicPrefixes`) that
|
|
343
|
-
* mean the same thing in a year, when the project has been rewritten twice and
|
|
344
|
-
* penv would otherwise still be reinterpreting a name it read once.
|
|
345
|
-
*
|
|
346
|
-
* Everything here is a suggestion. The one thing that is never suggested is an
|
|
347
|
-
* environment: deployment topology is not in `package.json`, and invariant 10
|
|
348
|
-
* forbids inferring it.
|
|
349
|
-
*/
|
|
350
|
-
/** A framework penv recognised, and what it implies about a project's layout. */
|
|
351
|
-
interface Detected {
|
|
352
|
-
/** The framework's own name, as a human writes it — `"Next.js"`. */
|
|
353
|
-
readonly name: string;
|
|
354
|
-
/** Where this framework's projects keep their modules, relative to the root. */
|
|
355
|
-
readonly schemaFile: string;
|
|
356
|
-
/**
|
|
357
|
-
* The conventional path penv stepped aside from, because a module that is not
|
|
358
|
-
* penv's schema already lives there. Set only when it happened, so the plan can
|
|
359
|
-
* say why the schema is not where the convention would have put it.
|
|
360
|
-
*/
|
|
361
|
-
readonly displacedFrom?: string;
|
|
362
|
-
/** The prefixes this framework inlines into its client bundle. */
|
|
363
|
-
readonly publicPrefixes: readonly string[];
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* The dotenv files a project's framework reads, and what each one says.
|
|
368
|
-
*
|
|
369
|
-
* One module answers this for both ends of adoption: `penv init` offers these
|
|
370
|
-
* files for the cutover, and `penv run` refuses the ones that come back
|
|
371
|
-
* afterwards. Two readings of "which files are active" would eventually let init
|
|
372
|
-
* move a file run does not watch, or run refuse a file init never offered.
|
|
373
|
-
*
|
|
374
|
-
* The four scopes are invariant 4's, which are Next.js's and Vite's:
|
|
375
|
-
* `.env` > `.env.local` > `.env.<environment>` > `.env.<environment>.local`.
|
|
376
|
-
* Anything else with an `.env` prefix is documentation (`.env.example`), a
|
|
377
|
-
* leftover (`.env.backup`), or a filename penv has no reading of — none of them
|
|
378
|
-
* is configuration a framework loads, so none is offered or refused.
|
|
379
|
-
*
|
|
380
|
-
* Reading an environment out of a filename here is not inference: nothing
|
|
381
|
-
* reaches `penv.config.ts` until a human selects the file, and
|
|
382
|
-
* {@link activeDotenvFiles} judges the segment against the declared whitelist
|
|
383
|
-
* (invariant 10) rather than believing it.
|
|
384
|
-
*/
|
|
385
|
-
|
|
386
|
-
/** Where a dotenv file sits in the cascade. */
|
|
387
|
-
type DotenvKind = "shared" | "local" | "environment" | "environment-local";
|
|
388
|
-
interface DotenvFile {
|
|
389
|
-
/** The filename exactly as it is on disk — what undo restores. */
|
|
390
|
-
readonly name: string;
|
|
391
|
-
readonly kind: DotenvKind;
|
|
392
|
-
/** The environment the filename names, for the two environment-scoped kinds. */
|
|
393
|
-
readonly environment?: string;
|
|
394
|
-
/** How the file is described in the selection table. */
|
|
395
|
-
readonly label: string;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* The draft schema penv writes when it adopts a dotenv file.
|
|
400
|
-
*
|
|
401
|
-
* It is a draft and says so in the file it lands in: single-sample inference
|
|
402
|
-
* cannot know that a boolean seen as `true` must also accept `1`, or that a
|
|
403
|
-
* string is really a URL. penv scaffolds `penv.schema.ts` once and never
|
|
404
|
-
* regenerates it (invariant 2), so every correction the reader makes is safe.
|
|
405
|
-
*
|
|
406
|
-
* Requiredness across several files is the one judgement here, and it is
|
|
407
|
-
* deliberately coarse: a field observed in every adopted environment starts
|
|
408
|
-
* required, and a field missing from any of them starts optional. That is not
|
|
409
|
-
* per-environment requiredness — there is one schema, never one per environment
|
|
410
|
-
* (invariant 1) — it is the weakest shape that every adopted environment
|
|
411
|
-
* satisfies, which is what makes the first `penv run` after a cutover pass with
|
|
412
|
-
* no edits.
|
|
413
|
-
*/
|
|
414
|
-
|
|
415
|
-
/** One schema field, rendered into `penv.schema.ts` as `<key>: <type>,`. */
|
|
416
|
-
interface SchemaField {
|
|
417
|
-
readonly key: string;
|
|
418
|
-
/** The Zod expression, e.g. `z.url()` — `.optional()` already applied when it belongs. */
|
|
419
|
-
readonly type: string;
|
|
420
|
-
}
|
|
421
|
-
/** A drafted field, plus the verdict a report prints. */
|
|
422
|
-
interface DraftField extends SchemaField {
|
|
423
|
-
readonly required: boolean;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/** What init touched, so a caller can report it and a test can assert it. */
|
|
427
|
-
type InitTarget = "penv-dir" | "schema" | "env" | "config" | "tsconfig" | "gitignore" | "seam";
|
|
428
|
-
/**
|
|
429
|
-
* `conflicted` is the one that is not a success. penv wanted to write something,
|
|
430
|
-
* found the user's file already saying something else about the same thing, and
|
|
431
|
-
* left it alone — so the step is reported with a warning rather than a ✓, and the
|
|
432
|
-
* text says what will not work until the user decides.
|
|
433
|
-
*
|
|
434
|
-
* `info` is a step penv did not perform automatically — a manual instruction (the
|
|
435
|
-
* injection seam for a framework penv cannot scaffold), reported so the user
|
|
436
|
-
* knows the one thing left to do.
|
|
437
|
-
*/
|
|
438
|
-
type InitAction = "created" | "kept" | "updated" | "conflicted" | "info";
|
|
439
|
-
interface InitStep {
|
|
440
|
-
readonly target: InitTarget;
|
|
441
|
-
readonly action: InitAction;
|
|
442
|
-
/** The reported line, in the docs' voice. */
|
|
443
|
-
readonly text: string;
|
|
444
|
-
readonly note?: string;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* The answers init writes down. Every one of these is a decision a human either
|
|
448
|
-
* made or consented to — never an identity penv recorded to reinterpret later.
|
|
449
|
-
* There is deliberately no `framework` here: `schemaFile` and `publicPrefixes`
|
|
450
|
-
* still mean exactly what they say after the project is rewritten in something
|
|
451
|
-
* else, and `framework: "next"` would not.
|
|
452
|
-
*/
|
|
453
|
-
interface InitDecisions {
|
|
454
|
-
/** The whitelist. Empty unless a human named them — penv never infers one. */
|
|
455
|
-
readonly environments: readonly string[];
|
|
456
|
-
/**
|
|
457
|
-
* The environment every command falls back to when `--env` is absent (seal 3).
|
|
458
|
-
* Written only when the cutover adopted one — a declared decision, so the
|
|
459
|
-
* whitelist rule is untouched, and CI keeps naming `--env` anyway.
|
|
460
|
-
*/
|
|
461
|
-
readonly defaultEnvironment?: string;
|
|
462
|
-
/** The schema module, relative to the project root, POSIX. */
|
|
463
|
-
readonly schemaFile: string;
|
|
464
|
-
/** The prefixes the framework inlines into its client bundle. */
|
|
465
|
-
readonly publicPrefixes: readonly string[];
|
|
466
|
-
/**
|
|
467
|
-
* How the user's code names the schema module — `@env` or `#env`.
|
|
468
|
-
*
|
|
469
|
-
* Two forms, resolved by two different things: `@env` is a tsconfig `paths`
|
|
470
|
-
* entry that a bundler resolves and plain Node does not, and `#env` is a
|
|
471
|
-
* package.json `imports` entry that Node resolves itself. Which one a project
|
|
472
|
-
* wants is a fact about the project, so penv reads what it already does and
|
|
473
|
-
* offers that.
|
|
474
|
-
*/
|
|
475
|
-
readonly alias: string;
|
|
476
|
-
/**
|
|
477
|
-
* Whether to inject the validated config into `process.env` for libraries that
|
|
478
|
-
* read it directly, so `env.ts` loads with `{ inject: true }` and penv places
|
|
479
|
-
* the framework's pre-app seam. Off by default and only ever turned on by an
|
|
480
|
-
* explicit yes — a project that reads config only through `@env` gets none.
|
|
481
|
-
*/
|
|
482
|
-
readonly inject: boolean;
|
|
483
|
-
}
|
|
484
|
-
interface InitResult {
|
|
485
|
-
readonly root: string;
|
|
486
|
-
readonly decisions: InitDecisions;
|
|
487
|
-
readonly steps: readonly InitStep[];
|
|
488
|
-
}
|
|
489
|
-
interface InitOptions {
|
|
490
|
-
readonly cwd: string;
|
|
491
|
-
/** What to write. Omitted means the plan's defaults, as `--yes` takes them. */
|
|
492
|
-
readonly decisions?: InitDecisions;
|
|
493
|
-
/** The detected framework name, passed by the command so the seam step need not re-detect it. */
|
|
494
|
-
readonly framework?: string;
|
|
495
|
-
}
|
|
496
|
-
interface InitPlan {
|
|
497
|
-
readonly detected: Detected | undefined;
|
|
498
|
-
/** What init writes unless a human edits it. */
|
|
499
|
-
readonly decisions: InitDecisions;
|
|
500
|
-
/** Environments the `.env*` files on disk are evidence for. Offered, never taken. */
|
|
501
|
-
readonly suggestedEnvironments: readonly string[];
|
|
502
|
-
/** Why each decision is what it is. Printed — a fallback penv takes silently is a guess. */
|
|
503
|
-
readonly notes: readonly string[];
|
|
504
|
-
}
|
|
505
|
-
interface AliasEdit {
|
|
506
|
-
readonly source: string;
|
|
507
|
-
readonly changed: boolean;
|
|
508
|
-
/**
|
|
509
|
-
* What the alias already points at, when that is not penv's schema.
|
|
510
|
-
*
|
|
511
|
-
* The alias is how the user's code reaches penv, so an alias that resolves
|
|
512
|
-
* somewhere else is not a small problem: `import { env } from "@env"` compiles,
|
|
513
|
-
* runs, and hands back another module's export. Reporting "kept the alias"
|
|
514
|
-
* because the *key* was present is how penv would say that was fine — a silent
|
|
515
|
-
* seam, in the scaffolder of the tool whose subject is silent seams.
|
|
516
|
-
*
|
|
517
|
-
* Left as the user's, never rewritten: penv cannot tell a stale mapping from a
|
|
518
|
-
* deliberate one, and the file is theirs.
|
|
519
|
-
*/
|
|
520
|
-
readonly conflict?: string;
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* `tsconfig.json` with the `@env` alias present, everything else untouched.
|
|
524
|
-
* Already-aliased input comes back unchanged rather than gaining a duplicate.
|
|
525
|
-
*
|
|
526
|
-
* The alias is why the schema can live anywhere: application code imports
|
|
527
|
-
* `@env`, and this line is the only thing that has to know where that is.
|
|
528
|
-
*/
|
|
529
|
-
declare function insertEnvAlias(source: string, target?: string, name?: string): AliasEdit;
|
|
530
|
-
/** One adopted file: what it holds, and the scope its values are written at. */
|
|
531
|
-
interface Adopted {
|
|
532
|
-
readonly file: DotenvFile;
|
|
533
|
-
readonly entries: readonly DotenvEntry[];
|
|
534
|
-
readonly refs: readonly ParameterRef[];
|
|
535
|
-
readonly scope: Scope;
|
|
536
|
-
}
|
|
537
|
-
interface AdoptionPlan {
|
|
538
|
-
readonly root: string;
|
|
539
|
-
/** Every dotenv file penv found, in the order the list shows them. */
|
|
540
|
-
readonly found: readonly DotenvFile[];
|
|
541
|
-
/** Checked when the list is first shown: the development cascade, where it exists. */
|
|
542
|
-
readonly preselected: readonly string[];
|
|
543
|
-
}
|
|
544
|
-
/** What there is to adopt, and what penv proposes taking. */
|
|
545
|
-
declare function planAdoption(root: string): AdoptionPlan;
|
|
546
|
-
interface CutoverPlan {
|
|
547
|
-
readonly root: string;
|
|
548
|
-
readonly selected: readonly DotenvFile[];
|
|
549
|
-
readonly adopted: readonly Adopted[];
|
|
550
|
-
/** The whitelist after this cutover — what the config declares, or is about to. */
|
|
551
|
-
readonly environments: readonly string[];
|
|
552
|
-
/**
|
|
553
|
-
* The environments this cutover is *about*: the ones its files name. Narrower
|
|
554
|
-
* than the whitelist on a project that already declared more, and the ones the
|
|
555
|
-
* draft is judged against and the import is validated for — an environment
|
|
556
|
-
* this cutover did not touch must not fail it for a state it was already in.
|
|
557
|
-
*/
|
|
558
|
-
readonly adopting: readonly string[];
|
|
559
|
-
readonly decisions: InitDecisions;
|
|
560
|
-
readonly fields: readonly DraftField[];
|
|
561
|
-
readonly variables: number;
|
|
562
|
-
/** Values the parser read but that look like a mistake. Shown, never fixed. */
|
|
563
|
-
readonly diagnostics: readonly DotenvDiagnostic[];
|
|
564
|
-
readonly install: InstallPlan;
|
|
565
|
-
readonly framework: string | undefined;
|
|
566
|
-
/** True when `penv.config.ts` already existed, so init keeps every decision it records. */
|
|
567
|
-
readonly configured: boolean;
|
|
568
|
-
}
|
|
569
|
-
interface CutoverInput {
|
|
570
|
-
readonly root: string;
|
|
571
|
-
/** What init would scaffold anyway: detection, the schema's home, the alias. */
|
|
572
|
-
readonly base: InitPlan;
|
|
573
|
-
readonly selected: readonly DotenvFile[];
|
|
574
|
-
/** The environment named when the selection declares none — `.env` alone declares nothing. */
|
|
575
|
-
readonly environment?: string;
|
|
576
|
-
/** The `@penvhq/penv` version to pin. Defaults to this engine's own. */
|
|
577
|
-
readonly version?: string;
|
|
578
|
-
readonly inject?: boolean;
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Everything a cutover needs, checked before anything is written.
|
|
582
|
-
*
|
|
583
|
-
* The order is the order the failures matter in: an unresolved bundle first
|
|
584
|
-
* (there is nothing to plan on top of it), then what the selection declares,
|
|
585
|
-
* then whether the selection is complete, then every variable name, and only
|
|
586
|
-
* then the schema and the install. Each one throws, so the caller has a plan or
|
|
587
|
-
* a refusal and never a half-answer.
|
|
588
|
-
*/
|
|
589
|
-
declare function planCutover(input: CutoverInput): CutoverPlan;
|
|
590
|
-
interface CutoverResult {
|
|
591
|
-
readonly plan: CutoverPlan;
|
|
592
|
-
readonly steps: readonly InitStep[];
|
|
593
|
-
/** The dotenv files moved into the bundle, by name. */
|
|
594
|
-
readonly moved: readonly string[];
|
|
595
|
-
/** The environments whose imported values were validated before the move. */
|
|
596
|
-
readonly validated: readonly string[];
|
|
597
|
-
}
|
|
598
|
-
interface CutoverOptions {
|
|
599
|
-
/** Injected in tests: how the runtime dependency is installed. Never spawns there. */
|
|
600
|
-
readonly install?: InstallRuntime;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Installs, scaffolds, imports, validates — and only then moves the dotenv
|
|
604
|
-
* files aside. The order is the guarantee: every step before the move leaves a
|
|
605
|
-
* project whose `.env` files are exactly where they were, so a refusal at any
|
|
606
|
-
* of them costs a re-run and nothing else.
|
|
607
|
-
*
|
|
608
|
-
* The scaffold is snapshotted first, and rolled back when anything after it
|
|
609
|
-
* refuses. Without that, "a refusal costs a re-run" was only true of the dotenv
|
|
610
|
-
* files: a failed run still left a config, a draft schema, an edited tsconfig
|
|
611
|
-
* and an imported records tree behind, and the next run kept every one of them
|
|
612
|
-
* rather than starting clean. The install is not rolled back — it is the one
|
|
613
|
-
* step the developer consented to by itself, and a re-run finds it satisfied.
|
|
614
|
-
*/
|
|
615
|
-
declare function applyCutover(plan: CutoverPlan, options?: CutoverOptions): Promise<CutoverResult>;
|
|
616
|
-
declare function runInit(options: InitOptions): InitResult;
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* Reading the user's schema, and the distance between it and the parameter tree.
|
|
620
|
-
*
|
|
621
|
-
* The schema declares what must exist and the tree holds what does. The gap
|
|
622
|
-
* between them is the signal `penv validate` exists to raise; this module makes
|
|
623
|
-
* it legible without closing it. Nothing here writes or deletes a value file —
|
|
624
|
-
* a declaration has no value, so materialising one could only invent it, and an
|
|
625
|
-
* invented value is the silent-value-reaching-runtime failure penv exists to
|
|
626
|
-
* delete. `penv set` stays the only writer.
|
|
627
|
-
*
|
|
628
|
-
* The introspection below lives here, not in `doctor`, because `doctor` and
|
|
629
|
-
* `watch` both report drift and two readers of the same schema would be two
|
|
630
|
-
* answers to one question.
|
|
631
|
-
*
|
|
632
|
-
* Every helper answers "I cannot tell" rather than guessing. A report is only
|
|
633
|
-
* worth reading if every line in it is true, so a field this module cannot
|
|
634
|
-
* understand produces no line at all.
|
|
635
|
-
*/
|
|
636
|
-
|
|
637
|
-
/** A parameter the schema declares that the tree has no value for. */
|
|
638
|
-
interface DeclaredDrift {
|
|
639
|
-
/** The parameter id, or the dotted schema path when no filename could reach it. */
|
|
640
|
-
readonly subject: string;
|
|
641
|
-
/** Absent when no filename reaches this key, which is drift `penv set` cannot close. */
|
|
642
|
-
readonly ref?: ParameterRef;
|
|
643
|
-
/** The line to paste: the `penv set` that closes this, or the rename that must precede it. */
|
|
644
|
-
readonly remedy: string;
|
|
645
|
-
readonly detail: string;
|
|
646
|
-
}
|
|
647
|
-
/** A parameter the tree holds a value for that the schema does not declare. */
|
|
648
|
-
interface UndeclaredDrift {
|
|
649
|
-
readonly ref: ParameterRef;
|
|
650
|
-
/** The generated variable, which is the name the application would have read. */
|
|
651
|
-
readonly variable: string;
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* A parameter the schema declares but does not require — `.optional()`,
|
|
655
|
-
* `.default()`, and their kin — that the tree has no value for. Not drift in the
|
|
656
|
-
* verdict sense: absence here is a state the schema itself blessed, so `doctor`
|
|
657
|
-
* and `watch` say nothing about it. It is measured for `fill`, whose reader is
|
|
658
|
-
* deciding what to write, and for whom "the schema would take an override here"
|
|
659
|
-
* is exactly the kind of fact a silent skip would hide.
|
|
660
|
-
*/
|
|
661
|
-
interface OptionalDrift {
|
|
662
|
-
/** The parameter id, or the dotted schema path when no filename could reach it. */
|
|
663
|
-
readonly subject: string;
|
|
664
|
-
/** Absent when no filename reaches this key — an override `penv set` cannot write. */
|
|
665
|
-
readonly ref?: ParameterRef;
|
|
666
|
-
/** What the schema falls back to, rendered for display, when it declares one this module can read. */
|
|
667
|
-
readonly defaultValue?: string;
|
|
668
|
-
/** The rename that must precede any override, for the key no filename reaches. */
|
|
669
|
-
readonly remedy: string;
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* The distance between the schema and the tree, in both directions. Named
|
|
673
|
-
* `declared`/`undeclared` for the side that has it, not for a verdict: neither
|
|
674
|
-
* direction is by itself an error, and only `validate` decides that. `optional`
|
|
675
|
-
* is the deliberately verdict-free third list — see {@link OptionalDrift}.
|
|
676
|
-
*/
|
|
677
|
-
interface DriftReport {
|
|
678
|
-
readonly declared: readonly DeclaredDrift[];
|
|
679
|
-
readonly undeclared: readonly UndeclaredDrift[];
|
|
680
|
-
readonly optional: readonly OptionalDrift[];
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
type ValidateIssueKind = "config" | "reserved" | "collision" | "schema" | "undecryptable";
|
|
684
|
-
interface ValidateIssue {
|
|
685
|
-
readonly kind: ValidateIssueKind;
|
|
686
|
-
/** What the line is about: a parameter, a variable, a token, or a file. */
|
|
687
|
-
readonly subject: string;
|
|
688
|
-
readonly message: string;
|
|
689
|
-
readonly remedy?: string;
|
|
690
|
-
}
|
|
691
|
-
interface ValidateResult {
|
|
692
|
-
readonly ok: boolean;
|
|
693
|
-
readonly environment: string;
|
|
694
|
-
readonly parameters: number;
|
|
695
|
-
readonly issues: readonly ValidateIssue[];
|
|
696
|
-
/**
|
|
697
|
-
* The distance between the schema and the tree, carried for the callers
|
|
698
|
-
* that report it (`watch`). Never folded into `ok` and never rendered by
|
|
699
|
-
* `renderValidate`: drift is a report, and CI's verdict must not move because
|
|
700
|
-
* a parameter the schema tolerates is absent. Empty when the schema did not
|
|
701
|
-
* load, since there is nothing to measure against.
|
|
702
|
-
*/
|
|
703
|
-
readonly drift: DriftReport;
|
|
704
|
-
}
|
|
705
|
-
interface ValidateOptions {
|
|
706
|
-
readonly cwd: string;
|
|
707
|
-
readonly environment?: string;
|
|
708
|
-
/** Bare flags the command did not declare — environment shorthands, judged against the whitelist. */
|
|
709
|
-
readonly envFlags?: readonly string[];
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* One environment, checked — and everything the check produced on the way.
|
|
713
|
-
*
|
|
714
|
-
* The verdict is `result` and it is the only verdict penv has. The rest is what
|
|
715
|
-
* `penv run` needs to build a child environment, handed back rather than
|
|
716
|
-
* recomputed: a second walk of the same tree could disagree with the one the
|
|
717
|
-
* verdict was reached on, and then `run` would start a process `validate`
|
|
718
|
-
* refuses.
|
|
719
|
-
*/
|
|
720
|
-
interface EnvironmentCheck {
|
|
721
|
-
readonly result: ValidateResult;
|
|
722
|
-
/** Absent when the schema module could not be evaluated — `result.issues` says why. */
|
|
723
|
-
readonly schema?: z.ZodType;
|
|
724
|
-
/** Every parameter the tree holds, resolved for this environment. */
|
|
725
|
-
readonly resolutions: readonly Resolution[];
|
|
726
|
-
/** The schema-validated object. Present only when the verdict passed. */
|
|
727
|
-
readonly validated?: unknown;
|
|
728
|
-
}
|
|
729
|
-
declare function runValidate(options: ValidateOptions): Promise<ValidateResult>;
|
|
730
|
-
declare function checkEnvironment(project: Project, environment: string): Promise<EnvironmentCheck>;
|
|
731
|
-
|
|
732
|
-
interface ImportOptions {
|
|
733
|
-
readonly cwd: string;
|
|
734
|
-
/** The dotenv file to adopt, absolute or relative to `cwd`. */
|
|
735
|
-
readonly file: string;
|
|
736
|
-
/**
|
|
737
|
-
* `--env`. It reads as "these are <environment>'s values", so for a file whose
|
|
738
|
-
* name carries no environment it names the *scope* as well as the environment
|
|
739
|
-
* to run against: `penv import prod-secrets.txt --env production` writes
|
|
740
|
-
* `<name>.production`, and `--env production` on `.env.local` writes
|
|
741
|
-
* `<name>.production.local`. The filename supplies both when it carries an
|
|
742
|
-
* environment, so the flag is needed only for a file that does not — and
|
|
743
|
-
* contradicting the filename is an error rather than a silent choice between
|
|
744
|
-
* the two.
|
|
745
|
-
*/
|
|
746
|
-
readonly environment?: string;
|
|
747
|
-
}
|
|
748
|
-
interface ImportReport {
|
|
749
|
-
readonly root: string;
|
|
750
|
-
readonly file: string;
|
|
751
|
-
readonly backup: string;
|
|
752
|
-
/** The scope the source named — filename, `--env`, or both — and the scope every value was written at. */
|
|
753
|
-
readonly scope: Scope;
|
|
754
|
-
/**
|
|
755
|
-
* The environment the import ran against, or `undefined` when none is set.
|
|
756
|
-
*
|
|
757
|
-
* Undefined only ever accompanies the unscoped default: any other scope names
|
|
758
|
-
* an environment, so it always has one. It means the values were written and
|
|
759
|
-
* the closing validation was skipped, which the output states.
|
|
760
|
-
*/
|
|
761
|
-
readonly environment: string | undefined;
|
|
762
|
-
/** The declared environments, so a skipped validation can name one to pass. */
|
|
763
|
-
readonly environments: readonly string[];
|
|
764
|
-
readonly variables: number;
|
|
765
|
-
/**
|
|
766
|
-
* Comment blocks that belonged to no variable. Reported rather than discarded
|
|
767
|
-
* silently: a file header has no parameter to describe, but that is not a
|
|
768
|
-
* reason to pretend it was never there.
|
|
769
|
-
*/
|
|
770
|
-
readonly orphanComments: number;
|
|
771
|
-
readonly steps: readonly InitStep[];
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* Adopts the file: parses it, scaffolds the project, writes one value file per
|
|
775
|
-
* variable and each attached comment into that parameter's meta, and backs the
|
|
776
|
-
* source up. Validation is the caller's next step rather than part of adoption —
|
|
777
|
-
* an inferred schema is a draft, and a draft that needs correcting has still
|
|
778
|
-
* imported every value correctly.
|
|
779
|
-
*
|
|
780
|
-
* Adoption is all or nothing. Every name is checked against the config, and any
|
|
781
|
-
* environment the source names resolved, before the tree is scaffolded or a
|
|
782
|
-
* value written. The two names that fail here fail *destructively*: a reserved
|
|
783
|
-
* name bricks every later command, and a lossy name renames the user's variable
|
|
784
|
-
* behind their back. What the source names is resolved here rather than left to
|
|
785
|
-
* the closing `validate` because a command that writes a tree and *then*
|
|
786
|
-
* discovers it cannot name an environment has already half-adopted the project
|
|
787
|
-
* it just refused. A half-imported tree would be the drift penv exists to
|
|
788
|
-
* remove, introduced by penv itself.
|
|
789
|
-
*
|
|
790
|
-
* An environment nothing names is a different case, and not an error: an
|
|
791
|
-
* unscoped import writes at the unscoped default, which needs no environment.
|
|
792
|
-
* Only the validation that follows needs one, so it is skipped and said to be
|
|
793
|
-
* skipped. Requiring one here would fail `penv import .env` on a greenfield
|
|
794
|
-
* project — the first command the quickstart gives, where no environment could
|
|
795
|
-
* plausibly be set yet — to satisfy a step that is the caller's next one.
|
|
796
|
-
*/
|
|
797
|
-
declare function importDotenv(options: ImportOptions): ImportReport;
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* `penv list` — every parameter, and the scope that wins for one environment.
|
|
801
|
-
*
|
|
802
|
-
* The winning scope is the point: `production` and `default` are both "it
|
|
803
|
-
* resolves", and only one of them means the value was written for production.
|
|
804
|
-
*/
|
|
805
|
-
interface ListOptions {
|
|
806
|
-
readonly cwd: string;
|
|
807
|
-
readonly environment?: string;
|
|
808
|
-
}
|
|
809
|
-
interface ListEntry {
|
|
810
|
-
readonly parameter: string;
|
|
811
|
-
/** The generated `.env` variable, so the two names are legible side by side. */
|
|
812
|
-
readonly variable: string;
|
|
813
|
-
/** `<env>.local`, `local`, an environment name, `default`, or `absent`. */
|
|
814
|
-
readonly scope: string;
|
|
815
|
-
/** The winning value file relative to the records tree, or `undefined` when nothing wins. */
|
|
816
|
-
readonly location: string | undefined;
|
|
817
|
-
readonly encrypted: boolean;
|
|
818
|
-
readonly viaUnscopedFallback: boolean;
|
|
819
|
-
}
|
|
820
|
-
interface ListResult {
|
|
821
|
-
readonly environment: string;
|
|
822
|
-
readonly parameters: readonly ListEntry[];
|
|
823
|
-
}
|
|
824
|
-
declare function runList(options: ListOptions): Promise<ListResult>;
|
|
825
|
-
|
|
826
|
-
/**
|
|
827
|
-
* `penv migrate` — move a project's records under `.penv/state/records/`.
|
|
828
|
-
*
|
|
829
|
-
* penv reads one layout, so this is the one command that knows two. It is a
|
|
830
|
-
* relocation and nothing else: records move byte for byte, keeping their names,
|
|
831
|
-
* so the grammar, the cascade, the meta and the AAD that binds a ciphertext to
|
|
832
|
-
* its address all mean afterwards exactly what they meant before.
|
|
833
|
-
* `penv.schema.ts`, `penv.config.ts` and `.penv/env.ts` are the project's, and
|
|
834
|
-
* are never touched.
|
|
835
|
-
*
|
|
836
|
-
* It previews before it moves, because the one thing a migration must not do is
|
|
837
|
-
* surprise the person who ran it — and it refuses a half-migrated tree rather
|
|
838
|
-
* than merging two, since which copy of a parameter is current is a question
|
|
839
|
-
* only the user can answer.
|
|
840
|
-
*/
|
|
841
|
-
/** One thing that moves, project-relative and POSIX. */
|
|
842
|
-
interface MigrateMove {
|
|
843
|
-
readonly from: string;
|
|
844
|
-
readonly to: string;
|
|
845
|
-
}
|
|
846
|
-
interface MigratePlan {
|
|
847
|
-
readonly root: string;
|
|
848
|
-
/** What moves, in the order it is reported. */
|
|
849
|
-
readonly moves: readonly MigrateMove[];
|
|
850
|
-
/** What penv writes that is not there yet — the tree root and the safety boundary. */
|
|
851
|
-
readonly creates: readonly string[];
|
|
852
|
-
/** What penv removes: the ignore file that described the old layout. */
|
|
853
|
-
readonly removes: readonly string[];
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
|
-
* `previewed` is a plan nobody approved, so nothing was written; `current` is a
|
|
857
|
-
* project that was already on the new layout, which is what a second run says.
|
|
858
|
-
*/
|
|
859
|
-
type MigrateStatus = "migrated" | "current" | "previewed";
|
|
860
|
-
interface MigrateResult extends MigratePlan {
|
|
861
|
-
readonly status: MigrateStatus;
|
|
862
|
-
}
|
|
863
|
-
interface MigrateOptions {
|
|
864
|
-
readonly cwd: string;
|
|
865
|
-
/** Approve the plan. Without it `migrate` previews and writes nothing. */
|
|
866
|
-
readonly yes?: boolean;
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* What a migration would do, without doing any of it.
|
|
870
|
-
*
|
|
871
|
-
* The move list is `oldLayoutEntries` — the same list every command's refusal is
|
|
872
|
-
* keyed off — so the preview can never describe a different migration from the
|
|
873
|
-
* one that runs.
|
|
874
|
-
*/
|
|
875
|
-
declare function planMigrate(cwd: string): MigratePlan;
|
|
876
|
-
/**
|
|
877
|
-
* Performs a plan. Separate from {@link planMigrate} so what runs is the plan the
|
|
878
|
-
* user approved, not a second reading of the disk between the question and the
|
|
879
|
-
* answer.
|
|
880
|
-
*/
|
|
881
|
-
declare function applyMigrate(plan: MigratePlan): MigrateResult;
|
|
882
|
-
/** Plans, and applies only when the move was approved. */
|
|
883
|
-
declare function runMigrate(options: MigrateOptions): MigrateResult;
|
|
884
|
-
declare function renderMigrate(result: MigrateResult): string[];
|
|
885
|
-
|
|
886
|
-
/**
|
|
887
|
-
* `penv mv <from> <to>` — rename a parameter, every scope at once.
|
|
888
|
-
*
|
|
889
|
-
* A parameter is not one file. It is up to eight — four cascade levels, each
|
|
890
|
-
* with a plaintext and an encrypted address — plus its meta, and a rename that
|
|
891
|
-
* moved some of them would split one parameter into two. So this moves all of
|
|
892
|
-
* them or none of them, and the whole plan is checked before a single byte is
|
|
893
|
-
* written.
|
|
894
|
-
*
|
|
895
|
-
* **This is the only correct way to move an encrypted value.** A ciphertext is
|
|
896
|
-
* sealed against the address it lives at, so `mv redis-password.production.enc
|
|
897
|
-
* redis/password.production.enc` at the shell produces a file that will never
|
|
898
|
-
* open again — the value is not moved, it is destroyed, and the shell reports
|
|
899
|
-
* success. Re-sealing at the new address is the whole reason this command
|
|
900
|
-
* exists: penv asked for namespacing to be "a deliberate refactor afterwards"
|
|
901
|
-
* and then, once values could be encrypted, made doing it by hand a way to lose
|
|
902
|
-
* them.
|
|
903
|
-
*
|
|
904
|
-
* It moves the tree and never the schema. `.penv/env.ts` is yours (invariant 2),
|
|
905
|
-
* so renaming `database-url` to `database/url` leaves it declaring the old access
|
|
906
|
-
* path — and the drift report is what says so. penv names the distance; you close
|
|
907
|
-
* it. This command's report says which line to change rather than changing it.
|
|
908
|
-
*/
|
|
909
|
-
interface MoveOptions {
|
|
910
|
-
readonly cwd: string;
|
|
911
|
-
readonly from: string;
|
|
912
|
-
readonly to: string;
|
|
913
|
-
}
|
|
914
|
-
interface MovedFile {
|
|
915
|
-
readonly from: string;
|
|
916
|
-
readonly to: string;
|
|
917
|
-
/** True when the value was opened and sealed again for its new address. */
|
|
918
|
-
readonly resealed: boolean;
|
|
919
|
-
}
|
|
920
|
-
interface MoveResult {
|
|
921
|
-
readonly from: string;
|
|
922
|
-
readonly to: string;
|
|
923
|
-
readonly files: readonly MovedFile[];
|
|
924
|
-
/** The meta file's new location, or `undefined` when the parameter had none. */
|
|
925
|
-
readonly meta: string | undefined;
|
|
926
|
-
/** The access path the schema still declares, and the one it should now. */
|
|
927
|
-
readonly schema: {
|
|
928
|
-
readonly was: string;
|
|
929
|
-
readonly now: string;
|
|
930
|
-
};
|
|
931
|
-
/** The file that holds the shape to rename — cohort-aware, so the tip names one that exists. */
|
|
932
|
-
readonly schemaFile: string;
|
|
933
|
-
}
|
|
934
|
-
declare function runMove(options: MoveOptions): Promise<MoveResult>;
|
|
935
|
-
declare function renderMove(result: MoveResult): string[];
|
|
936
|
-
|
|
937
|
-
interface PullOptions {
|
|
938
|
-
readonly cwd: string;
|
|
939
|
-
readonly environment?: string;
|
|
940
|
-
/** Bare flags the command did not declare — environment shorthands, judged against the whitelist. */
|
|
941
|
-
readonly envFlags?: readonly string[];
|
|
942
|
-
/** Injected in tests: the source provider. Defaults to the one the config declares. */
|
|
943
|
-
readonly source?: AnyProvider;
|
|
944
|
-
}
|
|
945
|
-
interface PullResult {
|
|
946
|
-
readonly environment: string;
|
|
947
|
-
/** The source provider's type — `filesystem` when the environment declares no separate backend. */
|
|
948
|
-
readonly source: string;
|
|
949
|
-
/**
|
|
950
|
-
* True when the source *is* the local tree, so there was nothing to pull. The
|
|
951
|
-
* caller distinguishes "pulled nothing because the backend was empty" from
|
|
952
|
-
* "there is no backend to pull from" — opposite situations.
|
|
953
|
-
*/
|
|
954
|
-
readonly localSource: boolean;
|
|
955
|
-
/** Value files written into the local tree. */
|
|
956
|
-
readonly values: number;
|
|
957
|
-
/** Meta files written into the local tree. */
|
|
958
|
-
readonly meta: number;
|
|
959
|
-
/** Distinct parameters the pull touched, at any scope. */
|
|
960
|
-
readonly refs: number;
|
|
961
|
-
/**
|
|
962
|
-
* True when the source declares `readsValues: false`: names and meta came
|
|
963
|
-
* down, values stayed absent — the destination never returns one, and the
|
|
964
|
-
* pull says so rather than dressing emptiness as freshness.
|
|
965
|
-
*/
|
|
966
|
-
readonly valuesUnreadable?: boolean;
|
|
967
|
-
}
|
|
968
|
-
declare function runPull(options: PullOptions): Promise<PullResult>;
|
|
969
|
-
declare function renderPull(result: PullResult): string[];
|
|
970
|
-
|
|
971
|
-
/** The per-environment meta field recording penv's last push, compared against the destination's `updatedAt`. */
|
|
972
|
-
declare const LAST_PUSHED_KEY = "lastPushedAt";
|
|
973
|
-
interface PushOptions {
|
|
974
|
-
readonly cwd: string;
|
|
975
|
-
readonly environment?: string;
|
|
976
|
-
/** Bare flags the command did not declare — environment shorthands, judged against the whitelist. */
|
|
977
|
-
readonly envFlags?: readonly string[];
|
|
978
|
-
/** Permits sealed values to be decrypted locally and pushed as plaintext for the destination to re-seal. */
|
|
979
|
-
readonly allowDecrypt?: boolean;
|
|
980
|
-
/** Pre-approves creating a missing destination-side target (`--yes`). */
|
|
981
|
-
readonly yes?: boolean;
|
|
982
|
-
/** Injected in tests: the destination provider. Defaults to the one the environment's entry declares. */
|
|
983
|
-
readonly provider?: AnyProvider;
|
|
984
|
-
/** Injected in tests: answers the create-target question. Defaults to a terminal prompt. */
|
|
985
|
-
readonly confirm?: (question: string) => Promise<boolean>;
|
|
986
|
-
/** Injected in tests: the wall-clock reading recorded in meta. Defaults to now. */
|
|
987
|
-
readonly now?: string;
|
|
988
|
-
}
|
|
989
|
-
interface PushResult {
|
|
990
|
-
readonly environment: string;
|
|
991
|
-
/** The destination provider's type — its package name. */
|
|
992
|
-
readonly destination: string;
|
|
993
|
-
/** What the destination holds, which decided what crossed. */
|
|
994
|
-
readonly mode: "records" | "projection";
|
|
995
|
-
/** Values sent — resolved secrets for a projection, value files for records. */
|
|
996
|
-
readonly pushed: number;
|
|
997
|
-
/** Meta records mirrored. Records mode only. */
|
|
998
|
-
readonly meta: number;
|
|
999
|
-
readonly repositorySecrets: number;
|
|
1000
|
-
readonly environmentSecrets: number;
|
|
1001
|
-
/** How many were sealed and crossed as plaintext for the destination to re-seal. */
|
|
1002
|
-
readonly decrypted: number;
|
|
1003
|
-
/** True when the destination-side target was created by this push, on approval. */
|
|
1004
|
-
readonly createdTarget: boolean;
|
|
1005
|
-
}
|
|
1006
|
-
declare function runPush(options: PushOptions): Promise<PushResult>;
|
|
1007
|
-
declare function renderPush(result: PushResult): string[];
|
|
1008
|
-
|
|
1009
|
-
interface RemoveOptions extends ScopeOptions {
|
|
1010
|
-
readonly cwd: string;
|
|
1011
|
-
readonly key: string;
|
|
1012
|
-
}
|
|
1013
|
-
interface RemoveResult {
|
|
1014
|
-
readonly parameter: string;
|
|
1015
|
-
/** The value files that existed and are now gone, relative to the records tree. */
|
|
1016
|
-
readonly removed: readonly string[];
|
|
1017
|
-
/** Both files penv looked at, whether or not they were there. */
|
|
1018
|
-
readonly considered: readonly string[];
|
|
1019
|
-
}
|
|
1020
|
-
declare function runRemove(options: RemoveOptions): Promise<RemoveResult>;
|
|
1021
|
-
|
|
1022
|
-
interface RotateOptions {
|
|
1023
|
-
readonly cwd: string;
|
|
1024
|
-
readonly key: string;
|
|
1025
|
-
readonly environment?: string;
|
|
1026
|
-
/** Open a `dual-valid` window: write the new value while the old is still retained. */
|
|
1027
|
-
readonly begin?: boolean;
|
|
1028
|
-
/** Close a `dual-valid` window: return to `active`, stamp the completion. */
|
|
1029
|
-
readonly complete?: boolean;
|
|
1030
|
-
/**
|
|
1031
|
-
* The new value. A `begin` and an `atomic-cutover` flip write it; a `complete`
|
|
1032
|
-
* does not touch the value at all, so it needs none. Injected in tests; on the
|
|
1033
|
-
* CLI it is the positional argument or stdin, the same source `set` reads.
|
|
1034
|
-
*/
|
|
1035
|
-
readonly value?: string;
|
|
1036
|
-
/** Injected in tests: the wall-clock reading recorded in meta. Defaults to now. */
|
|
1037
|
-
readonly now?: string;
|
|
1038
|
-
}
|
|
1039
|
-
/** The single step a run performed — the three the two mechanisms decompose into. */
|
|
1040
|
-
type RotatePhase = "begin" | "complete" | "cutover";
|
|
1041
|
-
interface RotateResult {
|
|
1042
|
-
readonly parameter: string;
|
|
1043
|
-
readonly environment: string;
|
|
1044
|
-
readonly mechanism: RotationMechanism;
|
|
1045
|
-
readonly phase: RotatePhase;
|
|
1046
|
-
/** The source provider's type — where the value and its meta were written. */
|
|
1047
|
-
readonly source: string;
|
|
1048
|
-
/** True when this run wrote a new value. `begin` and `cutover` do; `complete` does not. */
|
|
1049
|
-
readonly wroteValue: boolean;
|
|
1050
|
-
/** The rotation state after this run — `rotating` after a begin, `active` otherwise. */
|
|
1051
|
-
readonly state: RotationState;
|
|
1052
|
-
/** When the current window opened, ISO. Set only after a `begin`, else `null`. */
|
|
1053
|
-
readonly rotatingSince: string | null;
|
|
1054
|
-
/** When a rotation last completed, ISO. Set after a `complete` or a `cutover`. */
|
|
1055
|
-
readonly lastRotated: string | null;
|
|
1056
|
-
}
|
|
1057
|
-
declare function runRotate(options: RotateOptions): Promise<RotateResult>;
|
|
1058
|
-
declare function renderRotate(result: RotateResult): string[];
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* Starting someone else's command, opaquely.
|
|
1062
|
-
*
|
|
1063
|
-
* `penv run -- <command>` starts exactly what follows `--`: the argument
|
|
1064
|
-
* boundaries the shell already worked out are handed to the operating system
|
|
1065
|
-
* untouched, stdio is the parent's, and the child's exit code and terminating
|
|
1066
|
-
* signal come back out. penv never parses the command, never rebuilds a command
|
|
1067
|
-
* line from it, never wraps it in a shell — a shell would re-split what the user
|
|
1068
|
-
* already split, and `penv run -- node -e "console.log(1 > 2)"` would redirect to
|
|
1069
|
-
* a file called `2`.
|
|
1070
|
-
*
|
|
1071
|
-
* Windows is the one place where "hand it to the operating system" needs help.
|
|
1072
|
-
* `pnpm`, `next` and every other node-installed tool are `.cmd` shims there, and
|
|
1073
|
-
* Node refuses to execute one without a shell. So a `.cmd`/`.bat` target — and
|
|
1074
|
-
* only that — is started through `cmd.exe /d /s /c` with
|
|
1075
|
-
* `windowsVerbatimArguments`, building the one command line cmd will accept and
|
|
1076
|
-
* escaping every argument so that cmd hands the child the same bytes penv was
|
|
1077
|
-
* given. Everything else spawns directly, on every platform.
|
|
1078
|
-
*/
|
|
1079
|
-
|
|
1080
|
-
/** How a child ended. Exactly one of these is meaningful, and both are forwarded. */
|
|
1081
|
-
interface ChildResult {
|
|
1082
|
-
/** The child's own exit code, or 1 when a signal ended it. */
|
|
1083
|
-
readonly exitCode: number;
|
|
1084
|
-
/** The signal that ended the child, when one did. */
|
|
1085
|
-
readonly signal: NodeJS.Signals | null;
|
|
1086
|
-
}
|
|
1087
|
-
interface ChildInvocation {
|
|
1088
|
-
/** The command exactly as it followed `--`: the executable, then its arguments. */
|
|
1089
|
-
readonly command: readonly string[];
|
|
1090
|
-
readonly env: Record<string, string>;
|
|
1091
|
-
readonly cwd: string;
|
|
1092
|
-
/**
|
|
1093
|
-
* What penv is starting this on its own behalf to do — `init`'s dependency
|
|
1094
|
-
* install. Absent means the command is the user's, from after `--`, and the
|
|
1095
|
-
* two failures have opposite remedies: one is about what they typed, the other
|
|
1096
|
-
* about a program penv chose to run.
|
|
1097
|
-
*/
|
|
1098
|
-
readonly purpose?: string;
|
|
1099
|
-
}
|
|
1100
|
-
/** A started child: how it ends, and the one thing a wrapper may do to it. */
|
|
1101
|
-
interface ChildHandle {
|
|
1102
|
-
/** Resolves when the child has ended, however it ended. */
|
|
1103
|
-
readonly ended: Promise<ChildResult>;
|
|
1104
|
-
/** Asks the child to stop — what `--watch` does before it starts the next one. */
|
|
1105
|
-
kill(signal?: NodeJS.Signals): void;
|
|
1106
|
-
}
|
|
1107
|
-
/** The seam `run` starts a child through — replaced in tests that assert what it was given. */
|
|
1108
|
-
type StartChild = (invocation: ChildInvocation) => ChildHandle;
|
|
1109
|
-
|
|
1110
|
-
/** Where a run reads its values from. `snapshot` is the sealed artifact. */
|
|
1111
|
-
type RunSource = "project" | "snapshot";
|
|
1112
|
-
interface RunOptions {
|
|
1113
|
-
readonly cwd: string;
|
|
1114
|
-
readonly environment?: string;
|
|
1115
|
-
/** Bare flags the command did not declare — environment shorthands, judged against the whitelist. */
|
|
1116
|
-
readonly envFlags?: readonly string[];
|
|
1117
|
-
/** Defaults to `project`. */
|
|
1118
|
-
readonly source?: string;
|
|
1119
|
-
/** The one mode allowed to synchronise. Off by default. */
|
|
1120
|
-
readonly watch?: boolean;
|
|
1121
|
-
/** The command exactly as it followed `--`. */
|
|
1122
|
-
readonly command: readonly string[];
|
|
1123
|
-
/** The environment penv itself was started with. Defaults to `process.env`. */
|
|
1124
|
-
readonly host?: Readonly<Record<string, string | undefined>>;
|
|
1125
|
-
/** Injected in tests: how a child is started. */
|
|
1126
|
-
readonly start?: StartChild;
|
|
1127
|
-
/** Injected in tests: the sync `--watch` performs. */
|
|
1128
|
-
readonly pull?: (options: PullOptions) => Promise<PullResult>;
|
|
1129
|
-
/** Injected in tests: what tells `--watch` something changed. */
|
|
1130
|
-
readonly changes?: (onChange: () => void) => {
|
|
1131
|
-
close(): void;
|
|
1132
|
-
};
|
|
1133
|
-
/** How long a replaced child has to leave before `--watch` insists. Defaults to 5s. */
|
|
1134
|
-
readonly stopGraceMs?: number;
|
|
1135
|
-
}
|
|
1136
|
-
interface RunResult {
|
|
1137
|
-
readonly environment: string;
|
|
1138
|
-
readonly source: RunSource;
|
|
1139
|
-
readonly command: readonly string[];
|
|
1140
|
-
/** Declared variables written into the child. */
|
|
1141
|
-
readonly written: number;
|
|
1142
|
-
/** Declared-but-valueless variables deleted from the child. */
|
|
1143
|
-
readonly deleted: number;
|
|
1144
|
-
/** penv's own variables removed before the child saw them. */
|
|
1145
|
-
readonly stripped: readonly string[];
|
|
1146
|
-
readonly exitCode: number;
|
|
1147
|
-
readonly signal: NodeJS.Signals | null;
|
|
1148
|
-
/** How many times `--watch` replaced the child. */
|
|
1149
|
-
readonly restarts: number;
|
|
1150
|
-
}
|
|
1151
|
-
declare function runRun(options: RunOptions): Promise<RunResult>;
|
|
1152
|
-
|
|
1153
|
-
interface WatchOptions {
|
|
1154
|
-
readonly cwd: string;
|
|
1155
|
-
readonly environment?: string;
|
|
1156
|
-
/** Defaults to {@link DEBOUNCE_MS}. */
|
|
1157
|
-
readonly debounceMs?: number;
|
|
1158
|
-
/** Called with every completed validation, starting with the initial one. */
|
|
1159
|
-
readonly onResult?: (result: ValidateResult) => void;
|
|
1160
|
-
/**
|
|
1161
|
-
* Called when a cycle could not produce a result at all — an unreadable
|
|
1162
|
-
* config, a watcher the platform dropped. Never called for a *failing*
|
|
1163
|
-
* validation: that is a result, and it goes to `onResult`.
|
|
1164
|
-
*/
|
|
1165
|
-
readonly onError?: (error: unknown) => void;
|
|
1166
|
-
}
|
|
1167
|
-
interface WatchHandle {
|
|
1168
|
-
/** Stops watching. Idempotent, and safe to call from inside a callback. */
|
|
1169
|
-
close(): void;
|
|
1170
|
-
}
|
|
1171
|
-
/**
|
|
1172
|
-
* Watches, and re-validates on change.
|
|
1173
|
-
*
|
|
1174
|
-
* Returns a handle rather than blocking, so the loop is a plain object a test
|
|
1175
|
-
* can drive and close instead of a live process it would have to spawn. The
|
|
1176
|
-
* command below is the only thing that turns it into a process that waits.
|
|
1177
|
-
*/
|
|
1178
|
-
declare function runWatch(options: WatchOptions): WatchHandle;
|
|
1179
|
-
/**
|
|
1180
|
-
* One cycle's report: `penv validate`'s, with a rule above it — on a loop, the
|
|
1181
|
-
* reader's first question is where the last run ended — and the drift below it.
|
|
1182
|
-
*
|
|
1183
|
-
* Drift comes last because it is the part that is not a verdict. The rows above
|
|
1184
|
-
* say whether the configuration is valid; these say what the schema and the tree
|
|
1185
|
-
* disagree about, which is often *why*, and is worth reading even on a run that
|
|
1186
|
-
* passed.
|
|
1187
|
-
*/
|
|
1188
|
-
declare function renderWatch(result: ValidateResult): string[];
|
|
1189
|
-
|
|
1190
|
-
interface Cutover {
|
|
1191
|
-
readonly format: number;
|
|
1192
|
-
/** When the files were moved, ISO-8601 UTC. */
|
|
1193
|
-
readonly movedAt: string;
|
|
1194
|
-
/** The filenames as they were at the project root — what undo restores, exactly. */
|
|
1195
|
-
readonly files: readonly string[];
|
|
1196
|
-
/** The environments the cutover declared, so a report can name them without the config. */
|
|
1197
|
-
readonly environments: readonly string[];
|
|
1198
|
-
}
|
|
1199
|
-
interface UndoResult {
|
|
1200
|
-
readonly root: string;
|
|
1201
|
-
/** The files put back, in the order they were moved. */
|
|
1202
|
-
readonly restored: readonly string[];
|
|
1203
|
-
/** Recorded names already at the project root — what an interrupted undo had reached. */
|
|
1204
|
-
readonly alreadyBack: readonly string[];
|
|
1205
|
-
/** Recorded names in neither the bundle nor the project root. Nothing penv can restore. */
|
|
1206
|
-
readonly missing: readonly string[];
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Puts every bundled file back under its exact original name, then drops the
|
|
1210
|
-
* bundle and the state that named it.
|
|
1211
|
-
*
|
|
1212
|
-
* Undo is resumable, because the thing it recovers from is an interruption. A
|
|
1213
|
-
* name already at the project root and no longer in the bundle is a file an
|
|
1214
|
-
* earlier run put back, not a collision — only a name that is in both places at
|
|
1215
|
-
* once is, and that is the one case worth refusing over, since restoring would
|
|
1216
|
-
* write over whatever came back. A name in neither place is reported rather than
|
|
1217
|
-
* refused: the old refusal's remedy was `penv cleanup`, which would have deleted
|
|
1218
|
-
* every file that was still recoverable.
|
|
1219
|
-
*/
|
|
1220
|
-
declare function runUndo(options: {
|
|
1221
|
-
readonly cwd: string;
|
|
1222
|
-
}): UndoResult;
|
|
1223
|
-
interface CleanupResult {
|
|
1224
|
-
readonly root: string;
|
|
1225
|
-
/** The files the bundle held. Empty when there was nothing to clean up. */
|
|
1226
|
-
readonly removed: readonly string[];
|
|
1227
|
-
readonly cleaned: boolean;
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Drops the rollback bundle and the cutover state, and nothing else. The records
|
|
1231
|
-
* tree, the schema, the config and the loader are the project's — cleanup is the
|
|
1232
|
-
* end of the migration, not the end of the adoption.
|
|
1233
|
-
*/
|
|
1234
|
-
declare function runCleanup(options: {
|
|
1235
|
-
readonly cwd: string;
|
|
1236
|
-
}): CleanupResult;
|
|
1237
|
-
|
|
1238
|
-
/**
|
|
1239
|
-
* penv's command line.
|
|
1240
|
-
*
|
|
1241
|
-
* The wiring here is deliberately thin: every command's real work is a plain
|
|
1242
|
-
* exported function that takes a `cwd` and returns a result, and citty only
|
|
1243
|
-
* parses arguments, calls it, and prints what it returned. That is what lets the
|
|
1244
|
-
* tests call the commands rather than spawn them.
|
|
1245
|
-
*/
|
|
1246
|
-
declare const main: citty.CommandDef<citty.ArgsDef>;
|
|
1247
|
-
declare function runMain(): Promise<void>;
|
|
1248
|
-
|
|
1249
|
-
export { type AdoptionPlan, type ArtifactBuildOptions, type ArtifactBuildResult, type CleanupResult, type Cutover, type CutoverPlan, type CutoverResult, type DoctorCheck, type DoctorFinding, type DoctorReport, type DoctorSeverity, type EnvironmentCheck, type FillOptions, type FillPrompt, type FillResult, type GenerateResult, type GetExplanation, type ImportReport, type InitResult, type InitStep, LAST_PUSHED_KEY, type ListResult, type MigrateMove, type MigratePlan, type MigrateResult, type MigrateStatus, type MoveResult, type PullOptions, type PullResult, type PushOptions, type PushResult, type RemoveResult, type ResealResult, type RotateOptions, type RotatePhase, type RotateResult, type RunOptions, type RunResult, type RunSource, type SetResult, type UndoResult, type ValidateIssue, type ValidateResult, type WatchHandle, type WatchOptions, applyCutover, applyMigrate, checkEnvironment, generateDotenv, importDotenv, insertEnvAlias, main, planAdoption, planCutover, planMigrate, renderArtifactBuild, renderDoctor, renderFill, renderMigrate, renderMove, renderPull, renderPush, renderRotate, renderWatch, runArtifactBuild, runCleanup, runDecrypt, runDoctor, runEncrypt, runExplain, runFill, runGenerate, runGet, runInit, runList, runMain, runMigrate, runMove, runPull, runPush, runRemove, runRotate, runRun, runSet, runUndo, runValidate, runWatch };
|