@biffo/cli 0.303.1 → 0.303.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/dist/index.js +494 -203
- package/package.json +1 -1
- package/scripts/verify.sh +1 -0
package/dist/index.js
CHANGED
|
@@ -10920,6 +10920,246 @@ Already accepted? List it in docs/ADR/${ALLOWLIST_FILENAME} instead of leaving t
|
|
|
10920
10920
|
console.log("\u2713 ADR numbering guard: OK");
|
|
10921
10921
|
}
|
|
10922
10922
|
|
|
10923
|
+
// src/lib/api-gateway-integration-guard.ts
|
|
10924
|
+
import { readFileSync as readFileSync30, readdirSync as readdirSync16, statSync as statSync9 } from "fs";
|
|
10925
|
+
import { join as join41 } from "path";
|
|
10926
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
10927
|
+
var MODULE_TYPE = "module";
|
|
10928
|
+
var INTEGRATION_TYPE = "aws_apigatewayv2_integration";
|
|
10929
|
+
function lineNumber(text, index) {
|
|
10930
|
+
let count = 1;
|
|
10931
|
+
for (let i = 0; i < index && i < text.length; i++) {
|
|
10932
|
+
if (text[i] === "\n") count += 1;
|
|
10933
|
+
}
|
|
10934
|
+
return count;
|
|
10935
|
+
}
|
|
10936
|
+
function balancedBraceSpan(text, openIndex) {
|
|
10937
|
+
let depth = 0;
|
|
10938
|
+
for (let i = openIndex; i < text.length; i++) {
|
|
10939
|
+
if (text[i] === "{") depth += 1;
|
|
10940
|
+
else if (text[i] === "}") {
|
|
10941
|
+
depth -= 1;
|
|
10942
|
+
if (depth === 0) return i;
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
return null;
|
|
10946
|
+
}
|
|
10947
|
+
function findResourceBlocks(text, file, resourceType) {
|
|
10948
|
+
const blocks = [];
|
|
10949
|
+
const pattern = new RegExp(`resource\\s+"${resourceType}"\\s+"([\\w-]+)"\\s*\\{`, "g");
|
|
10950
|
+
let m;
|
|
10951
|
+
while ((m = pattern.exec(text)) !== null) {
|
|
10952
|
+
const name = m[1];
|
|
10953
|
+
const line = lineNumber(text, m.index);
|
|
10954
|
+
const openIndex = m.index + m[0].length - 1;
|
|
10955
|
+
const closeIndex = balancedBraceSpan(text, openIndex);
|
|
10956
|
+
if (closeIndex === null) {
|
|
10957
|
+
blocks.push({ file, line, type: resourceType, name, body: null });
|
|
10958
|
+
continue;
|
|
10959
|
+
}
|
|
10960
|
+
blocks.push({
|
|
10961
|
+
file,
|
|
10962
|
+
line,
|
|
10963
|
+
type: resourceType,
|
|
10964
|
+
name,
|
|
10965
|
+
body: text.slice(openIndex + 1, closeIndex)
|
|
10966
|
+
});
|
|
10967
|
+
}
|
|
10968
|
+
return blocks;
|
|
10969
|
+
}
|
|
10970
|
+
function findModuleBlocks(text, file) {
|
|
10971
|
+
const blocks = [];
|
|
10972
|
+
const pattern = /module\s+"([\w-]+)"\s*\{/g;
|
|
10973
|
+
let m;
|
|
10974
|
+
while ((m = pattern.exec(text)) !== null) {
|
|
10975
|
+
const name = m[1];
|
|
10976
|
+
const line = lineNumber(text, m.index);
|
|
10977
|
+
const openIndex = m.index + m[0].length - 1;
|
|
10978
|
+
const closeIndex = balancedBraceSpan(text, openIndex);
|
|
10979
|
+
if (closeIndex === null) {
|
|
10980
|
+
blocks.push({ file, line, type: MODULE_TYPE, name, body: null });
|
|
10981
|
+
continue;
|
|
10982
|
+
}
|
|
10983
|
+
blocks.push({
|
|
10984
|
+
file,
|
|
10985
|
+
line,
|
|
10986
|
+
type: MODULE_TYPE,
|
|
10987
|
+
name,
|
|
10988
|
+
body: text.slice(openIndex + 1, closeIndex)
|
|
10989
|
+
});
|
|
10990
|
+
}
|
|
10991
|
+
return blocks;
|
|
10992
|
+
}
|
|
10993
|
+
function countRawResourceDeclarations(text, resourceType) {
|
|
10994
|
+
const needle = resourceType === MODULE_TYPE ? 'module "' : `resource "${resourceType}"`;
|
|
10995
|
+
return text.split(needle).length - 1;
|
|
10996
|
+
}
|
|
10997
|
+
function walkTerraformFiles(root) {
|
|
10998
|
+
const out = [];
|
|
10999
|
+
const walk2 = (dir) => {
|
|
11000
|
+
let entries;
|
|
11001
|
+
try {
|
|
11002
|
+
entries = readdirSync16(dir);
|
|
11003
|
+
} catch {
|
|
11004
|
+
return;
|
|
11005
|
+
}
|
|
11006
|
+
for (const entry of entries) {
|
|
11007
|
+
const p = join41(dir, entry);
|
|
11008
|
+
let st;
|
|
11009
|
+
try {
|
|
11010
|
+
st = statSync9(p);
|
|
11011
|
+
} catch {
|
|
11012
|
+
continue;
|
|
11013
|
+
}
|
|
11014
|
+
if (st.isDirectory()) {
|
|
11015
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
11016
|
+
walk2(p);
|
|
11017
|
+
continue;
|
|
11018
|
+
}
|
|
11019
|
+
if (entry.endsWith(".tf")) out.push(p);
|
|
11020
|
+
}
|
|
11021
|
+
};
|
|
11022
|
+
walk2(root);
|
|
11023
|
+
return out.sort();
|
|
11024
|
+
}
|
|
11025
|
+
function isApiGatewaySource(body) {
|
|
11026
|
+
const m = /source\s*=\s*"([^"]+)"/.exec(body);
|
|
11027
|
+
if (!m) return false;
|
|
11028
|
+
return /(^|\/)modules\/cloud\/aws\/api-gateway\/?$/.test(m[1].trim());
|
|
11029
|
+
}
|
|
11030
|
+
function protectedFunctionModuleName(body) {
|
|
11031
|
+
const m = /lambda_function_arn\s*=\s*module\.([\w-]+)\.function_arn\b/.exec(body);
|
|
11032
|
+
return m ? m[1] : null;
|
|
11033
|
+
}
|
|
11034
|
+
function integrationUriOf(body) {
|
|
11035
|
+
const m = /integration_uri\s*=\s*(.+)/.exec(body);
|
|
11036
|
+
if (!m) return null;
|
|
11037
|
+
return m[1].trim().replace(/^"\$\{(.+)\}"$/, "$1").replace(/^"(.+)"$/, "$1").trim();
|
|
11038
|
+
}
|
|
11039
|
+
function auditApiGatewayIntegrations(root) {
|
|
11040
|
+
const files = walkTerraformFiles(root);
|
|
11041
|
+
if (files.length === 0) {
|
|
11042
|
+
throw new Error(
|
|
11043
|
+
`auditApiGatewayIntegrations: no .tf files found under ${root} \u2014 the guard cannot verify anything against input it cannot see. This is a hard failure, not "0 violations": fix the path, do not treat an empty scan as a clean pass.`
|
|
11044
|
+
);
|
|
11045
|
+
}
|
|
11046
|
+
const moduleBlocks = [];
|
|
11047
|
+
const integrationBlocks = [];
|
|
11048
|
+
let rawModuleCount = 0;
|
|
11049
|
+
let rawIntegrationCount = 0;
|
|
11050
|
+
for (const file of files) {
|
|
11051
|
+
const text = readFileSync30(file, "utf8");
|
|
11052
|
+
rawModuleCount += countRawResourceDeclarations(text, MODULE_TYPE);
|
|
11053
|
+
rawIntegrationCount += countRawResourceDeclarations(text, INTEGRATION_TYPE);
|
|
11054
|
+
moduleBlocks.push(...findModuleBlocks(text, file));
|
|
11055
|
+
integrationBlocks.push(...findResourceBlocks(text, file, INTEGRATION_TYPE));
|
|
11056
|
+
}
|
|
11057
|
+
const unterminatedBlocks = [...moduleBlocks, ...integrationBlocks].filter((b) => b.body === null);
|
|
11058
|
+
const moduleBlind = rawModuleCount > 0 && moduleBlocks.length === 0;
|
|
11059
|
+
const integrationBlind = rawIntegrationCount > 0 && integrationBlocks.length === 0;
|
|
11060
|
+
const protectedLambdas = [];
|
|
11061
|
+
for (const block of moduleBlocks) {
|
|
11062
|
+
if (block.body === null) continue;
|
|
11063
|
+
if (!isApiGatewaySource(block.body)) continue;
|
|
11064
|
+
if (block.file.split("/").includes("_skeletons")) continue;
|
|
11065
|
+
const functionModuleName = protectedFunctionModuleName(block.body);
|
|
11066
|
+
if (functionModuleName === null) continue;
|
|
11067
|
+
protectedLambdas.push({
|
|
11068
|
+
functionModuleName,
|
|
11069
|
+
gatewayModuleName: block.name,
|
|
11070
|
+
file: block.file,
|
|
11071
|
+
line: block.line
|
|
11072
|
+
});
|
|
11073
|
+
}
|
|
11074
|
+
const protectedNames = new Set(protectedLambdas.map((p) => p.functionModuleName));
|
|
11075
|
+
const integrations = [];
|
|
11076
|
+
for (const block of integrationBlocks) {
|
|
11077
|
+
if (block.body === null) continue;
|
|
11078
|
+
integrations.push({
|
|
11079
|
+
file: block.file,
|
|
11080
|
+
line: block.line,
|
|
11081
|
+
name: block.name,
|
|
11082
|
+
integrationUri: integrationUriOf(block.body)
|
|
11083
|
+
});
|
|
11084
|
+
}
|
|
11085
|
+
const violations = [];
|
|
11086
|
+
for (const integration of integrations) {
|
|
11087
|
+
if (integration.integrationUri === null) continue;
|
|
11088
|
+
const m = /^module\.([\w-]+)\.function_arn$/.exec(integration.integrationUri);
|
|
11089
|
+
if (!m) continue;
|
|
11090
|
+
const targetModuleName = m[1];
|
|
11091
|
+
if (!protectedNames.has(targetModuleName)) continue;
|
|
11092
|
+
const owner = protectedLambdas.find((p) => p.functionModuleName === targetModuleName);
|
|
11093
|
+
violations.push({
|
|
11094
|
+
file: integration.file,
|
|
11095
|
+
line: integration.line,
|
|
11096
|
+
integrationName: integration.name,
|
|
11097
|
+
targetModuleName,
|
|
11098
|
+
reason: `aws_apigatewayv2_integration.${integration.name} targets module.${targetModuleName}.function_arn (unqualified), but module.${owner?.gatewayModuleName ?? "<gateway>"} already grants that Lambda's "live" alias \u2014 not the unqualified function \u2014 invoke access (#1747). This integration will fail closed with a generic API Gateway 500 at runtime, invisible to terraform plan/apply on either side. Use module.${owner?.gatewayModuleName ?? "<gateway>"}.lambda_integration_uri instead (biffo-template#1900).`
|
|
11099
|
+
});
|
|
11100
|
+
}
|
|
11101
|
+
const ok = !moduleBlind && !integrationBlind && unterminatedBlocks.length === 0 && violations.length === 0;
|
|
11102
|
+
const summary = `scanned ${files.length} .tf file(s) under ${root}; ${moduleBlocks.length} module block(s) found (${protectedLambdas.length} api-gateway instance(s) fronting an alias-qualified Lambda), ${integrationBlocks.length} ${INTEGRATION_TYPE} block(s) found; ${violations.length} mis-qualified, ${unterminatedBlocks.length} unterminated.`;
|
|
11103
|
+
return {
|
|
11104
|
+
filesScanned: files.length,
|
|
11105
|
+
moduleBlocksFound: moduleBlocks.length,
|
|
11106
|
+
integrationBlocksFound: integrationBlocks.length,
|
|
11107
|
+
protectedLambdas,
|
|
11108
|
+
integrations,
|
|
11109
|
+
unterminatedBlocks,
|
|
11110
|
+
violations,
|
|
11111
|
+
moduleBlind,
|
|
11112
|
+
integrationBlind,
|
|
11113
|
+
ok,
|
|
11114
|
+
summary
|
|
11115
|
+
};
|
|
11116
|
+
}
|
|
11117
|
+
|
|
11118
|
+
// src/scripts/check-api-gateway-integration.ts
|
|
11119
|
+
async function runApiGatewayIntegrationCheck() {
|
|
11120
|
+
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11121
|
+
let report;
|
|
11122
|
+
try {
|
|
11123
|
+
report = auditApiGatewayIntegrations(root);
|
|
11124
|
+
} catch (err) {
|
|
11125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11126
|
+
if (message.includes("no .tf files found under")) {
|
|
11127
|
+
console.log(
|
|
11128
|
+
`\xB7 API Gateway integration-qualifier guard: not applicable \u2014 ${message} This repo carries no Terraform at all, which is expected for a non-instance satellite. Skipping.`
|
|
11129
|
+
);
|
|
11130
|
+
return;
|
|
11131
|
+
}
|
|
11132
|
+
console.error("\u2717 API Gateway integration-qualifier guard: could not run\n");
|
|
11133
|
+
console.error(message);
|
|
11134
|
+
process.exit(1);
|
|
11135
|
+
}
|
|
11136
|
+
console.log(`scanned ${report.filesScanned} .tf file(s) under ${root}`);
|
|
11137
|
+
if (!report.ok) {
|
|
11138
|
+
console.error("\u2717 API Gateway integration-qualifier guard: mis-qualified integration(s) found\n");
|
|
11139
|
+
if (report.moduleBlind) {
|
|
11140
|
+
console.error(
|
|
11141
|
+
' BLIND (module): raw source contains `module "..."` blocks but the block extractor found none \u2014 the extractor broke, this is not evidence there are no modules.'
|
|
11142
|
+
);
|
|
11143
|
+
}
|
|
11144
|
+
if (report.integrationBlind) {
|
|
11145
|
+
console.error(
|
|
11146
|
+
" BLIND (aws_apigatewayv2_integration): raw source contains this resource type but the block extractor found none \u2014 the extractor broke, this is not evidence there are no integrations."
|
|
11147
|
+
);
|
|
11148
|
+
}
|
|
11149
|
+
for (const b of report.unterminatedBlocks) {
|
|
11150
|
+
console.error(
|
|
11151
|
+
` UNTERMINATED ${b.file}:${b.line} ${b.type} "${b.name}" never closes \u2014 cannot verify what it targets or grants.`
|
|
11152
|
+
);
|
|
11153
|
+
}
|
|
11154
|
+
for (const v of report.violations) {
|
|
11155
|
+
console.error(` MIS-QUALIFIED ${v.file}:${v.line} ${v.reason}`);
|
|
11156
|
+
}
|
|
11157
|
+
console.error("\nSee biffo-template#1900.");
|
|
11158
|
+
process.exit(1);
|
|
11159
|
+
}
|
|
11160
|
+
console.log(`\u2713 API Gateway integration-qualifier guard: ${report.summary}`);
|
|
11161
|
+
}
|
|
11162
|
+
|
|
10923
11163
|
// src/scripts/check-branch-protection.ts
|
|
10924
11164
|
import { Octokit as Octokit2 } from "@octokit/rest";
|
|
10925
11165
|
|
|
@@ -11180,18 +11420,18 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
11180
11420
|
}
|
|
11181
11421
|
|
|
11182
11422
|
// src/lib/claim-invocation-parity.ts
|
|
11183
|
-
import { existsSync as existsSync40, readFileSync as
|
|
11184
|
-
import { join as
|
|
11423
|
+
import { existsSync as existsSync40, readFileSync as readFileSync31, readdirSync as readdirSync17 } from "fs";
|
|
11424
|
+
import { join as join42 } from "path";
|
|
11185
11425
|
function distributedAgentsDocs(root) {
|
|
11186
11426
|
const docs = [];
|
|
11187
|
-
const own =
|
|
11188
|
-
if (existsSync40(own)) docs.push({ path: "AGENTS.md", text:
|
|
11189
|
-
const skeletons =
|
|
11427
|
+
const own = join42(root, "AGENTS.md");
|
|
11428
|
+
if (existsSync40(own)) docs.push({ path: "AGENTS.md", text: readFileSync31(own, "utf8") });
|
|
11429
|
+
const skeletons = join42(root, "_skeletons");
|
|
11190
11430
|
if (existsSync40(skeletons)) {
|
|
11191
|
-
for (const name of
|
|
11192
|
-
const abs =
|
|
11431
|
+
for (const name of readdirSync17(skeletons).sort()) {
|
|
11432
|
+
const abs = join42(skeletons, name, "AGENTS.md");
|
|
11193
11433
|
if (!existsSync40(abs)) continue;
|
|
11194
|
-
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text:
|
|
11434
|
+
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync31(abs, "utf8") });
|
|
11195
11435
|
}
|
|
11196
11436
|
}
|
|
11197
11437
|
return docs;
|
|
@@ -11324,12 +11564,12 @@ async function runClaimInvocationCheck() {
|
|
|
11324
11564
|
|
|
11325
11565
|
// src/scripts/check-codeql-suppression.ts
|
|
11326
11566
|
import { existsSync as existsSync41 } from "fs";
|
|
11327
|
-
import { join as
|
|
11567
|
+
import { join as join44, relative as relative8 } from "path";
|
|
11328
11568
|
|
|
11329
11569
|
// src/lib/codeql-suppression-guard.ts
|
|
11330
|
-
import { readdirSync as
|
|
11331
|
-
import { join as
|
|
11332
|
-
var
|
|
11570
|
+
import { readdirSync as readdirSync18, readFileSync as readFileSync32, statSync as statSync10 } from "fs";
|
|
11571
|
+
import { join as join43 } from "path";
|
|
11572
|
+
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
11333
11573
|
".git",
|
|
11334
11574
|
".worktrees",
|
|
11335
11575
|
"node_modules",
|
|
@@ -11354,20 +11594,20 @@ function walkSourceFiles(root) {
|
|
|
11354
11594
|
const walk2 = (dir) => {
|
|
11355
11595
|
let entries;
|
|
11356
11596
|
try {
|
|
11357
|
-
entries =
|
|
11597
|
+
entries = readdirSync18(dir);
|
|
11358
11598
|
} catch {
|
|
11359
11599
|
return;
|
|
11360
11600
|
}
|
|
11361
11601
|
for (const entry of entries) {
|
|
11362
|
-
const p =
|
|
11602
|
+
const p = join43(dir, entry);
|
|
11363
11603
|
let st;
|
|
11364
11604
|
try {
|
|
11365
|
-
st =
|
|
11605
|
+
st = statSync10(p);
|
|
11366
11606
|
} catch {
|
|
11367
11607
|
continue;
|
|
11368
11608
|
}
|
|
11369
11609
|
if (st.isDirectory()) {
|
|
11370
|
-
if (
|
|
11610
|
+
if (SKIP_DIRS2.has(entry)) continue;
|
|
11371
11611
|
walk2(p);
|
|
11372
11612
|
continue;
|
|
11373
11613
|
}
|
|
@@ -11385,7 +11625,7 @@ function countSourceFiles(root) {
|
|
|
11385
11625
|
function sweepCodeqlSuppressionComments(root) {
|
|
11386
11626
|
const hits = [];
|
|
11387
11627
|
for (const path of walkSourceFiles(root)) {
|
|
11388
|
-
const text =
|
|
11628
|
+
const text = readFileSync32(path, "utf8");
|
|
11389
11629
|
for (const line of findCodeqlSuppressionComments(text)) {
|
|
11390
11630
|
hits.push({ path, line, text: text.split("\n")[line - 1] ?? "" });
|
|
11391
11631
|
}
|
|
@@ -11396,7 +11636,7 @@ function sweepCodeqlSuppressionComments(root) {
|
|
|
11396
11636
|
// src/scripts/check-codeql-suppression.ts
|
|
11397
11637
|
async function runCodeqlSuppressionCheck() {
|
|
11398
11638
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11399
|
-
const scanRoot =
|
|
11639
|
+
const scanRoot = join44(root, "cli", "src");
|
|
11400
11640
|
if (!existsSync41(scanRoot)) {
|
|
11401
11641
|
console.log(
|
|
11402
11642
|
"\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
|
|
@@ -11420,9 +11660,13 @@ async function runCodeqlSuppressionCheck() {
|
|
|
11420
11660
|
console.log("\u2713 codeql-suppression guard: no dead `codeql[...]` suppression comment found");
|
|
11421
11661
|
}
|
|
11422
11662
|
|
|
11663
|
+
// src/scripts/check-cognito-invite-template.ts
|
|
11664
|
+
import { existsSync as existsSync42 } from "fs";
|
|
11665
|
+
import { join as join46 } from "path";
|
|
11666
|
+
|
|
11423
11667
|
// src/lib/cognito-invite-template-guard.ts
|
|
11424
|
-
import { readdirSync as
|
|
11425
|
-
import { join as
|
|
11668
|
+
import { readdirSync as readdirSync19, readFileSync as readFileSync33, statSync as statSync11 } from "fs";
|
|
11669
|
+
import { join as join45 } from "path";
|
|
11426
11670
|
var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
|
|
11427
11671
|
var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
|
|
11428
11672
|
var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
|
|
@@ -11499,18 +11743,18 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
11499
11743
|
const walk2 = (dir, relative11) => {
|
|
11500
11744
|
let entries;
|
|
11501
11745
|
try {
|
|
11502
|
-
entries =
|
|
11746
|
+
entries = readdirSync19(dir);
|
|
11503
11747
|
} catch {
|
|
11504
11748
|
return;
|
|
11505
11749
|
}
|
|
11506
11750
|
for (const entry of entries) {
|
|
11507
11751
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees" || entry === ".venv")
|
|
11508
11752
|
continue;
|
|
11509
|
-
const full =
|
|
11753
|
+
const full = join45(dir, entry);
|
|
11510
11754
|
const rel = `${relative11}/${entry}`;
|
|
11511
11755
|
let isDir;
|
|
11512
11756
|
try {
|
|
11513
|
-
isDir =
|
|
11757
|
+
isDir = statSync11(full).isDirectory();
|
|
11514
11758
|
} catch {
|
|
11515
11759
|
continue;
|
|
11516
11760
|
}
|
|
@@ -11521,12 +11765,12 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
11521
11765
|
}
|
|
11522
11766
|
}
|
|
11523
11767
|
};
|
|
11524
|
-
walk2(
|
|
11768
|
+
walk2(join45(repoRoot, "modules"), "modules");
|
|
11525
11769
|
return found.sort();
|
|
11526
11770
|
}
|
|
11527
11771
|
function checkCognitoInviteTemplates(repoRoot) {
|
|
11528
11772
|
return findModuleTerraformFiles(repoRoot).flatMap(
|
|
11529
|
-
(file) => checkInviteTemplateSource(file,
|
|
11773
|
+
(file) => checkInviteTemplateSource(file, readFileSync33(join45(repoRoot, file), "utf8"))
|
|
11530
11774
|
);
|
|
11531
11775
|
}
|
|
11532
11776
|
|
|
@@ -11536,6 +11780,12 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
11536
11780
|
const files = findModuleTerraformFiles(root);
|
|
11537
11781
|
console.log(`audited ${files.length} .tf file(s) under modules/ under ${root}`);
|
|
11538
11782
|
if (files.length === 0) {
|
|
11783
|
+
if (!existsSync42(join46(root, "modules"))) {
|
|
11784
|
+
console.log(
|
|
11785
|
+
`\xB7 Cognito invite template guard: not applicable \u2014 no modules/ directory under ${root} \u2014 this is not a template/instance tree (a satellite repo never carries the template-owned Terraform modules). Skipping.`
|
|
11786
|
+
);
|
|
11787
|
+
return;
|
|
11788
|
+
}
|
|
11539
11789
|
console.error(
|
|
11540
11790
|
"\u2717 Cognito invite template guard: found 0 .tf files under modules/ \u2014 this looks like a broken scan, not a clean repo. Refusing to report success over zero input."
|
|
11541
11791
|
);
|
|
@@ -11554,11 +11804,11 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
11554
11804
|
}
|
|
11555
11805
|
|
|
11556
11806
|
// src/scripts/check-core-direct-paths.ts
|
|
11557
|
-
import { join as
|
|
11807
|
+
import { join as join48 } from "path";
|
|
11558
11808
|
|
|
11559
11809
|
// src/lib/core-direct-paths-audit.ts
|
|
11560
|
-
import { existsSync as
|
|
11561
|
-
import { join as
|
|
11810
|
+
import { existsSync as existsSync43, readFileSync as readFileSync34, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
|
|
11811
|
+
import { join as join47 } from "path";
|
|
11562
11812
|
var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
|
|
11563
11813
|
var API_ROUTE_PREFIX = "/api/v1";
|
|
11564
11814
|
var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
|
|
@@ -11605,7 +11855,7 @@ function* templateLiterals(text) {
|
|
|
11605
11855
|
i = j + 1;
|
|
11606
11856
|
}
|
|
11607
11857
|
}
|
|
11608
|
-
function
|
|
11858
|
+
function lineNumber2(text, index) {
|
|
11609
11859
|
let count = 1;
|
|
11610
11860
|
for (let i = 0; i < index && i < text.length; i++) {
|
|
11611
11861
|
if (text[i] === "\n") count += 1;
|
|
@@ -11673,7 +11923,7 @@ function extractCoreDirectPaths(rawText, file, externalBases = EXTERNAL_BASE_IDE
|
|
|
11673
11923
|
if (touchesConcatenation(text, start - 1, end + 1)) {
|
|
11674
11924
|
found.push({
|
|
11675
11925
|
file,
|
|
11676
|
-
line:
|
|
11926
|
+
line: lineNumber2(text, start),
|
|
11677
11927
|
raw,
|
|
11678
11928
|
normalized: null,
|
|
11679
11929
|
unresolvedReason: "built with string concatenation (+) \u2014 the extracted literal is a fragment, not the whole path",
|
|
@@ -11684,7 +11934,7 @@ function extractCoreDirectPaths(rawText, file, externalBases = EXTERNAL_BASE_IDE
|
|
|
11684
11934
|
if (isNestedTemplateLiteralArtifact(rest)) {
|
|
11685
11935
|
found.push({
|
|
11686
11936
|
file,
|
|
11687
|
-
line:
|
|
11937
|
+
line: lineNumber2(text, start),
|
|
11688
11938
|
raw,
|
|
11689
11939
|
normalized: null,
|
|
11690
11940
|
unresolvedReason: "contains a template literal nested inside its own interpolation \u2014 this extractor's scanner would otherwise truncate at the inner backtick, producing a corrupted path rather than a real mismatch",
|
|
@@ -11695,7 +11945,7 @@ function extractCoreDirectPaths(rawText, file, externalBases = EXTERNAL_BASE_IDE
|
|
|
11695
11945
|
const normalized = stripQueryString(resolveInterpolations(rest));
|
|
11696
11946
|
found.push({
|
|
11697
11947
|
file,
|
|
11698
|
-
line:
|
|
11948
|
+
line: lineNumber2(text, start),
|
|
11699
11949
|
raw,
|
|
11700
11950
|
normalized,
|
|
11701
11951
|
unresolvedReason: null,
|
|
@@ -11717,15 +11967,15 @@ function walkFiles(root, accept, skipDir) {
|
|
|
11717
11967
|
const walk2 = (dir) => {
|
|
11718
11968
|
let entries;
|
|
11719
11969
|
try {
|
|
11720
|
-
entries =
|
|
11970
|
+
entries = readdirSync20(dir);
|
|
11721
11971
|
} catch {
|
|
11722
11972
|
return;
|
|
11723
11973
|
}
|
|
11724
11974
|
for (const entry of entries) {
|
|
11725
|
-
const p =
|
|
11975
|
+
const p = join47(dir, entry);
|
|
11726
11976
|
let st;
|
|
11727
11977
|
try {
|
|
11728
|
-
st =
|
|
11978
|
+
st = statSync12(p);
|
|
11729
11979
|
} catch {
|
|
11730
11980
|
continue;
|
|
11731
11981
|
}
|
|
@@ -11752,7 +12002,7 @@ function auditFrontendExtraction(frontendSrcDir, externalBases = EXTERNAL_BASE_I
|
|
|
11752
12002
|
const extracted = [];
|
|
11753
12003
|
let rawTotal = 0;
|
|
11754
12004
|
for (const file of files) {
|
|
11755
|
-
const text =
|
|
12005
|
+
const text = readFileSync34(file, "utf8");
|
|
11756
12006
|
rawTotal += countRawExternalOccurrences(text, externalBases);
|
|
11757
12007
|
extracted.push(...extractCoreDirectPaths(text, file, externalBases));
|
|
11758
12008
|
}
|
|
@@ -11801,7 +12051,7 @@ function auditCoreRouteExtraction(apiSrcDir) {
|
|
|
11801
12051
|
const prefixSet = /* @__PURE__ */ new Set();
|
|
11802
12052
|
let rawApiRouterCount = 0;
|
|
11803
12053
|
for (const file of files) {
|
|
11804
|
-
const text =
|
|
12054
|
+
const text = readFileSync34(file, "utf8");
|
|
11805
12055
|
const extraction = extractCoreRoutePrefixes(text);
|
|
11806
12056
|
rawApiRouterCount += extraction.rawApiRouterCount;
|
|
11807
12057
|
for (const p of extraction.prefixes) prefixSet.add(normalizePrefix(p));
|
|
@@ -11817,10 +12067,10 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
|
|
|
11817
12067
|
}
|
|
11818
12068
|
function resolveSiblingCoreSrc(params) {
|
|
11819
12069
|
const { estateDir, sibling } = params;
|
|
11820
|
-
const configPath =
|
|
12070
|
+
const configPath = join47(estateDir, sibling, "biffo.sibling.json");
|
|
11821
12071
|
let raw;
|
|
11822
12072
|
try {
|
|
11823
|
-
raw =
|
|
12073
|
+
raw = readFileSync34(configPath, "utf8");
|
|
11824
12074
|
} catch (err) {
|
|
11825
12075
|
throw new Error(
|
|
11826
12076
|
`cannot resolve ${sibling}'s core: ${configPath} does not exist or is unreadable (${err.message}) -- refusing to guess which core serves this sibling.`
|
|
@@ -11840,8 +12090,8 @@ function resolveSiblingCoreSrc(params) {
|
|
|
11840
12090
|
`cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
|
|
11841
12091
|
);
|
|
11842
12092
|
}
|
|
11843
|
-
const coreApiSrcDir =
|
|
11844
|
-
if (!
|
|
12093
|
+
const coreApiSrcDir = join47(estateDir, coreProject, "services", "api", "src");
|
|
12094
|
+
if (!existsSync43(coreApiSrcDir)) {
|
|
11845
12095
|
throw new Error(
|
|
11846
12096
|
`cannot resolve ${sibling}'s core: biffo.sibling.json names core_project "${coreProject}", but ${coreApiSrcDir} does not exist -- the instance is missing from this estate checkout, not merely unmatched. Refusing to silently skip ${sibling} and shrink the audit's denominator.`
|
|
11847
12097
|
);
|
|
@@ -11884,7 +12134,7 @@ function auditSiblingCoreDirectPaths(params) {
|
|
|
11884
12134
|
async function runCoreDirectPathsCheck(opts = {}) {
|
|
11885
12135
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11886
12136
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
11887
|
-
const frontendSrcDir = opts.frontendSrc ??
|
|
12137
|
+
const frontendSrcDir = opts.frontendSrc ?? join48(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
11888
12138
|
let coreApiSrcDir;
|
|
11889
12139
|
let coreProject = null;
|
|
11890
12140
|
if (opts.coreSrc) {
|
|
@@ -11900,7 +12150,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
11900
12150
|
coreApiSrcDir = resolution.coreApiSrcDir;
|
|
11901
12151
|
coreProject = resolution.coreProject;
|
|
11902
12152
|
} else {
|
|
11903
|
-
coreApiSrcDir =
|
|
12153
|
+
coreApiSrcDir = join48(root, "services", "api", "src");
|
|
11904
12154
|
}
|
|
11905
12155
|
const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
|
|
11906
12156
|
console.log(
|
|
@@ -11965,8 +12215,8 @@ async function runOwnershipCheck(argv) {
|
|
|
11965
12215
|
const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
11966
12216
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
11967
12217
|
if (messageFile) {
|
|
11968
|
-
const { readFileSync:
|
|
11969
|
-
if (
|
|
12218
|
+
const { readFileSync: readFileSync47, existsSync: existsSync59 } = await import("fs");
|
|
12219
|
+
if (existsSync59(messageFile)) commitMessage = readFileSync47(messageFile, "utf8");
|
|
11970
12220
|
}
|
|
11971
12221
|
} else {
|
|
11972
12222
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -12067,21 +12317,21 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
12067
12317
|
}
|
|
12068
12318
|
|
|
12069
12319
|
// src/scripts/check-distribution-inventory.ts
|
|
12070
|
-
import { existsSync as
|
|
12071
|
-
import { join as
|
|
12320
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12321
|
+
import { join as join50 } from "path";
|
|
12072
12322
|
|
|
12073
12323
|
// src/lib/distribution-inventory.ts
|
|
12074
|
-
import { existsSync as
|
|
12075
|
-
import { join as
|
|
12324
|
+
import { existsSync as existsSync44, readFileSync as readFileSync35 } from "fs";
|
|
12325
|
+
import { join as join49 } from "path";
|
|
12076
12326
|
var INVENTORY_FILENAME = "distribution-inventory.json";
|
|
12077
12327
|
function loadDistributionInventory(root) {
|
|
12078
|
-
const path =
|
|
12079
|
-
if (!
|
|
12328
|
+
const path = join49(root, INVENTORY_FILENAME);
|
|
12329
|
+
if (!existsSync44(path)) {
|
|
12080
12330
|
throw new Error(
|
|
12081
12331
|
`${INVENTORY_FILENAME} not found at ${root} -- expected it beside core-manifest.json`
|
|
12082
12332
|
);
|
|
12083
12333
|
}
|
|
12084
|
-
return JSON.parse(
|
|
12334
|
+
return JSON.parse(readFileSync35(path, "utf8"));
|
|
12085
12335
|
}
|
|
12086
12336
|
function validateInventory(inventory) {
|
|
12087
12337
|
const violations = [];
|
|
@@ -12241,7 +12491,7 @@ async function runDistributionInventoryCheck(root) {
|
|
|
12241
12491
|
);
|
|
12242
12492
|
return;
|
|
12243
12493
|
}
|
|
12244
|
-
if (!
|
|
12494
|
+
if (!existsSync45(join50(repoRoot, INVENTORY_FILENAME))) {
|
|
12245
12495
|
console.log(
|
|
12246
12496
|
`\u2713 distribution-inventory: skipped \u2014 no ${INVENTORY_FILENAME} at ${repoRoot}. This is a template-only registry; a checkout that legitimately has none has nothing for this guard to check.`
|
|
12247
12497
|
);
|
|
@@ -12267,7 +12517,7 @@ async function runDistributionInventoryCheck(root) {
|
|
|
12267
12517
|
}
|
|
12268
12518
|
|
|
12269
12519
|
// src/scripts/check-distribution-remote-state.ts
|
|
12270
|
-
import { join as
|
|
12520
|
+
import { join as join51 } from "path";
|
|
12271
12521
|
async function ghExecCommand(file, args) {
|
|
12272
12522
|
const result = await execa(file, args, { reject: false });
|
|
12273
12523
|
return { stdout: String(result.stdout ?? ""), exitCode: result.exitCode ?? null };
|
|
@@ -12282,7 +12532,7 @@ async function runDistributionRemoteStateCheck(root) {
|
|
|
12282
12532
|
}
|
|
12283
12533
|
}
|
|
12284
12534
|
console.log(
|
|
12285
|
-
`distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${
|
|
12535
|
+
`distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join51(repoRoot, "distribution-inventory.json")})`
|
|
12286
12536
|
);
|
|
12287
12537
|
if (assertions.length === 0) {
|
|
12288
12538
|
console.log("\u2713 distribution-remote-state: nothing declared to check");
|
|
@@ -12327,9 +12577,9 @@ ${assertion.ref}`;
|
|
|
12327
12577
|
}
|
|
12328
12578
|
|
|
12329
12579
|
// src/lib/eventbridge-log-permission-guard.ts
|
|
12330
|
-
import { readFileSync as
|
|
12331
|
-
import { join as
|
|
12332
|
-
var
|
|
12580
|
+
import { readFileSync as readFileSync36, readdirSync as readdirSync21, statSync as statSync13 } from "fs";
|
|
12581
|
+
import { join as join52 } from "path";
|
|
12582
|
+
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
12333
12583
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
12334
12584
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
12335
12585
|
var LOG_GROUP_ARN_REF = /aws_cloudwatch_log_group\.([\w-]+)(?:\[[^\]]*\])?\.arn/g;
|
|
@@ -12350,14 +12600,14 @@ function servicePrincipalsIn(body) {
|
|
|
12350
12600
|
function grantsPrincipal(body, principal) {
|
|
12351
12601
|
return servicePrincipalsIn(body).includes(principal);
|
|
12352
12602
|
}
|
|
12353
|
-
function
|
|
12603
|
+
function lineNumber3(text, index) {
|
|
12354
12604
|
let count = 1;
|
|
12355
12605
|
for (let i = 0; i < index && i < text.length; i++) {
|
|
12356
12606
|
if (text[i] === "\n") count += 1;
|
|
12357
12607
|
}
|
|
12358
12608
|
return count;
|
|
12359
12609
|
}
|
|
12360
|
-
function
|
|
12610
|
+
function balancedBraceSpan2(text, openIndex) {
|
|
12361
12611
|
let depth = 0;
|
|
12362
12612
|
for (let i = openIndex; i < text.length; i++) {
|
|
12363
12613
|
if (text[i] === "{") depth += 1;
|
|
@@ -12368,15 +12618,15 @@ function balancedBraceSpan(text, openIndex) {
|
|
|
12368
12618
|
}
|
|
12369
12619
|
return null;
|
|
12370
12620
|
}
|
|
12371
|
-
function
|
|
12621
|
+
function findResourceBlocks2(text, file, resourceType) {
|
|
12372
12622
|
const blocks = [];
|
|
12373
12623
|
const pattern = new RegExp(`resource\\s+"${resourceType}"\\s+"([\\w-]+)"\\s*\\{`, "g");
|
|
12374
12624
|
let m;
|
|
12375
12625
|
while ((m = pattern.exec(text)) !== null) {
|
|
12376
12626
|
const name = m[1];
|
|
12377
|
-
const line =
|
|
12627
|
+
const line = lineNumber3(text, m.index);
|
|
12378
12628
|
const openIndex = m.index + m[0].length - 1;
|
|
12379
|
-
const closeIndex =
|
|
12629
|
+
const closeIndex = balancedBraceSpan2(text, openIndex);
|
|
12380
12630
|
if (closeIndex === null) {
|
|
12381
12631
|
blocks.push({ file, line, type: resourceType, name, body: null });
|
|
12382
12632
|
continue;
|
|
@@ -12391,29 +12641,29 @@ function findResourceBlocks(text, file, resourceType) {
|
|
|
12391
12641
|
}
|
|
12392
12642
|
return blocks;
|
|
12393
12643
|
}
|
|
12394
|
-
function
|
|
12644
|
+
function countRawResourceDeclarations2(text, resourceType) {
|
|
12395
12645
|
const needle = `resource "${resourceType}"`;
|
|
12396
12646
|
return text.split(needle).length - 1;
|
|
12397
12647
|
}
|
|
12398
|
-
function
|
|
12648
|
+
function walkTerraformFiles2(root) {
|
|
12399
12649
|
const out = [];
|
|
12400
12650
|
const walk2 = (dir) => {
|
|
12401
12651
|
let entries;
|
|
12402
12652
|
try {
|
|
12403
|
-
entries =
|
|
12653
|
+
entries = readdirSync21(dir);
|
|
12404
12654
|
} catch {
|
|
12405
12655
|
return;
|
|
12406
12656
|
}
|
|
12407
12657
|
for (const entry of entries) {
|
|
12408
|
-
const p =
|
|
12658
|
+
const p = join52(dir, entry);
|
|
12409
12659
|
let st;
|
|
12410
12660
|
try {
|
|
12411
|
-
st =
|
|
12661
|
+
st = statSync13(p);
|
|
12412
12662
|
} catch {
|
|
12413
12663
|
continue;
|
|
12414
12664
|
}
|
|
12415
12665
|
if (st.isDirectory()) {
|
|
12416
|
-
if (
|
|
12666
|
+
if (SKIP_DIRS3.has(entry)) continue;
|
|
12417
12667
|
walk2(p);
|
|
12418
12668
|
continue;
|
|
12419
12669
|
}
|
|
@@ -12437,7 +12687,7 @@ function logGroupNamesReferencedIn(body) {
|
|
|
12437
12687
|
return names;
|
|
12438
12688
|
}
|
|
12439
12689
|
function auditEventBridgeLogPermissions(root) {
|
|
12440
|
-
const files =
|
|
12690
|
+
const files = walkTerraformFiles2(root);
|
|
12441
12691
|
if (files.length === 0) {
|
|
12442
12692
|
throw new Error(
|
|
12443
12693
|
`auditEventBridgeLogPermissions: no .tf files found under ${root} \u2014 the guard cannot verify anything against input it cannot see. This is a hard failure, not "0 violations": fix the path, do not treat an empty scan as a clean pass.`
|
|
@@ -12448,11 +12698,11 @@ function auditEventBridgeLogPermissions(root) {
|
|
|
12448
12698
|
let rawEventTargetCount = 0;
|
|
12449
12699
|
let rawLogPolicyCount = 0;
|
|
12450
12700
|
for (const file of files) {
|
|
12451
|
-
const text =
|
|
12452
|
-
rawEventTargetCount +=
|
|
12453
|
-
rawLogPolicyCount +=
|
|
12454
|
-
eventTargetBlocks.push(...
|
|
12455
|
-
logResourcePolicyBlocks.push(...
|
|
12701
|
+
const text = readFileSync36(file, "utf8");
|
|
12702
|
+
rawEventTargetCount += countRawResourceDeclarations2(text, EVENT_TARGET_TYPE);
|
|
12703
|
+
rawLogPolicyCount += countRawResourceDeclarations2(text, LOG_RESOURCE_POLICY_TYPE);
|
|
12704
|
+
eventTargetBlocks.push(...findResourceBlocks2(text, file, EVENT_TARGET_TYPE));
|
|
12705
|
+
logResourcePolicyBlocks.push(...findResourceBlocks2(text, file, LOG_RESOURCE_POLICY_TYPE));
|
|
12456
12706
|
}
|
|
12457
12707
|
const unterminatedBlocks = [...eventTargetBlocks, ...logResourcePolicyBlocks].filter(
|
|
12458
12708
|
(b) => b.body === null
|
|
@@ -12506,8 +12756,15 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
12506
12756
|
try {
|
|
12507
12757
|
report = auditEventBridgeLogPermissions(root);
|
|
12508
12758
|
} catch (err) {
|
|
12759
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12760
|
+
if (message.includes("no .tf files found under")) {
|
|
12761
|
+
console.log(
|
|
12762
|
+
`\xB7 EventBridge log permission guard: not applicable \u2014 ${message} This repo carries no Terraform at all, which is expected for a non-instance satellite. Skipping.`
|
|
12763
|
+
);
|
|
12764
|
+
return;
|
|
12765
|
+
}
|
|
12509
12766
|
console.error("\u2717 EventBridge log permission guard: could not run\n");
|
|
12510
|
-
console.error(
|
|
12767
|
+
console.error(message);
|
|
12511
12768
|
process.exit(1);
|
|
12512
12769
|
}
|
|
12513
12770
|
console.log(`audited ${report.filesScanned} .tf file(s) under ${root}`);
|
|
@@ -12538,8 +12795,8 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
12538
12795
|
}
|
|
12539
12796
|
|
|
12540
12797
|
// src/scripts/check-instance-adoption.ts
|
|
12541
|
-
import { existsSync as
|
|
12542
|
-
import { join as
|
|
12798
|
+
import { existsSync as existsSync46 } from "fs";
|
|
12799
|
+
import { join as join53 } from "path";
|
|
12543
12800
|
async function runInstanceAdoptionCheck(opts = {}) {
|
|
12544
12801
|
if (!opts.instanceDir) {
|
|
12545
12802
|
console.error(
|
|
@@ -12547,7 +12804,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
12547
12804
|
);
|
|
12548
12805
|
process.exit(2);
|
|
12549
12806
|
}
|
|
12550
|
-
if (!
|
|
12807
|
+
if (!existsSync46(opts.instanceDir)) {
|
|
12551
12808
|
console.error(
|
|
12552
12809
|
`\u2717 instance-adoption guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it is adopted, and that is not the same as a clean pass.`
|
|
12553
12810
|
);
|
|
@@ -12555,7 +12812,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
12555
12812
|
}
|
|
12556
12813
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12557
12814
|
const theirsDir = opts.theirsDir ?? root;
|
|
12558
|
-
const instanceLabel = opts.instance ??
|
|
12815
|
+
const instanceLabel = opts.instance ?? join53(opts.instanceDir).split("/").filter(Boolean).pop();
|
|
12559
12816
|
const report = checkInstanceAdoption(theirsDir, opts.instanceDir);
|
|
12560
12817
|
console.log(
|
|
12561
12818
|
`examined ${report.examinedInstances} instance (${instanceLabel}) against ${report.registeredPairs} registered pair(s), ${report.applicablePairs} applicable to this instance, against template tree ${theirsDir}`
|
|
@@ -12577,12 +12834,12 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
12577
12834
|
}
|
|
12578
12835
|
|
|
12579
12836
|
// src/lib/lambda-output-guard.ts
|
|
12580
|
-
import { readFileSync as
|
|
12581
|
-
import { join as
|
|
12837
|
+
import { readFileSync as readFileSync38 } from "fs";
|
|
12838
|
+
import { join as join55 } from "path";
|
|
12582
12839
|
|
|
12583
12840
|
// src/lib/terraform-input-guard.ts
|
|
12584
|
-
import { existsSync as
|
|
12585
|
-
import { join as
|
|
12841
|
+
import { existsSync as existsSync47, readdirSync as readdirSync22, readFileSync as readFileSync37, statSync as statSync14 } from "fs";
|
|
12842
|
+
import { join as join54 } from "path";
|
|
12586
12843
|
var GUARDED_SUBCOMMANDS = [
|
|
12587
12844
|
"init",
|
|
12588
12845
|
"plan",
|
|
@@ -12596,18 +12853,18 @@ function stripComments2(source) {
|
|
|
12596
12853
|
return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
|
|
12597
12854
|
}
|
|
12598
12855
|
function vendoredPluginServiceDirs(repoRoot) {
|
|
12599
|
-
const servicesDir =
|
|
12856
|
+
const servicesDir = join54(repoRoot, "services");
|
|
12600
12857
|
const result = /* @__PURE__ */ new Set();
|
|
12601
12858
|
let entries;
|
|
12602
12859
|
try {
|
|
12603
|
-
entries =
|
|
12860
|
+
entries = readdirSync22(servicesDir);
|
|
12604
12861
|
} catch {
|
|
12605
12862
|
return result;
|
|
12606
12863
|
}
|
|
12607
12864
|
for (const entry of entries) {
|
|
12608
|
-
const full =
|
|
12609
|
-
if (!
|
|
12610
|
-
if (
|
|
12865
|
+
const full = join54(servicesDir, entry);
|
|
12866
|
+
if (!existsSync47(full) || !statSync14(full).isDirectory()) continue;
|
|
12867
|
+
if (existsSync47(join54(full, "biffo.plugin.json"))) {
|
|
12611
12868
|
result.add(entry);
|
|
12612
12869
|
}
|
|
12613
12870
|
}
|
|
@@ -12619,7 +12876,7 @@ function findWorkflowFiles(repoRoot) {
|
|
|
12619
12876
|
const walk2 = (dir, relative11) => {
|
|
12620
12877
|
let entries;
|
|
12621
12878
|
try {
|
|
12622
|
-
entries =
|
|
12879
|
+
entries = readdirSync22(dir);
|
|
12623
12880
|
} catch {
|
|
12624
12881
|
return;
|
|
12625
12882
|
}
|
|
@@ -12629,11 +12886,11 @@ function findWorkflowFiles(repoRoot) {
|
|
|
12629
12886
|
if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
|
|
12630
12887
|
continue;
|
|
12631
12888
|
}
|
|
12632
|
-
const full =
|
|
12889
|
+
const full = join54(dir, entry);
|
|
12633
12890
|
const rel = relative11 ? `${relative11}/${entry}` : entry;
|
|
12634
12891
|
let isDir;
|
|
12635
12892
|
try {
|
|
12636
|
-
isDir =
|
|
12893
|
+
isDir = statSync14(full).isDirectory();
|
|
12637
12894
|
} catch {
|
|
12638
12895
|
continue;
|
|
12639
12896
|
}
|
|
@@ -12679,7 +12936,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
12679
12936
|
}
|
|
12680
12937
|
function checkTerraformInput(repoRoot) {
|
|
12681
12938
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
12682
|
-
(file) => checkWorkflowSource(file,
|
|
12939
|
+
(file) => checkWorkflowSource(file, readFileSync37(join54(repoRoot, file), "utf8"))
|
|
12683
12940
|
);
|
|
12684
12941
|
}
|
|
12685
12942
|
|
|
@@ -12737,7 +12994,7 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
12737
12994
|
}
|
|
12738
12995
|
function checkLambdaOutput(repoRoot) {
|
|
12739
12996
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
12740
|
-
(file) => checkWorkflowSource2(file,
|
|
12997
|
+
(file) => checkWorkflowSource2(file, readFileSync38(join55(repoRoot, file), "utf8"))
|
|
12741
12998
|
);
|
|
12742
12999
|
}
|
|
12743
13000
|
|
|
@@ -12765,8 +13022,8 @@ async function runLambdaOutputCheck() {
|
|
|
12765
13022
|
}
|
|
12766
13023
|
|
|
12767
13024
|
// src/scripts/check-migration-body-change.ts
|
|
12768
|
-
import { existsSync as
|
|
12769
|
-
import { join as
|
|
13025
|
+
import { existsSync as existsSync48 } from "fs";
|
|
13026
|
+
import { join as join56 } from "path";
|
|
12770
13027
|
|
|
12771
13028
|
// src/lib/migration-body-change-guard.ts
|
|
12772
13029
|
function checkMigrationBodyChangeMarkers(diffs) {
|
|
@@ -12832,7 +13089,7 @@ async function runMigrationBodyChangeCheck(argv) {
|
|
|
12832
13089
|
);
|
|
12833
13090
|
return;
|
|
12834
13091
|
}
|
|
12835
|
-
if (!
|
|
13092
|
+
if (!existsSync48(join56(root, MIGRATIONS_VERSIONS_DIR))) {
|
|
12836
13093
|
console.log(`\u2713 migration body-change guard: no ${MIGRATIONS_VERSIONS_DIR} in this repo.`);
|
|
12837
13094
|
return;
|
|
12838
13095
|
}
|
|
@@ -12934,8 +13191,8 @@ ${BOLD2}What to do${OFF2}
|
|
|
12934
13191
|
}
|
|
12935
13192
|
|
|
12936
13193
|
// src/scripts/check-orphan-ratchet.ts
|
|
12937
|
-
import { existsSync as
|
|
12938
|
-
import { basename as basename3 } from "path";
|
|
13194
|
+
import { existsSync as existsSync49 } from "fs";
|
|
13195
|
+
import { basename as basename3, join as join57 } from "path";
|
|
12939
13196
|
function reportOrphan(entry, manifest) {
|
|
12940
13197
|
console.error(` ${entry.path}`);
|
|
12941
13198
|
const { templateOwnedMatch, nearestUserOwnedEntries } = explainOwnership(entry.path, manifest);
|
|
@@ -12957,7 +13214,7 @@ function reportOrphan(entry, manifest) {
|
|
|
12957
13214
|
}
|
|
12958
13215
|
}
|
|
12959
13216
|
async function runOrphanRatchetCheck(opts = {}) {
|
|
12960
|
-
if (opts.instanceDir !== void 0 && !
|
|
13217
|
+
if (opts.instanceDir !== void 0 && !existsSync49(opts.instanceDir)) {
|
|
12961
13218
|
console.error(
|
|
12962
13219
|
`\u2717 orphan-ratchet guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it carries unsanctioned files, and that is not the same as a clean pass.`
|
|
12963
13220
|
);
|
|
@@ -12974,6 +13231,12 @@ async function runOrphanRatchetCheck(opts = {}) {
|
|
|
12974
13231
|
"self-check mode: --instance-dir was not given, so it defaulted to --theirs-dir (this repo's own root) alongside --base-dir \u2014 all three trees are the same, so this run can only ever find zero orphans by construction. See this script's doc comment. Pass a real instance tree with --instance-dir for a check that can actually find something. biffo-template#1714."
|
|
12975
13232
|
);
|
|
12976
13233
|
}
|
|
13234
|
+
if (!existsSync49(join57(theirsDir, CORE_MANIFEST_FILE))) {
|
|
13235
|
+
console.log(
|
|
13236
|
+
`orphan-ratchet guard (${label}): no ${CORE_MANIFEST_FILE} in ${theirsDir} \u2014 this is not a Biffo template/instance tree (a satellite repo never carries one), so there is no ownership manifest to classify paths against. Not applicable; skipping.`
|
|
13237
|
+
);
|
|
13238
|
+
return;
|
|
13239
|
+
}
|
|
12977
13240
|
const manifest = readCoreManifest(theirsDir);
|
|
12978
13241
|
const plan = await planCoreUpgrade({ baseDir, oursDir: instanceDir, theirsDir, manifest });
|
|
12979
13242
|
const baseline = readOrphanBaseline(instanceDir);
|
|
@@ -12999,8 +13262,8 @@ baseline was ${String(ratchet.baseline)}, now ${String(ratchet.count)}. See biff
|
|
|
12999
13262
|
}
|
|
13000
13263
|
|
|
13001
13264
|
// src/scripts/check-pipe-trap.ts
|
|
13002
|
-
import { readFileSync as
|
|
13003
|
-
import { join as
|
|
13265
|
+
import { readFileSync as readFileSync39, readdirSync as readdirSync23 } from "fs";
|
|
13266
|
+
import { join as join58, relative as relative9 } from "path";
|
|
13004
13267
|
|
|
13005
13268
|
// src/lib/pipe-trap-guard.ts
|
|
13006
13269
|
var STATUS_BEARING = [
|
|
@@ -13096,17 +13359,17 @@ function findPipeTraps(source) {
|
|
|
13096
13359
|
function shellFiles(root) {
|
|
13097
13360
|
const out = [];
|
|
13098
13361
|
for (const dir of ["scripts", ".githooks"]) {
|
|
13099
|
-
const full =
|
|
13362
|
+
const full = join58(root, dir);
|
|
13100
13363
|
let entries;
|
|
13101
13364
|
try {
|
|
13102
|
-
entries =
|
|
13365
|
+
entries = readdirSync23(full, { withFileTypes: true });
|
|
13103
13366
|
} catch {
|
|
13104
13367
|
continue;
|
|
13105
13368
|
}
|
|
13106
13369
|
for (const entry of entries) {
|
|
13107
13370
|
if (!entry.isFile()) continue;
|
|
13108
13371
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
13109
|
-
out.push(
|
|
13372
|
+
out.push(join58(full, entry.name));
|
|
13110
13373
|
}
|
|
13111
13374
|
}
|
|
13112
13375
|
return out;
|
|
@@ -13122,7 +13385,7 @@ async function runPipeTrapCheck() {
|
|
|
13122
13385
|
process.exit(1);
|
|
13123
13386
|
}
|
|
13124
13387
|
const findings = files.flatMap(
|
|
13125
|
-
(file) => findPipeTraps(
|
|
13388
|
+
(file) => findPipeTraps(readFileSync39(file, "utf8")).map(
|
|
13126
13389
|
(t) => `${relative9(root, file)}:${t.line} ${t.text}
|
|
13127
13390
|
${t.reason}`
|
|
13128
13391
|
)
|
|
@@ -13138,9 +13401,13 @@ async function runPipeTrapCheck() {
|
|
|
13138
13401
|
console.log(`\u2713 Pipe-trap guard: no status-bearing command is piped away`);
|
|
13139
13402
|
}
|
|
13140
13403
|
|
|
13404
|
+
// src/scripts/check-plugin-allowlist-convention.ts
|
|
13405
|
+
import { existsSync as existsSync50 } from "fs";
|
|
13406
|
+
import { join as join60 } from "path";
|
|
13407
|
+
|
|
13141
13408
|
// src/lib/plugin-allowlist-convention.ts
|
|
13142
|
-
import { readFileSync as
|
|
13143
|
-
import { join as
|
|
13409
|
+
import { readFileSync as readFileSync40 } from "fs";
|
|
13410
|
+
import { join as join59 } from "path";
|
|
13144
13411
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
13145
13412
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
13146
13413
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -13151,7 +13418,7 @@ var PLUGIN = "<plugin>";
|
|
|
13151
13418
|
var ACCOUNT = "<account>";
|
|
13152
13419
|
function read(repoRoot, relative11) {
|
|
13153
13420
|
try {
|
|
13154
|
-
return
|
|
13421
|
+
return readFileSync40(join59(repoRoot, relative11), "utf8");
|
|
13155
13422
|
} catch {
|
|
13156
13423
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
|
|
13157
13424
|
}
|
|
@@ -13246,8 +13513,21 @@ Plugins would be rejected by require_service_principal (ADR-0009). Fix the glob,
|
|
|
13246
13513
|
}
|
|
13247
13514
|
|
|
13248
13515
|
// src/scripts/check-plugin-allowlist-convention.ts
|
|
13516
|
+
var REQUIRED_SOURCES = [
|
|
13517
|
+
COMPUTE_MAIN_TF,
|
|
13518
|
+
PLUGIN_TEMPLATE_MAIN_TF,
|
|
13519
|
+
ALLOWLIST_MAIN_TF,
|
|
13520
|
+
ALLOWLIST_VARIABLES_TF
|
|
13521
|
+
];
|
|
13249
13522
|
async function runPluginAllowlistConventionCheck() {
|
|
13250
13523
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13524
|
+
const missing = REQUIRED_SOURCES.filter((relative11) => !existsSync50(join60(root, relative11)));
|
|
13525
|
+
if (missing.length === REQUIRED_SOURCES.length) {
|
|
13526
|
+
console.log(
|
|
13527
|
+
`\xB7 Plugin-allowlist convention guard: not applicable \u2014 ${missing.join(", ")} not found under ${root}. This is not a template/instance tree (a satellite repo never carries the plugin-allowlist Terraform modules). Skipping.`
|
|
13528
|
+
);
|
|
13529
|
+
return;
|
|
13530
|
+
}
|
|
13251
13531
|
let violations;
|
|
13252
13532
|
try {
|
|
13253
13533
|
violations = checkAllowlistConvention(root);
|
|
@@ -13272,33 +13552,33 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
13272
13552
|
}
|
|
13273
13553
|
|
|
13274
13554
|
// src/scripts/check-plugin-collisions.ts
|
|
13275
|
-
import { existsSync as
|
|
13276
|
-
import { join as
|
|
13555
|
+
import { existsSync as existsSync52 } from "fs";
|
|
13556
|
+
import { join as join62 } from "path";
|
|
13277
13557
|
|
|
13278
13558
|
// src/lib/plugin-collision-guard.ts
|
|
13279
|
-
import { existsSync as
|
|
13280
|
-
import { join as
|
|
13559
|
+
import { existsSync as existsSync51, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
|
|
13560
|
+
import { join as join61 } from "path";
|
|
13281
13561
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
13282
13562
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
13283
13563
|
function subdirectories(dir) {
|
|
13284
|
-
if (!
|
|
13285
|
-
return
|
|
13564
|
+
if (!existsSync51(dir)) return [];
|
|
13565
|
+
return readdirSync24(dir).filter((entry) => {
|
|
13286
13566
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
13287
13567
|
try {
|
|
13288
|
-
return
|
|
13568
|
+
return statSync15(join61(dir, entry)).isDirectory();
|
|
13289
13569
|
} catch {
|
|
13290
13570
|
return false;
|
|
13291
13571
|
}
|
|
13292
13572
|
});
|
|
13293
13573
|
}
|
|
13294
13574
|
function regularPackagesOf(pluginDir2) {
|
|
13295
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
13575
|
+
return subdirectories(pluginDir2).filter((name) => existsSync51(join61(pluginDir2, name, "__init__.py"))).sort();
|
|
13296
13576
|
}
|
|
13297
13577
|
function bareTestModulesOf(pluginDir2) {
|
|
13298
|
-
const testsDir =
|
|
13299
|
-
if (!
|
|
13300
|
-
if (
|
|
13301
|
-
return
|
|
13578
|
+
const testsDir = join61(pluginDir2, "tests");
|
|
13579
|
+
if (!existsSync51(testsDir)) return [];
|
|
13580
|
+
if (existsSync51(join61(testsDir, "__init__.py"))) return [];
|
|
13581
|
+
return readdirSync24(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
13302
13582
|
}
|
|
13303
13583
|
function findCollisions(servicesDir, pluginDirs) {
|
|
13304
13584
|
const plugins = (pluginDirs ?? subdirectories(servicesDir)).filter((name) => !name.startsWith("_")).filter((name) => name !== "api").sort();
|
|
@@ -13306,7 +13586,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
13306
13586
|
const gather = (kind, namesOf) => {
|
|
13307
13587
|
const claims = /* @__PURE__ */ new Map();
|
|
13308
13588
|
for (const plugin of plugins) {
|
|
13309
|
-
for (const name of namesOf(
|
|
13589
|
+
for (const name of namesOf(join61(servicesDir, plugin))) {
|
|
13310
13590
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
13311
13591
|
}
|
|
13312
13592
|
}
|
|
@@ -13344,8 +13624,8 @@ function formatCollisions(collisions) {
|
|
|
13344
13624
|
// src/scripts/check-plugin-collisions.ts
|
|
13345
13625
|
async function runPluginCollisionCheck() {
|
|
13346
13626
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13347
|
-
const servicesDir =
|
|
13348
|
-
if (!
|
|
13627
|
+
const servicesDir = join62(root, "services");
|
|
13628
|
+
if (!existsSync52(servicesDir)) {
|
|
13349
13629
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
13350
13630
|
return;
|
|
13351
13631
|
}
|
|
@@ -13362,25 +13642,25 @@ async function runPluginCollisionCheck() {
|
|
|
13362
13642
|
}
|
|
13363
13643
|
|
|
13364
13644
|
// src/lib/plugin-terraform-guard.ts
|
|
13365
|
-
import { existsSync as
|
|
13366
|
-
import { dirname as dirname10, join as
|
|
13367
|
-
var
|
|
13645
|
+
import { existsSync as existsSync53, readFileSync as readFileSync41, readdirSync as readdirSync25 } from "fs";
|
|
13646
|
+
import { dirname as dirname10, join as join63, relative as relative10, sep as sep4 } from "path";
|
|
13647
|
+
var SKIP_DIRS4 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
13368
13648
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
13369
13649
|
function findPluginManifests(root) {
|
|
13370
13650
|
const found = [];
|
|
13371
13651
|
const walk2 = (dir) => {
|
|
13372
13652
|
let entries;
|
|
13373
13653
|
try {
|
|
13374
|
-
entries =
|
|
13654
|
+
entries = readdirSync25(dir, { withFileTypes: true });
|
|
13375
13655
|
} catch {
|
|
13376
13656
|
return;
|
|
13377
13657
|
}
|
|
13378
13658
|
for (const entry of entries) {
|
|
13379
13659
|
if (entry.isDirectory()) {
|
|
13380
|
-
if (
|
|
13381
|
-
walk2(
|
|
13660
|
+
if (SKIP_DIRS4.has(entry.name)) continue;
|
|
13661
|
+
walk2(join63(dir, entry.name));
|
|
13382
13662
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
13383
|
-
found.push(relative10(root,
|
|
13663
|
+
found.push(relative10(root, join63(dir, entry.name)).split(sep4).join("/"));
|
|
13384
13664
|
}
|
|
13385
13665
|
}
|
|
13386
13666
|
};
|
|
@@ -13390,7 +13670,7 @@ function findPluginManifests(root) {
|
|
|
13390
13670
|
function readSubscriptions(absManifestPath) {
|
|
13391
13671
|
let parsed;
|
|
13392
13672
|
try {
|
|
13393
|
-
parsed = JSON.parse(
|
|
13673
|
+
parsed = JSON.parse(readFileSync41(absManifestPath, "utf8"));
|
|
13394
13674
|
} catch {
|
|
13395
13675
|
return null;
|
|
13396
13676
|
}
|
|
@@ -13405,14 +13685,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
13405
13685
|
}
|
|
13406
13686
|
function checkPluginTerraform(root) {
|
|
13407
13687
|
const violations = [];
|
|
13408
|
-
const coreManifest =
|
|
13688
|
+
const coreManifest = existsSync53(join63(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
13409
13689
|
for (const manifest of findPluginManifests(root)) {
|
|
13410
13690
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
13411
|
-
const absManifest =
|
|
13691
|
+
const absManifest = join63(root, manifest);
|
|
13412
13692
|
const subscriptions = readSubscriptions(absManifest);
|
|
13413
13693
|
if (subscriptions === null) continue;
|
|
13414
13694
|
const pluginDir2 = dirname10(absManifest);
|
|
13415
|
-
if (
|
|
13695
|
+
if (existsSync53(join63(pluginDir2, "terraform"))) continue;
|
|
13416
13696
|
const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
|
|
13417
13697
|
violations.push({
|
|
13418
13698
|
manifest,
|
|
@@ -13443,12 +13723,12 @@ async function runPluginTerraformCheck() {
|
|
|
13443
13723
|
}
|
|
13444
13724
|
|
|
13445
13725
|
// src/scripts/check-plugin-tool-supply.ts
|
|
13446
|
-
import { existsSync as
|
|
13447
|
-
import { join as
|
|
13726
|
+
import { existsSync as existsSync55 } from "fs";
|
|
13727
|
+
import { join as join65 } from "path";
|
|
13448
13728
|
|
|
13449
13729
|
// src/lib/plugin-tool-supply-audit.ts
|
|
13450
|
-
import { existsSync as
|
|
13451
|
-
import { join as
|
|
13730
|
+
import { existsSync as existsSync54, readFileSync as readFileSync42, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
|
|
13731
|
+
import { join as join64 } from "path";
|
|
13452
13732
|
|
|
13453
13733
|
// src/lib/openrouter-model-snapshot.ts
|
|
13454
13734
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -13859,13 +14139,13 @@ var OPENROUTER_MODEL_IDS = [
|
|
|
13859
14139
|
function listDirs(root) {
|
|
13860
14140
|
let entries;
|
|
13861
14141
|
try {
|
|
13862
|
-
entries =
|
|
14142
|
+
entries = readdirSync26(root);
|
|
13863
14143
|
} catch {
|
|
13864
14144
|
return [];
|
|
13865
14145
|
}
|
|
13866
14146
|
return entries.filter((e) => {
|
|
13867
14147
|
try {
|
|
13868
|
-
return
|
|
14148
|
+
return statSync16(join64(root, e)).isDirectory();
|
|
13869
14149
|
} catch {
|
|
13870
14150
|
return false;
|
|
13871
14151
|
}
|
|
@@ -13876,15 +14156,15 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
13876
14156
|
const walk2 = (dir) => {
|
|
13877
14157
|
let entries;
|
|
13878
14158
|
try {
|
|
13879
|
-
entries =
|
|
14159
|
+
entries = readdirSync26(dir);
|
|
13880
14160
|
} catch {
|
|
13881
14161
|
return;
|
|
13882
14162
|
}
|
|
13883
14163
|
for (const entry of entries) {
|
|
13884
|
-
const p =
|
|
14164
|
+
const p = join64(dir, entry);
|
|
13885
14165
|
let st;
|
|
13886
14166
|
try {
|
|
13887
|
-
st =
|
|
14167
|
+
st = statSync16(p);
|
|
13888
14168
|
} catch {
|
|
13889
14169
|
continue;
|
|
13890
14170
|
}
|
|
@@ -13907,14 +14187,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
13907
14187
|
);
|
|
13908
14188
|
}
|
|
13909
14189
|
function pluginTerraformFiles(pluginDir2) {
|
|
13910
|
-
const tfDir =
|
|
14190
|
+
const tfDir = join64(pluginDir2, "terraform");
|
|
13911
14191
|
let entries;
|
|
13912
14192
|
try {
|
|
13913
|
-
entries =
|
|
14193
|
+
entries = readdirSync26(tfDir);
|
|
13914
14194
|
} catch {
|
|
13915
14195
|
return [];
|
|
13916
14196
|
}
|
|
13917
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
14197
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join64(tfDir, e)).sort();
|
|
13918
14198
|
}
|
|
13919
14199
|
function extractManifestTools(manifestText) {
|
|
13920
14200
|
let parsed;
|
|
@@ -14166,8 +14446,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
14166
14446
|
function normalizeModelId(id) {
|
|
14167
14447
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
14168
14448
|
}
|
|
14169
|
-
var CONFIG_PY_PATH =
|
|
14170
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
14449
|
+
var CONFIG_PY_PATH = join64("services", "api", "src", "api", "config.py");
|
|
14450
|
+
var ORCHESTRATION_SCHEMA_PATH = join64(
|
|
14171
14451
|
"services",
|
|
14172
14452
|
"api",
|
|
14173
14453
|
"src",
|
|
@@ -14179,10 +14459,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
14179
14459
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
14180
14460
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
14181
14461
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
14182
|
-
const configPath =
|
|
14183
|
-
const orchestrationPath =
|
|
14184
|
-
const configMissing = !
|
|
14185
|
-
const orchestrationSchemaMissing = !
|
|
14462
|
+
const configPath = join64(repoRoot, CONFIG_PY_PATH);
|
|
14463
|
+
const orchestrationPath = join64(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
14464
|
+
const configMissing = !existsSync54(configPath);
|
|
14465
|
+
const orchestrationSchemaMissing = !existsSync54(orchestrationPath);
|
|
14186
14466
|
const knownSet = new Set(knownModelIds);
|
|
14187
14467
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
14188
14468
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -14200,13 +14480,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
14200
14480
|
};
|
|
14201
14481
|
let settingsBlind = false;
|
|
14202
14482
|
if (!configMissing) {
|
|
14203
|
-
const settingsFields = extractSettingsModelFields(
|
|
14483
|
+
const settingsFields = extractSettingsModelFields(readFileSync42(configPath, "utf8"));
|
|
14204
14484
|
if (settingsFields.length === 0) settingsBlind = true;
|
|
14205
14485
|
for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
|
|
14206
14486
|
}
|
|
14207
14487
|
let curatedFieldsBlind = false;
|
|
14208
14488
|
if (!orchestrationSchemaMissing) {
|
|
14209
|
-
const curated = extractCuratedModelFields(
|
|
14489
|
+
const curated = extractCuratedModelFields(readFileSync42(orchestrationPath, "utf8"));
|
|
14210
14490
|
if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
|
|
14211
14491
|
curatedFieldsBlind = true;
|
|
14212
14492
|
}
|
|
@@ -14253,7 +14533,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
14253
14533
|
function discoverPluginDirs(pluginsRoot) {
|
|
14254
14534
|
return listDirs(pluginsRoot).filter((name) => {
|
|
14255
14535
|
try {
|
|
14256
|
-
return
|
|
14536
|
+
return statSync16(join64(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
14257
14537
|
} catch {
|
|
14258
14538
|
return false;
|
|
14259
14539
|
}
|
|
@@ -14266,8 +14546,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14266
14546
|
let terraformBlind = false;
|
|
14267
14547
|
let totalDeclaredTools = 0;
|
|
14268
14548
|
for (const name of pluginNames) {
|
|
14269
|
-
const pluginDir2 =
|
|
14270
|
-
const manifestText =
|
|
14549
|
+
const pluginDir2 = join64(pluginsRoot, name);
|
|
14550
|
+
const manifestText = readFileSync42(join64(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
14271
14551
|
const manifest = extractManifestTools(manifestText);
|
|
14272
14552
|
if (manifest.parseError) {
|
|
14273
14553
|
findings.push({
|
|
@@ -14285,13 +14565,13 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14285
14565
|
totalDeclaredTools += manifest.tools.length;
|
|
14286
14566
|
const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
|
|
14287
14567
|
file: f,
|
|
14288
|
-
text:
|
|
14568
|
+
text: readFileSync42(f, "utf8")
|
|
14289
14569
|
}));
|
|
14290
14570
|
const resolver = buildSymbolResolver(pySources);
|
|
14291
14571
|
const registry = extractToolRegistryEntries(pySources, resolver);
|
|
14292
14572
|
if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
|
|
14293
14573
|
const tfFiles = pluginTerraformFiles(pluginDir2);
|
|
14294
|
-
const tfText = tfFiles.map((f) =>
|
|
14574
|
+
const tfText = tfFiles.map((f) => readFileSync42(f, "utf8")).join("\n");
|
|
14295
14575
|
const terraform = extractTerraformEnvKeys(tfText);
|
|
14296
14576
|
if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
|
|
14297
14577
|
for (const toolName of manifest.tools) {
|
|
@@ -14365,7 +14645,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14365
14645
|
requiredEnvVars: envResult.envVars,
|
|
14366
14646
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
14367
14647
|
status: anyWired ? "ok" : "missing-env",
|
|
14368
|
-
detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${
|
|
14648
|
+
detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${join64(pluginDir2, "terraform")}, so this deployment can never supply it`
|
|
14369
14649
|
});
|
|
14370
14650
|
}
|
|
14371
14651
|
}
|
|
@@ -14398,8 +14678,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14398
14678
|
async function runPluginToolSupplyCheck() {
|
|
14399
14679
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14400
14680
|
let allOk = true;
|
|
14401
|
-
const pluginsRoot =
|
|
14402
|
-
if (!
|
|
14681
|
+
const pluginsRoot = join65(root, "services", "_plugins");
|
|
14682
|
+
if (!existsSync55(pluginsRoot)) {
|
|
14403
14683
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
14404
14684
|
} else {
|
|
14405
14685
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -14429,8 +14709,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
14429
14709
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
14430
14710
|
}
|
|
14431
14711
|
}
|
|
14432
|
-
const servicesApiRoot =
|
|
14433
|
-
if (!
|
|
14712
|
+
const servicesApiRoot = join65(root, "services", "api");
|
|
14713
|
+
if (!existsSync55(servicesApiRoot)) {
|
|
14434
14714
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
14435
14715
|
} else {
|
|
14436
14716
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -14604,10 +14884,10 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
14604
14884
|
}
|
|
14605
14885
|
|
|
14606
14886
|
// src/scripts/check-shared-file-reduction.ts
|
|
14607
|
-
import { readFileSync as
|
|
14887
|
+
import { readFileSync as readFileSync44 } from "fs";
|
|
14608
14888
|
|
|
14609
14889
|
// src/lib/shared-file-reduction-guard.ts
|
|
14610
|
-
import { readFileSync as
|
|
14890
|
+
import { readFileSync as readFileSync43 } from "fs";
|
|
14611
14891
|
var LEAF_TEST_CALLS = /* @__PURE__ */ new Set(["it", "test"]);
|
|
14612
14892
|
var SUITE_CALLS = /* @__PURE__ */ new Set(["describe", "suite"]);
|
|
14613
14893
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
@@ -14734,7 +15014,7 @@ function formatReductionReport(report) {
|
|
|
14734
15014
|
// src/scripts/check-shared-file-reduction.ts
|
|
14735
15015
|
function readStdin() {
|
|
14736
15016
|
try {
|
|
14737
|
-
return
|
|
15017
|
+
return readFileSync44(0, "utf8");
|
|
14738
15018
|
} catch {
|
|
14739
15019
|
return "";
|
|
14740
15020
|
}
|
|
@@ -14751,15 +15031,15 @@ function pairsFromTsv(tsv) {
|
|
|
14751
15031
|
const [target, existingPath, incomingPath] = fields;
|
|
14752
15032
|
pairs.push({
|
|
14753
15033
|
target,
|
|
14754
|
-
existing:
|
|
14755
|
-
incoming:
|
|
15034
|
+
existing: readFileSync44(existingPath, "utf8"),
|
|
15035
|
+
incoming: readFileSync44(incomingPath, "utf8")
|
|
14756
15036
|
});
|
|
14757
15037
|
}
|
|
14758
15038
|
return pairs;
|
|
14759
15039
|
}
|
|
14760
15040
|
function loadAccepted(manifestPath) {
|
|
14761
15041
|
if (!manifestPath) return {};
|
|
14762
|
-
const parsed = JSON.parse(
|
|
15042
|
+
const parsed = JSON.parse(readFileSync44(manifestPath, "utf8"));
|
|
14763
15043
|
return parsed.acceptedReductions ?? {};
|
|
14764
15044
|
}
|
|
14765
15045
|
async function runSharedFileReductionCheck(args) {
|
|
@@ -14767,13 +15047,13 @@ async function runSharedFileReductionCheck(args) {
|
|
|
14767
15047
|
let accepted;
|
|
14768
15048
|
try {
|
|
14769
15049
|
if (args.pairs) {
|
|
14770
|
-
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() :
|
|
15050
|
+
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync44(args.pairs, "utf8"));
|
|
14771
15051
|
} else if (args.target && args.existing && args.incoming) {
|
|
14772
15052
|
pairs = [
|
|
14773
15053
|
{
|
|
14774
15054
|
target: args.target,
|
|
14775
|
-
existing:
|
|
14776
|
-
incoming:
|
|
15055
|
+
existing: readFileSync44(args.existing, "utf8"),
|
|
15056
|
+
incoming: readFileSync44(args.incoming, "utf8")
|
|
14777
15057
|
}
|
|
14778
15058
|
];
|
|
14779
15059
|
} else {
|
|
@@ -14807,12 +15087,12 @@ async function runSharedFileReductionCheck(args) {
|
|
|
14807
15087
|
}
|
|
14808
15088
|
|
|
14809
15089
|
// src/scripts/check-skeleton-drift.ts
|
|
14810
|
-
import { existsSync as
|
|
14811
|
-
import { join as
|
|
15090
|
+
import { existsSync as existsSync56, readdirSync as readdirSync28 } from "fs";
|
|
15091
|
+
import { join as join67 } from "path";
|
|
14812
15092
|
|
|
14813
15093
|
// src/lib/skeleton-drift-guard.ts
|
|
14814
|
-
import { readFileSync as
|
|
14815
|
-
import { join as
|
|
15094
|
+
import { readFileSync as readFileSync45, readdirSync as readdirSync27, statSync as statSync17 } from "fs";
|
|
15095
|
+
import { join as join66 } from "path";
|
|
14816
15096
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
14817
15097
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
14818
15098
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -14870,16 +15150,16 @@ function walk(dir, base = dir) {
|
|
|
14870
15150
|
const out = [];
|
|
14871
15151
|
let entries;
|
|
14872
15152
|
try {
|
|
14873
|
-
entries =
|
|
15153
|
+
entries = readdirSync27(dir);
|
|
14874
15154
|
} catch {
|
|
14875
15155
|
return out;
|
|
14876
15156
|
}
|
|
14877
15157
|
for (const entry of entries) {
|
|
14878
15158
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
14879
|
-
const abs =
|
|
15159
|
+
const abs = join66(dir, entry);
|
|
14880
15160
|
let isDir;
|
|
14881
15161
|
try {
|
|
14882
|
-
isDir =
|
|
15162
|
+
isDir = statSync17(abs).isDirectory();
|
|
14883
15163
|
} catch {
|
|
14884
15164
|
continue;
|
|
14885
15165
|
}
|
|
@@ -14898,7 +15178,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
14898
15178
|
if (!rule.appliesTo(rel)) continue;
|
|
14899
15179
|
let contents;
|
|
14900
15180
|
try {
|
|
14901
|
-
contents =
|
|
15181
|
+
contents = readFileSync45(join66(skeletonRoot, rel), "utf8");
|
|
14902
15182
|
} catch {
|
|
14903
15183
|
continue;
|
|
14904
15184
|
}
|
|
@@ -14927,23 +15207,23 @@ function formatViolations2(violations) {
|
|
|
14927
15207
|
|
|
14928
15208
|
// src/scripts/check-skeleton-drift.ts
|
|
14929
15209
|
function discoverSkeletons(root) {
|
|
14930
|
-
const skeletonsDir =
|
|
15210
|
+
const skeletonsDir = join67(root, "_skeletons");
|
|
14931
15211
|
let entries;
|
|
14932
15212
|
try {
|
|
14933
|
-
entries =
|
|
15213
|
+
entries = readdirSync28(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
14934
15214
|
} catch {
|
|
14935
15215
|
return [];
|
|
14936
15216
|
}
|
|
14937
|
-
return entries.filter((name) =>
|
|
15217
|
+
return entries.filter((name) => existsSync56(join67(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
14938
15218
|
}
|
|
14939
15219
|
async function runSkeletonDriftCheck() {
|
|
14940
15220
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14941
15221
|
const skeletons = discoverSkeletons(root);
|
|
14942
15222
|
let filesConsidered = 0;
|
|
14943
15223
|
for (const name of skeletons) {
|
|
14944
|
-
const skeletonRoot =
|
|
15224
|
+
const skeletonRoot = join67(root, "_skeletons", name);
|
|
14945
15225
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
14946
|
-
if (
|
|
15226
|
+
if (existsSync56(join67(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
14947
15227
|
filesConsidered += 1;
|
|
14948
15228
|
}
|
|
14949
15229
|
}
|
|
@@ -14951,13 +15231,19 @@ async function runSkeletonDriftCheck() {
|
|
|
14951
15231
|
`audited ${skeletons.length} skeleton(s) (${skeletons.join(", ") || "none"}), ${filesConsidered} file(s) considered, under ${root}/_skeletons`
|
|
14952
15232
|
);
|
|
14953
15233
|
if (skeletons.length === 0) {
|
|
15234
|
+
if (!existsSync56(join67(root, "_skeletons"))) {
|
|
15235
|
+
console.log(
|
|
15236
|
+
`\xB7 Skeleton-drift guard: no _skeletons/ directory under ${root} \u2014 this is not a repo that ships scaffolding (a satellite repo never carries one). Not applicable; skipping.`
|
|
15237
|
+
);
|
|
15238
|
+
return;
|
|
15239
|
+
}
|
|
14954
15240
|
console.error(
|
|
14955
15241
|
"\u2717 Skeleton-drift guard: found 0 repo skeletons under _skeletons/ \u2014 this looks like a broken scan, not a repo with no scaffolding. Refusing to report success over zero input."
|
|
14956
15242
|
);
|
|
14957
15243
|
process.exit(1);
|
|
14958
15244
|
}
|
|
14959
15245
|
const violations = skeletons.flatMap(
|
|
14960
|
-
(name) => auditSkeleton(
|
|
15246
|
+
(name) => auditSkeleton(join67(root, "_skeletons", name), name)
|
|
14961
15247
|
);
|
|
14962
15248
|
if (violations.length > 0) {
|
|
14963
15249
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -14993,7 +15279,7 @@ async function runTerraformInputCheck() {
|
|
|
14993
15279
|
|
|
14994
15280
|
// src/commands/check.ts
|
|
14995
15281
|
var checkCommand = new Command24("check").description(
|
|
14996
|
-
"Repo guards (ownership, release subject, plugin terraform, plugin collisions, eventbridge-log-permissions, plugin-tool-supply, core-direct-paths, instance-adoption, orphan-ratchet, cognito-invite-template, lambda-output, pipe-trap, codeql-suppression, skeleton-drift, terraform-input, plugin-allowlist-convention, migration-body-change, distribution-inventory, distribution-remote-state) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
|
|
15282
|
+
"Repo guards (ownership, release subject, plugin terraform, plugin collisions, eventbridge-log-permissions, plugin-tool-supply, core-direct-paths, instance-adoption, orphan-ratchet, cognito-invite-template, lambda-output, pipe-trap, codeql-suppression, skeleton-drift, terraform-input, plugin-allowlist-convention, migration-body-change, distribution-inventory, distribution-remote-state, api-gateway-integration) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
|
|
14997
15283
|
);
|
|
14998
15284
|
checkCommand.command("ownership").description("Refuse changes to template-owned paths in an instance (#370)").argument("[base]", "Base branch to diff against; defaults to $GITHUB_BASE_REF").option("--staged <messageFile>", "Check staged changes instead of a branch diff (commit hook)").allowExcessArguments(true).action(async () => {
|
|
14999
15285
|
await runOwnershipCheck(rawArgsAfter("ownership"));
|
|
@@ -15117,6 +15403,11 @@ checkCommand.command("terraform-input").description(
|
|
|
15117
15403
|
).action(async () => {
|
|
15118
15404
|
await runTerraformInputCheck();
|
|
15119
15405
|
});
|
|
15406
|
+
checkCommand.command("api-gateway-integration").description(
|
|
15407
|
+
"Refuse an env-owned aws_apigatewayv2_integration targeting a Lambda's raw, unqualified function_arn when modules/cloud/aws/api-gateway already fronts that same Lambda with an alias-qualified aws_lambda_permission (#1747) \u2014 terraform validate/plan pass on both sides in isolation, so this is the only signal before a real deploy 500s (biffo-template#1900; tabsii-platform took 11 public routes down this exact way)."
|
|
15408
|
+
).action(async () => {
|
|
15409
|
+
await runApiGatewayIntegrationCheck();
|
|
15410
|
+
});
|
|
15120
15411
|
checkCommand.command("shared-file-reduction").description(
|
|
15121
15412
|
"Refuse a shared-files.json overwrite that would DELETE tests from the satellite it lands in (#1577) \u2014 filesIfPresent is a one-way cp and nothing ever compared the two copies. Invoked by scripts/shared-sync.sh at the moment of the write, not by per-PR CI: both sides of the comparison only exist together inside a sync run. Compares TEST TITLES in TS/JS test files and nothing else \u2014 see the guard module for what it does not catch."
|
|
15122
15413
|
).option("--pairs <tsv>", "TSV of target<TAB>existing<TAB>incoming lines, or - for stdin").option("--target <path>", "Single pair: path inside the satellite").option("--existing <path>", "Single pair: the satellite's current copy").option("--incoming <path>", "Single pair: the canonical copy that would replace it").option("--manifest <path>", "shared-files.json, read for acceptedReductions").action(
|
|
@@ -15148,8 +15439,8 @@ function rawArgsAfter(subcommand) {
|
|
|
15148
15439
|
}
|
|
15149
15440
|
|
|
15150
15441
|
// src/commands/doctor.ts
|
|
15151
|
-
import { existsSync as
|
|
15152
|
-
import { join as
|
|
15442
|
+
import { existsSync as existsSync57, readFileSync as readFileSync46 } from "fs";
|
|
15443
|
+
import { join as join68, resolve as resolve20 } from "path";
|
|
15153
15444
|
import chalk21 from "chalk";
|
|
15154
15445
|
import { Command as Command25 } from "commander";
|
|
15155
15446
|
|
|
@@ -15544,10 +15835,10 @@ async function runDoctorFixBranches(cwd, facts, deps = { git: new GitAdapter(),
|
|
|
15544
15835
|
return reapAllBareBranches(cwd, facts.branches, facts.worktrees, facts.currentBranch, deps);
|
|
15545
15836
|
}
|
|
15546
15837
|
function readLocalCoreVersion(cwd) {
|
|
15547
|
-
const path =
|
|
15548
|
-
if (!
|
|
15838
|
+
const path = join68(cwd, INSTANCE_CORE_FILE);
|
|
15839
|
+
if (!existsSync57(path)) return null;
|
|
15549
15840
|
try {
|
|
15550
|
-
return extractVersionField(
|
|
15841
|
+
return extractVersionField(readFileSync46(path, "utf8"));
|
|
15551
15842
|
} catch {
|
|
15552
15843
|
return null;
|
|
15553
15844
|
}
|
|
@@ -15567,10 +15858,10 @@ function extractVersionField(contents) {
|
|
|
15567
15858
|
return match?.[1] ?? null;
|
|
15568
15859
|
}
|
|
15569
15860
|
function readFossil(cwd) {
|
|
15570
|
-
const path =
|
|
15571
|
-
if (!
|
|
15861
|
+
const path = join68(cwd, CORE_VERSION_FILE);
|
|
15862
|
+
if (!existsSync57(path)) return null;
|
|
15572
15863
|
try {
|
|
15573
|
-
const value =
|
|
15864
|
+
const value = readFileSync46(path, "utf8").trim();
|
|
15574
15865
|
return value === "" ? null : value;
|
|
15575
15866
|
} catch {
|
|
15576
15867
|
return null;
|
|
@@ -16093,19 +16384,19 @@ function resolveGithubToken4() {
|
|
|
16093
16384
|
|
|
16094
16385
|
// src/lib/packaged-script-command.ts
|
|
16095
16386
|
import { spawnSync } from "child_process";
|
|
16096
|
-
import { chmodSync as chmodSync2, statSync as
|
|
16387
|
+
import { chmodSync as chmodSync2, statSync as statSync18 } from "fs";
|
|
16097
16388
|
import { dirname as dirname12 } from "path";
|
|
16098
16389
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
16099
16390
|
import { Command as Command27 } from "commander";
|
|
16100
16391
|
|
|
16101
16392
|
// src/lib/packaged-scripts.ts
|
|
16102
|
-
import { existsSync as
|
|
16103
|
-
import { dirname as dirname11, join as
|
|
16393
|
+
import { existsSync as existsSync58 } from "fs";
|
|
16394
|
+
import { dirname as dirname11, join as join69 } from "path";
|
|
16104
16395
|
function findPackagedScript(startDir, relativePath) {
|
|
16105
16396
|
let dir = startDir;
|
|
16106
16397
|
for (; ; ) {
|
|
16107
|
-
const candidate =
|
|
16108
|
-
if (
|
|
16398
|
+
const candidate = join69(dir, relativePath);
|
|
16399
|
+
if (existsSync58(candidate)) return candidate;
|
|
16109
16400
|
const parent = dirname11(dir);
|
|
16110
16401
|
if (parent === dir) return null;
|
|
16111
16402
|
dir = parent;
|
|
@@ -16119,7 +16410,7 @@ It ships with this package via cli/scripts/packaged-root-assets.mjs; if you are
|
|
|
16119
16410
|
// src/lib/packaged-script-command.ts
|
|
16120
16411
|
function ensureExecutable(script) {
|
|
16121
16412
|
try {
|
|
16122
|
-
const { mode } =
|
|
16413
|
+
const { mode } = statSync18(script);
|
|
16123
16414
|
if ((mode & 73) === 0) chmodSync2(script, mode | 493);
|
|
16124
16415
|
} catch {
|
|
16125
16416
|
}
|