@penvhq/cli 0.9.3 → 0.9.5
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/bin.cjs +515 -186
- package/dist/bin.cjs.map +1 -1
- package/dist/index.cjs +436 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -12
- package/dist/index.d.ts +39 -12
- package/dist/index.js +425 -150
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ var startChild = (invocation) => {
|
|
|
59
59
|
const ended = new Promise((resolve9, reject) => {
|
|
60
60
|
child.on("error", (cause) => {
|
|
61
61
|
release();
|
|
62
|
-
reject(cannotStart(executable, cause));
|
|
62
|
+
reject(cannotStart(executable, cause, invocation.purpose));
|
|
63
63
|
});
|
|
64
64
|
child.on("exit", (code, signal) => {
|
|
65
65
|
release();
|
|
@@ -80,8 +80,15 @@ function noCommand() {
|
|
|
80
80
|
"Put the command after `--`, e.g. `penv run -- pnpm dev`."
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
|
-
function cannotStart(executable, cause) {
|
|
83
|
+
function cannotStart(executable, cause, purpose) {
|
|
84
84
|
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
85
|
+
if (purpose !== void 0) {
|
|
86
|
+
return new PenvError(
|
|
87
|
+
"PENV_COMMAND_NOT_STARTED",
|
|
88
|
+
`penv could not start \`${executable}\` to ${purpose}: ${detail}`,
|
|
89
|
+
`Check that \`${executable}\` runs on its own \u2014 penv starts it the way your shell does, so it has to be on PATH. Nothing was changed.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
85
92
|
return new PenvError(
|
|
86
93
|
"RUN_COMMAND_NOT_STARTED",
|
|
87
94
|
`\`${executable}\` could not be started: ${detail}`,
|
|
@@ -110,12 +117,15 @@ function cmdCommandLine(resolved, args) {
|
|
|
110
117
|
" "
|
|
111
118
|
);
|
|
112
119
|
}
|
|
113
|
-
function extensions(env) {
|
|
120
|
+
function extensions(env, platform) {
|
|
121
|
+
if (platform !== "win32") {
|
|
122
|
+
return [""];
|
|
123
|
+
}
|
|
114
124
|
const declared = env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
|
|
115
|
-
return [
|
|
125
|
+
return [...declared.split(";").filter((extension) => extension.length > 0), ""];
|
|
116
126
|
}
|
|
117
|
-
function findExecutable(executable, env) {
|
|
118
|
-
const candidates = extensions(env);
|
|
127
|
+
function findExecutable(executable, env, platform = process.platform) {
|
|
128
|
+
const candidates = extensions(env, platform);
|
|
119
129
|
const isFile = (path2) => existsSync(path2) && statSync(path2).isFile();
|
|
120
130
|
if (executable.includes("/") || executable.includes("\\") || isAbsolute(executable)) {
|
|
121
131
|
return candidates.map((extension) => executable + extension).find(isFile);
|
|
@@ -141,6 +151,7 @@ function escapeArgument(argument, doubleEscape) {
|
|
|
141
151
|
|
|
142
152
|
// src/install.ts
|
|
143
153
|
var RUNTIME_PACKAGE = "@penvhq/penv";
|
|
154
|
+
var SCHEMA_PACKAGE = "zod";
|
|
144
155
|
var LOCKFILES = [
|
|
145
156
|
["pnpm", "pnpm-lock.yaml"],
|
|
146
157
|
["yarn", "yarn.lock"],
|
|
@@ -155,13 +166,9 @@ var ADD = {
|
|
|
155
166
|
bun: ["bun", "add", "--exact"]
|
|
156
167
|
};
|
|
157
168
|
function engineVersion() {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (typeof version === "string" && version.length > 0) {
|
|
162
|
-
return version;
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
169
|
+
const version = ownManifest()?.version;
|
|
170
|
+
if (typeof version === "string" && version.length > 0) {
|
|
171
|
+
return version;
|
|
165
172
|
}
|
|
166
173
|
throw new PenvError2(
|
|
167
174
|
"ENGINE_VERSION_UNREADABLE",
|
|
@@ -169,6 +176,29 @@ function engineVersion() {
|
|
|
169
176
|
`Reinstall penv, then run \`penv init\` again.`
|
|
170
177
|
);
|
|
171
178
|
}
|
|
179
|
+
function schemaPackageVersion() {
|
|
180
|
+
const peers = ownManifest()?.peerDependencies;
|
|
181
|
+
const declared = peers !== null && typeof peers === "object" && !Array.isArray(peers) ? peers[SCHEMA_PACKAGE] : void 0;
|
|
182
|
+
const floor = typeof declared === "string" ? declared.replace(/^[\^~>=\s]+/, "").trim() : "";
|
|
183
|
+
if (floor.length > 0) {
|
|
184
|
+
return floor;
|
|
185
|
+
}
|
|
186
|
+
throw new PenvError2(
|
|
187
|
+
"ENGINE_PEER_UNREADABLE",
|
|
188
|
+
`penv could not read its own \`${SCHEMA_PACKAGE}\` peer range, so it cannot say which ${SCHEMA_PACKAGE} this project needs`,
|
|
189
|
+
`Reinstall penv, then run \`penv init\` again.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function ownManifest() {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(
|
|
195
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
196
|
+
);
|
|
197
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
198
|
+
} catch {
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
172
202
|
function detectPackageManager(root) {
|
|
173
203
|
for (const [manager, lockfile] of LOCKFILES) {
|
|
174
204
|
if (existsSync2(join2(root, lockfile))) {
|
|
@@ -197,12 +227,12 @@ function manifestOf(root) {
|
|
|
197
227
|
return void 0;
|
|
198
228
|
}
|
|
199
229
|
}
|
|
200
|
-
function declaredVersion(root) {
|
|
230
|
+
function declaredVersion(root, name) {
|
|
201
231
|
const manifest = manifestOf(root);
|
|
202
232
|
for (const field of ["dependencies", "devDependencies"]) {
|
|
203
233
|
const block = manifest?.[field];
|
|
204
234
|
if (block !== null && typeof block === "object" && !Array.isArray(block)) {
|
|
205
|
-
const version = block[
|
|
235
|
+
const version = block[name];
|
|
206
236
|
if (typeof version === "string") {
|
|
207
237
|
return version;
|
|
208
238
|
}
|
|
@@ -215,27 +245,61 @@ function planInstall(root, version = engineVersion()) {
|
|
|
215
245
|
const lockfile = LOCKFILES.find(
|
|
216
246
|
([name, file]) => name === manager && existsSync2(join2(root, file))
|
|
217
247
|
)?.[1];
|
|
218
|
-
const
|
|
248
|
+
const runtimeDeclared = declaredVersion(root, RUNTIME_PACKAGE);
|
|
249
|
+
const zodDeclared = declaredVersion(root, SCHEMA_PACKAGE);
|
|
250
|
+
const packages = [
|
|
251
|
+
{
|
|
252
|
+
name: RUNTIME_PACKAGE,
|
|
253
|
+
version,
|
|
254
|
+
...runtimeDeclared === void 0 ? {} : { declared: runtimeDeclared },
|
|
255
|
+
satisfied: runtimeDeclared === version
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: SCHEMA_PACKAGE,
|
|
259
|
+
version: schemaPackageVersion(),
|
|
260
|
+
...zodDeclared === void 0 ? {} : { declared: zodDeclared },
|
|
261
|
+
// Any declared zod counts: which zod a project uses is the project's
|
|
262
|
+
// decision, and penv is here to make sure there is one, not to move it.
|
|
263
|
+
satisfied: zodDeclared !== void 0
|
|
264
|
+
}
|
|
265
|
+
];
|
|
266
|
+
const pending = packages.filter((entry) => !entry.satisfied);
|
|
267
|
+
const specs = (pending.length === 0 ? packages : pending).map(
|
|
268
|
+
(entry) => `${entry.name}@${entry.version}`
|
|
269
|
+
);
|
|
219
270
|
return {
|
|
220
271
|
root,
|
|
221
272
|
manager,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
command: [...ADD[manager], `${RUNTIME_PACKAGE}@${version}`],
|
|
273
|
+
packages,
|
|
274
|
+
command: [...ADD[manager], ...specs],
|
|
225
275
|
...lockfile === void 0 ? {} : { lockfile },
|
|
226
|
-
|
|
227
|
-
satisfied: declared === version
|
|
276
|
+
satisfied: pending.length === 0
|
|
228
277
|
};
|
|
229
278
|
}
|
|
279
|
+
function describe(entry) {
|
|
280
|
+
return `${entry.name} ${entry.version}`;
|
|
281
|
+
}
|
|
230
282
|
function renderInstallPlan(plan2) {
|
|
231
283
|
if (plan2.satisfied) {
|
|
232
|
-
return [
|
|
284
|
+
return [
|
|
285
|
+
`package.json already has ${plan2.packages.map(describe).join(" and ")} \u2014 nothing to install.`
|
|
286
|
+
];
|
|
233
287
|
}
|
|
234
|
-
const
|
|
288
|
+
const pending = plan2.packages.filter((entry) => !entry.satisfied);
|
|
289
|
+
const added = pending.filter((entry) => entry.declared === void 0);
|
|
290
|
+
const replaced = pending.filter((entry) => entry.declared !== void 0);
|
|
235
291
|
return [
|
|
236
292
|
"package.json",
|
|
237
|
-
...
|
|
238
|
-
|
|
293
|
+
...added.length === 0 ? [] : [
|
|
294
|
+
' + "dependencies": {',
|
|
295
|
+
...added.map((entry) => ` + "${entry.name}": "${entry.version}"`),
|
|
296
|
+
" + }"
|
|
297
|
+
],
|
|
298
|
+
...replaced.flatMap((entry) => [
|
|
299
|
+
` - "${entry.name}": "${entry.declared}"`,
|
|
300
|
+
` + "${entry.name}": "${entry.version}"`
|
|
301
|
+
]),
|
|
302
|
+
...plan2.lockfile === void 0 ? [] : [plan2.lockfile, ...pending.map((entry) => ` + ${entry.name}@${entry.version}`)],
|
|
239
303
|
"",
|
|
240
304
|
`Run with: ${plan2.command.join(" ")}`
|
|
241
305
|
];
|
|
@@ -244,7 +308,8 @@ var installWithPackageManager = async (plan2) => {
|
|
|
244
308
|
const child = startChild({
|
|
245
309
|
command: plan2.command,
|
|
246
310
|
env: process.env,
|
|
247
|
-
cwd: plan2.root
|
|
311
|
+
cwd: plan2.root,
|
|
312
|
+
purpose: `install ${plan2.packages.map(describe).join(" and ")}`
|
|
248
313
|
});
|
|
249
314
|
const ended = await child.ended;
|
|
250
315
|
if (ended.exitCode !== 0 || ended.signal !== null) {
|
|
@@ -260,7 +325,7 @@ function installFailed(plan2) {
|
|
|
260
325
|
}
|
|
261
326
|
|
|
262
327
|
// src/project.ts
|
|
263
|
-
import { existsSync as existsSync3, readFileSync as
|
|
328
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
264
329
|
import { dirname, join as join3 } from "path";
|
|
265
330
|
import {
|
|
266
331
|
assertMigrated,
|
|
@@ -341,10 +406,19 @@ function environmentFromShorthand(config, candidates, explicit) {
|
|
|
341
406
|
}
|
|
342
407
|
|
|
343
408
|
// src/registry.ts
|
|
409
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
344
410
|
import { createRequire } from "module";
|
|
345
411
|
import { resolve } from "path";
|
|
346
412
|
import { pathToFileURL } from "url";
|
|
347
|
-
import {
|
|
413
|
+
import {
|
|
414
|
+
holdsProjection,
|
|
415
|
+
LOCAL_EXTENSIONS_PATH,
|
|
416
|
+
localExtensionsFile,
|
|
417
|
+
PENV_DIR,
|
|
418
|
+
PenvError as PenvError4,
|
|
419
|
+
parseLocalExtensions,
|
|
420
|
+
recordsDir
|
|
421
|
+
} from "@penvhq/core";
|
|
348
422
|
import { createFilesystemProvider } from "@penvhq/provider-filesystem";
|
|
349
423
|
import { createMockProvider } from "@penvhq/provider-mock";
|
|
350
424
|
var PLUGIN_FACTORY_EXPORT = "penvProviderFactory";
|
|
@@ -413,16 +487,40 @@ async function loadPluginProvider(type, context) {
|
|
|
413
487
|
assertSatisfiesContract(provider, type);
|
|
414
488
|
return provider;
|
|
415
489
|
}
|
|
416
|
-
function assertProvidersRegistered(config, projectRoot) {
|
|
490
|
+
function assertProvidersRegistered(config, projectRoot, options) {
|
|
491
|
+
const local = localExtensions(projectRoot);
|
|
492
|
+
const ci = options?.ci ?? isCi(process.env.CI);
|
|
417
493
|
for (const [environment, provider] of Object.entries(config.providers)) {
|
|
418
494
|
if (isProviderRegistered(provider.type)) {
|
|
419
495
|
continue;
|
|
420
496
|
}
|
|
497
|
+
if (ci && local.includes(provider.type)) {
|
|
498
|
+
throw localExtensionInCi(provider.type, environment);
|
|
499
|
+
}
|
|
421
500
|
if (resolvePlugin(provider.type, projectRoot) === void 0) {
|
|
422
501
|
throw unknownProvider(provider.type, environment);
|
|
423
502
|
}
|
|
424
503
|
}
|
|
425
504
|
}
|
|
505
|
+
function localExtensions(projectRoot) {
|
|
506
|
+
let text;
|
|
507
|
+
try {
|
|
508
|
+
text = readFileSync2(localExtensionsFile(projectRoot), "utf8");
|
|
509
|
+
} catch {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
return parseLocalExtensions(text);
|
|
513
|
+
}
|
|
514
|
+
function isCi(value) {
|
|
515
|
+
return value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
516
|
+
}
|
|
517
|
+
function localExtensionInCi(type, environment) {
|
|
518
|
+
return new PenvError4(
|
|
519
|
+
"LOCAL_EXTENSION_IN_CI",
|
|
520
|
+
`The provider \`${type}\` for environment ${environment} is a local extension, and this is CI`,
|
|
521
|
+
`${LOCAL_EXTENSIONS_PATH} records it as a package this project develops, so nothing pins the bytes CI would run. Publish it and run \`penv add ${type}\` to pin a release.`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
426
524
|
function resolvePlugin(specifier, fromDir) {
|
|
427
525
|
try {
|
|
428
526
|
const require2 = createRequire(resolve(fromDir, "noop.js"));
|
|
@@ -460,7 +558,7 @@ function unknownProvider(type, environment) {
|
|
|
460
558
|
return new PenvError4(
|
|
461
559
|
"UNKNOWN_PROVIDER",
|
|
462
560
|
`The provider \`${type}\`${where} in penv.config.ts is not installed in this project`,
|
|
463
|
-
`
|
|
561
|
+
`A provider's \`type\` is the package penv imports, so it has to resolve from this project: install it with \`npm i ${type}\`, or \u2014 if this repository is the one that builds it \u2014 run \`penv add --local ${type}\` once it is a dependency of the root. The CLI ships ${preinstalled} pre-installed.`
|
|
464
562
|
);
|
|
465
563
|
}
|
|
466
564
|
|
|
@@ -493,7 +591,7 @@ function selfContainedSchemaModule(root, schemaFile) {
|
|
|
493
591
|
if (!existsSync3(file)) {
|
|
494
592
|
return void 0;
|
|
495
593
|
}
|
|
496
|
-
const source =
|
|
594
|
+
const source = readFileSync3(file, "utf8");
|
|
497
595
|
return source.includes("PenvSchemaShape") || source.includes("z.object") ? schemaFile : void 0;
|
|
498
596
|
}
|
|
499
597
|
function schemaShapeFileOf(project) {
|
|
@@ -729,10 +827,7 @@ function writeError(lines) {
|
|
|
729
827
|
}
|
|
730
828
|
function reportError(error) {
|
|
731
829
|
if (error instanceof PenvError6) {
|
|
732
|
-
|
|
733
|
-
${error.remedy}`;
|
|
734
|
-
const message = suffix !== void 0 && error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
|
|
735
|
-
process.stderr.write(`${err.red(CROSS)} ${message}
|
|
830
|
+
process.stderr.write(`${err.red(CROSS)} ${error.summary}
|
|
736
831
|
`);
|
|
737
832
|
if (error.remedy !== void 0) {
|
|
738
833
|
process.stderr.write(` ${err.cyan("\u2192")} ${error.remedy}
|
|
@@ -762,7 +857,7 @@ async function guard(run) {
|
|
|
762
857
|
}
|
|
763
858
|
|
|
764
859
|
// src/commands/run.ts
|
|
765
|
-
import { existsSync as existsSync4, readFileSync as
|
|
860
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, watch } from "fs";
|
|
766
861
|
import { createRequire as createRequire2 } from "module";
|
|
767
862
|
import { basename, dirname as dirname2, join as join4, resolve as resolve2 } from "path";
|
|
768
863
|
import {
|
|
@@ -1607,7 +1702,7 @@ function packageManifest(specifier, root) {
|
|
|
1607
1702
|
const file = join4(directory, "package.json");
|
|
1608
1703
|
if (existsSync4(file)) {
|
|
1609
1704
|
try {
|
|
1610
|
-
const parsed = JSON.parse(
|
|
1705
|
+
const parsed = JSON.parse(readFileSync4(file, "utf8"));
|
|
1611
1706
|
if (isPlainObject(parsed) && parsed.name === specifier) {
|
|
1612
1707
|
return parsed;
|
|
1613
1708
|
}
|
|
@@ -1680,7 +1775,7 @@ function snapshotPath(host) {
|
|
|
1680
1775
|
}
|
|
1681
1776
|
function readSnapshot(path) {
|
|
1682
1777
|
try {
|
|
1683
|
-
return
|
|
1778
|
+
return readFileSync4(path, "utf8");
|
|
1684
1779
|
} catch {
|
|
1685
1780
|
throw new PenvError8(
|
|
1686
1781
|
"RUN_SNAPSHOT_MISSING",
|
|
@@ -2101,7 +2196,7 @@ import {
|
|
|
2101
2196
|
existsSync as existsSync5,
|
|
2102
2197
|
mkdirSync as mkdirSync2,
|
|
2103
2198
|
readdirSync as readdirSync2,
|
|
2104
|
-
readFileSync as
|
|
2199
|
+
readFileSync as readFileSync5,
|
|
2105
2200
|
renameSync,
|
|
2106
2201
|
rmSync,
|
|
2107
2202
|
writeFileSync as writeFileSync2
|
|
@@ -2145,27 +2240,27 @@ function readCutover(root) {
|
|
|
2145
2240
|
}
|
|
2146
2241
|
let parsed;
|
|
2147
2242
|
try {
|
|
2148
|
-
parsed = JSON.parse(
|
|
2243
|
+
parsed = JSON.parse(readFileSync5(file, "utf8"));
|
|
2149
2244
|
} catch {
|
|
2150
2245
|
throw unreadable("it is not JSON");
|
|
2151
2246
|
}
|
|
2152
2247
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2153
2248
|
throw unreadable("it is not an object");
|
|
2154
2249
|
}
|
|
2155
|
-
const
|
|
2156
|
-
if (
|
|
2250
|
+
const record2 = parsed;
|
|
2251
|
+
if (record2.format !== CUTOVER_FORMAT) {
|
|
2157
2252
|
throw unreadable(
|
|
2158
|
-
`it is format ${JSON.stringify(
|
|
2253
|
+
`it is format ${JSON.stringify(record2.format)}, and penv reads format ${CUTOVER_FORMAT}`
|
|
2159
2254
|
);
|
|
2160
2255
|
}
|
|
2161
|
-
const files =
|
|
2256
|
+
const files = record2.files;
|
|
2162
2257
|
if (!Array.isArray(files) || files.some((name) => typeof name !== "string")) {
|
|
2163
2258
|
throw unreadable("it lists no filenames");
|
|
2164
2259
|
}
|
|
2165
|
-
const environments = Array.isArray(
|
|
2260
|
+
const environments = Array.isArray(record2.environments) ? record2.environments.filter((name) => typeof name === "string") : [];
|
|
2166
2261
|
return {
|
|
2167
2262
|
format: CUTOVER_FORMAT,
|
|
2168
|
-
movedAt: typeof
|
|
2263
|
+
movedAt: typeof record2.movedAt === "string" ? record2.movedAt : "",
|
|
2169
2264
|
files,
|
|
2170
2265
|
environments
|
|
2171
2266
|
};
|
|
@@ -2292,7 +2387,7 @@ var cleanupCommand = defineCommand5({
|
|
|
2292
2387
|
});
|
|
2293
2388
|
|
|
2294
2389
|
// src/commands/doctor.ts
|
|
2295
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2390
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
2296
2391
|
import { join as join6, relative as relative2 } from "path";
|
|
2297
2392
|
import {
|
|
2298
2393
|
ARTIFACT_BUILD_COMMAND as ARTIFACT_BUILD_COMMAND2,
|
|
@@ -2307,6 +2402,7 @@ import {
|
|
|
2307
2402
|
isSecret as isSecret3,
|
|
2308
2403
|
isStuck,
|
|
2309
2404
|
openValue as openValue2,
|
|
2405
|
+
RECORDS_PATH as RECORDS_PATH2,
|
|
2310
2406
|
resolveAll as resolveAll2,
|
|
2311
2407
|
rotationOf,
|
|
2312
2408
|
tryParseDuration,
|
|
@@ -2762,6 +2858,7 @@ async function runDoctor(options) {
|
|
|
2762
2858
|
findings.push(...unusedFindings(drift));
|
|
2763
2859
|
}
|
|
2764
2860
|
findings.push(...fallbackFindings(subjects, environment));
|
|
2861
|
+
findings.push(...secrecyFindings(subjects, environment));
|
|
2765
2862
|
findings.push(...plaintextSecretFindings(subjects, environment));
|
|
2766
2863
|
findings.push(...publicSecretFindings(subjects, environment, project.config));
|
|
2767
2864
|
findings.push(...encryptionFindings(subjects, environment));
|
|
@@ -2774,6 +2871,7 @@ async function runDoctor(options) {
|
|
|
2774
2871
|
}
|
|
2775
2872
|
findings.push(...await providerDriftFindings(project, environment, options.source));
|
|
2776
2873
|
findings.push(...await projectionFindings(project, environment, options.projection));
|
|
2874
|
+
findings.push(...localExtensionFindings(project));
|
|
2777
2875
|
findings.push(...artifactFindings(project));
|
|
2778
2876
|
findings.push({
|
|
2779
2877
|
check: "provider",
|
|
@@ -2911,6 +3009,31 @@ function fallbackFindings(subjects, environment) {
|
|
|
2911
3009
|
}
|
|
2912
3010
|
];
|
|
2913
3011
|
}
|
|
3012
|
+
function secrecyFindings(subjects, environment) {
|
|
3013
|
+
const undeclared = subjects.filter(
|
|
3014
|
+
({ meta }) => effectiveMeta(meta, environment).secret === void 0
|
|
3015
|
+
);
|
|
3016
|
+
if (undeclared.length === 0) {
|
|
3017
|
+
return [
|
|
3018
|
+
{
|
|
3019
|
+
check: "secrecy-undeclared",
|
|
3020
|
+
severity: "pass",
|
|
3021
|
+
label: "Secrecy policy",
|
|
3022
|
+
subject: subjects.length === 0 ? `no parameter resolves for ${environment}` : `every parameter declares whether it is secret for ${environment}`
|
|
3023
|
+
}
|
|
3024
|
+
];
|
|
3025
|
+
}
|
|
3026
|
+
return [
|
|
3027
|
+
{
|
|
3028
|
+
check: "secrecy-undeclared",
|
|
3029
|
+
severity: "unknown",
|
|
3030
|
+
label: "Secrecy policy",
|
|
3031
|
+
subject: `${undeclared.length} of ${subjects.length} declare neither way for ${environment}`,
|
|
3032
|
+
detail: "penv was never told which of these values must be encrypted",
|
|
3033
|
+
remedy: `declare \`"secret": true\` or \`"secret": false\` in a parameter's meta file, ${RECORDS_PATH2}/<name>.json`
|
|
3034
|
+
}
|
|
3035
|
+
];
|
|
3036
|
+
}
|
|
2914
3037
|
function plaintextSecretFindings(subjects, environment) {
|
|
2915
3038
|
const secrets = subjects.filter(({ meta }) => isSecret3(meta, environment));
|
|
2916
3039
|
const findings = [];
|
|
@@ -3231,6 +3354,29 @@ function scannableFiles(directory, out2) {
|
|
|
3231
3354
|
}
|
|
3232
3355
|
}
|
|
3233
3356
|
}
|
|
3357
|
+
function localExtensionFindings(project) {
|
|
3358
|
+
const names = localExtensions(project.root);
|
|
3359
|
+
if (names.length === 0) {
|
|
3360
|
+
return [
|
|
3361
|
+
{
|
|
3362
|
+
check: "local-extension",
|
|
3363
|
+
severity: "pass",
|
|
3364
|
+
label: "Extensions",
|
|
3365
|
+
subject: "every extension this project names is pinned"
|
|
3366
|
+
}
|
|
3367
|
+
];
|
|
3368
|
+
}
|
|
3369
|
+
return [
|
|
3370
|
+
{
|
|
3371
|
+
check: "local-extension",
|
|
3372
|
+
severity: "unknown",
|
|
3373
|
+
label: "Extensions",
|
|
3374
|
+
subject: names.join(", "),
|
|
3375
|
+
detail: "resolved from this project's node_modules \u2014 no release, no pinned bytes",
|
|
3376
|
+
remedy: `publish it and run \`penv add ${names[0]}\` when this stops being a development path`
|
|
3377
|
+
}
|
|
3378
|
+
];
|
|
3379
|
+
}
|
|
3234
3380
|
function artifactFindings(project) {
|
|
3235
3381
|
const files = [];
|
|
3236
3382
|
scannableFiles(project.root, files);
|
|
@@ -3241,7 +3387,7 @@ function artifactFindings(project) {
|
|
|
3241
3387
|
if (statSync2(file).size > ARTIFACT_SCAN_LIMIT) {
|
|
3242
3388
|
continue;
|
|
3243
3389
|
}
|
|
3244
|
-
text =
|
|
3390
|
+
text = readFileSync6(file, "utf8");
|
|
3245
3391
|
} catch {
|
|
3246
3392
|
continue;
|
|
3247
3393
|
}
|
|
@@ -4216,7 +4362,7 @@ var getCommand = defineCommand12({
|
|
|
4216
4362
|
});
|
|
4217
4363
|
|
|
4218
4364
|
// src/commands/import.ts
|
|
4219
|
-
import { copyFileSync, existsSync as
|
|
4365
|
+
import { copyFileSync, existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
4220
4366
|
import { basename as basename2, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve7 } from "path";
|
|
4221
4367
|
import {
|
|
4222
4368
|
assertNever as assertNever2,
|
|
@@ -4305,7 +4451,7 @@ function writeEntries(tree, entries, refs, scope) {
|
|
|
4305
4451
|
}
|
|
4306
4452
|
|
|
4307
4453
|
// src/detect.ts
|
|
4308
|
-
import { existsSync as existsSync6, readFileSync as
|
|
4454
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
4309
4455
|
import { join as join7 } from "path";
|
|
4310
4456
|
import { DEFAULT_SCHEMA_FILE } from "@penvhq/core";
|
|
4311
4457
|
var SIGNATURES = [
|
|
@@ -4323,7 +4469,7 @@ var SIGNATURES = [
|
|
|
4323
4469
|
function exportsSchema(file) {
|
|
4324
4470
|
let source;
|
|
4325
4471
|
try {
|
|
4326
|
-
source =
|
|
4472
|
+
source = readFileSync7(file, "utf8");
|
|
4327
4473
|
} catch {
|
|
4328
4474
|
return false;
|
|
4329
4475
|
}
|
|
@@ -4375,7 +4521,7 @@ function manifestOf2(cwd) {
|
|
|
4375
4521
|
}
|
|
4376
4522
|
let manifest;
|
|
4377
4523
|
try {
|
|
4378
|
-
manifest = JSON.parse(
|
|
4524
|
+
manifest = JSON.parse(readFileSync7(file, "utf8"));
|
|
4379
4525
|
} catch {
|
|
4380
4526
|
return void 0;
|
|
4381
4527
|
}
|
|
@@ -4470,8 +4616,8 @@ function draftFieldsAcross(sources, environments) {
|
|
|
4470
4616
|
}
|
|
4471
4617
|
|
|
4472
4618
|
// src/commands/init.ts
|
|
4473
|
-
import { existsSync as
|
|
4474
|
-
import { dirname as
|
|
4619
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
4620
|
+
import { dirname as dirname6, join as join9, resolve as resolve6 } from "path";
|
|
4475
4621
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
4476
4622
|
import {
|
|
4477
4623
|
CUTOVER_PATH as CUTOVER_PATH3,
|
|
@@ -4482,7 +4628,7 @@ import {
|
|
|
4482
4628
|
PenvError as PenvError18,
|
|
4483
4629
|
parameterId as parameterId6,
|
|
4484
4630
|
parseDotenv,
|
|
4485
|
-
RECORDS_PATH as
|
|
4631
|
+
RECORDS_PATH as RECORDS_PATH3,
|
|
4486
4632
|
RESERVED_TOKENS as RESERVED_TOKENS2,
|
|
4487
4633
|
ROLLBACK_DOTENV_PATH as ROLLBACK_DOTENV_PATH3,
|
|
4488
4634
|
renderStateGitignore,
|
|
@@ -4494,6 +4640,97 @@ import {
|
|
|
4494
4640
|
} from "@penvhq/core";
|
|
4495
4641
|
import { defineCommand as defineCommand13 } from "citty";
|
|
4496
4642
|
|
|
4643
|
+
// src/scaffold-undo.ts
|
|
4644
|
+
import {
|
|
4645
|
+
existsSync as existsSync7,
|
|
4646
|
+
mkdirSync as mkdirSync3,
|
|
4647
|
+
readdirSync as readdirSync4,
|
|
4648
|
+
readFileSync as readFileSync8,
|
|
4649
|
+
rmdirSync,
|
|
4650
|
+
statSync as statSync3,
|
|
4651
|
+
unlinkSync,
|
|
4652
|
+
writeFileSync as writeFileSync4
|
|
4653
|
+
} from "fs";
|
|
4654
|
+
import { dirname as dirname5, join as join8 } from "path";
|
|
4655
|
+
function record(path, files, dirs) {
|
|
4656
|
+
let stats;
|
|
4657
|
+
try {
|
|
4658
|
+
stats = statSync3(path);
|
|
4659
|
+
} catch {
|
|
4660
|
+
return;
|
|
4661
|
+
}
|
|
4662
|
+
if (stats.isDirectory()) {
|
|
4663
|
+
dirs.add(path);
|
|
4664
|
+
for (const entry of readdirSync4(path)) {
|
|
4665
|
+
record(join8(path, entry), files, dirs);
|
|
4666
|
+
}
|
|
4667
|
+
return;
|
|
4668
|
+
}
|
|
4669
|
+
if (stats.isFile()) {
|
|
4670
|
+
files.set(path, readFileSync8(path));
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
function captureScaffold(root, paths) {
|
|
4674
|
+
const files = /* @__PURE__ */ new Map();
|
|
4675
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
4676
|
+
for (const path of paths) {
|
|
4677
|
+
for (let dir = dirname5(path); dir.startsWith(root) && dir !== root; dir = dirname5(dir)) {
|
|
4678
|
+
if (existsSync7(dir)) {
|
|
4679
|
+
dirs.add(dir);
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
record(path, files, dirs);
|
|
4683
|
+
}
|
|
4684
|
+
return { root, paths: [...paths], files, dirs };
|
|
4685
|
+
}
|
|
4686
|
+
function restoreScaffold(undo) {
|
|
4687
|
+
for (const path of undo.paths) {
|
|
4688
|
+
removeAdded(path, undo);
|
|
4689
|
+
}
|
|
4690
|
+
for (const path of undo.paths) {
|
|
4691
|
+
pruneAncestors(path, undo);
|
|
4692
|
+
}
|
|
4693
|
+
for (const [file, contents] of undo.files) {
|
|
4694
|
+
mkdirSync3(dirname5(file), { recursive: true });
|
|
4695
|
+
writeFileSync4(file, contents);
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
function removeAdded(path, undo) {
|
|
4699
|
+
let stats;
|
|
4700
|
+
try {
|
|
4701
|
+
stats = statSync3(path);
|
|
4702
|
+
} catch {
|
|
4703
|
+
return;
|
|
4704
|
+
}
|
|
4705
|
+
if (stats.isFile()) {
|
|
4706
|
+
if (!undo.files.has(path)) {
|
|
4707
|
+
unlinkSync(path);
|
|
4708
|
+
}
|
|
4709
|
+
return;
|
|
4710
|
+
}
|
|
4711
|
+
if (!stats.isDirectory()) {
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
4714
|
+
for (const entry of readdirSync4(path)) {
|
|
4715
|
+
removeAdded(join8(path, entry), undo);
|
|
4716
|
+
}
|
|
4717
|
+
if (!undo.dirs.has(path) && readdirSync4(path).length === 0) {
|
|
4718
|
+
rmdirSync(path);
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
function pruneAncestors(path, undo) {
|
|
4722
|
+
for (let dir = dirname5(path); dir.startsWith(undo.root) && dir !== undo.root; dir = dirname5(dir)) {
|
|
4723
|
+
if (undo.dirs.has(dir)) {
|
|
4724
|
+
return;
|
|
4725
|
+
}
|
|
4726
|
+
try {
|
|
4727
|
+
rmdirSync(dir);
|
|
4728
|
+
} catch {
|
|
4729
|
+
return;
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4497
4734
|
// src/seams.ts
|
|
4498
4735
|
function nextjs({ alias, srcDir }) {
|
|
4499
4736
|
return {
|
|
@@ -4649,7 +4886,7 @@ var NOT_ENVIRONMENTS2 = [...RESERVED_TOKENS2, "example", "sample", "template"];
|
|
|
4649
4886
|
function suggestEnvironments(root) {
|
|
4650
4887
|
let entries;
|
|
4651
4888
|
try {
|
|
4652
|
-
entries =
|
|
4889
|
+
entries = readdirSync5(root);
|
|
4653
4890
|
} catch {
|
|
4654
4891
|
return [];
|
|
4655
4892
|
}
|
|
@@ -4693,8 +4930,8 @@ function configOf(decisions) {
|
|
|
4693
4930
|
return { environments: decisions.environments, providers: {}, schemaFile: decisions.schemaFile };
|
|
4694
4931
|
}
|
|
4695
4932
|
function declaredIn(root) {
|
|
4696
|
-
const file =
|
|
4697
|
-
if (!
|
|
4933
|
+
const file = join9(root, CONFIG_FILE);
|
|
4934
|
+
if (!existsSync8(file)) {
|
|
4698
4935
|
return void 0;
|
|
4699
4936
|
}
|
|
4700
4937
|
return loadConfigFrom(file);
|
|
@@ -5137,15 +5374,15 @@ function renderTsconfig(target, alias) {
|
|
|
5137
5374
|
}
|
|
5138
5375
|
function ensurePenvDir(root) {
|
|
5139
5376
|
const dir = resolve6(root, PENV_DIR2);
|
|
5140
|
-
if (
|
|
5377
|
+
if (existsSync8(dir)) {
|
|
5141
5378
|
return { target: "penv-dir", action: "kept", text: `Found ${PENV_DIR2}/` };
|
|
5142
5379
|
}
|
|
5143
|
-
|
|
5380
|
+
mkdirSync4(dir, { recursive: true });
|
|
5144
5381
|
return { target: "penv-dir", action: "created", text: `Created ${PENV_DIR2}/` };
|
|
5145
5382
|
}
|
|
5146
5383
|
function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS) {
|
|
5147
|
-
const file =
|
|
5148
|
-
if (
|
|
5384
|
+
const file = join9(root, SCHEMA_SHAPE_FILE3);
|
|
5385
|
+
if (existsSync8(file)) {
|
|
5149
5386
|
return {
|
|
5150
5387
|
target: "schema",
|
|
5151
5388
|
action: "kept",
|
|
@@ -5162,7 +5399,7 @@ function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS
|
|
|
5162
5399
|
note: `(the shape lives there, from before the ${SCHEMA_SHAPE_FILE3} split \u2014 penv did not add a second one. To adopt the split: move the \`z.object\` (and any \`declare module\` block) into ${SCHEMA_SHAPE_FILE3}, leaving ${oldLayout} importing \`schema\` from it and calling \`load\`.)`
|
|
5163
5400
|
};
|
|
5164
5401
|
}
|
|
5165
|
-
|
|
5402
|
+
writeFileSync5(file, renderSchemaShapeModule(fields, draft), "utf8");
|
|
5166
5403
|
return {
|
|
5167
5404
|
target: "schema",
|
|
5168
5405
|
action: "created",
|
|
@@ -5171,8 +5408,8 @@ function writeSchemaShapeFile(root, fields, draft, decisions = DEFAULT_DECISIONS
|
|
|
5171
5408
|
};
|
|
5172
5409
|
}
|
|
5173
5410
|
function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
|
|
5174
|
-
const file =
|
|
5175
|
-
if (
|
|
5411
|
+
const file = join9(root, ...decisions.schemaFile.split("/"));
|
|
5412
|
+
if (existsSync8(file)) {
|
|
5176
5413
|
return {
|
|
5177
5414
|
target: "env",
|
|
5178
5415
|
action: "kept",
|
|
@@ -5180,8 +5417,8 @@ function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
|
|
|
5180
5417
|
note: "(yours \u2014 penv never regenerates it)"
|
|
5181
5418
|
};
|
|
5182
5419
|
}
|
|
5183
|
-
|
|
5184
|
-
|
|
5420
|
+
mkdirSync4(dirname6(file), { recursive: true });
|
|
5421
|
+
writeFileSync5(file, renderEnvModule(decisions.schemaFile, decisions.inject), "utf8");
|
|
5185
5422
|
return {
|
|
5186
5423
|
target: "env",
|
|
5187
5424
|
action: "created",
|
|
@@ -5190,19 +5427,19 @@ function writeEnvFile(root, decisions = DEFAULT_DECISIONS) {
|
|
|
5190
5427
|
};
|
|
5191
5428
|
}
|
|
5192
5429
|
function writeConfigFile(root, decisions = DEFAULT_DECISIONS) {
|
|
5193
|
-
const file =
|
|
5194
|
-
if (
|
|
5430
|
+
const file = join9(root, CONFIG_FILE);
|
|
5431
|
+
if (existsSync8(file)) {
|
|
5195
5432
|
return { target: "config", action: "kept", text: `Kept ${CONFIG_FILE}` };
|
|
5196
5433
|
}
|
|
5197
|
-
|
|
5434
|
+
writeFileSync5(file, renderConfigModule(decisions), "utf8");
|
|
5198
5435
|
return { target: "config", action: "created", text: `Generated ${CONFIG_FILE}` };
|
|
5199
5436
|
}
|
|
5200
5437
|
function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
|
|
5201
5438
|
const alias = decisions.alias;
|
|
5202
5439
|
const imports = alias.startsWith(IMPORTS_PREFIX);
|
|
5203
|
-
const file =
|
|
5440
|
+
const file = join9(root, imports ? PACKAGE_FILE : TSCONFIG_FILE);
|
|
5204
5441
|
const where = imports ? PACKAGE_FILE : TSCONFIG_FILE;
|
|
5205
|
-
if (!
|
|
5442
|
+
if (!existsSync8(file)) {
|
|
5206
5443
|
if (imports) {
|
|
5207
5444
|
return {
|
|
5208
5445
|
target: "tsconfig",
|
|
@@ -5211,14 +5448,14 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
|
|
|
5211
5448
|
note: `(run \`npm init\` first, or use \`--alias @env\` to alias through ${TSCONFIG_FILE})`
|
|
5212
5449
|
};
|
|
5213
5450
|
}
|
|
5214
|
-
|
|
5451
|
+
writeFileSync5(file, renderTsconfig(decisions.schemaFile, alias), "utf8");
|
|
5215
5452
|
return {
|
|
5216
5453
|
target: "tsconfig",
|
|
5217
5454
|
action: "created",
|
|
5218
5455
|
text: `Created ${TSCONFIG_FILE} with the ${alias} path alias`
|
|
5219
5456
|
};
|
|
5220
5457
|
}
|
|
5221
|
-
const source =
|
|
5458
|
+
const source = readFileSync9(file, "utf8");
|
|
5222
5459
|
const edit = imports ? insertImportsAlias(source, decisions.schemaFile, alias) : insertEnvAlias(source, decisions.schemaFile, alias);
|
|
5223
5460
|
if (edit.conflict !== void 0) {
|
|
5224
5461
|
return {
|
|
@@ -5235,7 +5472,7 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
|
|
|
5235
5472
|
text: `Kept the ${alias} alias in ${where}`
|
|
5236
5473
|
};
|
|
5237
5474
|
}
|
|
5238
|
-
|
|
5475
|
+
writeFileSync5(file, edit.source, "utf8");
|
|
5239
5476
|
return {
|
|
5240
5477
|
target: "tsconfig",
|
|
5241
5478
|
action: "updated",
|
|
@@ -5243,14 +5480,14 @@ function writeTsconfigAlias(root, decisions = DEFAULT_DECISIONS) {
|
|
|
5243
5480
|
};
|
|
5244
5481
|
}
|
|
5245
5482
|
function writeGitignore(root, decisions = DEFAULT_DECISIONS) {
|
|
5246
|
-
const file =
|
|
5483
|
+
const file = join9(root, ...STATE_GITIGNORE_PATH.split("/"));
|
|
5247
5484
|
const wanted = renderStateGitignore(configOf(decisions));
|
|
5248
|
-
const existing =
|
|
5485
|
+
const existing = existsSync8(file) ? readFileSync9(file, "utf8") : void 0;
|
|
5249
5486
|
if (existing === wanted) {
|
|
5250
5487
|
return { target: "gitignore", action: "kept", text: `Kept ${STATE_GITIGNORE_PATH}` };
|
|
5251
5488
|
}
|
|
5252
|
-
|
|
5253
|
-
|
|
5489
|
+
mkdirSync4(dirname6(file), { recursive: true });
|
|
5490
|
+
writeFileSync5(file, wanted, "utf8");
|
|
5254
5491
|
return {
|
|
5255
5492
|
target: "gitignore",
|
|
5256
5493
|
action: existing === void 0 ? "created" : "updated",
|
|
@@ -5266,12 +5503,12 @@ function outdatedRuntimeWarning(root) {
|
|
|
5266
5503
|
return `Injection needs @penvhq/penv ${INJECT_MIN_VERSION}+ \u2014 this project has ${version}, whose \`load\` ignores \`{ inject: true }\`. Upgrade, or process.env stays empty.`;
|
|
5267
5504
|
}
|
|
5268
5505
|
function installedPenvVersion(root) {
|
|
5269
|
-
const file =
|
|
5270
|
-
if (!
|
|
5506
|
+
const file = join9(root, "node_modules", "@penvhq", "penv", "package.json");
|
|
5507
|
+
if (!existsSync8(file)) {
|
|
5271
5508
|
return void 0;
|
|
5272
5509
|
}
|
|
5273
5510
|
try {
|
|
5274
|
-
const version = JSON.parse(
|
|
5511
|
+
const version = JSON.parse(readFileSync9(file, "utf8")).version;
|
|
5275
5512
|
return typeof version === "string" ? version : void 0;
|
|
5276
5513
|
} catch {
|
|
5277
5514
|
return void 0;
|
|
@@ -5311,11 +5548,11 @@ function writeSeam(root, decisions = DEFAULT_DECISIONS, framework = detectFramew
|
|
|
5311
5548
|
};
|
|
5312
5549
|
}
|
|
5313
5550
|
const alsoNote = writeAlso(root, seam.also);
|
|
5314
|
-
const file =
|
|
5551
|
+
const file = join9(root, ...seam.file.split("/"));
|
|
5315
5552
|
const baseNotes = [...seam.notes, ...alsoNote === void 0 ? [] : [alsoNote]];
|
|
5316
5553
|
const notes = baseNotes.length === 0 ? "" : `
|
|
5317
5554
|
${baseNotes.map((n) => ` ${n}`).join("\n")}`;
|
|
5318
|
-
if (
|
|
5555
|
+
if (existsSync8(file)) {
|
|
5319
5556
|
return {
|
|
5320
5557
|
target: "seam",
|
|
5321
5558
|
action: "info",
|
|
@@ -5323,8 +5560,8 @@ ${baseNotes.map((n) => ` ${n}`).join("\n")}`;
|
|
|
5323
5560
|
note: withWarning(`${seam.ifPresent}${notes}`, outdated)
|
|
5324
5561
|
};
|
|
5325
5562
|
}
|
|
5326
|
-
|
|
5327
|
-
|
|
5563
|
+
mkdirSync4(dirname6(file), { recursive: true });
|
|
5564
|
+
writeFileSync5(file, seam.content, "utf8");
|
|
5328
5565
|
return {
|
|
5329
5566
|
target: "seam",
|
|
5330
5567
|
action: outdated === void 0 ? "created" : "info",
|
|
@@ -5336,12 +5573,12 @@ function writeAlso(root, also) {
|
|
|
5336
5573
|
if (also === void 0) {
|
|
5337
5574
|
return void 0;
|
|
5338
5575
|
}
|
|
5339
|
-
const file =
|
|
5340
|
-
if (
|
|
5576
|
+
const file = join9(root, ...also.file.split("/"));
|
|
5577
|
+
if (existsSync8(file)) {
|
|
5341
5578
|
return also.ifPresent;
|
|
5342
5579
|
}
|
|
5343
|
-
|
|
5344
|
-
|
|
5580
|
+
mkdirSync4(dirname6(file), { recursive: true });
|
|
5581
|
+
writeFileSync5(file, also.content, "utf8");
|
|
5345
5582
|
return `Wrote ${also.file} to register it.`;
|
|
5346
5583
|
}
|
|
5347
5584
|
function withWarning(note, warning) {
|
|
@@ -5366,6 +5603,23 @@ function scaffold(root, fields, draft, decisions = DEFAULT_DECISIONS, framework
|
|
|
5366
5603
|
const seam = writeSeam(root, decisions, framework);
|
|
5367
5604
|
return seam === void 0 ? steps : [...steps, seam];
|
|
5368
5605
|
}
|
|
5606
|
+
function scaffoldPaths(root, decisions = DEFAULT_DECISIONS, framework = detectFramework(root)?.name) {
|
|
5607
|
+
const fileAt = (relative5) => join9(root, ...relative5.split("/"));
|
|
5608
|
+
const seam = decisions.inject ? seamFor(framework, {
|
|
5609
|
+
alias: decisions.alias,
|
|
5610
|
+
srcDir: srcPrefix(root),
|
|
5611
|
+
schemaFile: decisions.schemaFile
|
|
5612
|
+
}) : void 0;
|
|
5613
|
+
return [
|
|
5614
|
+
fileAt(PENV_DIR2),
|
|
5615
|
+
fileAt(SCHEMA_SHAPE_FILE3),
|
|
5616
|
+
fileAt(decisions.schemaFile),
|
|
5617
|
+
fileAt(CONFIG_FILE),
|
|
5618
|
+
fileAt(TSCONFIG_FILE),
|
|
5619
|
+
fileAt(PACKAGE_FILE),
|
|
5620
|
+
...seam?.kind === "scaffold" ? [fileAt(seam.file), ...seam.also === void 0 ? [] : [fileAt(seam.also.file)]] : []
|
|
5621
|
+
];
|
|
5622
|
+
}
|
|
5369
5623
|
var DEVELOPMENT = "development";
|
|
5370
5624
|
var LOCAL_PROVIDER = "@penvhq/provider-filesystem";
|
|
5371
5625
|
function planAdoption(root) {
|
|
@@ -5428,7 +5682,7 @@ function planCutover(input) {
|
|
|
5428
5682
|
const diagnostics = [];
|
|
5429
5683
|
let variables = 0;
|
|
5430
5684
|
for (const file of selected) {
|
|
5431
|
-
const parsed = parseDotenv(
|
|
5685
|
+
const parsed = parseDotenv(readFileSync9(join9(root, file.name), "utf8"));
|
|
5432
5686
|
const fileRefs = refsForEntries(parsed.entries, file.name, config);
|
|
5433
5687
|
adopted.push({ file, entries: parsed.entries, refs: fileRefs, scope: scopeOf(file) });
|
|
5434
5688
|
refs.push(...fileRefs);
|
|
@@ -5539,17 +5793,27 @@ async function applyCutover(plan2, options = {}) {
|
|
|
5539
5793
|
if (!plan2.install.satisfied) {
|
|
5540
5794
|
await (options.install ?? installWithPackageManager)(plan2.install);
|
|
5541
5795
|
}
|
|
5542
|
-
const
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5796
|
+
const undo = captureScaffold(plan2.root, scaffoldPaths(plan2.root, plan2.decisions, plan2.framework));
|
|
5797
|
+
let steps;
|
|
5798
|
+
try {
|
|
5799
|
+
steps = scaffold(plan2.root, plan2.fields, true, plan2.decisions, plan2.framework);
|
|
5800
|
+
const project = openProject(plan2.root);
|
|
5801
|
+
const tree = localTree(project);
|
|
5802
|
+
for (const adopted of plan2.adopted) {
|
|
5803
|
+
writeEntries(tree, adopted.entries, adopted.refs, adopted.scope);
|
|
5804
|
+
}
|
|
5805
|
+
for (const environment of plan2.adopting) {
|
|
5806
|
+
const check = await checkEnvironment(project, environment);
|
|
5807
|
+
if (check.schema === void 0) {
|
|
5808
|
+
throw draftNotLoaded(check.result);
|
|
5809
|
+
}
|
|
5810
|
+
if (!check.result.ok) {
|
|
5811
|
+
throw invalidAfterImport(check.result);
|
|
5812
|
+
}
|
|
5552
5813
|
}
|
|
5814
|
+
} catch (error) {
|
|
5815
|
+
restoreScaffold(undo);
|
|
5816
|
+
throw error;
|
|
5553
5817
|
}
|
|
5554
5818
|
const cutover = bundleDotenvFiles(
|
|
5555
5819
|
plan2.root,
|
|
@@ -5558,13 +5822,24 @@ async function applyCutover(plan2, options = {}) {
|
|
|
5558
5822
|
);
|
|
5559
5823
|
return { plan: plan2, steps, moved: cutover.files, validated: plan2.adopting };
|
|
5560
5824
|
}
|
|
5825
|
+
function issueLines(result2) {
|
|
5826
|
+
return result2.issues.map((issue) => ` ${issue.subject}: ${issue.message}`).join("\n");
|
|
5827
|
+
}
|
|
5828
|
+
var SCAFFOLD_ROLLED_BACK = "penv put the project back as it found it \u2014 your dotenv files and everything else are exactly where they were.";
|
|
5561
5829
|
function invalidAfterImport(result2) {
|
|
5562
|
-
const lines = result2.issues.map((issue) => ` ${issue.subject}: ${issue.message}`).join("\n");
|
|
5563
5830
|
return new PenvError18(
|
|
5564
5831
|
"INIT_CUTOVER_INVALID",
|
|
5565
|
-
`The imported values do not satisfy the draft schema for ${result2.environment}
|
|
5566
|
-
${
|
|
5567
|
-
`Correct
|
|
5832
|
+
`The imported values do not satisfy the draft schema for ${result2.environment}:
|
|
5833
|
+
${issueLines(result2)}`,
|
|
5834
|
+
`Correct the values above, then run \`penv init\` again. ${SCAFFOLD_ROLLED_BACK}`
|
|
5835
|
+
);
|
|
5836
|
+
}
|
|
5837
|
+
function draftNotLoaded(result2) {
|
|
5838
|
+
return new PenvError18(
|
|
5839
|
+
"INIT_DRAFT_NOT_LOADED",
|
|
5840
|
+
`penv could not load the schema it drafted for ${result2.environment}, so nothing was checked against it:
|
|
5841
|
+
${issueLines(result2)}`,
|
|
5842
|
+
`Fix the error above, then run \`penv init\` again. ${SCAFFOLD_ROLLED_BACK}`
|
|
5568
5843
|
);
|
|
5569
5844
|
}
|
|
5570
5845
|
function renderSelection(plan2, selected = plan2.preselected) {
|
|
@@ -5595,7 +5870,7 @@ function renderCutoverPlan(plan2) {
|
|
|
5595
5870
|
]);
|
|
5596
5871
|
rows.push([
|
|
5597
5872
|
` ${out.dim("values")}`,
|
|
5598
|
-
`${
|
|
5873
|
+
`${RECORDS_PATH3}/`,
|
|
5599
5874
|
out.dim(`\u2190 from ${plan2.variables} variables`)
|
|
5600
5875
|
]);
|
|
5601
5876
|
rows.push([
|
|
@@ -5627,11 +5902,11 @@ function renderCutover(result2) {
|
|
|
5627
5902
|
const glyph = step.action === "conflicted" ? WARN : step.action === "info" ? "\u2192" : CHECK;
|
|
5628
5903
|
return step.note === void 0 ? { glyph, text: step.text } : { glyph, text: step.text, note: step.note };
|
|
5629
5904
|
}),
|
|
5630
|
-
...plan2.install.
|
|
5905
|
+
...plan2.install.packages.filter((entry) => !entry.satisfied).map((entry) => ({ glyph: CHECK, text: `Installed ${entry.name}`, note: entry.version })),
|
|
5631
5906
|
{
|
|
5632
5907
|
glyph: CHECK,
|
|
5633
5908
|
text: `Imported ${plan2.fields.length} parameters`,
|
|
5634
|
-
note: `into ${
|
|
5909
|
+
note: `into ${RECORDS_PATH3}/`
|
|
5635
5910
|
},
|
|
5636
5911
|
{ glyph: CHECK, text: `Validated ${result2.validated.join(", ")}` },
|
|
5637
5912
|
{
|
|
@@ -5651,12 +5926,12 @@ function dailyCommand(root) {
|
|
|
5651
5926
|
return `penv run -- ${detectPackageManager(root)} ${devScript(root)}`;
|
|
5652
5927
|
}
|
|
5653
5928
|
function devScript(root) {
|
|
5654
|
-
const file =
|
|
5655
|
-
if (!
|
|
5929
|
+
const file = join9(root, PACKAGE_FILE);
|
|
5930
|
+
if (!existsSync8(file)) {
|
|
5656
5931
|
return "dev";
|
|
5657
5932
|
}
|
|
5658
5933
|
try {
|
|
5659
|
-
const scripts = JSON.parse(
|
|
5934
|
+
const scripts = JSON.parse(readFileSync9(file, "utf8")).scripts;
|
|
5660
5935
|
if (scripts !== null && typeof scripts === "object" && !Array.isArray(scripts)) {
|
|
5661
5936
|
const named = Object.keys(scripts);
|
|
5662
5937
|
return ["dev", "start"].find((script) => named.includes(script)) ?? named[0] ?? "dev";
|
|
@@ -5890,7 +6165,7 @@ function runUndoAction(root, action) {
|
|
|
5890
6165
|
]),
|
|
5891
6166
|
"",
|
|
5892
6167
|
result2.missing.length === 0 ? `${out.green(CHECK)} ${out.bold("Undone.")} Your dotenv files are back exactly as they were, and ${CUTOVER_PATH3} is gone.` : `${out.yellow(WARN)} ${out.bold("Undone as far as penv could.")} Everything still in the bundle is back, and ${CUTOVER_PATH3} is gone.`,
|
|
5893
|
-
`penv's records are still in ${
|
|
6168
|
+
`penv's records are still in ${RECORDS_PATH3}/ \u2014 nothing penv scaffolded is yours to lose.`
|
|
5894
6169
|
]);
|
|
5895
6170
|
}
|
|
5896
6171
|
|
|
@@ -6025,14 +6300,14 @@ function configInEffect(cwd, environment) {
|
|
|
6025
6300
|
function importDotenv(options) {
|
|
6026
6301
|
const cwd = resolve7(options.cwd);
|
|
6027
6302
|
const file = isAbsolute4(options.file) ? options.file : resolve7(cwd, options.file);
|
|
6028
|
-
if (!
|
|
6303
|
+
if (!existsSync9(file)) {
|
|
6029
6304
|
throw new PenvError19(
|
|
6030
6305
|
"IMPORT_FILE_MISSING",
|
|
6031
6306
|
`There is no file at ${file} to import`,
|
|
6032
6307
|
"Point `penv import` at an existing dotenv file, e.g. `penv import .env`."
|
|
6033
6308
|
);
|
|
6034
6309
|
}
|
|
6035
|
-
const parsed = parseDotenv2(
|
|
6310
|
+
const parsed = parseDotenv2(readFileSync10(file, "utf8"));
|
|
6036
6311
|
const { config, decisions } = configInEffect(cwd, environmentNamed(file, options.environment));
|
|
6037
6312
|
const source = displayPath3(cwd, file);
|
|
6038
6313
|
const named = scopeFromFilename(file, config);
|
|
@@ -6303,7 +6578,7 @@ var keyCommand = defineCommand15({
|
|
|
6303
6578
|
});
|
|
6304
6579
|
|
|
6305
6580
|
// src/commands/list.ts
|
|
6306
|
-
import { assertNever as assertNever3, RECORDS_PATH as
|
|
6581
|
+
import { assertNever as assertNever3, RECORDS_PATH as RECORDS_PATH4, resolveAll as resolveAll3, variableName as variableName8 } from "@penvhq/core";
|
|
6307
6582
|
import { defineCommand as defineCommand16 } from "citty";
|
|
6308
6583
|
function scopeLabel2(scope) {
|
|
6309
6584
|
switch (scope.kind) {
|
|
@@ -6347,7 +6622,7 @@ function paintScope(entry) {
|
|
|
6347
6622
|
function renderList(result2) {
|
|
6348
6623
|
if (result2.parameters.length === 0) {
|
|
6349
6624
|
return [
|
|
6350
|
-
`No parameters in ${
|
|
6625
|
+
`No parameters in ${RECORDS_PATH4}/ for environment ${result2.environment}.`,
|
|
6351
6626
|
tip(`penv set <key> --env ${result2.environment}`)
|
|
6352
6627
|
];
|
|
6353
6628
|
}
|
|
@@ -6383,22 +6658,22 @@ var listCommand = defineCommand16({
|
|
|
6383
6658
|
|
|
6384
6659
|
// src/commands/migrate.ts
|
|
6385
6660
|
import {
|
|
6386
|
-
existsSync as
|
|
6387
|
-
mkdirSync as
|
|
6388
|
-
readdirSync as
|
|
6389
|
-
readFileSync as
|
|
6661
|
+
existsSync as existsSync10,
|
|
6662
|
+
mkdirSync as mkdirSync5,
|
|
6663
|
+
readdirSync as readdirSync6,
|
|
6664
|
+
readFileSync as readFileSync11,
|
|
6390
6665
|
renameSync as renameSync2,
|
|
6391
6666
|
rmSync as rmSync2,
|
|
6392
|
-
writeFileSync as
|
|
6667
|
+
writeFileSync as writeFileSync6
|
|
6393
6668
|
} from "fs";
|
|
6394
|
-
import { dirname as
|
|
6669
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
6395
6670
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
6396
6671
|
import {
|
|
6397
6672
|
loadConfig as loadConfig2,
|
|
6398
6673
|
oldLayoutEntries,
|
|
6399
6674
|
PENV_DIR as PENV_DIR3,
|
|
6400
6675
|
PenvError as PenvError22,
|
|
6401
|
-
RECORDS_PATH as
|
|
6676
|
+
RECORDS_PATH as RECORDS_PATH5,
|
|
6402
6677
|
recordsDir as recordsDir3,
|
|
6403
6678
|
renderStateGitignore as renderStateGitignore2,
|
|
6404
6679
|
STATE_GITIGNORE_PATH as STATE_GITIGNORE_PATH2
|
|
@@ -6407,48 +6682,48 @@ import { defineCommand as defineCommand17 } from "citty";
|
|
|
6407
6682
|
var OLD_GITIGNORE = `${PENV_DIR3}/.gitignore`;
|
|
6408
6683
|
function planMigrate(cwd) {
|
|
6409
6684
|
const { config, file } = loadConfig2(cwd);
|
|
6410
|
-
const root =
|
|
6685
|
+
const root = dirname7(file);
|
|
6411
6686
|
const entries = oldLayoutEntries(root, config);
|
|
6412
6687
|
const tree = recordsDir3(root);
|
|
6413
6688
|
const collisions = collidingEntries(entries, tree);
|
|
6414
6689
|
if (collisions.length > 0) {
|
|
6415
6690
|
throw new PenvError22(
|
|
6416
6691
|
"HALF_MIGRATED",
|
|
6417
|
-
`${
|
|
6418
|
-
`Move what is left under \`${PENV_DIR3}/\` into \`${
|
|
6692
|
+
`${describe2(collisions)} in both \`${PENV_DIR3}/\` and \`${RECORDS_PATH5}/\`, and penv cannot tell which copy is current`,
|
|
6693
|
+
`Move what is left under \`${PENV_DIR3}/\` into \`${RECORDS_PATH5}/\` yourself, keeping the copy you want, then run \`penv validate\`.`
|
|
6419
6694
|
);
|
|
6420
6695
|
}
|
|
6421
6696
|
const creates = [];
|
|
6422
|
-
if (entries.length > 0 && !
|
|
6423
|
-
creates.push(`${
|
|
6697
|
+
if (entries.length > 0 && !existsSync10(tree)) {
|
|
6698
|
+
creates.push(`${RECORDS_PATH5}/`);
|
|
6424
6699
|
}
|
|
6425
|
-
if (readIfPresent(
|
|
6700
|
+
if (readIfPresent(join10(root, ...STATE_GITIGNORE_PATH2.split("/"))) !== renderStateGitignore2(config)) {
|
|
6426
6701
|
creates.push(STATE_GITIGNORE_PATH2);
|
|
6427
6702
|
}
|
|
6428
6703
|
return {
|
|
6429
6704
|
root,
|
|
6430
6705
|
moves: entries.map((entry) => ({
|
|
6431
6706
|
from: `${PENV_DIR3}/${entry}`,
|
|
6432
|
-
to: `${
|
|
6707
|
+
to: `${RECORDS_PATH5}/${entry}`
|
|
6433
6708
|
})),
|
|
6434
6709
|
creates,
|
|
6435
|
-
removes:
|
|
6710
|
+
removes: existsSync10(join10(root, ...OLD_GITIGNORE.split("/"))) ? [OLD_GITIGNORE] : []
|
|
6436
6711
|
};
|
|
6437
6712
|
}
|
|
6438
6713
|
function readIfPresent(file) {
|
|
6439
|
-
return
|
|
6714
|
+
return existsSync10(file) ? readFileSync11(file, "utf8") : void 0;
|
|
6440
6715
|
}
|
|
6441
6716
|
function collidingEntries(entries, tree) {
|
|
6442
6717
|
let held;
|
|
6443
6718
|
try {
|
|
6444
|
-
held =
|
|
6719
|
+
held = readdirSync6(tree);
|
|
6445
6720
|
} catch {
|
|
6446
6721
|
return [];
|
|
6447
6722
|
}
|
|
6448
6723
|
const taken = new Set(held.map((name) => name.toLowerCase()));
|
|
6449
6724
|
return entries.filter((entry) => taken.has(entry.toLowerCase())).sort();
|
|
6450
6725
|
}
|
|
6451
|
-
function
|
|
6726
|
+
function describe2(names) {
|
|
6452
6727
|
return names.length === 1 ? `\`${names[0]}\` is` : `${names.map((name) => `\`${name}\``).join(", ")} are`;
|
|
6453
6728
|
}
|
|
6454
6729
|
function isNoop(plan2) {
|
|
@@ -6460,18 +6735,18 @@ function applyMigrate(plan2) {
|
|
|
6460
6735
|
}
|
|
6461
6736
|
const { config } = loadConfig2(plan2.root);
|
|
6462
6737
|
if (plan2.moves.length > 0) {
|
|
6463
|
-
|
|
6738
|
+
mkdirSync5(recordsDir3(plan2.root), { recursive: true });
|
|
6464
6739
|
for (const move of plan2.moves) {
|
|
6465
|
-
renameSync2(
|
|
6740
|
+
renameSync2(join10(plan2.root, ...move.from.split("/")), join10(plan2.root, ...move.to.split("/")));
|
|
6466
6741
|
}
|
|
6467
6742
|
}
|
|
6468
6743
|
if (plan2.creates.includes(STATE_GITIGNORE_PATH2)) {
|
|
6469
|
-
const ignore =
|
|
6470
|
-
|
|
6471
|
-
|
|
6744
|
+
const ignore = join10(plan2.root, ...STATE_GITIGNORE_PATH2.split("/"));
|
|
6745
|
+
mkdirSync5(dirname7(ignore), { recursive: true });
|
|
6746
|
+
writeFileSync6(ignore, renderStateGitignore2(config), "utf8");
|
|
6472
6747
|
}
|
|
6473
6748
|
for (const removed of plan2.removes) {
|
|
6474
|
-
rmSync2(
|
|
6749
|
+
rmSync2(join10(plan2.root, ...removed.split("/")), { force: true });
|
|
6475
6750
|
}
|
|
6476
6751
|
return { ...plan2, status: "migrated" };
|
|
6477
6752
|
}
|
|
@@ -6484,7 +6759,7 @@ function runMigrate(options) {
|
|
|
6484
6759
|
}
|
|
6485
6760
|
function renderMigrate(result2) {
|
|
6486
6761
|
if (result2.status === "current") {
|
|
6487
|
-
return [`${out.green(CHECK)} Already on ${
|
|
6762
|
+
return [`${out.green(CHECK)} Already on ${RECORDS_PATH5}/ \u2014 nothing to migrate.`];
|
|
6488
6763
|
}
|
|
6489
6764
|
const done = result2.status === "migrated";
|
|
6490
6765
|
const glyph = done ? CHECK : "\u2192";
|
|
@@ -6525,7 +6800,7 @@ async function approveOnTty() {
|
|
|
6525
6800
|
var migrateCommand = defineCommand17({
|
|
6526
6801
|
meta: {
|
|
6527
6802
|
name: "migrate",
|
|
6528
|
-
description: `Move a project written under an earlier layout to ${
|
|
6803
|
+
description: `Move a project written under an earlier layout to ${RECORDS_PATH5}/`
|
|
6529
6804
|
},
|
|
6530
6805
|
args: {
|
|
6531
6806
|
yes: { type: "boolean", description: "Apply the previewed move without asking" }
|
|
@@ -6973,8 +7248,8 @@ var rotateCommand = defineCommand20({
|
|
|
6973
7248
|
});
|
|
6974
7249
|
|
|
6975
7250
|
// src/commands/watch.ts
|
|
6976
|
-
import { existsSync as
|
|
6977
|
-
import { basename as basename3, dirname as
|
|
7251
|
+
import { existsSync as existsSync11, watch as watch2 } from "fs";
|
|
7252
|
+
import { basename as basename3, dirname as dirname8, resolve as resolve8 } from "path";
|
|
6978
7253
|
import { SCHEMA_SHAPE_FILE as SCHEMA_SHAPE_FILE4, schemaFileOf as schemaFileOf5, schemaInsideTree } from "@penvhq/core";
|
|
6979
7254
|
import { defineCommand as defineCommand21 } from "citty";
|
|
6980
7255
|
var DEBOUNCE_MS2 = 100;
|
|
@@ -7033,7 +7308,7 @@ function runWatch(options) {
|
|
|
7033
7308
|
watcher.close();
|
|
7034
7309
|
}
|
|
7035
7310
|
function armRecovery(target, recursive, only) {
|
|
7036
|
-
const parent =
|
|
7311
|
+
const parent = dirname8(target);
|
|
7037
7312
|
const name = basename3(target);
|
|
7038
7313
|
let recovery;
|
|
7039
7314
|
try {
|
|
@@ -7041,14 +7316,14 @@ function runWatch(options) {
|
|
|
7041
7316
|
if (closed || recovery === void 0) {
|
|
7042
7317
|
return;
|
|
7043
7318
|
}
|
|
7044
|
-
if (!
|
|
7319
|
+
if (!existsSync11(parent)) {
|
|
7045
7320
|
stop2(recovery);
|
|
7046
7321
|
return;
|
|
7047
7322
|
}
|
|
7048
7323
|
if (filename !== null && basename3(filename) !== name) {
|
|
7049
7324
|
return;
|
|
7050
7325
|
}
|
|
7051
|
-
if (!
|
|
7326
|
+
if (!existsSync11(target)) {
|
|
7052
7327
|
return;
|
|
7053
7328
|
}
|
|
7054
7329
|
stop2(recovery);
|
|
@@ -7072,7 +7347,7 @@ function runWatch(options) {
|
|
|
7072
7347
|
if (closed) {
|
|
7073
7348
|
return;
|
|
7074
7349
|
}
|
|
7075
|
-
if (!
|
|
7350
|
+
if (!existsSync11(target)) {
|
|
7076
7351
|
if (watcher !== void 0) {
|
|
7077
7352
|
stop2(watcher);
|
|
7078
7353
|
}
|
|
@@ -7107,11 +7382,11 @@ function runWatch(options) {
|
|
|
7107
7382
|
watchers.add(watcher);
|
|
7108
7383
|
}
|
|
7109
7384
|
addWatcher(project.recordsDir, true);
|
|
7110
|
-
addWatcher(
|
|
7385
|
+
addWatcher(dirname8(project.configFile), false, configFile);
|
|
7111
7386
|
addWatcher(project.root, false, SCHEMA_SHAPE_FILE4);
|
|
7112
7387
|
if (schemaInsideTree(project.config) === void 0) {
|
|
7113
7388
|
const schemaFile = resolve8(project.root, schemaFileOf5(project.config));
|
|
7114
|
-
addWatcher(
|
|
7389
|
+
addWatcher(dirname8(schemaFile), false, basename3(schemaFile));
|
|
7115
7390
|
}
|
|
7116
7391
|
void validate();
|
|
7117
7392
|
return {
|