@biffo/cli 0.287.11 → 0.288.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +531 -442
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -570,8 +570,8 @@ async function runCoreStatus(options) {
|
|
|
570
570
|
|
|
571
571
|
// src/commands/core-upgrade.ts
|
|
572
572
|
import { execSync as execSync2 } from "child_process";
|
|
573
|
-
import { existsSync as
|
|
574
|
-
import { join as
|
|
573
|
+
import { existsSync as existsSync15, rmSync as rmSync5 } from "fs";
|
|
574
|
+
import { join as join16, resolve as resolve3 } from "path";
|
|
575
575
|
import chalk4 from "chalk";
|
|
576
576
|
import { execa as execa3 } from "execa";
|
|
577
577
|
import { Command as Command3 } from "commander";
|
|
@@ -2961,9 +2961,48 @@ function findNewUndeclaredSeams(baseDir, theirsDir, oursDir, portalRelDir = "app
|
|
|
2961
2961
|
return undeclared.sort((a, b) => a.specifier.localeCompare(b.specifier));
|
|
2962
2962
|
}
|
|
2963
2963
|
|
|
2964
|
-
// src/lib/
|
|
2964
|
+
// src/lib/instance-adoption.ts
|
|
2965
2965
|
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
2966
2966
|
import { join as join11 } from "path";
|
|
2967
|
+
var REGISTERED_ADOPTION_PAIRS = [
|
|
2968
|
+
{
|
|
2969
|
+
id: "core-api-environment",
|
|
2970
|
+
description: '`infra/environments/dev/core-api-environment.core.tf` declares `local.core_api_environment` (BIFFO_PLUGIN_MEDIA_BUCKET, BIFFO_PR_SIGNER_FUNCTION_NAME, ...), but `module "core_api"` is a user-owned block Terraform cannot inject an argument into from another file \u2014 so the channel only takes effect once `main.tf` itself merges it in (#1538/#1540/#1579).',
|
|
2971
|
+
templateFile: "infra/environments/dev/core-api-environment.core.tf",
|
|
2972
|
+
userFile: "infra/environments/dev/main.tf",
|
|
2973
|
+
adoptedPattern: /environment_variables\s*=\s*merge\(\s*local\.core_api_environment\s*,/,
|
|
2974
|
+
remedy: 'In `module "core_api"` (infra/environments/dev/main.tf), change `environment_variables = {` to `environment_variables = merge(local.core_api_environment, {` and close the extra paren on the block\u2019s closing `}` \u2014 then `terraform apply`.'
|
|
2975
|
+
}
|
|
2976
|
+
];
|
|
2977
|
+
function isAdopted(pair, userFileContent) {
|
|
2978
|
+
if (userFileContent === null) return false;
|
|
2979
|
+
return pair.adoptedPattern.test(userFileContent);
|
|
2980
|
+
}
|
|
2981
|
+
function checkInstanceAdoption(theirsDir, oursDir, pairs = REGISTERED_ADOPTION_PAIRS) {
|
|
2982
|
+
const findings = [];
|
|
2983
|
+
let applicablePairs = 0;
|
|
2984
|
+
for (const pair of pairs) {
|
|
2985
|
+
const shipped = existsSync10(join11(theirsDir, pair.templateFile));
|
|
2986
|
+
if (!shipped) {
|
|
2987
|
+
findings.push({ pair, status: "not-applicable" });
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
applicablePairs++;
|
|
2991
|
+
const userPath = join11(oursDir, pair.userFile);
|
|
2992
|
+
const content = existsSync10(userPath) ? readFileSync8(userPath, "utf8") : null;
|
|
2993
|
+
findings.push({ pair, status: isAdopted(pair, content) ? "adopted" : "unadopted" });
|
|
2994
|
+
}
|
|
2995
|
+
return {
|
|
2996
|
+
examinedInstances: 1,
|
|
2997
|
+
registeredPairs: pairs.length,
|
|
2998
|
+
applicablePairs,
|
|
2999
|
+
findings
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
// src/lib/breaking-changes.ts
|
|
3004
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
3005
|
+
import { join as join12 } from "path";
|
|
2967
3006
|
var UPGRADE_GUIDE_PATH = "docs/guides/core-upgrade.md";
|
|
2968
3007
|
var SECTION_HEADING = "## Breaking changes by version";
|
|
2969
3008
|
var ENTRY_HEADING = /^###\s+(\d+\.\d+\.\d+)\s*[—-]\s*(.+?)\s*$/;
|
|
@@ -2988,9 +3027,9 @@ function parseBreakingChanges(guide) {
|
|
|
2988
3027
|
return entries;
|
|
2989
3028
|
}
|
|
2990
3029
|
function readBreakingChanges(templateRoot) {
|
|
2991
|
-
const path =
|
|
2992
|
-
if (!
|
|
2993
|
-
return parseBreakingChanges(
|
|
3030
|
+
const path = join12(templateRoot, UPGRADE_GUIDE_PATH);
|
|
3031
|
+
if (!existsSync11(path)) return [];
|
|
3032
|
+
return parseBreakingChanges(readFileSync9(path, "utf8"));
|
|
2994
3033
|
}
|
|
2995
3034
|
function breakingChangesBetween(from, to, entries) {
|
|
2996
3035
|
parseCoreVersion(from);
|
|
@@ -3008,15 +3047,15 @@ var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
|
|
|
3008
3047
|
|
|
3009
3048
|
// src/lib/plugin-terraform-wiring.ts
|
|
3010
3049
|
import {
|
|
3011
|
-
existsSync as
|
|
3050
|
+
existsSync as existsSync12,
|
|
3012
3051
|
mkdirSync as mkdirSync3,
|
|
3013
|
-
readFileSync as
|
|
3052
|
+
readFileSync as readFileSync10,
|
|
3014
3053
|
readdirSync as readdirSync3,
|
|
3015
3054
|
rmSync as rmSync4,
|
|
3016
3055
|
statSync as statSync2,
|
|
3017
3056
|
writeFileSync as writeFileSync4
|
|
3018
3057
|
} from "fs";
|
|
3019
|
-
import { join as
|
|
3058
|
+
import { join as join13, relative as relative2, sep as sep2 } from "path";
|
|
3020
3059
|
var TEMPLATE_MODULE_DIR = "_template";
|
|
3021
3060
|
var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
|
|
3022
3061
|
var GENERATED_TF_FILE = "plugins.generated.tf";
|
|
@@ -3070,7 +3109,7 @@ function standardArguments(pluginName, handler) {
|
|
|
3070
3109
|
];
|
|
3071
3110
|
}
|
|
3072
3111
|
function listPluginModules(cwd) {
|
|
3073
|
-
const dir =
|
|
3112
|
+
const dir = join13(cwd, "modules", "plugins");
|
|
3074
3113
|
let entries;
|
|
3075
3114
|
try {
|
|
3076
3115
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -3082,7 +3121,7 @@ function listPluginModules(cwd) {
|
|
|
3082
3121
|
var FIRST_PARTY_TERRAFORM = (name) => `../../../services/_plugins/${name}/terraform`;
|
|
3083
3122
|
var THIRD_PARTY_TERRAFORM = (name) => `../../../modules/plugins/${name}`;
|
|
3084
3123
|
function isFirstPartyPlugin(cwd, name) {
|
|
3085
|
-
return
|
|
3124
|
+
return existsSync12(join13(cwd, "services", "_plugins", name, "terraform", "main.tf"));
|
|
3086
3125
|
}
|
|
3087
3126
|
function pluginModuleSource(cwd, name) {
|
|
3088
3127
|
return isFirstPartyPlugin(cwd, name) ? FIRST_PARTY_TERRAFORM(name) : THIRD_PARTY_TERRAFORM(name);
|
|
@@ -3091,7 +3130,7 @@ function listWireablePlugins(cwd) {
|
|
|
3091
3130
|
return listPluginModules(cwd).filter((name) => !isFirstPartyPlugin(cwd, name)).sort();
|
|
3092
3131
|
}
|
|
3093
3132
|
function firstPartyPluginNames(cwd) {
|
|
3094
|
-
const dir =
|
|
3133
|
+
const dir = join13(cwd, "services", "_plugins");
|
|
3095
3134
|
let entries;
|
|
3096
3135
|
try {
|
|
3097
3136
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -3114,7 +3153,7 @@ function walkTfFiles(root) {
|
|
|
3114
3153
|
return;
|
|
3115
3154
|
}
|
|
3116
3155
|
for (const entry of entries) {
|
|
3117
|
-
const p =
|
|
3156
|
+
const p = join13(dir, entry);
|
|
3118
3157
|
let st;
|
|
3119
3158
|
try {
|
|
3120
3159
|
st = statSync2(p);
|
|
@@ -3135,7 +3174,7 @@ function escapeRegExp(value) {
|
|
|
3135
3174
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3136
3175
|
}
|
|
3137
3176
|
function findPluginModuleReferences(cwd, name) {
|
|
3138
|
-
const infraDir =
|
|
3177
|
+
const infraDir = join13(cwd, "infra");
|
|
3139
3178
|
const sourceSegments = ["modules", "plugins", name];
|
|
3140
3179
|
const sourceLinePattern = /^\s*source\s*=\s*"([^"]+)"/;
|
|
3141
3180
|
const moduleRefPattern = new RegExp(`module\\.plugin_${escapeRegExp(name)}(?![A-Za-z0-9_-])`);
|
|
@@ -3143,7 +3182,7 @@ function findPluginModuleReferences(cwd, name) {
|
|
|
3143
3182
|
for (const absPath of walkTfFiles(infraDir)) {
|
|
3144
3183
|
let contents;
|
|
3145
3184
|
try {
|
|
3146
|
-
contents =
|
|
3185
|
+
contents = readFileSync10(absPath, "utf8");
|
|
3147
3186
|
} catch {
|
|
3148
3187
|
continue;
|
|
3149
3188
|
}
|
|
@@ -3161,7 +3200,7 @@ function findPluginModuleReferences(cwd, name) {
|
|
|
3161
3200
|
return refs;
|
|
3162
3201
|
}
|
|
3163
3202
|
function listEnvironments(cwd) {
|
|
3164
|
-
const dir =
|
|
3203
|
+
const dir = join13(cwd, "infra", "environments");
|
|
3165
3204
|
let entries;
|
|
3166
3205
|
try {
|
|
3167
3206
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -3169,12 +3208,12 @@ function listEnvironments(cwd) {
|
|
|
3169
3208
|
return [];
|
|
3170
3209
|
}
|
|
3171
3210
|
return entries.filter((e) => {
|
|
3172
|
-
if (!e.isDirectory() || !
|
|
3173
|
-
return declaredVariables(
|
|
3211
|
+
if (!e.isDirectory() || !existsSync12(join13(dir, e.name, "main.tf"))) return false;
|
|
3212
|
+
return declaredVariables(join13(dir, e.name)).has("enabled_plugins");
|
|
3174
3213
|
}).map((e) => e.name).sort();
|
|
3175
3214
|
}
|
|
3176
3215
|
function listUnwirableEnvironments(cwd) {
|
|
3177
|
-
const dir =
|
|
3216
|
+
const dir = join13(cwd, "infra", "environments");
|
|
3178
3217
|
let entries;
|
|
3179
3218
|
try {
|
|
3180
3219
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -3182,7 +3221,7 @@ function listUnwirableEnvironments(cwd) {
|
|
|
3182
3221
|
return [];
|
|
3183
3222
|
}
|
|
3184
3223
|
return entries.filter(
|
|
3185
|
-
(e) => e.isDirectory() &&
|
|
3224
|
+
(e) => e.isDirectory() && existsSync12(join13(dir, e.name, "main.tf")) && !declaredVariables(join13(dir, e.name)).has("enabled_plugins")
|
|
3186
3225
|
).map((e) => e.name).sort();
|
|
3187
3226
|
}
|
|
3188
3227
|
function declaredVariables(moduleDir) {
|
|
@@ -3197,7 +3236,7 @@ function declaredVariables(moduleDir) {
|
|
|
3197
3236
|
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
3198
3237
|
let contents;
|
|
3199
3238
|
try {
|
|
3200
|
-
contents =
|
|
3239
|
+
contents = readFileSync10(join13(moduleDir, entry.name), "utf8");
|
|
3201
3240
|
} catch {
|
|
3202
3241
|
continue;
|
|
3203
3242
|
}
|
|
@@ -3219,7 +3258,7 @@ function declaredOutputs(moduleDir) {
|
|
|
3219
3258
|
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
3220
3259
|
let contents;
|
|
3221
3260
|
try {
|
|
3222
|
-
contents =
|
|
3261
|
+
contents = readFileSync10(join13(moduleDir, entry.name), "utf8");
|
|
3223
3262
|
} catch {
|
|
3224
3263
|
continue;
|
|
3225
3264
|
}
|
|
@@ -3301,7 +3340,7 @@ function syncPluginTerraform(cwd) {
|
|
|
3301
3340
|
const skippedEnvironments = listUnwirableEnvironments(cwd);
|
|
3302
3341
|
const changedPaths = [];
|
|
3303
3342
|
const rendered = plugins.map((name) => {
|
|
3304
|
-
const moduleDir =
|
|
3343
|
+
const moduleDir = join13(cwd, "modules", "plugins", name);
|
|
3305
3344
|
return {
|
|
3306
3345
|
name,
|
|
3307
3346
|
declaredVariables: declaredVariables(moduleDir),
|
|
@@ -3310,16 +3349,16 @@ function syncPluginTerraform(cwd) {
|
|
|
3310
3349
|
};
|
|
3311
3350
|
});
|
|
3312
3351
|
for (const env of environments) {
|
|
3313
|
-
const envDir =
|
|
3314
|
-
const tfPath =
|
|
3315
|
-
const tfvarsPath =
|
|
3352
|
+
const envDir = join13(cwd, "infra", "environments", env);
|
|
3353
|
+
const tfPath = join13(envDir, GENERATED_TF_FILE);
|
|
3354
|
+
const tfvarsPath = join13(envDir, GENERATED_TFVARS_FILE);
|
|
3316
3355
|
const relBase = `infra/environments/${env}`;
|
|
3317
3356
|
if (plugins.length === 0) {
|
|
3318
3357
|
for (const [abs, rel] of [
|
|
3319
3358
|
[tfPath, `${relBase}/${GENERATED_TF_FILE}`],
|
|
3320
3359
|
[tfvarsPath, `${relBase}/${GENERATED_TFVARS_FILE}`]
|
|
3321
3360
|
]) {
|
|
3322
|
-
if (
|
|
3361
|
+
if (existsSync12(abs)) {
|
|
3323
3362
|
rmSync4(abs);
|
|
3324
3363
|
changedPaths.push(rel);
|
|
3325
3364
|
}
|
|
@@ -3335,8 +3374,8 @@ function syncPluginTerraform(cwd) {
|
|
|
3335
3374
|
}
|
|
3336
3375
|
|
|
3337
3376
|
// src/lib/lockfile-refresh.ts
|
|
3338
|
-
import { existsSync as
|
|
3339
|
-
import { join as
|
|
3377
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3378
|
+
import { join as join14 } from "path";
|
|
3340
3379
|
var LOCKFILE_TRIGGERS = [
|
|
3341
3380
|
{
|
|
3342
3381
|
manifest: "package.json",
|
|
@@ -3359,7 +3398,7 @@ function lockfilesNeedingRefresh(changedPaths, instanceDir, triggers = LOCKFILE_
|
|
|
3359
3398
|
const locked = changedPaths.filter((p) => !isForeignManifest(p));
|
|
3360
3399
|
return triggers.filter((t) => {
|
|
3361
3400
|
const touched = locked.some((p) => p === t.manifest || p.endsWith(`/${t.manifest}`));
|
|
3362
|
-
return touched &&
|
|
3401
|
+
return touched && existsSync13(join14(instanceDir, t.lockfile));
|
|
3363
3402
|
});
|
|
3364
3403
|
}
|
|
3365
3404
|
async function refreshLockfiles(instanceDir, triggers, run) {
|
|
@@ -3379,11 +3418,11 @@ function describeFailures(outcomes) {
|
|
|
3379
3418
|
}
|
|
3380
3419
|
|
|
3381
3420
|
// src/lib/instance-dependency-install.ts
|
|
3382
|
-
import { existsSync as
|
|
3383
|
-
import { join as
|
|
3421
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3422
|
+
import { join as join15 } from "path";
|
|
3384
3423
|
function dependencyInstallSteps(instanceDir) {
|
|
3385
3424
|
const steps = [{ ecosystem: "pnpm", command: ["pnpm", "install"] }];
|
|
3386
|
-
if (
|
|
3425
|
+
if (existsSync14(join15(instanceDir, "pyproject.toml"))) {
|
|
3387
3426
|
steps.push({ ecosystem: "uv", command: ["uv", "sync"] });
|
|
3388
3427
|
}
|
|
3389
3428
|
return steps;
|
|
@@ -3595,6 +3634,7 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
3595
3634
|
});
|
|
3596
3635
|
const orphanRatchet = checkOrphanRatchet(plan.orphaned.length, readOrphanBaseline(options.cwd));
|
|
3597
3636
|
const newSeams = findNewUndeclaredSeams(baseDir, theirsDir, options.cwd);
|
|
3637
|
+
const adoption = checkInstanceAdoption(theirsDir, options.cwd);
|
|
3598
3638
|
const migrations = planMigrationCarry({
|
|
3599
3639
|
templateDir: theirsDir,
|
|
3600
3640
|
instanceDir: options.cwd,
|
|
@@ -3621,6 +3661,7 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
3621
3661
|
throw new Error(fidelityFailure(fidelity, toVersion));
|
|
3622
3662
|
}
|
|
3623
3663
|
printNewInstanceSeams(newSeams);
|
|
3664
|
+
printAdoptionReport(adoption);
|
|
3624
3665
|
printOrphanReport(plan.orphaned, orphanRatchet);
|
|
3625
3666
|
if (orphanRatchet.increased) {
|
|
3626
3667
|
throw new Error(
|
|
@@ -3679,10 +3720,11 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
3679
3720
|
theirsDir,
|
|
3680
3721
|
coreVersionCleanup,
|
|
3681
3722
|
orphanRatchet,
|
|
3682
|
-
newSeams
|
|
3723
|
+
newSeams,
|
|
3724
|
+
adoption
|
|
3683
3725
|
);
|
|
3684
3726
|
}
|
|
3685
|
-
async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet, newSeams) {
|
|
3727
|
+
async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet, newSeams, adoption) {
|
|
3686
3728
|
if (breaking.length > 0 && !options.acknowledgeBreaking) {
|
|
3687
3729
|
throw new Error(
|
|
3688
3730
|
`This upgrade crosses ${breaking.length} documented breaking change(s): ${breaking.map((b) => b.version).join(", ")}. They are printed above and in ${UPGRADE_GUIDE_PATH}. Read what each one requires \u2014 some destroy data or need manual work after the deploy \u2014 then re-run with --acknowledge-breaking.`
|
|
@@ -3715,6 +3757,7 @@ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVe
|
|
|
3715
3757
|
coreVersionCleanup,
|
|
3716
3758
|
orphanRatchet,
|
|
3717
3759
|
newSeams,
|
|
3760
|
+
adoption,
|
|
3718
3761
|
branch,
|
|
3719
3762
|
token
|
|
3720
3763
|
);
|
|
@@ -3749,7 +3792,7 @@ async function restoreCallerBranch(git, cwd, callerBranch, upgradeBranch) {
|
|
|
3749
3792
|
);
|
|
3750
3793
|
}
|
|
3751
3794
|
}
|
|
3752
|
-
async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet, newSeams, branch, token) {
|
|
3795
|
+
async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet, newSeams, adoption, branch, token) {
|
|
3753
3796
|
const { git } = deps;
|
|
3754
3797
|
const applied = applyUpgradePlan(options.cwd, plan, theirsDir);
|
|
3755
3798
|
const carried = applyMigrationCarry(options.cwd, migrations);
|
|
@@ -3762,7 +3805,7 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
|
|
|
3762
3805
|
);
|
|
3763
3806
|
}
|
|
3764
3807
|
const cleanedCoreVersion = coreVersionCleanup?.action === "delete";
|
|
3765
|
-
if (cleanedCoreVersion &&
|
|
3808
|
+
if (cleanedCoreVersion && existsSync15(coreVersionCleanup.path)) {
|
|
3766
3809
|
rmSync5(coreVersionCleanup.path);
|
|
3767
3810
|
log.info(
|
|
3768
3811
|
coreVersionCleanup.stale ? `Deleted stale ${CORE_VERSION_FILE} (recorded ${coreVersionCleanup.found}, behind the version biffo.core.json records \u2014 this instance has moved past it) (#842).` : `Deleted orphaned ${CORE_VERSION_FILE} (inherited copy recording ${coreVersionCleanup.found}, superseded by biffo.core.json) \u2014 nothing reads it as an authority (#434).`
|
|
@@ -3819,7 +3862,8 @@ ${marker}` : "this upgrade carries no template PRs, so no provenance marker is n
|
|
|
3819
3862
|
cleanedCoreVersion ? coreVersionCleanup : null,
|
|
3820
3863
|
carriedPrs,
|
|
3821
3864
|
newSeams,
|
|
3822
|
-
installFailures
|
|
3865
|
+
installFailures,
|
|
3866
|
+
adoption.findings
|
|
3823
3867
|
)
|
|
3824
3868
|
});
|
|
3825
3869
|
if (plan.conflicts.length > 0) {
|
|
@@ -3875,7 +3919,7 @@ function carriedPrNumbers(subjects) {
|
|
|
3875
3919
|
}
|
|
3876
3920
|
return [...new Set(numbers)].sort((a, b) => a - b);
|
|
3877
3921
|
}
|
|
3878
|
-
function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = [], newSeams = [], installFailures = []) {
|
|
3922
|
+
function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = [], newSeams = [], installFailures = [], adoptionFindings = []) {
|
|
3879
3923
|
const lines = [];
|
|
3880
3924
|
if (breaking.length > 0) {
|
|
3881
3925
|
lines.push(
|
|
@@ -3907,6 +3951,27 @@ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, loc
|
|
|
3907
3951
|
""
|
|
3908
3952
|
);
|
|
3909
3953
|
}
|
|
3954
|
+
const unadopted = adoptionFindings.filter((f) => f.status === "unadopted");
|
|
3955
|
+
if (unadopted.length > 0) {
|
|
3956
|
+
lines.push(
|
|
3957
|
+
`## \u26A0 ${unadopted.length} instance adoption gap(s) \u2014 shipped but not consumed (#1538/#1570)`,
|
|
3958
|
+
"",
|
|
3959
|
+
"The template-owned file(s) below already exist in this instance, but the **user-owned** file that must read them has not been edited to do so. The mechanism ships, CI is green, and it does nothing at runtime \u2014 `biffo core upgrade` cannot fix this itself (the file it would need to edit is user-owned), so it is surfaced here on every upgrade until it is adopted by hand.",
|
|
3960
|
+
"",
|
|
3961
|
+
...unadopted.flatMap((f) => [
|
|
3962
|
+
`### \`${f.pair.id}\``,
|
|
3963
|
+
"",
|
|
3964
|
+
f.pair.description,
|
|
3965
|
+
"",
|
|
3966
|
+
`- Template-owned: \`${f.pair.templateFile}\` (already present in this instance)`,
|
|
3967
|
+
`- Needs editing: \`${f.pair.userFile}\``,
|
|
3968
|
+
`- Fix: ${f.pair.remedy}`,
|
|
3969
|
+
""
|
|
3970
|
+
]),
|
|
3971
|
+
"---",
|
|
3972
|
+
""
|
|
3973
|
+
);
|
|
3974
|
+
}
|
|
3910
3975
|
lines.push(
|
|
3911
3976
|
"Automated core upgrade generated by `biffo core upgrade` (ADR-0006).",
|
|
3912
3977
|
"",
|
|
@@ -4107,6 +4172,30 @@ function printNewInstanceSeams(seams) {
|
|
|
4107
4172
|
}
|
|
4108
4173
|
console.log();
|
|
4109
4174
|
}
|
|
4175
|
+
function printAdoptionReport(report) {
|
|
4176
|
+
console.log(
|
|
4177
|
+
chalk4.dim(
|
|
4178
|
+
` Instance adoption: examined ${String(report.examinedInstances)} instance against ${String(report.registeredPairs)} registered pair(s), ${String(report.applicablePairs)} applicable to this upgrade.`
|
|
4179
|
+
)
|
|
4180
|
+
);
|
|
4181
|
+
const unadopted = report.findings.filter((f) => f.status === "unadopted");
|
|
4182
|
+
if (unadopted.length === 0) {
|
|
4183
|
+
console.log();
|
|
4184
|
+
return;
|
|
4185
|
+
}
|
|
4186
|
+
console.log(
|
|
4187
|
+
chalk4.red.bold(
|
|
4188
|
+
` \u26A0 ${String(unadopted.length)} adoption gap(s) \u2014 shipped but not consumed (#1538/#1570):`
|
|
4189
|
+
)
|
|
4190
|
+
);
|
|
4191
|
+
for (const f of unadopted) {
|
|
4192
|
+
console.log(
|
|
4193
|
+
` ${chalk4.bold(f.pair.id)} \u2014 ${chalk4.yellow(f.pair.userFile)} does not consume ` + chalk4.yellow(f.pair.templateFile)
|
|
4194
|
+
);
|
|
4195
|
+
console.log(chalk4.dim(` ${f.pair.remedy}`));
|
|
4196
|
+
}
|
|
4197
|
+
console.log();
|
|
4198
|
+
}
|
|
4110
4199
|
function printTargetFidelity(report, toVersion) {
|
|
4111
4200
|
if (report.unverifiable !== null) {
|
|
4112
4201
|
console.log(chalk4.yellow(` Target fidelity: NOT VERIFIED \u2014 ${report.unverifiable}`));
|
|
@@ -4223,8 +4312,8 @@ function printBreakingChanges(breaking, applying) {
|
|
|
4223
4312
|
}
|
|
4224
4313
|
function versionOfCheckout(dir, explicit) {
|
|
4225
4314
|
if (explicit) return explicit;
|
|
4226
|
-
const file =
|
|
4227
|
-
if (
|
|
4315
|
+
const file = join16(dir, CORE_VERSION_FILE);
|
|
4316
|
+
if (existsSync15(file)) return readCoreVersionFile(file);
|
|
4228
4317
|
throw new Error(
|
|
4229
4318
|
`Cannot determine the core version of ${dir}: it has no ${CORE_VERSION_FILE}, and a checkout supplied explicitly is not resolved from a tag. Pass --to to state which version this tree is.`
|
|
4230
4319
|
);
|
|
@@ -4232,8 +4321,8 @@ function versionOfCheckout(dir, explicit) {
|
|
|
4232
4321
|
function latestCoreVersion(repo) {
|
|
4233
4322
|
const fromTags = latestCoreVersionFromTags(repo);
|
|
4234
4323
|
if (fromTags) return fromTags;
|
|
4235
|
-
const file =
|
|
4236
|
-
if (
|
|
4324
|
+
const file = join16(repo, CORE_VERSION_FILE);
|
|
4325
|
+
if (existsSync15(file)) return readCoreVersionFile(file);
|
|
4237
4326
|
throw new Error(
|
|
4238
4327
|
`Cannot determine the template's core version: ${repo} has no core-v* tags and no ${CORE_VERSION_FILE}. Fetch tags (\`git fetch --tags\`) or pass --to explicitly.`
|
|
4239
4328
|
);
|
|
@@ -4251,7 +4340,7 @@ coreCommand.addCommand(coreUpgradeCommand);
|
|
|
4251
4340
|
import { Command as Command8 } from "commander";
|
|
4252
4341
|
|
|
4253
4342
|
// src/commands/data-apply.ts
|
|
4254
|
-
import { existsSync as
|
|
4343
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
|
|
4255
4344
|
import { resolve as resolve4 } from "path";
|
|
4256
4345
|
import chalk5 from "chalk";
|
|
4257
4346
|
import { Command as Command5 } from "commander";
|
|
@@ -4702,16 +4791,16 @@ function isTemplatePlaceholderConfig(raw) {
|
|
|
4702
4791
|
|
|
4703
4792
|
// src/lib/session.ts
|
|
4704
4793
|
import {
|
|
4705
|
-
existsSync as
|
|
4794
|
+
existsSync as existsSync16,
|
|
4706
4795
|
mkdirSync as mkdirSync4,
|
|
4707
4796
|
readdirSync as readdirSync4,
|
|
4708
|
-
readFileSync as
|
|
4797
|
+
readFileSync as readFileSync11,
|
|
4709
4798
|
rmSync as rmSync6,
|
|
4710
4799
|
statSync as statSync3,
|
|
4711
4800
|
writeFileSync as writeFileSync5
|
|
4712
4801
|
} from "fs";
|
|
4713
4802
|
import { homedir } from "os";
|
|
4714
|
-
import { join as
|
|
4803
|
+
import { join as join17 } from "path";
|
|
4715
4804
|
var LEGACY_STEP_ALIASES = {
|
|
4716
4805
|
github_config: ["github_branches", "github_instance_files", "github_settings"]
|
|
4717
4806
|
};
|
|
@@ -4720,39 +4809,39 @@ function hasCompleted(session, step) {
|
|
|
4720
4809
|
return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
|
|
4721
4810
|
}
|
|
4722
4811
|
function sessionsDir() {
|
|
4723
|
-
return process.env["BIFFO_SESSIONS_DIR"] ??
|
|
4812
|
+
return process.env["BIFFO_SESSIONS_DIR"] ?? join17(homedir(), ".biffo", "sessions");
|
|
4724
4813
|
}
|
|
4725
4814
|
function sessionPath(projectName) {
|
|
4726
|
-
return
|
|
4815
|
+
return join17(sessionsDir(), `${projectName}.json`);
|
|
4727
4816
|
}
|
|
4728
4817
|
function loadSession(projectName) {
|
|
4729
4818
|
const path = sessionPath(projectName);
|
|
4730
|
-
if (!
|
|
4819
|
+
if (!existsSync16(path)) return null;
|
|
4731
4820
|
try {
|
|
4732
|
-
return JSON.parse(
|
|
4821
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
4733
4822
|
} catch {
|
|
4734
4823
|
return null;
|
|
4735
4824
|
}
|
|
4736
4825
|
}
|
|
4737
4826
|
function findLatestSession() {
|
|
4738
4827
|
const dir = sessionsDir();
|
|
4739
|
-
if (!
|
|
4828
|
+
if (!existsSync16(dir)) return null;
|
|
4740
4829
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
4741
4830
|
if (files.length === 0) return null;
|
|
4742
4831
|
const sorted = files.map((f) => {
|
|
4743
|
-
const fullPath =
|
|
4744
|
-
const mtime =
|
|
4832
|
+
const fullPath = join17(dir, f);
|
|
4833
|
+
const mtime = existsSync16(fullPath) ? statSync3(fullPath).mtimeMs : -1;
|
|
4745
4834
|
return { f, mtime };
|
|
4746
4835
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
4747
4836
|
try {
|
|
4748
|
-
return JSON.parse(
|
|
4837
|
+
return JSON.parse(readFileSync11(join17(dir, sorted[0].f), "utf8"));
|
|
4749
4838
|
} catch {
|
|
4750
4839
|
return null;
|
|
4751
4840
|
}
|
|
4752
4841
|
}
|
|
4753
4842
|
function saveSession(session) {
|
|
4754
4843
|
const dir = sessionsDir();
|
|
4755
|
-
if (!
|
|
4844
|
+
if (!existsSync16(dir)) mkdirSync4(dir, { recursive: true });
|
|
4756
4845
|
const name = session.config.project?.name ?? "unknown";
|
|
4757
4846
|
const prior = loadSession(name);
|
|
4758
4847
|
if (prior) {
|
|
@@ -4774,36 +4863,36 @@ function markStepComplete(session, step) {
|
|
|
4774
4863
|
}
|
|
4775
4864
|
function deleteSession(projectName) {
|
|
4776
4865
|
const path = sessionPath(projectName);
|
|
4777
|
-
if (
|
|
4866
|
+
if (existsSync16(path)) rmSync6(path);
|
|
4778
4867
|
}
|
|
4779
4868
|
function projectsDir() {
|
|
4780
|
-
return process.env["BIFFO_PROJECTS_DIR"] ??
|
|
4869
|
+
return process.env["BIFFO_PROJECTS_DIR"] ?? join17(homedir(), ".biffo", "projects");
|
|
4781
4870
|
}
|
|
4782
4871
|
function saveProjectConfig(config) {
|
|
4783
4872
|
const dir = projectsDir();
|
|
4784
|
-
if (!
|
|
4785
|
-
writeFileSync5(
|
|
4873
|
+
if (!existsSync16(dir)) mkdirSync4(dir, { recursive: true });
|
|
4874
|
+
writeFileSync5(join17(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
|
|
4786
4875
|
}
|
|
4787
4876
|
function loadProjectConfig(name) {
|
|
4788
|
-
const path =
|
|
4789
|
-
if (!
|
|
4877
|
+
const path = join17(projectsDir(), `${name}.json`);
|
|
4878
|
+
if (!existsSync16(path)) return null;
|
|
4790
4879
|
try {
|
|
4791
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
4880
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync11(path, "utf8")));
|
|
4792
4881
|
return result.success ? result.data : null;
|
|
4793
4882
|
} catch {
|
|
4794
4883
|
return null;
|
|
4795
4884
|
}
|
|
4796
4885
|
}
|
|
4797
4886
|
function deleteProjectConfig(name) {
|
|
4798
|
-
const path =
|
|
4799
|
-
if (
|
|
4887
|
+
const path = join17(projectsDir(), `${name}.json`);
|
|
4888
|
+
if (existsSync16(path)) rmSync6(path);
|
|
4800
4889
|
}
|
|
4801
4890
|
function listProjectConfigs() {
|
|
4802
4891
|
const dir = projectsDir();
|
|
4803
|
-
if (!
|
|
4892
|
+
if (!existsSync16(dir)) return [];
|
|
4804
4893
|
return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
|
|
4805
4894
|
try {
|
|
4806
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
4895
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync11(join17(dir, f), "utf8")));
|
|
4807
4896
|
return result.success ? [result.data] : [];
|
|
4808
4897
|
} catch {
|
|
4809
4898
|
return [];
|
|
@@ -4872,7 +4961,7 @@ async function runDataApply(name, environment, config, aws) {
|
|
|
4872
4961
|
}
|
|
4873
4962
|
async function resolveConfig(options) {
|
|
4874
4963
|
if (options.config) {
|
|
4875
|
-
const raw = JSON.parse(
|
|
4964
|
+
const raw = JSON.parse(readFileSync12(resolve4(options.config), "utf8"));
|
|
4876
4965
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
4877
4966
|
if (!result.success) {
|
|
4878
4967
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -4892,8 +4981,8 @@ async function resolveConfig(options) {
|
|
|
4892
4981
|
return cfg;
|
|
4893
4982
|
}
|
|
4894
4983
|
const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
|
|
4895
|
-
if (
|
|
4896
|
-
const raw = JSON.parse(
|
|
4984
|
+
if (existsSync17(localConfigPath)) {
|
|
4985
|
+
const raw = JSON.parse(readFileSync12(localConfigPath, "utf8"));
|
|
4897
4986
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
4898
4987
|
if (result.success) return result.data;
|
|
4899
4988
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -4939,8 +5028,8 @@ async function resolveConfig(options) {
|
|
|
4939
5028
|
|
|
4940
5029
|
// src/commands/data-import.ts
|
|
4941
5030
|
import { execSync as execSync3 } from "child_process";
|
|
4942
|
-
import { cpSync, existsSync as
|
|
4943
|
-
import { join as
|
|
5031
|
+
import { cpSync, existsSync as existsSync18, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
5032
|
+
import { join as join18, resolve as resolve5 } from "path";
|
|
4944
5033
|
import chalk6 from "chalk";
|
|
4945
5034
|
import { Command as Command6 } from "commander";
|
|
4946
5035
|
import inquirer2 from "inquirer";
|
|
@@ -4980,23 +5069,23 @@ async function runDataImport(name, options, deps) {
|
|
|
4980
5069
|
`Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
|
|
4981
5070
|
);
|
|
4982
5071
|
}
|
|
4983
|
-
const servicesDir =
|
|
4984
|
-
if (!
|
|
5072
|
+
const servicesDir = join18(options.cwd, "services");
|
|
5073
|
+
if (!existsSync18(servicesDir)) {
|
|
4985
5074
|
throw new Error(
|
|
4986
5075
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
4987
5076
|
);
|
|
4988
5077
|
}
|
|
4989
|
-
const targetDir =
|
|
4990
|
-
if (
|
|
5078
|
+
const targetDir = join18(options.cwd, "db", "imports", name);
|
|
5079
|
+
if (existsSync18(targetDir)) {
|
|
4991
5080
|
throw new Error(
|
|
4992
5081
|
`DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
|
|
4993
5082
|
);
|
|
4994
5083
|
}
|
|
4995
|
-
const isLocalDir =
|
|
5084
|
+
const isLocalDir = existsSync18(options.source) && statSync4(options.source).isDirectory();
|
|
4996
5085
|
let sourceDir;
|
|
4997
5086
|
let cleanupClone = null;
|
|
4998
5087
|
if (isLocalDir) {
|
|
4999
|
-
sourceDir = options.path ?
|
|
5088
|
+
sourceDir = options.path ? join18(options.source, options.path) : options.source;
|
|
5000
5089
|
} else {
|
|
5001
5090
|
const token = options.token ?? await resolveDdlImportToken();
|
|
5002
5091
|
log.info(`Cloning ${options.source}...`);
|
|
@@ -5004,10 +5093,10 @@ async function runDataImport(name, options, deps) {
|
|
|
5004
5093
|
cleanupClone = () => {
|
|
5005
5094
|
deps.git.cleanup(tmpDir);
|
|
5006
5095
|
};
|
|
5007
|
-
sourceDir = options.path ?
|
|
5096
|
+
sourceDir = options.path ? join18(tmpDir, options.path) : tmpDir;
|
|
5008
5097
|
}
|
|
5009
5098
|
try {
|
|
5010
|
-
if (!
|
|
5099
|
+
if (!existsSync18(sourceDir)) {
|
|
5011
5100
|
throw new Error(`Source directory does not exist: ${sourceDir}`);
|
|
5012
5101
|
}
|
|
5013
5102
|
const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
|
|
@@ -5032,7 +5121,7 @@ async function runDataImport(name, options, deps) {
|
|
|
5032
5121
|
}
|
|
5033
5122
|
mkdirSync5(targetDir, { recursive: true });
|
|
5034
5123
|
for (const file of sqlFiles) {
|
|
5035
|
-
cpSync(
|
|
5124
|
+
cpSync(join18(sourceDir, file), join18(targetDir, file));
|
|
5036
5125
|
}
|
|
5037
5126
|
log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
|
|
5038
5127
|
const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
|
|
@@ -5084,8 +5173,8 @@ function printDryRun(name, sqlFiles) {
|
|
|
5084
5173
|
}
|
|
5085
5174
|
|
|
5086
5175
|
// src/commands/data-list.ts
|
|
5087
|
-
import { existsSync as
|
|
5088
|
-
import { join as
|
|
5176
|
+
import { existsSync as existsSync19, readdirSync as readdirSync6 } from "fs";
|
|
5177
|
+
import { join as join19, resolve as resolve6 } from "path";
|
|
5089
5178
|
import chalk7 from "chalk";
|
|
5090
5179
|
import { Command as Command7 } from "commander";
|
|
5091
5180
|
var dataListCommand = new Command7("list").description("List DDL imports vendored in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
|
|
@@ -5098,15 +5187,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
|
|
|
5098
5187
|
}
|
|
5099
5188
|
});
|
|
5100
5189
|
async function runDataList(options) {
|
|
5101
|
-
const importsDir =
|
|
5102
|
-
if (!
|
|
5190
|
+
const importsDir = join19(options.cwd, "db", "imports");
|
|
5191
|
+
if (!existsSync19(importsDir)) {
|
|
5103
5192
|
console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
|
|
5104
5193
|
return;
|
|
5105
5194
|
}
|
|
5106
5195
|
const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
5107
5196
|
const imports = [];
|
|
5108
5197
|
for (const name of candidates) {
|
|
5109
|
-
const fileCount = readdirSync6(
|
|
5198
|
+
const fileCount = readdirSync6(join19(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
|
|
5110
5199
|
if (fileCount > 0) imports.push({ name, fileCount });
|
|
5111
5200
|
}
|
|
5112
5201
|
if (imports.length === 0) {
|
|
@@ -5136,7 +5225,7 @@ dataCommand.addCommand(dataListCommand);
|
|
|
5136
5225
|
|
|
5137
5226
|
// src/commands/deploy.ts
|
|
5138
5227
|
import { execSync as execSync4 } from "child_process";
|
|
5139
|
-
import { existsSync as
|
|
5228
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
|
|
5140
5229
|
import { resolve as resolve7 } from "path";
|
|
5141
5230
|
import chalk8 from "chalk";
|
|
5142
5231
|
import { Command as Command9 } from "commander";
|
|
@@ -5493,7 +5582,7 @@ var deployCommand = new Command9("deploy").description("Deploy infrastructure an
|
|
|
5493
5582
|
);
|
|
5494
5583
|
async function resolveConfig2(options) {
|
|
5495
5584
|
if (options.config) {
|
|
5496
|
-
const raw = JSON.parse(
|
|
5585
|
+
const raw = JSON.parse(readFileSync13(resolve7(options.config), "utf8"));
|
|
5497
5586
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
5498
5587
|
if (!result.success) {
|
|
5499
5588
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -5513,8 +5602,8 @@ async function resolveConfig2(options) {
|
|
|
5513
5602
|
return cfg;
|
|
5514
5603
|
}
|
|
5515
5604
|
const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
|
|
5516
|
-
if (
|
|
5517
|
-
const raw = JSON.parse(
|
|
5605
|
+
if (existsSync20(localConfigPath)) {
|
|
5606
|
+
const raw = JSON.parse(readFileSync13(localConfigPath, "utf8"));
|
|
5518
5607
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
5519
5608
|
if (result.success) return result.data;
|
|
5520
5609
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -5910,7 +5999,7 @@ function resolveGithubToken() {
|
|
|
5910
5999
|
|
|
5911
6000
|
// src/commands/destroy.ts
|
|
5912
6001
|
import { execSync as execSync5 } from "child_process";
|
|
5913
|
-
import { readFileSync as
|
|
6002
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
5914
6003
|
import { resolve as resolve8 } from "path";
|
|
5915
6004
|
import chalk9 from "chalk";
|
|
5916
6005
|
import { Command as Command10 } from "commander";
|
|
@@ -6000,7 +6089,7 @@ var destroyCommand = new Command10("destroy").description("Destroy infrastructur
|
|
|
6000
6089
|
});
|
|
6001
6090
|
async function resolveConfig3(options) {
|
|
6002
6091
|
if (options.config) {
|
|
6003
|
-
const raw = JSON.parse(
|
|
6092
|
+
const raw = JSON.parse(readFileSync14(resolve8(options.config), "utf8"));
|
|
6004
6093
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
6005
6094
|
if (!result.success) {
|
|
6006
6095
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -6020,7 +6109,7 @@ async function resolveConfig3(options) {
|
|
|
6020
6109
|
return cfg;
|
|
6021
6110
|
}
|
|
6022
6111
|
try {
|
|
6023
|
-
const raw = JSON.parse(
|
|
6112
|
+
const raw = JSON.parse(readFileSync14(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
|
|
6024
6113
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
6025
6114
|
if (result.success) return result.data;
|
|
6026
6115
|
} catch {
|
|
@@ -6070,15 +6159,15 @@ function resolveGithubToken2() {
|
|
|
6070
6159
|
}
|
|
6071
6160
|
|
|
6072
6161
|
// src/commands/init.ts
|
|
6073
|
-
import { readFileSync as
|
|
6162
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
6074
6163
|
import { resolve as resolve10 } from "path";
|
|
6075
6164
|
import chalk12 from "chalk";
|
|
6076
6165
|
import { Command as Command12 } from "commander";
|
|
6077
6166
|
import inquirer5 from "inquirer";
|
|
6078
6167
|
|
|
6079
6168
|
// src/lib/build-freshness.ts
|
|
6080
|
-
import { existsSync as
|
|
6081
|
-
import { dirname as dirname5, join as
|
|
6169
|
+
import { existsSync as existsSync21, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
6170
|
+
import { dirname as dirname5, join as join20, relative as relative3, sep as sep3 } from "path";
|
|
6082
6171
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6083
6172
|
var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
|
|
6084
6173
|
function checkBuildFreshness(options = {}) {
|
|
@@ -6092,7 +6181,7 @@ function checkBuildFreshness(options = {}) {
|
|
|
6092
6181
|
if (!packageRoot) {
|
|
6093
6182
|
return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
|
|
6094
6183
|
}
|
|
6095
|
-
const distDir =
|
|
6184
|
+
const distDir = join20(packageRoot, "dist");
|
|
6096
6185
|
if (!isInside(distDir, moduleDir)) {
|
|
6097
6186
|
return {
|
|
6098
6187
|
status: "skipped",
|
|
@@ -6100,16 +6189,16 @@ function checkBuildFreshness(options = {}) {
|
|
|
6100
6189
|
newerSources: []
|
|
6101
6190
|
};
|
|
6102
6191
|
}
|
|
6103
|
-
const srcDir =
|
|
6104
|
-
if (!
|
|
6192
|
+
const srcDir = join20(packageRoot, "src");
|
|
6193
|
+
if (!existsSync21(srcDir)) {
|
|
6105
6194
|
return {
|
|
6106
6195
|
status: "skipped",
|
|
6107
6196
|
reason: "no src/ alongside dist/ \u2014 this is a shipped package",
|
|
6108
6197
|
newerSources: []
|
|
6109
6198
|
};
|
|
6110
6199
|
}
|
|
6111
|
-
const entry =
|
|
6112
|
-
if (!
|
|
6200
|
+
const entry = join20(distDir, "index.js");
|
|
6201
|
+
if (!existsSync21(entry)) {
|
|
6113
6202
|
return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
|
|
6114
6203
|
}
|
|
6115
6204
|
const builtAt = statSync5(entry).mtimeMs;
|
|
@@ -6153,7 +6242,7 @@ function collectSourceFiles(srcDir) {
|
|
|
6153
6242
|
const found = [];
|
|
6154
6243
|
const walk2 = (dir) => {
|
|
6155
6244
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
6156
|
-
const full =
|
|
6245
|
+
const full = join20(dir, entry.name);
|
|
6157
6246
|
if (entry.isDirectory()) {
|
|
6158
6247
|
if (entry.name === "node_modules") continue;
|
|
6159
6248
|
walk2(full);
|
|
@@ -6172,7 +6261,7 @@ function collectSourceFiles(srcDir) {
|
|
|
6172
6261
|
function findPackageRoot(from) {
|
|
6173
6262
|
let dir = from;
|
|
6174
6263
|
for (; ; ) {
|
|
6175
|
-
if (
|
|
6264
|
+
if (existsSync21(join20(dir, "package.json"))) return dir;
|
|
6176
6265
|
const parent = dirname5(dir);
|
|
6177
6266
|
if (parent === dir) return null;
|
|
6178
6267
|
dir = parent;
|
|
@@ -6186,9 +6275,9 @@ function isInside(parent, child) {
|
|
|
6186
6275
|
|
|
6187
6276
|
// src/lib/credentials.ts
|
|
6188
6277
|
import { execSync as execSync6 } from "child_process";
|
|
6189
|
-
import { existsSync as
|
|
6278
|
+
import { existsSync as existsSync22, readFileSync as readFileSync15 } from "fs";
|
|
6190
6279
|
import { homedir as homedir2 } from "os";
|
|
6191
|
-
import { join as
|
|
6280
|
+
import { join as join21 } from "path";
|
|
6192
6281
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
|
|
6193
6282
|
import chalk10 from "chalk";
|
|
6194
6283
|
import inquirer4 from "inquirer";
|
|
@@ -6367,11 +6456,11 @@ async function verifySelectedAwsCredentials(profile, region) {
|
|
|
6367
6456
|
return sts.send(new GetCallerIdentityCommand2({}));
|
|
6368
6457
|
}
|
|
6369
6458
|
function discoverAwsProfiles() {
|
|
6370
|
-
const files = [
|
|
6459
|
+
const files = [join21(homedir2(), ".aws", "credentials"), join21(homedir2(), ".aws", "config")];
|
|
6371
6460
|
const profiles = /* @__PURE__ */ new Set();
|
|
6372
6461
|
for (const file of files) {
|
|
6373
|
-
if (!
|
|
6374
|
-
const content =
|
|
6462
|
+
if (!existsSync22(file)) continue;
|
|
6463
|
+
const content = readFileSync15(file, "utf8");
|
|
6375
6464
|
for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
|
|
6376
6465
|
const section = match[1]?.trim();
|
|
6377
6466
|
if (!section) continue;
|
|
@@ -6461,34 +6550,34 @@ var SiblingConfigSchema = z6.object({
|
|
|
6461
6550
|
|
|
6462
6551
|
// src/lib/sibling-session.ts
|
|
6463
6552
|
import {
|
|
6464
|
-
existsSync as
|
|
6553
|
+
existsSync as existsSync23,
|
|
6465
6554
|
mkdirSync as mkdirSync6,
|
|
6466
6555
|
readdirSync as readdirSync8,
|
|
6467
|
-
readFileSync as
|
|
6556
|
+
readFileSync as readFileSync16,
|
|
6468
6557
|
rmSync as rmSync7,
|
|
6469
6558
|
statSync as statSync6,
|
|
6470
6559
|
writeFileSync as writeFileSync6
|
|
6471
6560
|
} from "fs";
|
|
6472
6561
|
import { homedir as homedir3 } from "os";
|
|
6473
|
-
import { join as
|
|
6562
|
+
import { join as join22 } from "path";
|
|
6474
6563
|
function sessionsDir2() {
|
|
6475
|
-
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ??
|
|
6564
|
+
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join22(homedir3(), ".biffo", "sibling-sessions");
|
|
6476
6565
|
}
|
|
6477
6566
|
function sessionPath2(projectName) {
|
|
6478
|
-
return
|
|
6567
|
+
return join22(sessionsDir2(), `${projectName}.json`);
|
|
6479
6568
|
}
|
|
6480
6569
|
function loadSiblingSession(projectName) {
|
|
6481
6570
|
const path = sessionPath2(projectName);
|
|
6482
|
-
if (!
|
|
6571
|
+
if (!existsSync23(path)) return null;
|
|
6483
6572
|
try {
|
|
6484
|
-
return JSON.parse(
|
|
6573
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
6485
6574
|
} catch {
|
|
6486
6575
|
return null;
|
|
6487
6576
|
}
|
|
6488
6577
|
}
|
|
6489
6578
|
function saveSiblingSession(session) {
|
|
6490
6579
|
const dir = sessionsDir2();
|
|
6491
|
-
if (!
|
|
6580
|
+
if (!existsSync23(dir)) mkdirSync6(dir, { recursive: true });
|
|
6492
6581
|
const name = session.config.project?.name ?? "unknown";
|
|
6493
6582
|
const prior = loadSiblingSession(name);
|
|
6494
6583
|
if (prior) {
|
|
@@ -6510,30 +6599,30 @@ function markSiblingStepComplete(session, step) {
|
|
|
6510
6599
|
}
|
|
6511
6600
|
function deleteSiblingSession(projectName) {
|
|
6512
6601
|
const path = sessionPath2(projectName);
|
|
6513
|
-
if (
|
|
6602
|
+
if (existsSync23(path)) rmSync7(path);
|
|
6514
6603
|
}
|
|
6515
6604
|
|
|
6516
6605
|
// src/commands/sibling-create.ts
|
|
6517
|
-
import { cpSync as cpSync2, existsSync as
|
|
6606
|
+
import { cpSync as cpSync2, existsSync as existsSync24, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync17, writeFileSync as writeFileSync7 } from "fs";
|
|
6518
6607
|
import { tmpdir as tmpdir4 } from "os";
|
|
6519
|
-
import { dirname as dirname6, join as
|
|
6608
|
+
import { dirname as dirname6, join as join24, resolve as resolve9 } from "path";
|
|
6520
6609
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6521
6610
|
import chalk11 from "chalk";
|
|
6522
6611
|
import { Command as Command11 } from "commander";
|
|
6523
6612
|
|
|
6524
6613
|
// src/lib/skeleton-dotfiles.ts
|
|
6525
6614
|
import { readdirSync as readdirSync9, renameSync } from "fs";
|
|
6526
|
-
import { join as
|
|
6615
|
+
import { join as join23 } from "path";
|
|
6527
6616
|
var PACKAGED_GITIGNORE = "_gitignore";
|
|
6528
6617
|
var REAL_GITIGNORE = ".gitignore";
|
|
6529
6618
|
function restorePackagedDotfiles(dir) {
|
|
6530
6619
|
const restored = [];
|
|
6531
6620
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
6532
|
-
const full =
|
|
6621
|
+
const full = join23(dir, entry.name);
|
|
6533
6622
|
if (entry.isDirectory()) {
|
|
6534
6623
|
restored.push(...restorePackagedDotfiles(full));
|
|
6535
6624
|
} else if (entry.name === PACKAGED_GITIGNORE) {
|
|
6536
|
-
const target =
|
|
6625
|
+
const target = join23(dir, REAL_GITIGNORE);
|
|
6537
6626
|
renameSync(full, target);
|
|
6538
6627
|
restored.push(target);
|
|
6539
6628
|
}
|
|
@@ -6578,7 +6667,7 @@ async function runSiblingCreateCommand(name, options) {
|
|
|
6578
6667
|
printDryRun2(config, coreConfig, options.templateRoot);
|
|
6579
6668
|
return;
|
|
6580
6669
|
}
|
|
6581
|
-
if (!
|
|
6670
|
+
if (!existsSync24(options.templateRoot)) {
|
|
6582
6671
|
throw new Error(`Sibling template not found at ${options.templateRoot}`);
|
|
6583
6672
|
}
|
|
6584
6673
|
let session = null;
|
|
@@ -6795,7 +6884,7 @@ function assertPathPrefixIsAllowed(pathPrefix) {
|
|
|
6795
6884
|
}
|
|
6796
6885
|
}
|
|
6797
6886
|
function readSiblingConfig(path, root = false) {
|
|
6798
|
-
const raw = JSON.parse(
|
|
6887
|
+
const raw = JSON.parse(readFileSync17(path, "utf8"));
|
|
6799
6888
|
const withDefaults = raw && typeof raw === "object" && "project" in raw && "core" in raw ? {
|
|
6800
6889
|
...raw,
|
|
6801
6890
|
core: {
|
|
@@ -6829,7 +6918,7 @@ function resolveCoreConfig(config, configPath) {
|
|
|
6829
6918
|
throw new Error("Either core.project_name or core.config_path is required.");
|
|
6830
6919
|
}
|
|
6831
6920
|
function parseCoreConfig(path) {
|
|
6832
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
6921
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync17(path, "utf8")));
|
|
6833
6922
|
if (!result.success) {
|
|
6834
6923
|
throw new Error(
|
|
6835
6924
|
`Invalid core configuration at ${path}:
|
|
@@ -6868,7 +6957,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
|
|
|
6868
6957
|
return coreIdentity;
|
|
6869
6958
|
}
|
|
6870
6959
|
async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
|
|
6871
|
-
const workDir = mkdtempSync4(
|
|
6960
|
+
const workDir = mkdtempSync4(join24(tmpdir4(), `biffo-sibling-${config.project.name}-`));
|
|
6872
6961
|
try {
|
|
6873
6962
|
writeSiblingTemplate(skeletonRoot, workDir, config, {
|
|
6874
6963
|
coreProjectName: coreConfig.project.name,
|
|
@@ -6893,13 +6982,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
|
|
|
6893
6982
|
}
|
|
6894
6983
|
}
|
|
6895
6984
|
function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
6896
|
-
if (!
|
|
6985
|
+
if (!existsSync24(templateRoot)) {
|
|
6897
6986
|
throw new Error(`Sibling template not found at ${templateRoot}`);
|
|
6898
6987
|
}
|
|
6899
6988
|
cpSync2(templateRoot, targetDir, { recursive: true });
|
|
6900
6989
|
restorePackagedDotfiles(targetDir);
|
|
6901
6990
|
writeFileSync7(
|
|
6902
|
-
|
|
6991
|
+
join24(targetDir, "biffo.sibling.json"),
|
|
6903
6992
|
JSON.stringify(
|
|
6904
6993
|
{
|
|
6905
6994
|
name: config.project.name,
|
|
@@ -6918,14 +7007,14 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
|
6918
7007
|
) + "\n"
|
|
6919
7008
|
);
|
|
6920
7009
|
writeFileSync7(
|
|
6921
|
-
|
|
7010
|
+
join24(targetDir, ".biffo-shared-version"),
|
|
6922
7011
|
`core-v${context.templateVersion.replace(/^core-v/, "")}
|
|
6923
7012
|
`
|
|
6924
7013
|
);
|
|
6925
|
-
const envPath =
|
|
7014
|
+
const envPath = join24(targetDir, "apps", "frontend", ".env.example");
|
|
6926
7015
|
try {
|
|
6927
7016
|
const path = basePathFor(context.pathPrefix);
|
|
6928
|
-
const content =
|
|
7017
|
+
const content = readFileSync17(envPath, "utf8").replace(/^NEXT_PUBLIC_SIBLING_NAME=.*$/m, `NEXT_PUBLIC_SIBLING_NAME=${config.project.name}`).replace(/^NEXT_PUBLIC_SIBLING_PATH_PREFIX=.*$/m, `NEXT_PUBLIC_SIBLING_PATH_PREFIX=${path}`).replace(/^NEXT_PUBLIC_BASE_PATH=.*$/m, `NEXT_PUBLIC_BASE_PATH=${path}`);
|
|
6929
7018
|
writeFileSync7(envPath, content);
|
|
6930
7019
|
} catch (err) {
|
|
6931
7020
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -6972,7 +7061,7 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
|
|
|
6972
7061
|
}
|
|
6973
7062
|
function readExistingSiblingOrigins(filePath) {
|
|
6974
7063
|
try {
|
|
6975
|
-
return JSON.parse(
|
|
7064
|
+
return JSON.parse(readFileSync17(filePath, "utf8"));
|
|
6976
7065
|
} catch (err) {
|
|
6977
7066
|
if (err.code === "ENOENT") return {};
|
|
6978
7067
|
throw err;
|
|
@@ -6991,10 +7080,10 @@ function assertGitIdentity(identity) {
|
|
|
6991
7080
|
);
|
|
6992
7081
|
}
|
|
6993
7082
|
function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
|
|
6994
|
-
const cdnVarsPath =
|
|
7083
|
+
const cdnVarsPath = join24(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
|
|
6995
7084
|
let declaresSiblingOrigins = false;
|
|
6996
7085
|
try {
|
|
6997
|
-
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(
|
|
7086
|
+
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync17(cdnVarsPath, "utf8"));
|
|
6998
7087
|
} catch {
|
|
6999
7088
|
declaresSiblingOrigins = false;
|
|
7000
7089
|
}
|
|
@@ -7004,10 +7093,10 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
|
|
|
7004
7093
|
);
|
|
7005
7094
|
}
|
|
7006
7095
|
if (!isRootPathPrefix(pathPrefix)) return;
|
|
7007
|
-
const cdnMainPath =
|
|
7096
|
+
const cdnMainPath = join24(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
|
|
7008
7097
|
let supportsRoot = false;
|
|
7009
7098
|
try {
|
|
7010
|
-
supportsRoot = /root_sibling_registered/.test(
|
|
7099
|
+
supportsRoot = /root_sibling_registered/.test(readFileSync17(cdnMainPath, "utf8"));
|
|
7011
7100
|
} catch {
|
|
7012
7101
|
supportsRoot = false;
|
|
7013
7102
|
}
|
|
@@ -7037,8 +7126,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
|
|
|
7037
7126
|
for (const env of config.environments) {
|
|
7038
7127
|
const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
|
|
7039
7128
|
const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
|
|
7040
|
-
const relativePath =
|
|
7041
|
-
const filePath =
|
|
7129
|
+
const relativePath = join24("infra", "environments", env, "siblings.auto.tfvars.json");
|
|
7130
|
+
const filePath = join24(cloneDir, relativePath);
|
|
7042
7131
|
const existing = readExistingSiblingOrigins(filePath);
|
|
7043
7132
|
const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
|
|
7044
7133
|
name,
|
|
@@ -7118,8 +7207,8 @@ function defaultSiblingTemplateRoot() {
|
|
|
7118
7207
|
const start = dirname6(fileURLToPath4(import.meta.url));
|
|
7119
7208
|
let dir = start;
|
|
7120
7209
|
for (; ; ) {
|
|
7121
|
-
const candidate =
|
|
7122
|
-
if (
|
|
7210
|
+
const candidate = join24(dir, "_skeletons", "sibling-template");
|
|
7211
|
+
if (existsSync24(candidate)) return candidate;
|
|
7123
7212
|
const parent = dirname6(dir);
|
|
7124
7213
|
if (parent === dir) break;
|
|
7125
7214
|
dir = parent;
|
|
@@ -7143,7 +7232,7 @@ var initCommand = new Command12("init").description("Scaffold a new project from
|
|
|
7143
7232
|
let config;
|
|
7144
7233
|
let githubToken;
|
|
7145
7234
|
if (options.config) {
|
|
7146
|
-
const rawConfig = JSON.parse(
|
|
7235
|
+
const rawConfig = JSON.parse(readFileSync18(resolve10(options.config), "utf8"));
|
|
7147
7236
|
config = parseConfig(rawConfig);
|
|
7148
7237
|
const { account_id: accountId, region } = config.cloud.config;
|
|
7149
7238
|
session = resolveConfigFileSession(config, accountId, region, options.fresh === true);
|
|
@@ -7588,8 +7677,8 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
7588
7677
|
import { Command as Command21 } from "commander";
|
|
7589
7678
|
|
|
7590
7679
|
// src/commands/plugin-create.ts
|
|
7591
|
-
import { existsSync as
|
|
7592
|
-
import { dirname as dirname8, join as
|
|
7680
|
+
import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
|
|
7681
|
+
import { dirname as dirname8, join as join27, resolve as resolve11 } from "path";
|
|
7593
7682
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7594
7683
|
import chalk13 from "chalk";
|
|
7595
7684
|
import { Command as Command13 } from "commander";
|
|
@@ -7653,21 +7742,21 @@ function workflowCheckContexts(workflow) {
|
|
|
7653
7742
|
}
|
|
7654
7743
|
|
|
7655
7744
|
// src/lib/plugin-locations.ts
|
|
7656
|
-
import { existsSync as
|
|
7657
|
-
import { join as
|
|
7745
|
+
import { existsSync as existsSync25, readdirSync as readdirSync10 } from "fs";
|
|
7746
|
+
import { join as join25 } from "path";
|
|
7658
7747
|
var FIRST_PARTY_PLUGINS_DIR = "_plugins";
|
|
7659
7748
|
var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
|
|
7660
7749
|
function pluginDir(name, channel) {
|
|
7661
7750
|
return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
|
|
7662
7751
|
}
|
|
7663
7752
|
function scanDir(absDir, relDir, channel) {
|
|
7664
|
-
if (!
|
|
7753
|
+
if (!existsSync25(absDir)) return [];
|
|
7665
7754
|
const found = [];
|
|
7666
7755
|
for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
|
|
7667
7756
|
if (!entry.isDirectory()) continue;
|
|
7668
7757
|
if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
|
|
7669
|
-
const manifestPath =
|
|
7670
|
-
if (!
|
|
7758
|
+
const manifestPath = join25(absDir, entry.name, PLUGIN_MANIFEST_FILE);
|
|
7759
|
+
if (!existsSync25(manifestPath)) continue;
|
|
7671
7760
|
found.push({
|
|
7672
7761
|
dirName: entry.name,
|
|
7673
7762
|
relDir: `${relDir}/${entry.name}`,
|
|
@@ -7678,11 +7767,11 @@ function scanDir(absDir, relDir, channel) {
|
|
|
7678
7767
|
return found;
|
|
7679
7768
|
}
|
|
7680
7769
|
function findInstalledPlugins(cwd) {
|
|
7681
|
-
const servicesDir =
|
|
7770
|
+
const servicesDir = join25(cwd, "services");
|
|
7682
7771
|
return [
|
|
7683
7772
|
...scanDir(servicesDir, "services", "third-party"),
|
|
7684
7773
|
...scanDir(
|
|
7685
|
-
|
|
7774
|
+
join25(servicesDir, FIRST_PARTY_PLUGINS_DIR),
|
|
7686
7775
|
`services/${FIRST_PARTY_PLUGINS_DIR}`,
|
|
7687
7776
|
"first-party"
|
|
7688
7777
|
)
|
|
@@ -7911,13 +8000,13 @@ function validateManifest(raw) {
|
|
|
7911
8000
|
// src/lib/plugin-scaffold.ts
|
|
7912
8001
|
import {
|
|
7913
8002
|
copyFileSync,
|
|
7914
|
-
existsSync as
|
|
8003
|
+
existsSync as existsSync26,
|
|
7915
8004
|
mkdirSync as mkdirSync8,
|
|
7916
|
-
readFileSync as
|
|
8005
|
+
readFileSync as readFileSync19,
|
|
7917
8006
|
readdirSync as readdirSync11,
|
|
7918
8007
|
writeFileSync as writeFileSync8
|
|
7919
8008
|
} from "fs";
|
|
7920
|
-
import { dirname as dirname7, join as
|
|
8009
|
+
import { dirname as dirname7, join as join26 } from "path";
|
|
7921
8010
|
var STANDALONE_ONLY_ENTRIES = {
|
|
7922
8011
|
".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
|
|
7923
8012
|
"registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
|
|
@@ -7971,10 +8060,10 @@ function applySubstitutions(text, names) {
|
|
|
7971
8060
|
var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
|
|
7972
8061
|
function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
7973
8062
|
const layout = options.layout ?? "in-tree";
|
|
7974
|
-
if (!
|
|
8063
|
+
if (!existsSync26(skeletonRoot)) {
|
|
7975
8064
|
throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
|
|
7976
8065
|
}
|
|
7977
|
-
if (!
|
|
8066
|
+
if (!existsSync26(join26(skeletonRoot, "terraform"))) {
|
|
7978
8067
|
throw new Error(
|
|
7979
8068
|
`Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
|
|
7980
8069
|
);
|
|
@@ -7982,7 +8071,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
|
7982
8071
|
const skipped = [];
|
|
7983
8072
|
const files = [];
|
|
7984
8073
|
const walk2 = (relDir) => {
|
|
7985
|
-
const absDir =
|
|
8074
|
+
const absDir = join26(skeletonRoot, relDir);
|
|
7986
8075
|
for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
|
|
7987
8076
|
(a, b) => a.name.localeCompare(b.name)
|
|
7988
8077
|
)) {
|
|
@@ -7997,14 +8086,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
|
7997
8086
|
continue;
|
|
7998
8087
|
}
|
|
7999
8088
|
const destRel = applySubstitutions(relPath, names);
|
|
8000
|
-
const destPath =
|
|
8089
|
+
const destPath = join26(destDir, destRel);
|
|
8001
8090
|
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
8002
8091
|
if (BINARY_EXTENSIONS.test(entry.name)) {
|
|
8003
|
-
copyFileSync(
|
|
8092
|
+
copyFileSync(join26(skeletonRoot, relPath), destPath);
|
|
8004
8093
|
} else {
|
|
8005
8094
|
writeFileSync8(
|
|
8006
8095
|
destPath,
|
|
8007
|
-
applySubstitutions(
|
|
8096
|
+
applySubstitutions(readFileSync19(join26(skeletonRoot, relPath), "utf8"), names)
|
|
8008
8097
|
);
|
|
8009
8098
|
}
|
|
8010
8099
|
files.push(destRel);
|
|
@@ -8021,8 +8110,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
|
8021
8110
|
function findSkeletonRoot(startDir, skeleton) {
|
|
8022
8111
|
let dir = startDir;
|
|
8023
8112
|
for (; ; ) {
|
|
8024
|
-
const candidate =
|
|
8025
|
-
if (
|
|
8113
|
+
const candidate = join26(dir, "_skeletons", skeleton);
|
|
8114
|
+
if (existsSync26(candidate)) return candidate;
|
|
8026
8115
|
const parent = dirname7(dir);
|
|
8027
8116
|
if (parent === dir) return null;
|
|
8028
8117
|
dir = parent;
|
|
@@ -8090,7 +8179,7 @@ async function runPluginCreate(name, options, deps) {
|
|
|
8090
8179
|
reportBranchProtectionSummary();
|
|
8091
8180
|
return;
|
|
8092
8181
|
}
|
|
8093
|
-
const isInstance =
|
|
8182
|
+
const isInstance = existsSync27(join27(options.cwd, INSTANCE_CORE_FILE));
|
|
8094
8183
|
if (options.firstParty && isInstance) {
|
|
8095
8184
|
throw new Error(
|
|
8096
8185
|
`--first-party scaffolds into services/_plugins/, which is template-owned: \`biffo core upgrade\` three-way-merges it against the template on every upgrade, and the template has no '${names.slug}'. This checkout is a Biffo instance (${INSTANCE_CORE_FILE} is present), so your plugin belongs in the user-owned ${pluginDir(names.slug, "third-party")}/ \u2014 re-run without --first-party.`
|
|
@@ -8098,19 +8187,19 @@ async function runPluginCreate(name, options, deps) {
|
|
|
8098
8187
|
}
|
|
8099
8188
|
const channel = options.firstParty ? "first-party" : "third-party";
|
|
8100
8189
|
const relDir = pluginDir(names.slug, channel);
|
|
8101
|
-
const destDir =
|
|
8102
|
-
const servicesDir =
|
|
8103
|
-
if (!
|
|
8190
|
+
const destDir = join27(options.cwd, relDir);
|
|
8191
|
+
const servicesDir = join27(options.cwd, "services");
|
|
8192
|
+
if (!existsSync27(servicesDir)) {
|
|
8104
8193
|
throw new Error(
|
|
8105
8194
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8106
8195
|
);
|
|
8107
8196
|
}
|
|
8108
|
-
if (
|
|
8197
|
+
if (existsSync27(destDir)) {
|
|
8109
8198
|
throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
|
|
8110
8199
|
}
|
|
8111
8200
|
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
8112
|
-
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ??
|
|
8113
|
-
if (!
|
|
8201
|
+
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join27(options.cwd, "_skeletons", "plugin-template");
|
|
8202
|
+
if (!existsSync27(skeletonRoot)) {
|
|
8114
8203
|
throw new Error(
|
|
8115
8204
|
`Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
|
|
8116
8205
|
);
|
|
@@ -8125,8 +8214,8 @@ async function runPluginCreate(name, options, deps) {
|
|
|
8125
8214
|
for (const { entry, reason } of skipped) {
|
|
8126
8215
|
log.info(`Skipped ${entry} \u2014 ${reason}`);
|
|
8127
8216
|
}
|
|
8128
|
-
const manifestPath =
|
|
8129
|
-
const manifest = validateManifest(JSON.parse(
|
|
8217
|
+
const manifestPath = join27(destDir, "biffo.plugin.json");
|
|
8218
|
+
const manifest = validateManifest(JSON.parse(readFileSync20(manifestPath, "utf8")));
|
|
8130
8219
|
if (manifest.name !== names.slug) {
|
|
8131
8220
|
throw new Error(
|
|
8132
8221
|
`Scaffolded manifest declares name '${manifest.name}', expected '${names.slug}'. The skeleton's manifest name may have diverged from 'example-plugin'.`
|
|
@@ -8149,8 +8238,8 @@ async function runPluginCreate(name, options, deps) {
|
|
|
8149
8238
|
printNextSteps(names, relDir, channel);
|
|
8150
8239
|
}
|
|
8151
8240
|
async function runStandaloneCreate(names, options, deps) {
|
|
8152
|
-
const destDir =
|
|
8153
|
-
if (
|
|
8241
|
+
const destDir = join27(options.cwd, names.dist);
|
|
8242
|
+
if (existsSync27(destDir)) {
|
|
8154
8243
|
throw new Error(`${names.dist}/ already exists. Choose a different name, or remove it first.`);
|
|
8155
8244
|
}
|
|
8156
8245
|
const skeletonRoot = resolveSkeletonRoot(options);
|
|
@@ -8168,7 +8257,7 @@ async function runStandaloneCreate(names, options, deps) {
|
|
|
8168
8257
|
restorePackagedDotfiles(destDir);
|
|
8169
8258
|
log.success(`Scaffolded ${String(files.length)} file(s) into ${names.dist}/`);
|
|
8170
8259
|
const manifest = validateManifest(
|
|
8171
|
-
JSON.parse(
|
|
8260
|
+
JSON.parse(readFileSync20(join27(destDir, "biffo.plugin.json"), "utf8"))
|
|
8172
8261
|
);
|
|
8173
8262
|
if (manifest.name !== names.slug) {
|
|
8174
8263
|
throw new Error(
|
|
@@ -8179,7 +8268,7 @@ async function runStandaloneCreate(names, options, deps) {
|
|
|
8179
8268
|
`Manifest valid \u2014 ${String(manifest.tables.length)} table(s), ${String(manifest.api_routes.length)} route(s)`
|
|
8180
8269
|
);
|
|
8181
8270
|
writeFileSync9(
|
|
8182
|
-
|
|
8271
|
+
join27(destDir, ".biffo-shared-version"),
|
|
8183
8272
|
`core-v${getLatestCoreVersion().replace(/^core-v/, "")}
|
|
8184
8273
|
`
|
|
8185
8274
|
);
|
|
@@ -8212,8 +8301,8 @@ async function createAndPushStandaloneRepo(org, names, destDir, options, deps) {
|
|
|
8212
8301
|
await deps.git.push(destDir, "dev", { token });
|
|
8213
8302
|
log.success(`Pushed dev to ${org}/${names.dist}`);
|
|
8214
8303
|
await github.setDefaultBranch(org, names.dist, "dev");
|
|
8215
|
-
const ciPath =
|
|
8216
|
-
const contexts =
|
|
8304
|
+
const ciPath = join27(destDir, ".github", "workflows", "ci.yml");
|
|
8305
|
+
const contexts = existsSync27(ciPath) ? workflowCheckContexts(readFileSync20(ciPath, "utf8")) : [];
|
|
8217
8306
|
if (contexts.length === 0) {
|
|
8218
8307
|
log.warn(
|
|
8219
8308
|
`Could not determine required status checks from ${ciPath} \u2014 skipping branch protection. Configure it manually on dev once you know the CI job names.`
|
|
@@ -8242,8 +8331,8 @@ async function registerInRegistrySources(names, cloneUrl, token, deps) {
|
|
|
8242
8331
|
let dir;
|
|
8243
8332
|
try {
|
|
8244
8333
|
dir = await deps.git.cloneForEditing(REGISTRY_REPO, "biffo-registry", token);
|
|
8245
|
-
const path =
|
|
8246
|
-
const file = JSON.parse(
|
|
8334
|
+
const path = join27(dir, "sources.json");
|
|
8335
|
+
const file = JSON.parse(readFileSync20(path, "utf8"));
|
|
8247
8336
|
const next = addSource(file, {
|
|
8248
8337
|
name: names.slug,
|
|
8249
8338
|
repo: cloneUrl.replace(/\.git$/, ""),
|
|
@@ -8351,8 +8440,8 @@ function printStandaloneNextSteps(names, minor) {
|
|
|
8351
8440
|
}
|
|
8352
8441
|
function resolveSkeletonRoot(options) {
|
|
8353
8442
|
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
8354
|
-
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ??
|
|
8355
|
-
if (!
|
|
8443
|
+
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join27(options.cwd, "_skeletons", "plugin-template");
|
|
8444
|
+
if (!existsSync27(skeletonRoot)) {
|
|
8356
8445
|
throw new Error(
|
|
8357
8446
|
`Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
|
|
8358
8447
|
);
|
|
@@ -8543,14 +8632,14 @@ function printEntry(entry) {
|
|
|
8543
8632
|
}
|
|
8544
8633
|
|
|
8545
8634
|
// src/commands/plugin-install.ts
|
|
8546
|
-
import { cpSync as cpSync5, existsSync as
|
|
8547
|
-
import { join as
|
|
8635
|
+
import { cpSync as cpSync5, existsSync as existsSync31, mkdirSync as mkdirSync11, readFileSync as readFileSync23, statSync as statSync7 } from "fs";
|
|
8636
|
+
import { join as join33, relative as relative4, resolve as resolve12 } from "path";
|
|
8548
8637
|
import chalk15 from "chalk";
|
|
8549
8638
|
import { Command as Command15 } from "commander";
|
|
8550
8639
|
|
|
8551
8640
|
// src/adapters/plugin-migrations/index.ts
|
|
8552
8641
|
import { execa as execa4 } from "execa";
|
|
8553
|
-
import { join as
|
|
8642
|
+
import { join as join28 } from "path";
|
|
8554
8643
|
var PluginMigrationsAdapter = class {
|
|
8555
8644
|
/**
|
|
8556
8645
|
* Generates migration file(s) for `pluginNames` (every discovered
|
|
@@ -8559,22 +8648,22 @@ var PluginMigrationsAdapter = class {
|
|
|
8559
8648
|
* or declared no tables.
|
|
8560
8649
|
*/
|
|
8561
8650
|
async generate(cwd, pluginNames) {
|
|
8562
|
-
const scriptPath =
|
|
8651
|
+
const scriptPath = join28(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
|
|
8563
8652
|
const args = [
|
|
8564
8653
|
"run",
|
|
8565
8654
|
"python",
|
|
8566
8655
|
scriptPath,
|
|
8567
8656
|
"--services-root",
|
|
8568
|
-
|
|
8657
|
+
join28(cwd, "services"),
|
|
8569
8658
|
"--versions-dir",
|
|
8570
|
-
|
|
8659
|
+
join28(cwd, "services", "api", "migrations", "versions")
|
|
8571
8660
|
];
|
|
8572
8661
|
for (const name of pluginNames ?? []) {
|
|
8573
8662
|
args.push("--plugin", name);
|
|
8574
8663
|
}
|
|
8575
8664
|
let result;
|
|
8576
8665
|
try {
|
|
8577
|
-
result = await execa4("uv", args, { cwd:
|
|
8666
|
+
result = await execa4("uv", args, { cwd: join28(cwd, "services", "api") });
|
|
8578
8667
|
} catch (err) {
|
|
8579
8668
|
const cause = err;
|
|
8580
8669
|
if (cause.code === "ENOENT") {
|
|
@@ -8591,8 +8680,8 @@ var PluginMigrationsAdapter = class {
|
|
|
8591
8680
|
};
|
|
8592
8681
|
|
|
8593
8682
|
// src/lib/plugin-provenance.ts
|
|
8594
|
-
import { existsSync as
|
|
8595
|
-
import { join as
|
|
8683
|
+
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
|
|
8684
|
+
import { join as join29 } from "path";
|
|
8596
8685
|
import { execa as execa5 } from "execa";
|
|
8597
8686
|
var PLUGIN_PROVENANCE_FILENAME = ".biffo-plugin-provenance.json";
|
|
8598
8687
|
function isPluginProvenance(value) {
|
|
@@ -8601,11 +8690,11 @@ function isPluginProvenance(value) {
|
|
|
8601
8690
|
return typeof v["origin"] === "string" && (typeof v["ref"] === "string" || v["ref"] === null) && (typeof v["sha"] === "string" || v["sha"] === null) && typeof v["recordedAt"] === "string" && typeof v["inTree"] === "boolean";
|
|
8602
8691
|
}
|
|
8603
8692
|
function readProvenance(pluginDir2) {
|
|
8604
|
-
const path =
|
|
8605
|
-
if (!
|
|
8693
|
+
const path = join29(pluginDir2, PLUGIN_PROVENANCE_FILENAME);
|
|
8694
|
+
if (!existsSync28(path)) return { status: "absent" };
|
|
8606
8695
|
let parsed;
|
|
8607
8696
|
try {
|
|
8608
|
-
parsed = JSON.parse(
|
|
8697
|
+
parsed = JSON.parse(readFileSync21(path, "utf8"));
|
|
8609
8698
|
} catch (err) {
|
|
8610
8699
|
return {
|
|
8611
8700
|
status: "invalid",
|
|
@@ -8621,7 +8710,7 @@ function readProvenance(pluginDir2) {
|
|
|
8621
8710
|
return { status: "present", record: parsed };
|
|
8622
8711
|
}
|
|
8623
8712
|
function writePluginProvenance(pluginDir2, record) {
|
|
8624
|
-
writeFileSync10(
|
|
8713
|
+
writeFileSync10(join29(pluginDir2, PLUGIN_PROVENANCE_FILENAME), `${JSON.stringify(record, null, 2)}
|
|
8625
8714
|
`);
|
|
8626
8715
|
}
|
|
8627
8716
|
function reconcileProvenance(previous, next) {
|
|
@@ -8671,8 +8760,8 @@ async function tryGit(cwd, args) {
|
|
|
8671
8760
|
}
|
|
8672
8761
|
|
|
8673
8762
|
// src/lib/plugin-seed-vendor.ts
|
|
8674
|
-
import { cpSync as cpSync3, existsSync as
|
|
8675
|
-
import { join as
|
|
8763
|
+
import { cpSync as cpSync3, existsSync as existsSync29, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync8 } from "fs";
|
|
8764
|
+
import { join as join30 } from "path";
|
|
8676
8765
|
var VENDOR_PREFIX = "_plugin-";
|
|
8677
8766
|
function pluginSeedImportDir(pluginName) {
|
|
8678
8767
|
return `db/imports/${VENDOR_PREFIX}${pluginName}`;
|
|
@@ -8681,8 +8770,8 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
8681
8770
|
if (!manifest.seed) {
|
|
8682
8771
|
return { vendored: false };
|
|
8683
8772
|
}
|
|
8684
|
-
const sourceSeedDir =
|
|
8685
|
-
if (!
|
|
8773
|
+
const sourceSeedDir = join30(pluginSourceDir, manifest.seed.dir);
|
|
8774
|
+
if (!existsSync29(sourceSeedDir)) {
|
|
8686
8775
|
throw new Error(
|
|
8687
8776
|
`${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} does not exist in the plugin's source.`
|
|
8688
8777
|
);
|
|
@@ -8694,11 +8783,11 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
8694
8783
|
);
|
|
8695
8784
|
}
|
|
8696
8785
|
const relTargetDir = pluginSeedImportDir(manifest.name);
|
|
8697
|
-
const targetDir =
|
|
8786
|
+
const targetDir = join30(cwd, relTargetDir);
|
|
8698
8787
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
8699
8788
|
mkdirSync9(targetDir, { recursive: true });
|
|
8700
8789
|
for (const file of sqlFiles) {
|
|
8701
|
-
cpSync3(
|
|
8790
|
+
cpSync3(join30(sourceSeedDir, file), join30(targetDir, file));
|
|
8702
8791
|
}
|
|
8703
8792
|
log.success(
|
|
8704
8793
|
`Vendored ${sqlFiles.length} seed file(s) to ${relTargetDir}/ (baseline_tables: ${manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
@@ -8711,7 +8800,7 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
8711
8800
|
|
|
8712
8801
|
// src/lib/plugin-source-copy.ts
|
|
8713
8802
|
import { copyFileSync as copyFileSync2, cpSync as cpSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
8714
|
-
import { basename, dirname as dirname9, join as
|
|
8803
|
+
import { basename, dirname as dirname9, join as join31 } from "path";
|
|
8715
8804
|
import { execa as execa6 } from "execa";
|
|
8716
8805
|
var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
8717
8806
|
".git",
|
|
@@ -8728,9 +8817,9 @@ async function copyPluginSource(sourceDir, targetDir) {
|
|
|
8728
8817
|
if (await isGitWorkingTree2(sourceDir)) {
|
|
8729
8818
|
const files = await listGitFiles(sourceDir);
|
|
8730
8819
|
for (const relPath of files) {
|
|
8731
|
-
const destPath =
|
|
8820
|
+
const destPath = join31(targetDir, relPath);
|
|
8732
8821
|
mkdirSync10(dirname9(destPath), { recursive: true });
|
|
8733
|
-
copyFileSync2(
|
|
8822
|
+
copyFileSync2(join31(sourceDir, relPath), destPath);
|
|
8734
8823
|
}
|
|
8735
8824
|
return { usedGitIgnoreRules: true };
|
|
8736
8825
|
}
|
|
@@ -8762,8 +8851,8 @@ async function listGitFiles(dir) {
|
|
|
8762
8851
|
}
|
|
8763
8852
|
|
|
8764
8853
|
// src/lib/plugin-workspace-sources.ts
|
|
8765
|
-
import { existsSync as
|
|
8766
|
-
import { join as
|
|
8854
|
+
import { existsSync as existsSync30, readdirSync as readdirSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "fs";
|
|
8855
|
+
import { join as join32 } from "path";
|
|
8767
8856
|
function readTomlStringArray(text, key) {
|
|
8768
8857
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
8769
8858
|
if (!open) return [];
|
|
@@ -8807,9 +8896,9 @@ function readDependencyNames(text) {
|
|
|
8807
8896
|
return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
|
|
8808
8897
|
}
|
|
8809
8898
|
function workspaceMemberNames(instanceRoot) {
|
|
8810
|
-
const rootPyproject =
|
|
8811
|
-
if (!
|
|
8812
|
-
const text =
|
|
8899
|
+
const rootPyproject = join32(instanceRoot, "pyproject.toml");
|
|
8900
|
+
if (!existsSync30(rootPyproject)) return /* @__PURE__ */ new Set();
|
|
8901
|
+
const text = readFileSync22(rootPyproject, "utf8");
|
|
8813
8902
|
const members = readTomlStringArray(text, "members");
|
|
8814
8903
|
const excluded = new Set(readTomlStringArray(text, "exclude"));
|
|
8815
8904
|
const dirs = [];
|
|
@@ -8818,7 +8907,7 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8818
8907
|
const base = member.slice(0, -2);
|
|
8819
8908
|
let entries;
|
|
8820
8909
|
try {
|
|
8821
|
-
entries = readdirSync13(
|
|
8910
|
+
entries = readdirSync13(join32(instanceRoot, base), { withFileTypes: true });
|
|
8822
8911
|
} catch {
|
|
8823
8912
|
continue;
|
|
8824
8913
|
}
|
|
@@ -8832,9 +8921,9 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8832
8921
|
}
|
|
8833
8922
|
const names = /* @__PURE__ */ new Set();
|
|
8834
8923
|
for (const dir of dirs) {
|
|
8835
|
-
const pp =
|
|
8836
|
-
if (!
|
|
8837
|
-
const name = readProjectName(
|
|
8924
|
+
const pp = join32(instanceRoot, dir, "pyproject.toml");
|
|
8925
|
+
if (!existsSync30(pp)) continue;
|
|
8926
|
+
const name = readProjectName(readFileSync22(pp, "utf8"));
|
|
8838
8927
|
if (name) names.add(name);
|
|
8839
8928
|
}
|
|
8840
8929
|
return names;
|
|
@@ -8845,8 +8934,8 @@ function existingWorkspaceSources(text) {
|
|
|
8845
8934
|
);
|
|
8846
8935
|
}
|
|
8847
8936
|
function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
|
|
8848
|
-
if (!
|
|
8849
|
-
const text =
|
|
8937
|
+
if (!existsSync30(pluginPyprojectPath) || memberNames.size === 0) return [];
|
|
8938
|
+
const text = readFileSync22(pluginPyprojectPath, "utf8");
|
|
8850
8939
|
const already = existingWorkspaceSources(text);
|
|
8851
8940
|
const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
|
|
8852
8941
|
if (toAdd.length === 0) return [];
|
|
@@ -8870,8 +8959,8 @@ ${lines.join("\n")}
|
|
|
8870
8959
|
return toAdd;
|
|
8871
8960
|
}
|
|
8872
8961
|
function applyWorkspaceSources(targetDir, cwd, relTargetDir) {
|
|
8873
|
-
const pluginPyproject =
|
|
8874
|
-
if (!
|
|
8962
|
+
const pluginPyproject = join32(targetDir, "pyproject.toml");
|
|
8963
|
+
if (!existsSync30(pluginPyproject)) return;
|
|
8875
8964
|
const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(cwd));
|
|
8876
8965
|
if (sourced.length > 0) {
|
|
8877
8966
|
log.info(
|
|
@@ -8911,14 +9000,14 @@ var pluginInstallCommand = new Command15("install").description(
|
|
|
8911
9000
|
}
|
|
8912
9001
|
);
|
|
8913
9002
|
function resolveLocalPlugin(localPath) {
|
|
8914
|
-
if (!
|
|
9003
|
+
if (!existsSync31(localPath)) {
|
|
8915
9004
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
8916
9005
|
}
|
|
8917
9006
|
if (!statSync7(localPath).isDirectory()) {
|
|
8918
9007
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
8919
9008
|
}
|
|
8920
|
-
const manifestPath =
|
|
8921
|
-
if (!
|
|
9009
|
+
const manifestPath = join33(localPath, "biffo.plugin.json");
|
|
9010
|
+
if (!existsSync31(manifestPath)) {
|
|
8922
9011
|
throw new Error(
|
|
8923
9012
|
`${localPath} does not contain a biffo.plugin.json manifest at its root \u2014 is it a plugin directory? (Scaffold one with \`biffo plugin create <name>\`.)`
|
|
8924
9013
|
);
|
|
@@ -8944,8 +9033,8 @@ function parsePluginTarget(target) {
|
|
|
8944
9033
|
async function cloneAndValidatePlugin(entry, git) {
|
|
8945
9034
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
8946
9035
|
try {
|
|
8947
|
-
const manifestPath =
|
|
8948
|
-
if (!
|
|
9036
|
+
const manifestPath = join33(tmpDir, "biffo.plugin.json");
|
|
9037
|
+
if (!existsSync31(manifestPath)) {
|
|
8949
9038
|
throw new Error(
|
|
8950
9039
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
8951
9040
|
);
|
|
@@ -8973,8 +9062,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8973
9062
|
`Nothing to install. Pass a registry target (e.g. \`biffo plugin install acme-crm@1.0\`) or a local plugin directory (\`biffo plugin install --local services/acme-crm\`).`
|
|
8974
9063
|
);
|
|
8975
9064
|
}
|
|
8976
|
-
const servicesDir =
|
|
8977
|
-
if (!
|
|
9065
|
+
const servicesDir = join33(options.cwd, "services");
|
|
9066
|
+
if (!existsSync31(servicesDir)) {
|
|
8978
9067
|
throw new Error(
|
|
8979
9068
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8980
9069
|
);
|
|
@@ -8992,10 +9081,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8992
9081
|
}
|
|
8993
9082
|
const pluginName = entry ? entry.name : source.name;
|
|
8994
9083
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
8995
|
-
const targetDir =
|
|
8996
|
-
const modulesDir =
|
|
9084
|
+
const targetDir = join33(options.cwd, relTargetDir);
|
|
9085
|
+
const modulesDir = join33(options.cwd, "modules", "plugins", pluginName);
|
|
8997
9086
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
8998
|
-
if (
|
|
9087
|
+
if (existsSync31(targetDir) && !inTreeSource) {
|
|
8999
9088
|
throw new Error(
|
|
9000
9089
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
9001
9090
|
);
|
|
@@ -9039,8 +9128,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9039
9128
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9040
9129
|
applyWorkspaceSources(targetDir, options.cwd, relTargetDir);
|
|
9041
9130
|
const stagePaths = [relTargetDir];
|
|
9042
|
-
const tfSourceDir =
|
|
9043
|
-
if (
|
|
9131
|
+
const tfSourceDir = join33(targetDir, "terraform");
|
|
9132
|
+
if (existsSync31(tfSourceDir)) {
|
|
9044
9133
|
mkdirSync11(modulesDir, { recursive: true });
|
|
9045
9134
|
cpSync5(tfSourceDir, modulesDir, { recursive: true });
|
|
9046
9135
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
@@ -9101,7 +9190,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9101
9190
|
}
|
|
9102
9191
|
function parseManifestFile(path) {
|
|
9103
9192
|
try {
|
|
9104
|
-
return JSON.parse(
|
|
9193
|
+
return JSON.parse(readFileSync23(path, "utf8"));
|
|
9105
9194
|
} catch (err) {
|
|
9106
9195
|
throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
|
|
9107
9196
|
}
|
|
@@ -9143,8 +9232,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
|
|
|
9143
9232
|
}
|
|
9144
9233
|
|
|
9145
9234
|
// src/commands/plugin-list.ts
|
|
9146
|
-
import { existsSync as
|
|
9147
|
-
import { join as
|
|
9235
|
+
import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
|
|
9236
|
+
import { join as join34, resolve as resolve13 } from "path";
|
|
9148
9237
|
import chalk16 from "chalk";
|
|
9149
9238
|
import { Command as Command16 } from "commander";
|
|
9150
9239
|
var pluginListCommand = new Command16("list").description("List plugins installed in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
|
|
@@ -9157,8 +9246,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
9157
9246
|
}
|
|
9158
9247
|
});
|
|
9159
9248
|
async function runPluginList(options) {
|
|
9160
|
-
const servicesDir =
|
|
9161
|
-
if (!
|
|
9249
|
+
const servicesDir = join34(options.cwd, "services");
|
|
9250
|
+
if (!existsSync32(servicesDir)) {
|
|
9162
9251
|
throw new Error(
|
|
9163
9252
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9164
9253
|
);
|
|
@@ -9166,7 +9255,7 @@ async function runPluginList(options) {
|
|
|
9166
9255
|
const plugins = [];
|
|
9167
9256
|
for (const location of findInstalledPlugins(options.cwd)) {
|
|
9168
9257
|
try {
|
|
9169
|
-
const manifest = validateManifest(JSON.parse(
|
|
9258
|
+
const manifest = validateManifest(JSON.parse(readFileSync24(location.manifestPath, "utf8")));
|
|
9170
9259
|
plugins.push({
|
|
9171
9260
|
name: manifest.name,
|
|
9172
9261
|
version: manifest.version,
|
|
@@ -9207,14 +9296,14 @@ import { resolve as resolve14 } from "path";
|
|
|
9207
9296
|
import { Command as Command17 } from "commander";
|
|
9208
9297
|
|
|
9209
9298
|
// src/lib/plugin-staleness.ts
|
|
9210
|
-
import { existsSync as
|
|
9211
|
-
import { join as
|
|
9299
|
+
import { existsSync as existsSync33, readFileSync as readFileSync25, readdirSync as readdirSync14, statSync as statSync8 } from "fs";
|
|
9300
|
+
import { join as join35, relative as relative5 } from "path";
|
|
9212
9301
|
function discoverVendoredPlugins(servicesDir) {
|
|
9213
|
-
if (!
|
|
9214
|
-
return readdirSync14(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) =>
|
|
9302
|
+
if (!existsSync33(servicesDir)) return [];
|
|
9303
|
+
return readdirSync14(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) => existsSync33(join35(servicesDir, name, "biffo.plugin.json"))).sort();
|
|
9215
9304
|
}
|
|
9216
9305
|
async function checkPluginStaleness(cwd, deps) {
|
|
9217
|
-
const servicesDir =
|
|
9306
|
+
const servicesDir = join35(cwd, "services");
|
|
9218
9307
|
const names = discoverVendoredPlugins(servicesDir);
|
|
9219
9308
|
let registryRepoByName = null;
|
|
9220
9309
|
const resolveRegistryRepo = async (name) => {
|
|
@@ -9230,7 +9319,7 @@ async function checkPluginStaleness(cwd, deps) {
|
|
|
9230
9319
|
};
|
|
9231
9320
|
const results = [];
|
|
9232
9321
|
for (const name of names) {
|
|
9233
|
-
results.push(await checkOnePlugin(
|
|
9322
|
+
results.push(await checkOnePlugin(join35(servicesDir, name), name, resolveRegistryRepo, deps.git));
|
|
9234
9323
|
}
|
|
9235
9324
|
return results;
|
|
9236
9325
|
}
|
|
@@ -9256,7 +9345,7 @@ async function checkOnePlugin(pluginDir2, name, resolveRegistryRepo, git) {
|
|
|
9256
9345
|
if (record?.sha && isFetchableUrl(record.origin)) {
|
|
9257
9346
|
return checkViaProvenance(name, record, record.origin, git);
|
|
9258
9347
|
}
|
|
9259
|
-
const localOrigin = record && !isFetchableUrl(record.origin) &&
|
|
9348
|
+
const localOrigin = record && !isFetchableUrl(record.origin) && existsSync33(record.origin) ? record.origin : null;
|
|
9260
9349
|
if (localOrigin) {
|
|
9261
9350
|
return checkViaContentDiff(name, pluginDir2, localOrigin, { isLocalDir: true }, git);
|
|
9262
9351
|
}
|
|
@@ -9390,8 +9479,8 @@ async function countDifferingFiles(sourceDir, pluginDir2) {
|
|
|
9390
9479
|
differing++;
|
|
9391
9480
|
continue;
|
|
9392
9481
|
}
|
|
9393
|
-
const a =
|
|
9394
|
-
const b =
|
|
9482
|
+
const a = readFileSync25(join35(sourceDir, relPath));
|
|
9483
|
+
const b = readFileSync25(join35(pluginDir2, relPath));
|
|
9395
9484
|
if (!a.equals(b)) differing++;
|
|
9396
9485
|
}
|
|
9397
9486
|
return differing;
|
|
@@ -9406,11 +9495,11 @@ function vendorFileList(dir) {
|
|
|
9406
9495
|
return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
|
|
9407
9496
|
}
|
|
9408
9497
|
function walkExcluding(root, dir, excludes) {
|
|
9409
|
-
if (!
|
|
9498
|
+
if (!existsSync33(dir)) return [];
|
|
9410
9499
|
const out = [];
|
|
9411
9500
|
for (const entry of readdirSync14(dir)) {
|
|
9412
9501
|
if (excludes.has(entry) || entry === ".git") continue;
|
|
9413
|
-
const full =
|
|
9502
|
+
const full = join35(dir, entry);
|
|
9414
9503
|
const stat = statSync8(full);
|
|
9415
9504
|
if (stat.isDirectory()) {
|
|
9416
9505
|
out.push(...walkExcluding(root, full, excludes));
|
|
@@ -9462,8 +9551,8 @@ var pluginStalenessCommand = new Command17("staleness").description(
|
|
|
9462
9551
|
});
|
|
9463
9552
|
|
|
9464
9553
|
// src/commands/plugin-sync-migrations.ts
|
|
9465
|
-
import { existsSync as
|
|
9466
|
-
import { join as
|
|
9554
|
+
import { existsSync as existsSync34 } from "fs";
|
|
9555
|
+
import { join as join36, relative as relative6, resolve as resolve15 } from "path";
|
|
9467
9556
|
import chalk17 from "chalk";
|
|
9468
9557
|
import { Command as Command18 } from "commander";
|
|
9469
9558
|
var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
@@ -9484,11 +9573,11 @@ var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
|
9484
9573
|
}
|
|
9485
9574
|
);
|
|
9486
9575
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
9487
|
-
const servicesDir =
|
|
9488
|
-
if (!
|
|
9576
|
+
const servicesDir = join36(options.cwd, "services");
|
|
9577
|
+
if (!existsSync34(servicesDir)) {
|
|
9489
9578
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
9490
9579
|
}
|
|
9491
|
-
if (name && !
|
|
9580
|
+
if (name && !existsSync34(join36(servicesDir, name, "biffo.plugin.json"))) {
|
|
9492
9581
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
9493
9582
|
}
|
|
9494
9583
|
if (options.dryRun) {
|
|
@@ -9524,8 +9613,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
9524
9613
|
}
|
|
9525
9614
|
|
|
9526
9615
|
// src/commands/plugin-uninstall.ts
|
|
9527
|
-
import { existsSync as
|
|
9528
|
-
import { join as
|
|
9616
|
+
import { existsSync as existsSync35, readFileSync as readFileSync26, rmSync as rmSync9 } from "fs";
|
|
9617
|
+
import { join as join37, resolve as resolve16 } from "path";
|
|
9529
9618
|
import chalk18 from "chalk";
|
|
9530
9619
|
import { Command as Command19 } from "commander";
|
|
9531
9620
|
import inquirer6 from "inquirer";
|
|
@@ -9557,16 +9646,16 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9557
9646
|
if (!NAME_PATTERN2.test(name)) {
|
|
9558
9647
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
9559
9648
|
}
|
|
9560
|
-
const servicesDir =
|
|
9561
|
-
if (!
|
|
9649
|
+
const servicesDir = join37(options.cwd, "services");
|
|
9650
|
+
if (!existsSync35(servicesDir)) {
|
|
9562
9651
|
throw new Error(
|
|
9563
9652
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9564
9653
|
);
|
|
9565
9654
|
}
|
|
9566
|
-
const targetDir =
|
|
9567
|
-
if (!
|
|
9568
|
-
const firstParty =
|
|
9569
|
-
if (
|
|
9655
|
+
const targetDir = join37(servicesDir, name);
|
|
9656
|
+
if (!existsSync35(targetDir)) {
|
|
9657
|
+
const firstParty = join37(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
9658
|
+
if (existsSync35(firstParty)) {
|
|
9570
9659
|
throw new Error(
|
|
9571
9660
|
`Plugin '${name}' is a first-party plugin at ${pluginDir(name, "first-party")}/, which is template-owned \u2014 \`biffo core upgrade\` would restore it on the next upgrade. Disable it instead by removing '${name}' from \`enabled_plugins\` in infra/environments/<env>/main.tf and re-applying.`
|
|
9572
9661
|
);
|
|
@@ -9574,9 +9663,9 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9574
9663
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
9575
9664
|
}
|
|
9576
9665
|
const version = readInstalledVersion(targetDir);
|
|
9577
|
-
const modulesDir =
|
|
9666
|
+
const modulesDir = join37(options.cwd, "modules", "plugins", name);
|
|
9578
9667
|
const stagePaths = [`services/${name}`];
|
|
9579
|
-
if (
|
|
9668
|
+
if (existsSync35(modulesDir)) {
|
|
9580
9669
|
stagePaths.push(`modules/plugins/${name}`);
|
|
9581
9670
|
}
|
|
9582
9671
|
if (options.dryRun) {
|
|
@@ -9596,7 +9685,7 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9596
9685
|
`${options.cwd} is not a git repository \u2014 biffo plugin uninstall must be run from a Biffo project checkout.`
|
|
9597
9686
|
);
|
|
9598
9687
|
}
|
|
9599
|
-
if (
|
|
9688
|
+
if (existsSync35(modulesDir)) {
|
|
9600
9689
|
const refs = findPluginModuleReferences(options.cwd, name).filter(
|
|
9601
9690
|
(r) => !r.file.endsWith(`/${GENERATED_TF_FILE}`) && r.file !== GENERATED_TF_FILE
|
|
9602
9691
|
);
|
|
@@ -9611,7 +9700,7 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
9611
9700
|
}
|
|
9612
9701
|
rmSync9(targetDir, { recursive: true, force: true });
|
|
9613
9702
|
log.success(`Removed services/${name}/`);
|
|
9614
|
-
if (
|
|
9703
|
+
if (existsSync35(modulesDir)) {
|
|
9615
9704
|
rmSync9(modulesDir, { recursive: true, force: true });
|
|
9616
9705
|
log.success(`Removed modules/plugins/${name}/`);
|
|
9617
9706
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -9646,17 +9735,17 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
9646
9735
|
"Any tables this plugin created remain in the database, and its migration file at services/api/migrations/versions/ is NOT removed (it is a permanent historical record \u2014 see notes). Dropping tables, if desired, requires a manual Alembic migration written against the Core API."
|
|
9647
9736
|
);
|
|
9648
9737
|
}
|
|
9649
|
-
if (
|
|
9738
|
+
if (existsSync35(join37(options.cwd, pluginSeedImportDir(name)))) {
|
|
9650
9739
|
log.warn(
|
|
9651
9740
|
`${pluginSeedImportDir(name)}/ (this plugin's vendored baseline-row seed, biffo-template#1554) was NOT removed either, for the same reason \u2014 see notes. Delete it by hand if you are certain the rows it applied should go too, but note nothing drops rows already applied to the database; that still needs a manual migration.`
|
|
9652
9741
|
);
|
|
9653
9742
|
}
|
|
9654
9743
|
}
|
|
9655
9744
|
function readInstalledVersion(targetDir) {
|
|
9656
|
-
const manifestPath =
|
|
9657
|
-
if (!
|
|
9745
|
+
const manifestPath = join37(targetDir, "biffo.plugin.json");
|
|
9746
|
+
if (!existsSync35(manifestPath)) return void 0;
|
|
9658
9747
|
try {
|
|
9659
|
-
return validateManifest(JSON.parse(
|
|
9748
|
+
return validateManifest(JSON.parse(readFileSync26(manifestPath, "utf8"))).version;
|
|
9660
9749
|
} catch {
|
|
9661
9750
|
return void 0;
|
|
9662
9751
|
}
|
|
@@ -9689,8 +9778,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
9689
9778
|
}
|
|
9690
9779
|
|
|
9691
9780
|
// src/commands/plugin-upgrade.ts
|
|
9692
|
-
import { cpSync as cpSync6, existsSync as
|
|
9693
|
-
import { join as
|
|
9781
|
+
import { cpSync as cpSync6, existsSync as existsSync36, mkdirSync as mkdirSync12, readFileSync as readFileSync27, rmSync as rmSync10 } from "fs";
|
|
9782
|
+
import { join as join38, relative as relative7, resolve as resolve17 } from "path";
|
|
9694
9783
|
import chalk19 from "chalk";
|
|
9695
9784
|
import { Command as Command20 } from "commander";
|
|
9696
9785
|
import { execa as execa7 } from "execa";
|
|
@@ -9738,8 +9827,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9738
9827
|
`Nothing to upgrade. Pass a registry target (e.g. \`biffo plugin upgrade acme-crm@1.1\`) or a local checkout to refresh from (\`biffo plugin upgrade --local ../acme-crm\`).`
|
|
9739
9828
|
);
|
|
9740
9829
|
}
|
|
9741
|
-
const servicesDir =
|
|
9742
|
-
if (!
|
|
9830
|
+
const servicesDir = join38(options.cwd, "services");
|
|
9831
|
+
if (!existsSync36(servicesDir)) {
|
|
9743
9832
|
throw new Error(
|
|
9744
9833
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9745
9834
|
);
|
|
@@ -9748,8 +9837,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9748
9837
|
return runLocalPluginRefresh(options.local, options, deps);
|
|
9749
9838
|
}
|
|
9750
9839
|
const { name, minor } = parsePluginTarget(target);
|
|
9751
|
-
const targetDir =
|
|
9752
|
-
if (!
|
|
9840
|
+
const targetDir = join38(servicesDir, name);
|
|
9841
|
+
if (!existsSync36(targetDir)) {
|
|
9753
9842
|
throw new Error(
|
|
9754
9843
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
9755
9844
|
);
|
|
@@ -9763,7 +9852,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9763
9852
|
`Plugin declares required_core_version '${entry.required_core_version}'. The CLI cannot verify this against your deployment \u2014 the Core API exposes no version endpoint and services/api/pyproject.toml's version is a static placeholder, not a real release marker. Confirm compatibility yourself before deploying.`
|
|
9764
9853
|
);
|
|
9765
9854
|
}
|
|
9766
|
-
const modulesDir =
|
|
9855
|
+
const modulesDir = join38(options.cwd, "modules", "plugins", entry.name);
|
|
9767
9856
|
if (options.dryRun) {
|
|
9768
9857
|
printDryRun6(entry, currentVersion);
|
|
9769
9858
|
return;
|
|
@@ -9791,7 +9880,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9791
9880
|
log.success(
|
|
9792
9881
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9793
9882
|
);
|
|
9794
|
-
if (!
|
|
9883
|
+
if (!existsSync36(join38(tmpDir, "terraform"))) {
|
|
9795
9884
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, entry.name);
|
|
9796
9885
|
}
|
|
9797
9886
|
const previousProvenance = readProvenance(targetDir);
|
|
@@ -9808,11 +9897,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9808
9897
|
applyWorkspaceSources(targetDir, options.cwd, `services/${entry.name}`);
|
|
9809
9898
|
const newPyproject = readPyprojectIfPresent(targetDir);
|
|
9810
9899
|
const stagePaths = [`services/${entry.name}`];
|
|
9811
|
-
if (
|
|
9900
|
+
if (existsSync36(modulesDir)) {
|
|
9812
9901
|
rmSync10(modulesDir, { recursive: true, force: true });
|
|
9813
9902
|
}
|
|
9814
|
-
const tfSourceDir =
|
|
9815
|
-
if (
|
|
9903
|
+
const tfSourceDir = join38(targetDir, "terraform");
|
|
9904
|
+
if (existsSync36(tfSourceDir)) {
|
|
9816
9905
|
mkdirSync12(modulesDir, { recursive: true });
|
|
9817
9906
|
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
9818
9907
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
@@ -9873,16 +9962,16 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9873
9962
|
async function runLocalPluginRefresh(localPath, options, deps) {
|
|
9874
9963
|
const source = resolveLocalPlugin(localPath);
|
|
9875
9964
|
log.success(`Resolved ${source.name}@${source.version} from ${source.origin}`);
|
|
9876
|
-
const servicesDir =
|
|
9877
|
-
const targetDir =
|
|
9878
|
-
if (!
|
|
9965
|
+
const servicesDir = join38(options.cwd, "services");
|
|
9966
|
+
const targetDir = join38(servicesDir, source.name);
|
|
9967
|
+
if (!existsSync36(targetDir)) {
|
|
9879
9968
|
throw new Error(
|
|
9880
9969
|
`Plugin '${source.name}' is not installed at services/${source.name}/. Use 'biffo plugin install --local ${localPath}' instead.`
|
|
9881
9970
|
);
|
|
9882
9971
|
}
|
|
9883
9972
|
const inTreeSource = resolve17(source.sourceDir) === resolve17(targetDir);
|
|
9884
9973
|
const currentVersion = readInstalledVersion2(targetDir);
|
|
9885
|
-
const modulesDir =
|
|
9974
|
+
const modulesDir = join38(options.cwd, "modules", "plugins", source.name);
|
|
9886
9975
|
if (options.dryRun) {
|
|
9887
9976
|
printLocalDryRun(source, currentVersion, inTreeSource);
|
|
9888
9977
|
return;
|
|
@@ -9905,7 +9994,7 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9905
9994
|
log.success(
|
|
9906
9995
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9907
9996
|
);
|
|
9908
|
-
if (!
|
|
9997
|
+
if (!existsSync36(join38(source.sourceDir, "terraform"))) {
|
|
9909
9998
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, source.name);
|
|
9910
9999
|
}
|
|
9911
10000
|
const previousProvenance = readProvenance(targetDir);
|
|
@@ -9925,11 +10014,11 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9925
10014
|
applyWorkspaceSources(targetDir, options.cwd, `services/${source.name}`);
|
|
9926
10015
|
const newPyproject = readPyprojectIfPresent(targetDir);
|
|
9927
10016
|
const stagePaths = [`services/${source.name}`];
|
|
9928
|
-
if (
|
|
10017
|
+
if (existsSync36(modulesDir)) {
|
|
9929
10018
|
rmSync10(modulesDir, { recursive: true, force: true });
|
|
9930
10019
|
}
|
|
9931
|
-
const tfSourceDir =
|
|
9932
|
-
if (
|
|
10020
|
+
const tfSourceDir = join38(targetDir, "terraform");
|
|
10021
|
+
if (existsSync36(tfSourceDir)) {
|
|
9933
10022
|
mkdirSync12(modulesDir, { recursive: true });
|
|
9934
10023
|
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
9935
10024
|
stagePaths.push(`modules/plugins/${source.name}`);
|
|
@@ -9993,7 +10082,7 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9993
10082
|
}
|
|
9994
10083
|
}
|
|
9995
10084
|
function refuseIfModuleStillReferenced(cwd, modulesDir, name) {
|
|
9996
|
-
if (!
|
|
10085
|
+
if (!existsSync36(modulesDir)) return;
|
|
9997
10086
|
const refs = findPluginModuleReferences(cwd, name);
|
|
9998
10087
|
if (refs.length === 0) return;
|
|
9999
10088
|
const refList = refs.map((r) => ` ${r.file}:${r.line} ${r.text}`).join("\n");
|
|
@@ -10031,8 +10120,8 @@ var defaultRunCommand2 = async (command, cwd) => {
|
|
|
10031
10120
|
}
|
|
10032
10121
|
};
|
|
10033
10122
|
function readPyprojectIfPresent(targetDir) {
|
|
10034
|
-
const path =
|
|
10035
|
-
return
|
|
10123
|
+
const path = join38(targetDir, "pyproject.toml");
|
|
10124
|
+
return existsSync36(path) ? readFileSync27(path, "utf8") : null;
|
|
10036
10125
|
}
|
|
10037
10126
|
function dependenciesChanged(before, after) {
|
|
10038
10127
|
if (before === after) return false;
|
|
@@ -10060,10 +10149,10 @@ function tomlTableBody(text, header) {
|
|
|
10060
10149
|
return nextHeader ? rest.slice(0, nextHeader.index) : rest;
|
|
10061
10150
|
}
|
|
10062
10151
|
function readInstalledVersion2(targetDir) {
|
|
10063
|
-
const manifestPath =
|
|
10064
|
-
if (!
|
|
10152
|
+
const manifestPath = join38(targetDir, "biffo.plugin.json");
|
|
10153
|
+
if (!existsSync36(manifestPath)) return void 0;
|
|
10065
10154
|
try {
|
|
10066
|
-
return validateManifest(JSON.parse(
|
|
10155
|
+
return validateManifest(JSON.parse(readFileSync27(manifestPath, "utf8"))).version;
|
|
10067
10156
|
} catch {
|
|
10068
10157
|
return void 0;
|
|
10069
10158
|
}
|
|
@@ -10146,7 +10235,7 @@ pluginCommand.addCommand(pluginStalenessCommand);
|
|
|
10146
10235
|
import { Command as Command23 } from "commander";
|
|
10147
10236
|
|
|
10148
10237
|
// src/commands/sibling-check-identity.ts
|
|
10149
|
-
import { existsSync as
|
|
10238
|
+
import { existsSync as existsSync37, readFileSync as readFileSync28 } from "fs";
|
|
10150
10239
|
import { resolve as resolve18 } from "path";
|
|
10151
10240
|
import chalk20 from "chalk";
|
|
10152
10241
|
import { Command as Command22 } from "commander";
|
|
@@ -10340,7 +10429,7 @@ async function fetchPublishedIdentity(portalUrl) {
|
|
|
10340
10429
|
}
|
|
10341
10430
|
async function resolveConfig4(options) {
|
|
10342
10431
|
if (options.config) {
|
|
10343
|
-
const raw = JSON.parse(
|
|
10432
|
+
const raw = JSON.parse(readFileSync28(resolve18(options.config), "utf8"));
|
|
10344
10433
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
10345
10434
|
if (!result.success) {
|
|
10346
10435
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -10360,8 +10449,8 @@ async function resolveConfig4(options) {
|
|
|
10360
10449
|
return cfg;
|
|
10361
10450
|
}
|
|
10362
10451
|
const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
|
|
10363
|
-
if (
|
|
10364
|
-
const raw = JSON.parse(
|
|
10452
|
+
if (existsSync37(localConfigPath)) {
|
|
10453
|
+
const raw = JSON.parse(readFileSync28(localConfigPath, "utf8"));
|
|
10365
10454
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
10366
10455
|
if (result.success) return result.data;
|
|
10367
10456
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -10407,21 +10496,21 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
|
10407
10496
|
import { Command as Command24 } from "commander";
|
|
10408
10497
|
|
|
10409
10498
|
// src/scripts/check-adr-numbering.ts
|
|
10410
|
-
import { existsSync as
|
|
10411
|
-
import { join as
|
|
10499
|
+
import { existsSync as existsSync39 } from "fs";
|
|
10500
|
+
import { join as join40 } from "path";
|
|
10412
10501
|
import { execa as execa8 } from "execa";
|
|
10413
10502
|
|
|
10414
10503
|
// src/lib/adr-numbering-guard.ts
|
|
10415
|
-
import { existsSync as
|
|
10416
|
-
import { join as
|
|
10504
|
+
import { existsSync as existsSync38, readdirSync as readdirSync15, readFileSync as readFileSync29 } from "fs";
|
|
10505
|
+
import { join as join39 } from "path";
|
|
10417
10506
|
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
10418
10507
|
var ALLOWLIST_FILENAME = ".numbering-allowlist";
|
|
10419
10508
|
var TEMPLATE_ADR_RESERVED_UPTO = "0099";
|
|
10420
10509
|
function readAdrNumberingAllowlist(adrDir) {
|
|
10421
|
-
const path =
|
|
10422
|
-
if (!
|
|
10510
|
+
const path = join39(adrDir, ALLOWLIST_FILENAME);
|
|
10511
|
+
if (!existsSync38(path)) return /* @__PURE__ */ new Set();
|
|
10423
10512
|
const numbers = /* @__PURE__ */ new Set();
|
|
10424
|
-
for (const rawLine of
|
|
10513
|
+
for (const rawLine of readFileSync29(path, "utf8").split("\n")) {
|
|
10425
10514
|
const line = rawLine.split("#")[0].trim();
|
|
10426
10515
|
if (line) numbers.add(line);
|
|
10427
10516
|
}
|
|
@@ -10429,7 +10518,7 @@ function readAdrNumberingAllowlist(adrDir) {
|
|
|
10429
10518
|
}
|
|
10430
10519
|
function adrNumbersIn(adrDir) {
|
|
10431
10520
|
const claims = /* @__PURE__ */ new Map();
|
|
10432
|
-
if (!
|
|
10521
|
+
if (!existsSync38(adrDir)) return claims;
|
|
10433
10522
|
for (const entry of readdirSync15(adrDir).sort()) {
|
|
10434
10523
|
const match = ADR_FILENAME.exec(entry);
|
|
10435
10524
|
if (!match) continue;
|
|
@@ -10484,8 +10573,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
|
|
|
10484
10573
|
// src/scripts/check-adr-numbering.ts
|
|
10485
10574
|
async function runAdrNumberingCheck() {
|
|
10486
10575
|
const root = (await execa8("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10487
|
-
const adrDir =
|
|
10488
|
-
if (!
|
|
10576
|
+
const adrDir = join40(root, "docs", "ADR");
|
|
10577
|
+
if (!existsSync39(adrDir)) {
|
|
10489
10578
|
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
10490
10579
|
return;
|
|
10491
10580
|
}
|
|
@@ -10786,18 +10875,18 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
10786
10875
|
import { execa as execa10 } from "execa";
|
|
10787
10876
|
|
|
10788
10877
|
// src/lib/claim-invocation-parity.ts
|
|
10789
|
-
import { existsSync as
|
|
10790
|
-
import { join as
|
|
10878
|
+
import { existsSync as existsSync40, readFileSync as readFileSync30, readdirSync as readdirSync16 } from "fs";
|
|
10879
|
+
import { join as join41 } from "path";
|
|
10791
10880
|
function distributedAgentsDocs(root) {
|
|
10792
10881
|
const docs = [];
|
|
10793
|
-
const own =
|
|
10794
|
-
if (
|
|
10795
|
-
const skeletons =
|
|
10796
|
-
if (
|
|
10882
|
+
const own = join41(root, "AGENTS.md");
|
|
10883
|
+
if (existsSync40(own)) docs.push({ path: "AGENTS.md", text: readFileSync30(own, "utf8") });
|
|
10884
|
+
const skeletons = join41(root, "_skeletons");
|
|
10885
|
+
if (existsSync40(skeletons)) {
|
|
10797
10886
|
for (const name of readdirSync16(skeletons).sort()) {
|
|
10798
|
-
const abs =
|
|
10799
|
-
if (!
|
|
10800
|
-
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text:
|
|
10887
|
+
const abs = join41(skeletons, name, "AGENTS.md");
|
|
10888
|
+
if (!existsSync40(abs)) continue;
|
|
10889
|
+
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync30(abs, "utf8") });
|
|
10801
10890
|
}
|
|
10802
10891
|
}
|
|
10803
10892
|
return docs;
|
|
@@ -10929,13 +11018,13 @@ async function runClaimInvocationCheck() {
|
|
|
10929
11018
|
}
|
|
10930
11019
|
|
|
10931
11020
|
// src/scripts/check-codeql-suppression.ts
|
|
10932
|
-
import { existsSync as
|
|
10933
|
-
import { join as
|
|
11021
|
+
import { existsSync as existsSync41 } from "fs";
|
|
11022
|
+
import { join as join43, relative as relative8 } from "path";
|
|
10934
11023
|
import { execa as execa11 } from "execa";
|
|
10935
11024
|
|
|
10936
11025
|
// src/lib/codeql-suppression-guard.ts
|
|
10937
|
-
import { readdirSync as readdirSync17, readFileSync as
|
|
10938
|
-
import { join as
|
|
11026
|
+
import { readdirSync as readdirSync17, readFileSync as readFileSync31, statSync as statSync9 } from "fs";
|
|
11027
|
+
import { join as join42 } from "path";
|
|
10939
11028
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10940
11029
|
".git",
|
|
10941
11030
|
".worktrees",
|
|
@@ -10966,7 +11055,7 @@ function walkSourceFiles(root) {
|
|
|
10966
11055
|
return;
|
|
10967
11056
|
}
|
|
10968
11057
|
for (const entry of entries) {
|
|
10969
|
-
const p =
|
|
11058
|
+
const p = join42(dir, entry);
|
|
10970
11059
|
let st;
|
|
10971
11060
|
try {
|
|
10972
11061
|
st = statSync9(p);
|
|
@@ -10992,7 +11081,7 @@ function countSourceFiles(root) {
|
|
|
10992
11081
|
function sweepCodeqlSuppressionComments(root) {
|
|
10993
11082
|
const hits = [];
|
|
10994
11083
|
for (const path of walkSourceFiles(root)) {
|
|
10995
|
-
const text =
|
|
11084
|
+
const text = readFileSync31(path, "utf8");
|
|
10996
11085
|
for (const line of findCodeqlSuppressionComments(text)) {
|
|
10997
11086
|
hits.push({ path, line, text: text.split("\n")[line - 1] ?? "" });
|
|
10998
11087
|
}
|
|
@@ -11003,8 +11092,8 @@ function sweepCodeqlSuppressionComments(root) {
|
|
|
11003
11092
|
// src/scripts/check-codeql-suppression.ts
|
|
11004
11093
|
async function runCodeqlSuppressionCheck() {
|
|
11005
11094
|
const root = (await execa11("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11006
|
-
const scanRoot =
|
|
11007
|
-
if (!
|
|
11095
|
+
const scanRoot = join43(root, "cli", "src");
|
|
11096
|
+
if (!existsSync41(scanRoot)) {
|
|
11008
11097
|
console.log(
|
|
11009
11098
|
"\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
|
|
11010
11099
|
);
|
|
@@ -11031,8 +11120,8 @@ async function runCodeqlSuppressionCheck() {
|
|
|
11031
11120
|
import { execa as execa12 } from "execa";
|
|
11032
11121
|
|
|
11033
11122
|
// src/lib/cognito-invite-template-guard.ts
|
|
11034
|
-
import { readdirSync as readdirSync18, readFileSync as
|
|
11035
|
-
import { join as
|
|
11123
|
+
import { readdirSync as readdirSync18, readFileSync as readFileSync32, statSync as statSync10 } from "fs";
|
|
11124
|
+
import { join as join44 } from "path";
|
|
11036
11125
|
var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
|
|
11037
11126
|
var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
|
|
11038
11127
|
var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
|
|
@@ -11115,7 +11204,7 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
11115
11204
|
}
|
|
11116
11205
|
for (const entry of entries) {
|
|
11117
11206
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees") continue;
|
|
11118
|
-
const full =
|
|
11207
|
+
const full = join44(dir, entry);
|
|
11119
11208
|
const rel = `${relative11}/${entry}`;
|
|
11120
11209
|
if (statSync10(full).isDirectory()) {
|
|
11121
11210
|
walk2(full, rel);
|
|
@@ -11124,12 +11213,12 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
11124
11213
|
}
|
|
11125
11214
|
}
|
|
11126
11215
|
};
|
|
11127
|
-
walk2(
|
|
11216
|
+
walk2(join44(repoRoot, "modules"), "modules");
|
|
11128
11217
|
return found.sort();
|
|
11129
11218
|
}
|
|
11130
11219
|
function checkCognitoInviteTemplates(repoRoot) {
|
|
11131
11220
|
return findModuleTerraformFiles(repoRoot).flatMap(
|
|
11132
|
-
(file) => checkInviteTemplateSource(file,
|
|
11221
|
+
(file) => checkInviteTemplateSource(file, readFileSync32(join44(repoRoot, file), "utf8"))
|
|
11133
11222
|
);
|
|
11134
11223
|
}
|
|
11135
11224
|
|
|
@@ -11157,12 +11246,12 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
11157
11246
|
}
|
|
11158
11247
|
|
|
11159
11248
|
// src/scripts/check-core-direct-paths.ts
|
|
11160
|
-
import { join as
|
|
11249
|
+
import { join as join46 } from "path";
|
|
11161
11250
|
import { execa as execa13 } from "execa";
|
|
11162
11251
|
|
|
11163
11252
|
// src/lib/core-direct-paths-audit.ts
|
|
11164
|
-
import { existsSync as
|
|
11165
|
-
import { join as
|
|
11253
|
+
import { existsSync as existsSync42, readFileSync as readFileSync33, readdirSync as readdirSync19, statSync as statSync11 } from "fs";
|
|
11254
|
+
import { join as join45 } from "path";
|
|
11166
11255
|
var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
|
|
11167
11256
|
var API_ROUTE_PREFIX = "/api/v1";
|
|
11168
11257
|
var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
|
|
@@ -11326,7 +11415,7 @@ function walkFiles(root, accept, skipDir) {
|
|
|
11326
11415
|
return;
|
|
11327
11416
|
}
|
|
11328
11417
|
for (const entry of entries) {
|
|
11329
|
-
const p =
|
|
11418
|
+
const p = join45(dir, entry);
|
|
11330
11419
|
let st;
|
|
11331
11420
|
try {
|
|
11332
11421
|
st = statSync11(p);
|
|
@@ -11356,7 +11445,7 @@ function auditFrontendExtraction(frontendSrcDir, externalBases = EXTERNAL_BASE_I
|
|
|
11356
11445
|
const extracted = [];
|
|
11357
11446
|
let rawTotal = 0;
|
|
11358
11447
|
for (const file of files) {
|
|
11359
|
-
const text =
|
|
11448
|
+
const text = readFileSync33(file, "utf8");
|
|
11360
11449
|
rawTotal += countRawExternalOccurrences(text, externalBases);
|
|
11361
11450
|
extracted.push(...extractCoreDirectPaths(text, file, externalBases));
|
|
11362
11451
|
}
|
|
@@ -11405,7 +11494,7 @@ function auditCoreRouteExtraction(apiSrcDir) {
|
|
|
11405
11494
|
const prefixSet = /* @__PURE__ */ new Set();
|
|
11406
11495
|
let rawApiRouterCount = 0;
|
|
11407
11496
|
for (const file of files) {
|
|
11408
|
-
const text =
|
|
11497
|
+
const text = readFileSync33(file, "utf8");
|
|
11409
11498
|
const extraction = extractCoreRoutePrefixes(text);
|
|
11410
11499
|
rawApiRouterCount += extraction.rawApiRouterCount;
|
|
11411
11500
|
for (const p of extraction.prefixes) prefixSet.add(normalizePrefix(p));
|
|
@@ -11421,10 +11510,10 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
|
|
|
11421
11510
|
}
|
|
11422
11511
|
function resolveSiblingCoreSrc(params) {
|
|
11423
11512
|
const { estateDir, sibling } = params;
|
|
11424
|
-
const configPath =
|
|
11513
|
+
const configPath = join45(estateDir, sibling, "biffo.sibling.json");
|
|
11425
11514
|
let raw;
|
|
11426
11515
|
try {
|
|
11427
|
-
raw =
|
|
11516
|
+
raw = readFileSync33(configPath, "utf8");
|
|
11428
11517
|
} catch (err) {
|
|
11429
11518
|
throw new Error(
|
|
11430
11519
|
`cannot resolve ${sibling}'s core: ${configPath} does not exist or is unreadable (${err.message}) -- refusing to guess which core serves this sibling.`
|
|
@@ -11444,8 +11533,8 @@ function resolveSiblingCoreSrc(params) {
|
|
|
11444
11533
|
`cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
|
|
11445
11534
|
);
|
|
11446
11535
|
}
|
|
11447
|
-
const coreApiSrcDir =
|
|
11448
|
-
if (!
|
|
11536
|
+
const coreApiSrcDir = join45(estateDir, coreProject, "services", "api", "src");
|
|
11537
|
+
if (!existsSync42(coreApiSrcDir)) {
|
|
11449
11538
|
throw new Error(
|
|
11450
11539
|
`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.`
|
|
11451
11540
|
);
|
|
@@ -11488,7 +11577,7 @@ function auditSiblingCoreDirectPaths(params) {
|
|
|
11488
11577
|
async function runCoreDirectPathsCheck(opts = {}) {
|
|
11489
11578
|
const root = (await execa13("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11490
11579
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
11491
|
-
const frontendSrcDir = opts.frontendSrc ??
|
|
11580
|
+
const frontendSrcDir = opts.frontendSrc ?? join46(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
11492
11581
|
let coreApiSrcDir;
|
|
11493
11582
|
let coreProject = null;
|
|
11494
11583
|
if (opts.coreSrc) {
|
|
@@ -11504,7 +11593,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
11504
11593
|
coreApiSrcDir = resolution.coreApiSrcDir;
|
|
11505
11594
|
coreProject = resolution.coreProject;
|
|
11506
11595
|
} else {
|
|
11507
|
-
coreApiSrcDir =
|
|
11596
|
+
coreApiSrcDir = join46(root, "services", "api", "src");
|
|
11508
11597
|
}
|
|
11509
11598
|
const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
|
|
11510
11599
|
console.log(
|
|
@@ -11570,8 +11659,8 @@ async function runOwnershipCheck(argv) {
|
|
|
11570
11659
|
const { stdout } = await execa14("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
11571
11660
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
11572
11661
|
if (messageFile) {
|
|
11573
|
-
const { readFileSync:
|
|
11574
|
-
if (
|
|
11662
|
+
const { readFileSync: readFileSync45, existsSync: existsSync52 } = await import("fs");
|
|
11663
|
+
if (existsSync52(messageFile)) commitMessage = readFileSync45(messageFile, "utf8");
|
|
11575
11664
|
}
|
|
11576
11665
|
} else {
|
|
11577
11666
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -11675,8 +11764,8 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
11675
11764
|
import { execa as execa15 } from "execa";
|
|
11676
11765
|
|
|
11677
11766
|
// src/lib/eventbridge-log-permission-guard.ts
|
|
11678
|
-
import { readFileSync as
|
|
11679
|
-
import { join as
|
|
11767
|
+
import { readFileSync as readFileSync34, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
|
|
11768
|
+
import { join as join47 } from "path";
|
|
11680
11769
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
11681
11770
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
11682
11771
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
@@ -11753,7 +11842,7 @@ function walkTerraformFiles(root) {
|
|
|
11753
11842
|
return;
|
|
11754
11843
|
}
|
|
11755
11844
|
for (const entry of entries) {
|
|
11756
|
-
const p =
|
|
11845
|
+
const p = join47(dir, entry);
|
|
11757
11846
|
let st;
|
|
11758
11847
|
try {
|
|
11759
11848
|
st = statSync12(p);
|
|
@@ -11796,7 +11885,7 @@ function auditEventBridgeLogPermissions(root) {
|
|
|
11796
11885
|
let rawEventTargetCount = 0;
|
|
11797
11886
|
let rawLogPolicyCount = 0;
|
|
11798
11887
|
for (const file of files) {
|
|
11799
|
-
const text =
|
|
11888
|
+
const text = readFileSync34(file, "utf8");
|
|
11800
11889
|
rawEventTargetCount += countRawResourceDeclarations(text, EVENT_TARGET_TYPE);
|
|
11801
11890
|
rawLogPolicyCount += countRawResourceDeclarations(text, LOG_RESOURCE_POLICY_TYPE);
|
|
11802
11891
|
eventTargetBlocks.push(...findResourceBlocks(text, file, EVENT_TARGET_TYPE));
|
|
@@ -11889,12 +11978,12 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
11889
11978
|
import { execa as execa16 } from "execa";
|
|
11890
11979
|
|
|
11891
11980
|
// src/lib/lambda-output-guard.ts
|
|
11892
|
-
import { readFileSync as
|
|
11893
|
-
import { join as
|
|
11981
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
11982
|
+
import { join as join49 } from "path";
|
|
11894
11983
|
|
|
11895
11984
|
// src/lib/terraform-input-guard.ts
|
|
11896
|
-
import { existsSync as
|
|
11897
|
-
import { join as
|
|
11985
|
+
import { existsSync as existsSync43, readdirSync as readdirSync21, readFileSync as readFileSync35, statSync as statSync13 } from "fs";
|
|
11986
|
+
import { join as join48 } from "path";
|
|
11898
11987
|
var GUARDED_SUBCOMMANDS = [
|
|
11899
11988
|
"init",
|
|
11900
11989
|
"plan",
|
|
@@ -11908,7 +11997,7 @@ function stripComments2(source) {
|
|
|
11908
11997
|
return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
|
|
11909
11998
|
}
|
|
11910
11999
|
function vendoredPluginServiceDirs(repoRoot) {
|
|
11911
|
-
const servicesDir =
|
|
12000
|
+
const servicesDir = join48(repoRoot, "services");
|
|
11912
12001
|
const result = /* @__PURE__ */ new Set();
|
|
11913
12002
|
let entries;
|
|
11914
12003
|
try {
|
|
@@ -11917,9 +12006,9 @@ function vendoredPluginServiceDirs(repoRoot) {
|
|
|
11917
12006
|
return result;
|
|
11918
12007
|
}
|
|
11919
12008
|
for (const entry of entries) {
|
|
11920
|
-
const full =
|
|
11921
|
-
if (!
|
|
11922
|
-
if (
|
|
12009
|
+
const full = join48(servicesDir, entry);
|
|
12010
|
+
if (!existsSync43(full) || !statSync13(full).isDirectory()) continue;
|
|
12011
|
+
if (existsSync43(join48(full, "biffo.plugin.json"))) {
|
|
11923
12012
|
result.add(entry);
|
|
11924
12013
|
}
|
|
11925
12014
|
}
|
|
@@ -11940,7 +12029,7 @@ function findWorkflowFiles(repoRoot) {
|
|
|
11940
12029
|
if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
|
|
11941
12030
|
continue;
|
|
11942
12031
|
}
|
|
11943
|
-
const full =
|
|
12032
|
+
const full = join48(dir, entry);
|
|
11944
12033
|
const rel = relative11 ? `${relative11}/${entry}` : entry;
|
|
11945
12034
|
if (statSync13(full).isDirectory()) {
|
|
11946
12035
|
walk2(full, rel);
|
|
@@ -11984,7 +12073,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
11984
12073
|
}
|
|
11985
12074
|
function checkTerraformInput(repoRoot) {
|
|
11986
12075
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
11987
|
-
(file) => checkWorkflowSource(file,
|
|
12076
|
+
(file) => checkWorkflowSource(file, readFileSync35(join48(repoRoot, file), "utf8"))
|
|
11988
12077
|
);
|
|
11989
12078
|
}
|
|
11990
12079
|
|
|
@@ -12042,7 +12131,7 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
12042
12131
|
}
|
|
12043
12132
|
function checkLambdaOutput(repoRoot) {
|
|
12044
12133
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
12045
|
-
(file) => checkWorkflowSource2(file,
|
|
12134
|
+
(file) => checkWorkflowSource2(file, readFileSync36(join49(repoRoot, file), "utf8"))
|
|
12046
12135
|
);
|
|
12047
12136
|
}
|
|
12048
12137
|
|
|
@@ -12070,8 +12159,8 @@ async function runLambdaOutputCheck() {
|
|
|
12070
12159
|
}
|
|
12071
12160
|
|
|
12072
12161
|
// src/scripts/check-pipe-trap.ts
|
|
12073
|
-
import { readFileSync as
|
|
12074
|
-
import { join as
|
|
12162
|
+
import { readFileSync as readFileSync37, readdirSync as readdirSync22 } from "fs";
|
|
12163
|
+
import { join as join50, relative as relative9 } from "path";
|
|
12075
12164
|
import { execa as execa17 } from "execa";
|
|
12076
12165
|
|
|
12077
12166
|
// src/lib/pipe-trap-guard.ts
|
|
@@ -12168,7 +12257,7 @@ function findPipeTraps(source) {
|
|
|
12168
12257
|
function shellFiles(root) {
|
|
12169
12258
|
const out = [];
|
|
12170
12259
|
for (const dir of ["scripts", ".githooks"]) {
|
|
12171
|
-
const full =
|
|
12260
|
+
const full = join50(root, dir);
|
|
12172
12261
|
let entries;
|
|
12173
12262
|
try {
|
|
12174
12263
|
entries = readdirSync22(full, { withFileTypes: true });
|
|
@@ -12178,7 +12267,7 @@ function shellFiles(root) {
|
|
|
12178
12267
|
for (const entry of entries) {
|
|
12179
12268
|
if (!entry.isFile()) continue;
|
|
12180
12269
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
12181
|
-
out.push(
|
|
12270
|
+
out.push(join50(full, entry.name));
|
|
12182
12271
|
}
|
|
12183
12272
|
}
|
|
12184
12273
|
return out;
|
|
@@ -12194,7 +12283,7 @@ async function runPipeTrapCheck() {
|
|
|
12194
12283
|
process.exit(1);
|
|
12195
12284
|
}
|
|
12196
12285
|
const findings = files.flatMap(
|
|
12197
|
-
(file) => findPipeTraps(
|
|
12286
|
+
(file) => findPipeTraps(readFileSync37(file, "utf8")).map(
|
|
12198
12287
|
(t) => `${relative9(root, file)}:${t.line} ${t.text}
|
|
12199
12288
|
${t.reason}`
|
|
12200
12289
|
)
|
|
@@ -12214,8 +12303,8 @@ async function runPipeTrapCheck() {
|
|
|
12214
12303
|
import { execa as execa18 } from "execa";
|
|
12215
12304
|
|
|
12216
12305
|
// src/lib/plugin-allowlist-convention.ts
|
|
12217
|
-
import { readFileSync as
|
|
12218
|
-
import { join as
|
|
12306
|
+
import { readFileSync as readFileSync38 } from "fs";
|
|
12307
|
+
import { join as join51 } from "path";
|
|
12219
12308
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
12220
12309
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
12221
12310
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -12226,7 +12315,7 @@ var PLUGIN = "<plugin>";
|
|
|
12226
12315
|
var ACCOUNT = "<account>";
|
|
12227
12316
|
function read(repoRoot, relative11) {
|
|
12228
12317
|
try {
|
|
12229
|
-
return
|
|
12318
|
+
return readFileSync38(join51(repoRoot, relative11), "utf8");
|
|
12230
12319
|
} catch {
|
|
12231
12320
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
|
|
12232
12321
|
}
|
|
@@ -12347,33 +12436,33 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
12347
12436
|
}
|
|
12348
12437
|
|
|
12349
12438
|
// src/scripts/check-plugin-collisions.ts
|
|
12350
|
-
import { existsSync as
|
|
12351
|
-
import { join as
|
|
12439
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12440
|
+
import { join as join53 } from "path";
|
|
12352
12441
|
import { execa as execa19 } from "execa";
|
|
12353
12442
|
|
|
12354
12443
|
// src/lib/plugin-collision-guard.ts
|
|
12355
|
-
import { existsSync as
|
|
12356
|
-
import { join as
|
|
12444
|
+
import { existsSync as existsSync44, readdirSync as readdirSync23, statSync as statSync14 } from "fs";
|
|
12445
|
+
import { join as join52 } from "path";
|
|
12357
12446
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
12358
12447
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
12359
12448
|
function subdirectories(dir) {
|
|
12360
|
-
if (!
|
|
12449
|
+
if (!existsSync44(dir)) return [];
|
|
12361
12450
|
return readdirSync23(dir).filter((entry) => {
|
|
12362
12451
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
12363
12452
|
try {
|
|
12364
|
-
return statSync14(
|
|
12453
|
+
return statSync14(join52(dir, entry)).isDirectory();
|
|
12365
12454
|
} catch {
|
|
12366
12455
|
return false;
|
|
12367
12456
|
}
|
|
12368
12457
|
});
|
|
12369
12458
|
}
|
|
12370
12459
|
function regularPackagesOf(pluginDir2) {
|
|
12371
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
12460
|
+
return subdirectories(pluginDir2).filter((name) => existsSync44(join52(pluginDir2, name, "__init__.py"))).sort();
|
|
12372
12461
|
}
|
|
12373
12462
|
function bareTestModulesOf(pluginDir2) {
|
|
12374
|
-
const testsDir =
|
|
12375
|
-
if (!
|
|
12376
|
-
if (
|
|
12463
|
+
const testsDir = join52(pluginDir2, "tests");
|
|
12464
|
+
if (!existsSync44(testsDir)) return [];
|
|
12465
|
+
if (existsSync44(join52(testsDir, "__init__.py"))) return [];
|
|
12377
12466
|
return readdirSync23(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
12378
12467
|
}
|
|
12379
12468
|
function findCollisions(servicesDir, pluginDirs) {
|
|
@@ -12382,7 +12471,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
12382
12471
|
const gather = (kind, namesOf) => {
|
|
12383
12472
|
const claims = /* @__PURE__ */ new Map();
|
|
12384
12473
|
for (const plugin of plugins) {
|
|
12385
|
-
for (const name of namesOf(
|
|
12474
|
+
for (const name of namesOf(join52(servicesDir, plugin))) {
|
|
12386
12475
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
12387
12476
|
}
|
|
12388
12477
|
}
|
|
@@ -12420,8 +12509,8 @@ function formatCollisions(collisions) {
|
|
|
12420
12509
|
// src/scripts/check-plugin-collisions.ts
|
|
12421
12510
|
async function runPluginCollisionCheck() {
|
|
12422
12511
|
const root = (await execa19("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12423
|
-
const servicesDir =
|
|
12424
|
-
if (!
|
|
12512
|
+
const servicesDir = join53(root, "services");
|
|
12513
|
+
if (!existsSync45(servicesDir)) {
|
|
12425
12514
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
12426
12515
|
return;
|
|
12427
12516
|
}
|
|
@@ -12441,8 +12530,8 @@ async function runPluginCollisionCheck() {
|
|
|
12441
12530
|
import { execa as execa20 } from "execa";
|
|
12442
12531
|
|
|
12443
12532
|
// src/lib/plugin-terraform-guard.ts
|
|
12444
|
-
import { existsSync as
|
|
12445
|
-
import { dirname as dirname10, join as
|
|
12533
|
+
import { existsSync as existsSync46, readFileSync as readFileSync39, readdirSync as readdirSync24 } from "fs";
|
|
12534
|
+
import { dirname as dirname10, join as join54, relative as relative10, sep as sep4 } from "path";
|
|
12446
12535
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
12447
12536
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
12448
12537
|
function findPluginManifests(root) {
|
|
@@ -12457,9 +12546,9 @@ function findPluginManifests(root) {
|
|
|
12457
12546
|
for (const entry of entries) {
|
|
12458
12547
|
if (entry.isDirectory()) {
|
|
12459
12548
|
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
12460
|
-
walk2(
|
|
12549
|
+
walk2(join54(dir, entry.name));
|
|
12461
12550
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
12462
|
-
found.push(relative10(root,
|
|
12551
|
+
found.push(relative10(root, join54(dir, entry.name)).split(sep4).join("/"));
|
|
12463
12552
|
}
|
|
12464
12553
|
}
|
|
12465
12554
|
};
|
|
@@ -12469,7 +12558,7 @@ function findPluginManifests(root) {
|
|
|
12469
12558
|
function readSubscriptions(absManifestPath) {
|
|
12470
12559
|
let parsed;
|
|
12471
12560
|
try {
|
|
12472
|
-
parsed = JSON.parse(
|
|
12561
|
+
parsed = JSON.parse(readFileSync39(absManifestPath, "utf8"));
|
|
12473
12562
|
} catch {
|
|
12474
12563
|
return null;
|
|
12475
12564
|
}
|
|
@@ -12484,14 +12573,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
12484
12573
|
}
|
|
12485
12574
|
function checkPluginTerraform(root) {
|
|
12486
12575
|
const violations = [];
|
|
12487
|
-
const coreManifest =
|
|
12576
|
+
const coreManifest = existsSync46(join54(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
12488
12577
|
for (const manifest of findPluginManifests(root)) {
|
|
12489
12578
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
12490
|
-
const absManifest =
|
|
12579
|
+
const absManifest = join54(root, manifest);
|
|
12491
12580
|
const subscriptions = readSubscriptions(absManifest);
|
|
12492
12581
|
if (subscriptions === null) continue;
|
|
12493
12582
|
const pluginDir2 = dirname10(absManifest);
|
|
12494
|
-
if (
|
|
12583
|
+
if (existsSync46(join54(pluginDir2, "terraform"))) continue;
|
|
12495
12584
|
const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
|
|
12496
12585
|
violations.push({
|
|
12497
12586
|
manifest,
|
|
@@ -12522,13 +12611,13 @@ async function runPluginTerraformCheck() {
|
|
|
12522
12611
|
}
|
|
12523
12612
|
|
|
12524
12613
|
// src/scripts/check-plugin-tool-supply.ts
|
|
12525
|
-
import { existsSync as
|
|
12526
|
-
import { join as
|
|
12614
|
+
import { existsSync as existsSync48 } from "fs";
|
|
12615
|
+
import { join as join56 } from "path";
|
|
12527
12616
|
import { execa as execa21 } from "execa";
|
|
12528
12617
|
|
|
12529
12618
|
// src/lib/plugin-tool-supply-audit.ts
|
|
12530
|
-
import { existsSync as
|
|
12531
|
-
import { join as
|
|
12619
|
+
import { existsSync as existsSync47, readFileSync as readFileSync40, readdirSync as readdirSync25, statSync as statSync15 } from "fs";
|
|
12620
|
+
import { join as join55 } from "path";
|
|
12532
12621
|
|
|
12533
12622
|
// src/lib/openrouter-model-snapshot.ts
|
|
12534
12623
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -12945,7 +13034,7 @@ function listDirs(root) {
|
|
|
12945
13034
|
}
|
|
12946
13035
|
return entries.filter((e) => {
|
|
12947
13036
|
try {
|
|
12948
|
-
return statSync15(
|
|
13037
|
+
return statSync15(join55(root, e)).isDirectory();
|
|
12949
13038
|
} catch {
|
|
12950
13039
|
return false;
|
|
12951
13040
|
}
|
|
@@ -12961,7 +13050,7 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
12961
13050
|
return;
|
|
12962
13051
|
}
|
|
12963
13052
|
for (const entry of entries) {
|
|
12964
|
-
const p =
|
|
13053
|
+
const p = join55(dir, entry);
|
|
12965
13054
|
let st;
|
|
12966
13055
|
try {
|
|
12967
13056
|
st = statSync15(p);
|
|
@@ -12987,14 +13076,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
12987
13076
|
);
|
|
12988
13077
|
}
|
|
12989
13078
|
function pluginTerraformFiles(pluginDir2) {
|
|
12990
|
-
const tfDir =
|
|
13079
|
+
const tfDir = join55(pluginDir2, "terraform");
|
|
12991
13080
|
let entries;
|
|
12992
13081
|
try {
|
|
12993
13082
|
entries = readdirSync25(tfDir);
|
|
12994
13083
|
} catch {
|
|
12995
13084
|
return [];
|
|
12996
13085
|
}
|
|
12997
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
13086
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join55(tfDir, e)).sort();
|
|
12998
13087
|
}
|
|
12999
13088
|
function extractManifestTools(manifestText) {
|
|
13000
13089
|
let parsed;
|
|
@@ -13246,8 +13335,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
13246
13335
|
function normalizeModelId(id) {
|
|
13247
13336
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
13248
13337
|
}
|
|
13249
|
-
var CONFIG_PY_PATH =
|
|
13250
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
13338
|
+
var CONFIG_PY_PATH = join55("services", "api", "src", "api", "config.py");
|
|
13339
|
+
var ORCHESTRATION_SCHEMA_PATH = join55(
|
|
13251
13340
|
"services",
|
|
13252
13341
|
"api",
|
|
13253
13342
|
"src",
|
|
@@ -13259,10 +13348,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13259
13348
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
13260
13349
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
13261
13350
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
13262
|
-
const configPath =
|
|
13263
|
-
const orchestrationPath =
|
|
13264
|
-
const configMissing = !
|
|
13265
|
-
const orchestrationSchemaMissing = !
|
|
13351
|
+
const configPath = join55(repoRoot, CONFIG_PY_PATH);
|
|
13352
|
+
const orchestrationPath = join55(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
13353
|
+
const configMissing = !existsSync47(configPath);
|
|
13354
|
+
const orchestrationSchemaMissing = !existsSync47(orchestrationPath);
|
|
13266
13355
|
const knownSet = new Set(knownModelIds);
|
|
13267
13356
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
13268
13357
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -13280,13 +13369,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13280
13369
|
};
|
|
13281
13370
|
let settingsBlind = false;
|
|
13282
13371
|
if (!configMissing) {
|
|
13283
|
-
const settingsFields = extractSettingsModelFields(
|
|
13372
|
+
const settingsFields = extractSettingsModelFields(readFileSync40(configPath, "utf8"));
|
|
13284
13373
|
if (settingsFields.length === 0) settingsBlind = true;
|
|
13285
13374
|
for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
|
|
13286
13375
|
}
|
|
13287
13376
|
let curatedFieldsBlind = false;
|
|
13288
13377
|
if (!orchestrationSchemaMissing) {
|
|
13289
|
-
const curated = extractCuratedModelFields(
|
|
13378
|
+
const curated = extractCuratedModelFields(readFileSync40(orchestrationPath, "utf8"));
|
|
13290
13379
|
if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
|
|
13291
13380
|
curatedFieldsBlind = true;
|
|
13292
13381
|
}
|
|
@@ -13333,7 +13422,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13333
13422
|
function discoverPluginDirs(pluginsRoot) {
|
|
13334
13423
|
return listDirs(pluginsRoot).filter((name) => {
|
|
13335
13424
|
try {
|
|
13336
|
-
return statSync15(
|
|
13425
|
+
return statSync15(join55(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
13337
13426
|
} catch {
|
|
13338
13427
|
return false;
|
|
13339
13428
|
}
|
|
@@ -13346,8 +13435,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13346
13435
|
let terraformBlind = false;
|
|
13347
13436
|
let totalDeclaredTools = 0;
|
|
13348
13437
|
for (const name of pluginNames) {
|
|
13349
|
-
const pluginDir2 =
|
|
13350
|
-
const manifestText =
|
|
13438
|
+
const pluginDir2 = join55(pluginsRoot, name);
|
|
13439
|
+
const manifestText = readFileSync40(join55(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
13351
13440
|
const manifest = extractManifestTools(manifestText);
|
|
13352
13441
|
if (manifest.parseError) {
|
|
13353
13442
|
findings.push({
|
|
@@ -13365,13 +13454,13 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13365
13454
|
totalDeclaredTools += manifest.tools.length;
|
|
13366
13455
|
const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
|
|
13367
13456
|
file: f,
|
|
13368
|
-
text:
|
|
13457
|
+
text: readFileSync40(f, "utf8")
|
|
13369
13458
|
}));
|
|
13370
13459
|
const resolver = buildSymbolResolver(pySources);
|
|
13371
13460
|
const registry = extractToolRegistryEntries(pySources, resolver);
|
|
13372
13461
|
if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
|
|
13373
13462
|
const tfFiles = pluginTerraformFiles(pluginDir2);
|
|
13374
|
-
const tfText = tfFiles.map((f) =>
|
|
13463
|
+
const tfText = tfFiles.map((f) => readFileSync40(f, "utf8")).join("\n");
|
|
13375
13464
|
const terraform = extractTerraformEnvKeys(tfText);
|
|
13376
13465
|
if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
|
|
13377
13466
|
for (const toolName of manifest.tools) {
|
|
@@ -13445,7 +13534,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13445
13534
|
requiredEnvVars: envResult.envVars,
|
|
13446
13535
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
13447
13536
|
status: anyWired ? "ok" : "missing-env",
|
|
13448
|
-
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 ${
|
|
13537
|
+
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 ${join55(pluginDir2, "terraform")}, so this deployment can never supply it`
|
|
13449
13538
|
});
|
|
13450
13539
|
}
|
|
13451
13540
|
}
|
|
@@ -13478,8 +13567,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13478
13567
|
async function runPluginToolSupplyCheck() {
|
|
13479
13568
|
const root = (await execa21("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13480
13569
|
let allOk = true;
|
|
13481
|
-
const pluginsRoot =
|
|
13482
|
-
if (!
|
|
13570
|
+
const pluginsRoot = join56(root, "services", "_plugins");
|
|
13571
|
+
if (!existsSync48(pluginsRoot)) {
|
|
13483
13572
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
13484
13573
|
} else {
|
|
13485
13574
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -13509,8 +13598,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
13509
13598
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
13510
13599
|
}
|
|
13511
13600
|
}
|
|
13512
|
-
const servicesApiRoot =
|
|
13513
|
-
if (!
|
|
13601
|
+
const servicesApiRoot = join56(root, "services", "api");
|
|
13602
|
+
if (!existsSync48(servicesApiRoot)) {
|
|
13514
13603
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
13515
13604
|
} else {
|
|
13516
13605
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -13687,10 +13776,10 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
13687
13776
|
}
|
|
13688
13777
|
|
|
13689
13778
|
// src/scripts/check-shared-file-reduction.ts
|
|
13690
|
-
import { readFileSync as
|
|
13779
|
+
import { readFileSync as readFileSync42 } from "fs";
|
|
13691
13780
|
|
|
13692
13781
|
// src/lib/shared-file-reduction-guard.ts
|
|
13693
|
-
import { readFileSync as
|
|
13782
|
+
import { readFileSync as readFileSync41 } from "fs";
|
|
13694
13783
|
var LEAF_TEST_CALLS = /* @__PURE__ */ new Set(["it", "test"]);
|
|
13695
13784
|
var SUITE_CALLS = /* @__PURE__ */ new Set(["describe", "suite"]);
|
|
13696
13785
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
@@ -13817,7 +13906,7 @@ function formatReductionReport(report) {
|
|
|
13817
13906
|
// src/scripts/check-shared-file-reduction.ts
|
|
13818
13907
|
function readStdin() {
|
|
13819
13908
|
try {
|
|
13820
|
-
return
|
|
13909
|
+
return readFileSync42(0, "utf8");
|
|
13821
13910
|
} catch {
|
|
13822
13911
|
return "";
|
|
13823
13912
|
}
|
|
@@ -13834,15 +13923,15 @@ function pairsFromTsv(tsv) {
|
|
|
13834
13923
|
const [target, existingPath, incomingPath] = fields;
|
|
13835
13924
|
pairs.push({
|
|
13836
13925
|
target,
|
|
13837
|
-
existing:
|
|
13838
|
-
incoming:
|
|
13926
|
+
existing: readFileSync42(existingPath, "utf8"),
|
|
13927
|
+
incoming: readFileSync42(incomingPath, "utf8")
|
|
13839
13928
|
});
|
|
13840
13929
|
}
|
|
13841
13930
|
return pairs;
|
|
13842
13931
|
}
|
|
13843
13932
|
function loadAccepted(manifestPath) {
|
|
13844
13933
|
if (!manifestPath) return {};
|
|
13845
|
-
const parsed = JSON.parse(
|
|
13934
|
+
const parsed = JSON.parse(readFileSync42(manifestPath, "utf8"));
|
|
13846
13935
|
return parsed.acceptedReductions ?? {};
|
|
13847
13936
|
}
|
|
13848
13937
|
async function runSharedFileReductionCheck(args) {
|
|
@@ -13850,13 +13939,13 @@ async function runSharedFileReductionCheck(args) {
|
|
|
13850
13939
|
let accepted;
|
|
13851
13940
|
try {
|
|
13852
13941
|
if (args.pairs) {
|
|
13853
|
-
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() :
|
|
13942
|
+
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync42(args.pairs, "utf8"));
|
|
13854
13943
|
} else if (args.target && args.existing && args.incoming) {
|
|
13855
13944
|
pairs = [
|
|
13856
13945
|
{
|
|
13857
13946
|
target: args.target,
|
|
13858
|
-
existing:
|
|
13859
|
-
incoming:
|
|
13947
|
+
existing: readFileSync42(args.existing, "utf8"),
|
|
13948
|
+
incoming: readFileSync42(args.incoming, "utf8")
|
|
13860
13949
|
}
|
|
13861
13950
|
];
|
|
13862
13951
|
} else {
|
|
@@ -13890,13 +13979,13 @@ async function runSharedFileReductionCheck(args) {
|
|
|
13890
13979
|
}
|
|
13891
13980
|
|
|
13892
13981
|
// src/scripts/check-skeleton-drift.ts
|
|
13893
|
-
import { existsSync as
|
|
13894
|
-
import { join as
|
|
13982
|
+
import { existsSync as existsSync49, readdirSync as readdirSync27 } from "fs";
|
|
13983
|
+
import { join as join58 } from "path";
|
|
13895
13984
|
import { execa as execa23 } from "execa";
|
|
13896
13985
|
|
|
13897
13986
|
// src/lib/skeleton-drift-guard.ts
|
|
13898
|
-
import { readFileSync as
|
|
13899
|
-
import { join as
|
|
13987
|
+
import { readFileSync as readFileSync43, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
|
|
13988
|
+
import { join as join57 } from "path";
|
|
13900
13989
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
13901
13990
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
13902
13991
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -13960,7 +14049,7 @@ function walk(dir, base = dir) {
|
|
|
13960
14049
|
}
|
|
13961
14050
|
for (const entry of entries) {
|
|
13962
14051
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
13963
|
-
const abs =
|
|
14052
|
+
const abs = join57(dir, entry);
|
|
13964
14053
|
let isDir;
|
|
13965
14054
|
try {
|
|
13966
14055
|
isDir = statSync16(abs).isDirectory();
|
|
@@ -13982,7 +14071,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
13982
14071
|
if (!rule.appliesTo(rel)) continue;
|
|
13983
14072
|
let contents;
|
|
13984
14073
|
try {
|
|
13985
|
-
contents =
|
|
14074
|
+
contents = readFileSync43(join57(skeletonRoot, rel), "utf8");
|
|
13986
14075
|
} catch {
|
|
13987
14076
|
continue;
|
|
13988
14077
|
}
|
|
@@ -14011,23 +14100,23 @@ function formatViolations2(violations) {
|
|
|
14011
14100
|
|
|
14012
14101
|
// src/scripts/check-skeleton-drift.ts
|
|
14013
14102
|
function discoverSkeletons(root) {
|
|
14014
|
-
const skeletonsDir =
|
|
14103
|
+
const skeletonsDir = join58(root, "_skeletons");
|
|
14015
14104
|
let entries;
|
|
14016
14105
|
try {
|
|
14017
14106
|
entries = readdirSync27(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
14018
14107
|
} catch {
|
|
14019
14108
|
return [];
|
|
14020
14109
|
}
|
|
14021
|
-
return entries.filter((name) =>
|
|
14110
|
+
return entries.filter((name) => existsSync49(join58(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
14022
14111
|
}
|
|
14023
14112
|
async function runSkeletonDriftCheck() {
|
|
14024
14113
|
const root = (await execa23("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14025
14114
|
const skeletons = discoverSkeletons(root);
|
|
14026
14115
|
let filesConsidered = 0;
|
|
14027
14116
|
for (const name of skeletons) {
|
|
14028
|
-
const skeletonRoot =
|
|
14117
|
+
const skeletonRoot = join58(root, "_skeletons", name);
|
|
14029
14118
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
14030
|
-
if (
|
|
14119
|
+
if (existsSync49(join58(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
14031
14120
|
filesConsidered += 1;
|
|
14032
14121
|
}
|
|
14033
14122
|
}
|
|
@@ -14041,7 +14130,7 @@ async function runSkeletonDriftCheck() {
|
|
|
14041
14130
|
process.exit(1);
|
|
14042
14131
|
}
|
|
14043
14132
|
const violations = skeletons.flatMap(
|
|
14044
|
-
(name) => auditSkeleton(
|
|
14133
|
+
(name) => auditSkeleton(join58(root, "_skeletons", name), name)
|
|
14045
14134
|
);
|
|
14046
14135
|
if (violations.length > 0) {
|
|
14047
14136
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -14191,8 +14280,8 @@ function rawArgsAfter(subcommand) {
|
|
|
14191
14280
|
}
|
|
14192
14281
|
|
|
14193
14282
|
// src/commands/doctor.ts
|
|
14194
|
-
import { existsSync as
|
|
14195
|
-
import { join as
|
|
14283
|
+
import { existsSync as existsSync50, readFileSync as readFileSync44 } from "fs";
|
|
14284
|
+
import { join as join59, resolve as resolve20 } from "path";
|
|
14196
14285
|
import chalk21 from "chalk";
|
|
14197
14286
|
import { Command as Command25 } from "commander";
|
|
14198
14287
|
|
|
@@ -14371,10 +14460,10 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
14371
14460
|
return runDoctorChecks(facts);
|
|
14372
14461
|
}
|
|
14373
14462
|
function readLocalCoreVersion(cwd) {
|
|
14374
|
-
const path =
|
|
14375
|
-
if (!
|
|
14463
|
+
const path = join59(cwd, INSTANCE_CORE_FILE);
|
|
14464
|
+
if (!existsSync50(path)) return null;
|
|
14376
14465
|
try {
|
|
14377
|
-
return extractVersionField(
|
|
14466
|
+
return extractVersionField(readFileSync44(path, "utf8"));
|
|
14378
14467
|
} catch {
|
|
14379
14468
|
return null;
|
|
14380
14469
|
}
|
|
@@ -14394,10 +14483,10 @@ function extractVersionField(contents) {
|
|
|
14394
14483
|
return match?.[1] ?? null;
|
|
14395
14484
|
}
|
|
14396
14485
|
function readFossil(cwd) {
|
|
14397
|
-
const path =
|
|
14398
|
-
if (!
|
|
14486
|
+
const path = join59(cwd, CORE_VERSION_FILE);
|
|
14487
|
+
if (!existsSync50(path)) return null;
|
|
14399
14488
|
try {
|
|
14400
|
-
const value =
|
|
14489
|
+
const value = readFileSync44(path, "utf8").trim();
|
|
14401
14490
|
return value === "" ? null : value;
|
|
14402
14491
|
} catch {
|
|
14403
14492
|
return null;
|
|
@@ -14846,13 +14935,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
14846
14935
|
import { Command as Command27 } from "commander";
|
|
14847
14936
|
|
|
14848
14937
|
// src/lib/packaged-scripts.ts
|
|
14849
|
-
import { existsSync as
|
|
14850
|
-
import { dirname as dirname11, join as
|
|
14938
|
+
import { existsSync as existsSync51 } from "fs";
|
|
14939
|
+
import { dirname as dirname11, join as join60 } from "path";
|
|
14851
14940
|
function findPackagedScript(startDir, relativePath) {
|
|
14852
14941
|
let dir = startDir;
|
|
14853
14942
|
for (; ; ) {
|
|
14854
|
-
const candidate =
|
|
14855
|
-
if (
|
|
14943
|
+
const candidate = join60(dir, relativePath);
|
|
14944
|
+
if (existsSync51(candidate)) return candidate;
|
|
14856
14945
|
const parent = dirname11(dir);
|
|
14857
14946
|
if (parent === dir) return null;
|
|
14858
14947
|
dir = parent;
|