mandrel-platform 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -31,7 +31,9 @@ import { promisify } from "node:util";
|
|
|
31
31
|
import { test } from "node:test";
|
|
32
32
|
|
|
33
33
|
import {
|
|
34
|
+
KEY_SCHEMA,
|
|
34
35
|
MANIFEST_SCHEMA,
|
|
36
|
+
SLUG_MAPPED_SURFACES,
|
|
35
37
|
SHAPE_NAMES,
|
|
36
38
|
SHAPE_VOCABULARY,
|
|
37
39
|
applyExceptions,
|
|
@@ -49,6 +51,7 @@ import {
|
|
|
49
51
|
redactUrl,
|
|
50
52
|
renderReport,
|
|
51
53
|
resolveScriptName,
|
|
54
|
+
resolveSurfaceEnvironment,
|
|
52
55
|
runDoctor,
|
|
53
56
|
runOfflineChecks,
|
|
54
57
|
} from "./env-doctor.mjs";
|
|
@@ -1043,6 +1046,388 @@ test("no secret VALUE reaches stdout or stderr on a real run against a mock Infi
|
|
|
1043
1046
|
}
|
|
1044
1047
|
});
|
|
1045
1048
|
|
|
1049
|
+
// ---------------------------------------------------------------------------
|
|
1050
|
+
// Infisical environment slugs and folder residency (Story #464)
|
|
1051
|
+
// ---------------------------------------------------------------------------
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* A manifest with no local residency and no GitHub residency, so every finding
|
|
1055
|
+
* under test comes from the Infisical probe. One Worker is kept so the
|
|
1056
|
+
* Cloudflare surface can be asserted to keep the DEPLOY name while Infisical
|
|
1057
|
+
* is asked for the mapped slug.
|
|
1058
|
+
*/
|
|
1059
|
+
function infisicalOnlyManifest(infisical, { environmentSlugs, keys } = {}) {
|
|
1060
|
+
return parseManifest({
|
|
1061
|
+
environments: ["staging", "production"],
|
|
1062
|
+
...(environmentSlugs ? { environmentSlugs } : {}),
|
|
1063
|
+
workers: { site: { scriptName: "acme-site-{env}" } },
|
|
1064
|
+
keys:
|
|
1065
|
+
keys ??
|
|
1066
|
+
[
|
|
1067
|
+
{
|
|
1068
|
+
name: "SHARED_TOKEN",
|
|
1069
|
+
kind: "secret",
|
|
1070
|
+
sensitivity: "secret",
|
|
1071
|
+
residency: { local: null, github: null, cloudflare: null },
|
|
1072
|
+
infisical,
|
|
1073
|
+
},
|
|
1074
|
+
],
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Run the Infisical probe against a `{ "<environment-slug><folder>": [names] }`
|
|
1080
|
+
* map, recording every (environment, folder) pair the client was asked for.
|
|
1081
|
+
* The map is keyed by the slug the CLIENT sees, which is the whole point: a
|
|
1082
|
+
* remapped environment must be looked up under its store slug.
|
|
1083
|
+
*/
|
|
1084
|
+
async function infisicalRun({ manifest, present, environments = ["staging", "production"], cloudflare = null }) {
|
|
1085
|
+
const root = makeRepo({});
|
|
1086
|
+
const asked = [];
|
|
1087
|
+
try {
|
|
1088
|
+
const report = await runDoctor({
|
|
1089
|
+
manifest,
|
|
1090
|
+
repoRoot: root,
|
|
1091
|
+
environments,
|
|
1092
|
+
cloudflare,
|
|
1093
|
+
infisical: {
|
|
1094
|
+
listNames: async ({ environment, folder }) => {
|
|
1095
|
+
asked.push(`${environment}${folder}`);
|
|
1096
|
+
return present[`${environment}${folder}`] ?? [];
|
|
1097
|
+
},
|
|
1098
|
+
listValues: async () => new Map(),
|
|
1099
|
+
},
|
|
1100
|
+
});
|
|
1101
|
+
return { asked, findings: report.findings.filter((f) => f.surface === "infisical"), report };
|
|
1102
|
+
} finally {
|
|
1103
|
+
rmSync(root, { recursive: true, force: true });
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
test("a remapped Infisical slug is probed at the slug while Cloudflare keeps the deploy name", async () => {
|
|
1108
|
+
// The motivating case: deploy environments are staging/production, but the
|
|
1109
|
+
// Infisical project's slugs are staging/prod. Before this, `production`
|
|
1110
|
+
// reached Infisical verbatim and 404'd the whole surface into `error`.
|
|
1111
|
+
const scripts = [];
|
|
1112
|
+
const { asked } = await infisicalRun({
|
|
1113
|
+
manifest: infisicalOnlyManifest(
|
|
1114
|
+
{ folder: "/shared" },
|
|
1115
|
+
{ environmentSlugs: { infisical: { production: "prod" } } }
|
|
1116
|
+
),
|
|
1117
|
+
present: { "staging/shared": ["SHARED_TOKEN"], "prod/shared": ["SHARED_TOKEN"] },
|
|
1118
|
+
cloudflare: {
|
|
1119
|
+
secretNames: async (scriptName) => {
|
|
1120
|
+
scripts.push(scriptName);
|
|
1121
|
+
return [];
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
assert.deepEqual(asked, ["staging/shared", "prod/shared"]);
|
|
1127
|
+
assert.ok(!asked.some((a) => a.startsWith("production")), "the deploy name must not reach Infisical");
|
|
1128
|
+
// The Cloudflare surface resolves {env} from the DEPLOY name, unmapped —
|
|
1129
|
+
// which is why it needs no slug map of its own.
|
|
1130
|
+
assert.deepEqual(scripts, []);
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
test("a manifest declaring a Cloudflare secret still resolves {env} from the unmapped deploy name", async () => {
|
|
1134
|
+
const scripts = [];
|
|
1135
|
+
await infisicalRun({
|
|
1136
|
+
manifest: infisicalOnlyManifest(undefined, {
|
|
1137
|
+
environmentSlugs: { infisical: { production: "prod" } },
|
|
1138
|
+
keys: [
|
|
1139
|
+
{
|
|
1140
|
+
name: "SHARED_TOKEN",
|
|
1141
|
+
kind: "secret",
|
|
1142
|
+
sensitivity: "secret",
|
|
1143
|
+
residency: { local: null, github: null, cloudflare: { workers: ["site"], kind: "secret" } },
|
|
1144
|
+
infisical: { folder: "/shared" },
|
|
1145
|
+
},
|
|
1146
|
+
],
|
|
1147
|
+
}),
|
|
1148
|
+
present: {},
|
|
1149
|
+
cloudflare: {
|
|
1150
|
+
secretNames: async (scriptName) => {
|
|
1151
|
+
scripts.push(scriptName);
|
|
1152
|
+
return ["SHARED_TOKEN"];
|
|
1153
|
+
},
|
|
1154
|
+
},
|
|
1155
|
+
});
|
|
1156
|
+
assert.deepEqual(scripts, ["acme-site-staging", "acme-site-production"]);
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
test("environmentSlugs rejects a surface that is not slug-mapped, and names it", () => {
|
|
1160
|
+
assert.throws(
|
|
1161
|
+
() =>
|
|
1162
|
+
infisicalOnlyManifest({ folder: "/" }, { environmentSlugs: { cloudflare: { production: "prod" } } }),
|
|
1163
|
+
(err) => {
|
|
1164
|
+
assert.match(err.message, /"cloudflare" is not a slug-mapped surface/);
|
|
1165
|
+
assert.match(err.message, /infisical/);
|
|
1166
|
+
// The message must say WHY, or the author re-files the same request.
|
|
1167
|
+
assert.match(err.message, /scriptName/);
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
);
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
test("environmentSlugs rejects an unmapped environment and a slug that is not a non-empty string", () => {
|
|
1174
|
+
assert.throws(
|
|
1175
|
+
() => infisicalOnlyManifest({ folder: "/" }, { environmentSlugs: { infisical: { preview: "prev" } } }),
|
|
1176
|
+
/maps "preview", absent from manifest.environments/
|
|
1177
|
+
);
|
|
1178
|
+
assert.throws(
|
|
1179
|
+
() => infisicalOnlyManifest({ folder: "/" }, { environmentSlugs: { infisical: { production: "" } } }),
|
|
1180
|
+
/must be a non-empty slug string/
|
|
1181
|
+
);
|
|
1182
|
+
assert.throws(
|
|
1183
|
+
() => infisicalOnlyManifest({ folder: "/" }, { environmentSlugs: { infisical: { production: 7 } } }),
|
|
1184
|
+
/must be a non-empty slug string/
|
|
1185
|
+
);
|
|
1186
|
+
assert.throws(
|
|
1187
|
+
() => infisicalOnlyManifest({ folder: "/" }, { environmentSlugs: { infisical: ["prod"] } }),
|
|
1188
|
+
/must be an object mapping environment -> slug/
|
|
1189
|
+
);
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
test("with no environmentSlugs every environment resolves to itself", async () => {
|
|
1193
|
+
const manifest = infisicalOnlyManifest({ folder: "/shared" });
|
|
1194
|
+
// The container is normalized to a total-but-empty map, so no caller has to
|
|
1195
|
+
// distinguish "absent" from "empty".
|
|
1196
|
+
assert.deepEqual(manifest.environmentSlugs, { infisical: {} });
|
|
1197
|
+
assert.equal(resolveSurfaceEnvironment(manifest, "infisical", "production"), "production");
|
|
1198
|
+
const { asked } = await infisicalRun({ manifest, present: { "staging/shared": ["SHARED_TOKEN"] } });
|
|
1199
|
+
assert.deepEqual(asked, ["staging/shared", "production/shared"]);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
test("parseManifest normalizes the single-object infisical form to a one-entry folders array", () => {
|
|
1203
|
+
// The pre-#464 authored shape, parsed: one shape reaches probeInfisical, the
|
|
1204
|
+
// same treatment residency.github received in #459.
|
|
1205
|
+
const m = infisicalOnlyManifest({ folder: "/shared", environments: ["production"] });
|
|
1206
|
+
assert.deepEqual(m.keys[0].infisical, { folders: [{ folder: "/shared", environments: ["production"] }] });
|
|
1207
|
+
|
|
1208
|
+
const defaulted = infisicalOnlyManifest({ folder: "/shared" });
|
|
1209
|
+
assert.deepEqual(defaulted.keys[0].infisical, {
|
|
1210
|
+
folders: [{ folder: "/shared", environments: ["staging", "production"] }],
|
|
1211
|
+
});
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
test("the folders array accepts bare paths and per-entry environments", () => {
|
|
1215
|
+
const m = infisicalOnlyManifest({
|
|
1216
|
+
folders: ["/shared", { folder: "/github", environments: ["staging"] }],
|
|
1217
|
+
});
|
|
1218
|
+
assert.deepEqual(m.keys[0].infisical, {
|
|
1219
|
+
folders: [
|
|
1220
|
+
{ folder: "/shared", environments: ["staging", "production"] },
|
|
1221
|
+
{ folder: "/github", environments: ["staging"] },
|
|
1222
|
+
],
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
test("a key resident in two folders and present in both reports no finding", async () => {
|
|
1227
|
+
// The folder-import case: /cloudflare imports /shared, so the value is
|
|
1228
|
+
// genuinely readable through both. Both statements are true.
|
|
1229
|
+
const { findings } = await infisicalRun({
|
|
1230
|
+
manifest: infisicalOnlyManifest({ folders: ["/shared", "/cloudflare"] }),
|
|
1231
|
+
present: {
|
|
1232
|
+
"staging/shared": ["SHARED_TOKEN"],
|
|
1233
|
+
"staging/cloudflare": ["SHARED_TOKEN"],
|
|
1234
|
+
"production/shared": ["SHARED_TOKEN"],
|
|
1235
|
+
"production/cloudflare": ["SHARED_TOKEN"],
|
|
1236
|
+
},
|
|
1237
|
+
});
|
|
1238
|
+
assert.deepEqual(findings, []);
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
test("a misplacement across two declared folders is reported ONCE, as the missing", async () => {
|
|
1242
|
+
// Declared in /shared, actually resident in /cloudflare. Before #464 this
|
|
1243
|
+
// was two findings for one fact — a missing AND an orphan — and the orphan
|
|
1244
|
+
// was unsuppressable by an exception, so --strict-orphans could never go
|
|
1245
|
+
// green on a manifest that was merely imprecise about placement.
|
|
1246
|
+
const { findings } = await infisicalRun({
|
|
1247
|
+
manifest: infisicalOnlyManifest({ folders: ["/shared", "/cloudflare"] }),
|
|
1248
|
+
present: {
|
|
1249
|
+
"staging/cloudflare": ["SHARED_TOKEN"],
|
|
1250
|
+
"production/cloudflare": ["SHARED_TOKEN"],
|
|
1251
|
+
},
|
|
1252
|
+
environments: ["staging"],
|
|
1253
|
+
});
|
|
1254
|
+
assert.equal(findings.length, 1);
|
|
1255
|
+
assert.equal(findings[0].kind, "missing");
|
|
1256
|
+
assert.equal(findings[0].key, "SHARED_TOKEN");
|
|
1257
|
+
assert.match(findings[0].detail, /folder \/shared/);
|
|
1258
|
+
assert.equal(
|
|
1259
|
+
findings.filter((f) => f.kind === "orphan").length,
|
|
1260
|
+
0,
|
|
1261
|
+
"the sibling declared folder must not also orphan the same key"
|
|
1262
|
+
);
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
test("suppression does not cross environment — a staging-only key found in production orphans", async () => {
|
|
1266
|
+
// SHARED_TOKEN is declared in /github for staging only. Finding it in
|
|
1267
|
+
// /shared in production is undeclared presence in that environment, which
|
|
1268
|
+
// is the most interesting thing this surface can report.
|
|
1269
|
+
const { findings } = await infisicalRun({
|
|
1270
|
+
manifest: infisicalOnlyManifest({
|
|
1271
|
+
folders: [{ folder: "/github", environments: ["staging"] }, { folder: "/shared", environments: ["staging"] }],
|
|
1272
|
+
}),
|
|
1273
|
+
present: {
|
|
1274
|
+
"staging/github": ["SHARED_TOKEN"],
|
|
1275
|
+
"staging/shared": ["SHARED_TOKEN"],
|
|
1276
|
+
"production/shared": ["SHARED_TOKEN"],
|
|
1277
|
+
},
|
|
1278
|
+
});
|
|
1279
|
+
assert.equal(findings.length, 1);
|
|
1280
|
+
assert.equal(findings[0].kind, "orphan");
|
|
1281
|
+
assert.equal(findings[0].environment, "production");
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
test("a per-environment folder entry reports no missing for an environment it never names", async () => {
|
|
1285
|
+
const { findings } = await infisicalRun({
|
|
1286
|
+
manifest: infisicalOnlyManifest({ folders: [{ folder: "/operator", environments: ["production"] }] }),
|
|
1287
|
+
present: { "production/operator": ["SHARED_TOKEN"] },
|
|
1288
|
+
});
|
|
1289
|
+
assert.deepEqual(findings, []);
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
test("infisical folder residency fails closed on every malformed shape", () => {
|
|
1293
|
+
const bad = (infisical) => () => infisicalOnlyManifest(infisical);
|
|
1294
|
+
assert.throws(bad({ folders: [] }), /folders must be a non-empty array/);
|
|
1295
|
+
assert.throws(bad({ folders: ["/shared", "/shared"] }), /repeats the folder "\/shared"/);
|
|
1296
|
+
assert.throws(bad({ folders: [{ folder: "/s", environments: ["preview"] }] }), /absent from manifest.environments/);
|
|
1297
|
+
assert.throws(bad({ folder: "/s", folders: ["/t"] }), /declares both "folder" and "folders"/);
|
|
1298
|
+
assert.throws(bad({ folders: ["/s"], environments: ["staging"] }), /meaningful only beside a single "folder"/);
|
|
1299
|
+
assert.throws(bad({}), /must declare "folder" or "folders"/);
|
|
1300
|
+
assert.throws(bad({ folders: [""] }), /must be a non-empty folder path string/);
|
|
1301
|
+
assert.throws(bad({ folders: [42] }), /must be a folder path string or \{folder, environments\}/);
|
|
1302
|
+
assert.throws(bad("nope"), /must be "unmanaged", \{folder, environments\} or \{folders: \[\.\.\.\]\}/);
|
|
1303
|
+
// Unchanged from before #464: the single-object form's own env validation.
|
|
1304
|
+
assert.throws(bad({ folder: "/s", environments: ["preview"] }), /absent from manifest.environments/);
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
test("a multi-folder key with a shape earns ONE verdict per environment, not one per folder", async () => {
|
|
1308
|
+
// A key resident in two folders is READ twice, but it is one value — so
|
|
1309
|
+
// scoring it per folder would re-introduce double-reporting in the shape
|
|
1310
|
+
// stage, the very defect #464 removes from the residency stage.
|
|
1311
|
+
const root = makeRepo({});
|
|
1312
|
+
try {
|
|
1313
|
+
const manifest = infisicalOnlyManifest(undefined, {
|
|
1314
|
+
keys: [
|
|
1315
|
+
{
|
|
1316
|
+
name: "SHARED_TOKEN",
|
|
1317
|
+
kind: "var",
|
|
1318
|
+
sensitivity: "public",
|
|
1319
|
+
residency: { local: null, github: null, cloudflare: null },
|
|
1320
|
+
infisical: { folders: ["/shared", "/cloudflare"] },
|
|
1321
|
+
shape: "url",
|
|
1322
|
+
},
|
|
1323
|
+
],
|
|
1324
|
+
});
|
|
1325
|
+
const report = await runDoctor({
|
|
1326
|
+
manifest,
|
|
1327
|
+
repoRoot: root,
|
|
1328
|
+
environments: ["staging"],
|
|
1329
|
+
infisical: {
|
|
1330
|
+
listNames: async () => ["SHARED_TOKEN"],
|
|
1331
|
+
listValues: async () => new Map([["SHARED_TOKEN", "example.test"]]),
|
|
1332
|
+
},
|
|
1333
|
+
});
|
|
1334
|
+
const shapeFails = report.findings.filter((f) => f.kind === "shape-fail");
|
|
1335
|
+
assert.equal(shapeFails.length, 1, "one value, one verdict");
|
|
1336
|
+
assert.equal(shapeFails[0].key, "SHARED_TOKEN");
|
|
1337
|
+
} finally {
|
|
1338
|
+
rmSync(root, { recursive: true, force: true });
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
test("MANIFEST_SCHEMA and KEY_SCHEMA describe the slug container and the folders array", () => {
|
|
1343
|
+
assert.ok(Object.hasOwn(MANIFEST_SCHEMA, "environmentSlugs"));
|
|
1344
|
+
assert.match(MANIFEST_SCHEMA.environmentSlugs, /infisical/);
|
|
1345
|
+
assert.match(KEY_SCHEMA.infisical, /folders/);
|
|
1346
|
+
assert.deepEqual(SLUG_MAPPED_SURFACES, ["infisical"]);
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
test("the documented manifest schema block names the new shapes", () => {
|
|
1350
|
+
// The script exports the schema so the doc and the code describe one shape;
|
|
1351
|
+
// this asserts the DOC kept its half of that bargain.
|
|
1352
|
+
const doc = readFileSync(join(HERE, "..", "docs", "reusable-workflows.md"), "utf8");
|
|
1353
|
+
assert.match(doc, /"environmentSlugs"/);
|
|
1354
|
+
assert.match(doc, /"folders"/);
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
test("no secret VALUE reaches stdout or stderr through the remapped-slug, multi-folder path", async () => {
|
|
1358
|
+
// The values-safety guarantee, re-asserted over the shapes #464 adds: a
|
|
1359
|
+
// remapped environment slug and a key resident in two folders. Same
|
|
1360
|
+
// low-entropy dictionary canaries as the sibling leak test, for the same
|
|
1361
|
+
// reason (a key-shaped fixture is a true positive for gitleaks).
|
|
1362
|
+
const INJECTED = {
|
|
1363
|
+
PUBLIC_SITE_URL: "example.test/no-scheme-here",
|
|
1364
|
+
TURSO_AUTH_TOKEN: "second-canary-that-must-never-be-printed",
|
|
1365
|
+
};
|
|
1366
|
+
const requested = [];
|
|
1367
|
+
|
|
1368
|
+
const server = createServer((req, res) => {
|
|
1369
|
+
if (req.method === "POST" && req.url.startsWith("/api/v1/auth/universal-auth/login")) {
|
|
1370
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1371
|
+
res.end(JSON.stringify({ accessToken: "mock-token", expiresIn: 3600, tokenType: "Bearer" }));
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (req.url.startsWith("/api/v4/secrets")) {
|
|
1375
|
+
const params = new URL(req.url, "http://localhost").searchParams;
|
|
1376
|
+
requested.push(`${params.get("environment")}${params.get("secretPath")}`);
|
|
1377
|
+
const withValues = params.get("viewSecretValue") === "true";
|
|
1378
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1379
|
+
res.end(
|
|
1380
|
+
JSON.stringify({
|
|
1381
|
+
secrets: Object.entries(INJECTED).map(([secretKey, secretValue]) => ({
|
|
1382
|
+
secretKey,
|
|
1383
|
+
...(withValues ? { secretValue } : {}),
|
|
1384
|
+
})),
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
res.writeHead(404).end("{}");
|
|
1390
|
+
});
|
|
1391
|
+
await new Promise((r) => server.listen(0, "127.0.0.1", r));
|
|
1392
|
+
const site = `http://127.0.0.1:${server.address().port}`;
|
|
1393
|
+
|
|
1394
|
+
const raw = singleWorkerManifest();
|
|
1395
|
+
raw.environmentSlugs = { infisical: { production: "prod" } };
|
|
1396
|
+
for (const key of raw.keys) key.infisical = { folders: ["/", "/shared"] };
|
|
1397
|
+
|
|
1398
|
+
const root = makeRepo(CONSISTENT_REPO);
|
|
1399
|
+
const manifestPath = join(root, "env.manifest.json");
|
|
1400
|
+
writeFileSync(manifestPath, JSON.stringify(raw));
|
|
1401
|
+
|
|
1402
|
+
try {
|
|
1403
|
+
const run = await runCli(
|
|
1404
|
+
[
|
|
1405
|
+
"--manifest", manifestPath,
|
|
1406
|
+
"--repo-root", root,
|
|
1407
|
+
"--environments", "production",
|
|
1408
|
+
"--infisical-project", "proj-1",
|
|
1409
|
+
"--infisical-site", site,
|
|
1410
|
+
"--json",
|
|
1411
|
+
],
|
|
1412
|
+
{ env: { INFISICAL_CLIENT_ID: "id", INFISICAL_CLIENT_SECRET: "sec", ENV_DRIFT_GITHUB_TOKEN: "", CLOUDFLARE_API_TOKEN: "" } }
|
|
1413
|
+
);
|
|
1414
|
+
|
|
1415
|
+
// End-to-end proof that the slug reaches the wire through the real CLI:
|
|
1416
|
+
// every request names `prod`, never the `production` deploy name.
|
|
1417
|
+
assert.ok(requested.length > 0, "the mock store should have been asked for something");
|
|
1418
|
+
assert.deepEqual([...new Set(requested.map((r) => r.split("/")[0]))], ["prod"]);
|
|
1419
|
+
|
|
1420
|
+
const captured = run.stdout + run.stderr;
|
|
1421
|
+
assert.ok(captured.includes("PUBLIC_SITE_URL"), "the failing key's NAME should be reported");
|
|
1422
|
+
for (const [name, value] of Object.entries(INJECTED)) {
|
|
1423
|
+
assert.ok(!captured.includes(value), `the VALUE of ${name} leaked into the doctor's output`);
|
|
1424
|
+
}
|
|
1425
|
+
} finally {
|
|
1426
|
+
rmSync(root, { recursive: true, force: true });
|
|
1427
|
+
await new Promise((r) => server.close(r));
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1046
1431
|
// ---------------------------------------------------------------------------
|
|
1047
1432
|
// Shipped workflow shape
|
|
1048
1433
|
// ---------------------------------------------------------------------------
|