@biffo/cli 0.253.6 → 0.253.8
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
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 existsSync13, rmSync as rmSync5 } from "fs";
|
|
574
|
+
import { join as join14, 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";
|
|
@@ -3146,6 +3146,32 @@ function describeFailures(outcomes) {
|
|
|
3146
3146
|
);
|
|
3147
3147
|
}
|
|
3148
3148
|
|
|
3149
|
+
// src/lib/instance-dependency-install.ts
|
|
3150
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3151
|
+
import { join as join13 } from "path";
|
|
3152
|
+
function dependencyInstallSteps(instanceDir) {
|
|
3153
|
+
const steps = [{ ecosystem: "pnpm", command: ["pnpm", "install"] }];
|
|
3154
|
+
if (existsSync12(join13(instanceDir, "pyproject.toml"))) {
|
|
3155
|
+
steps.push({ ecosystem: "uv", command: ["uv", "sync"] });
|
|
3156
|
+
}
|
|
3157
|
+
return steps;
|
|
3158
|
+
}
|
|
3159
|
+
async function installInstanceDependencies(instanceDir, run, steps = dependencyInstallSteps(instanceDir)) {
|
|
3160
|
+
const outcomes = [];
|
|
3161
|
+
for (const step of steps) {
|
|
3162
|
+
const result = await run(step.command, instanceDir);
|
|
3163
|
+
const outcome = { step, ok: result.ok };
|
|
3164
|
+
if (result.error !== void 0) outcome.error = result.error;
|
|
3165
|
+
outcomes.push(outcome);
|
|
3166
|
+
}
|
|
3167
|
+
return outcomes;
|
|
3168
|
+
}
|
|
3169
|
+
function describeInstallFailures(outcomes) {
|
|
3170
|
+
return outcomes.filter((o) => !o.ok).map(
|
|
3171
|
+
(o) => `${o.step.ecosystem} dependency install failed: ${o.error ?? "unknown error"}. Run \`${o.step.command.join(" ")}\` in the instance before pushing, or the pre-push gate (\`scripts/verify.sh\`) will reject the push against a tree with no installed dependencies (#1040).`
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3149
3175
|
// src/commands/core-upgrade.ts
|
|
3150
3176
|
var MISSING_TEMPLATE_ROOT_GUIDANCE2 = "Pass --template-repo <path> to a biffo-template git checkout, e.g. `biffo core upgrade --template-repo /path/to/biffo-template`.";
|
|
3151
3177
|
function resolveTemplateRepoFlag(templateRepo, template) {
|
|
@@ -3492,7 +3518,7 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
|
|
|
3492
3518
|
);
|
|
3493
3519
|
}
|
|
3494
3520
|
const cleanedCoreVersion = coreVersionCleanup?.action === "delete";
|
|
3495
|
-
if (cleanedCoreVersion &&
|
|
3521
|
+
if (cleanedCoreVersion && existsSync13(coreVersionCleanup.path)) {
|
|
3496
3522
|
rmSync5(coreVersionCleanup.path);
|
|
3497
3523
|
log.info(
|
|
3498
3524
|
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).`
|
|
@@ -3507,10 +3533,26 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
|
|
|
3507
3533
|
const carriedPrs = readCarriedPrs(options.templateRepo, fromVersion, toVersion);
|
|
3508
3534
|
await git.add(options.cwd, ["-A"]);
|
|
3509
3535
|
await git.commit(options.cwd, buildCommitMessage(fromVersion, toVersion, carriedPrs));
|
|
3536
|
+
const run = deps.runCommand ?? defaultRunCommand;
|
|
3537
|
+
const installOutcomes = await installInstanceDependencies(options.cwd, run);
|
|
3538
|
+
const installFailures = describeInstallFailures(installOutcomes);
|
|
3539
|
+
for (const message of installFailures) log.warn(message);
|
|
3510
3540
|
log.step(3, 4, `Pushing ${branch}`);
|
|
3511
3541
|
const pushOpts = { token };
|
|
3512
3542
|
if (options.remote) pushOpts.remote = options.remote;
|
|
3513
|
-
|
|
3543
|
+
try {
|
|
3544
|
+
await git.push(options.cwd, branch, pushOpts);
|
|
3545
|
+
} catch (err) {
|
|
3546
|
+
const marker = carriedPrsSection(carriedPrs).join("\n").trim();
|
|
3547
|
+
log.error(
|
|
3548
|
+
`Push failed: ${err.message}
|
|
3549
|
+
|
|
3550
|
+
The branch ${branch} is committed locally. Push it and open the PR by hand \u2014 and ` + (marker.length > 0 ? `include this in the PR body so #767's time-to-feature metric can join on it:
|
|
3551
|
+
|
|
3552
|
+
${marker}` : "this upgrade carries no template PRs, so no provenance marker is needed.")
|
|
3553
|
+
);
|
|
3554
|
+
throw err;
|
|
3555
|
+
}
|
|
3514
3556
|
log.step(4, 4, "Opening pull request");
|
|
3515
3557
|
const remoteUrl = await git.getRemoteUrl(options.cwd, options.remote);
|
|
3516
3558
|
const { owner, repo } = parseGitHubRepo(remoteUrl);
|
|
@@ -3532,7 +3574,8 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
|
|
|
3532
3574
|
breaking,
|
|
3533
3575
|
cleanedCoreVersion ? coreVersionCleanup : null,
|
|
3534
3576
|
carriedPrs,
|
|
3535
|
-
newSeams
|
|
3577
|
+
newSeams,
|
|
3578
|
+
installFailures
|
|
3536
3579
|
)
|
|
3537
3580
|
});
|
|
3538
3581
|
if (plan.conflicts.length > 0) {
|
|
@@ -3588,7 +3631,7 @@ function carriedPrNumbers(subjects) {
|
|
|
3588
3631
|
}
|
|
3589
3632
|
return [...new Set(numbers)].sort((a, b) => a - b);
|
|
3590
3633
|
}
|
|
3591
|
-
function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = [], newSeams = []) {
|
|
3634
|
+
function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = [], newSeams = [], installFailures = []) {
|
|
3592
3635
|
const lines = [];
|
|
3593
3636
|
if (breaking.length > 0) {
|
|
3594
3637
|
lines.push(
|
|
@@ -3697,6 +3740,16 @@ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, loc
|
|
|
3697
3740
|
...lockfiles.map((o) => `- \`${o.trigger.lockfile}\` (\`${o.trigger.command.join(" ")}\`)`)
|
|
3698
3741
|
);
|
|
3699
3742
|
}
|
|
3743
|
+
if (installFailures.length > 0) {
|
|
3744
|
+
lines.push(
|
|
3745
|
+
"",
|
|
3746
|
+
"## \u26A0 Dependency install failed",
|
|
3747
|
+
"",
|
|
3748
|
+
"`pnpm install` (and `uv sync`, where this instance has Python) could not run on the machine that opened this PR, so it may not have been possible to verify this tree before pushing it (#1040):",
|
|
3749
|
+
"",
|
|
3750
|
+
...installFailures.map((f) => `- ${f}`)
|
|
3751
|
+
);
|
|
3752
|
+
}
|
|
3700
3753
|
if (plan.conflicts.length > 0) {
|
|
3701
3754
|
lines.push(
|
|
3702
3755
|
"",
|
|
@@ -3903,8 +3956,8 @@ function printBreakingChanges(breaking, applying) {
|
|
|
3903
3956
|
}
|
|
3904
3957
|
function versionOfCheckout(dir, explicit) {
|
|
3905
3958
|
if (explicit) return explicit;
|
|
3906
|
-
const file =
|
|
3907
|
-
if (
|
|
3959
|
+
const file = join14(dir, CORE_VERSION_FILE);
|
|
3960
|
+
if (existsSync13(file)) return readCoreVersionFile(file);
|
|
3908
3961
|
throw new Error(
|
|
3909
3962
|
`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.`
|
|
3910
3963
|
);
|
|
@@ -3912,8 +3965,8 @@ function versionOfCheckout(dir, explicit) {
|
|
|
3912
3965
|
function latestCoreVersion(repo) {
|
|
3913
3966
|
const fromTags = latestCoreVersionFromTags(repo);
|
|
3914
3967
|
if (fromTags) return fromTags;
|
|
3915
|
-
const file =
|
|
3916
|
-
if (
|
|
3968
|
+
const file = join14(repo, CORE_VERSION_FILE);
|
|
3969
|
+
if (existsSync13(file)) return readCoreVersionFile(file);
|
|
3917
3970
|
throw new Error(
|
|
3918
3971
|
`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.`
|
|
3919
3972
|
);
|
|
@@ -3931,7 +3984,7 @@ coreCommand.addCommand(coreUpgradeCommand);
|
|
|
3931
3984
|
import { Command as Command8 } from "commander";
|
|
3932
3985
|
|
|
3933
3986
|
// src/commands/data-apply.ts
|
|
3934
|
-
import { existsSync as
|
|
3987
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
|
|
3935
3988
|
import { resolve as resolve4 } from "path";
|
|
3936
3989
|
import chalk5 from "chalk";
|
|
3937
3990
|
import { Command as Command5 } from "commander";
|
|
@@ -4382,7 +4435,7 @@ function isTemplatePlaceholderConfig(raw) {
|
|
|
4382
4435
|
|
|
4383
4436
|
// src/lib/session.ts
|
|
4384
4437
|
import {
|
|
4385
|
-
existsSync as
|
|
4438
|
+
existsSync as existsSync14,
|
|
4386
4439
|
mkdirSync as mkdirSync4,
|
|
4387
4440
|
readdirSync as readdirSync4,
|
|
4388
4441
|
readFileSync as readFileSync9,
|
|
@@ -4391,7 +4444,7 @@ import {
|
|
|
4391
4444
|
writeFileSync as writeFileSync5
|
|
4392
4445
|
} from "fs";
|
|
4393
4446
|
import { homedir } from "os";
|
|
4394
|
-
import { join as
|
|
4447
|
+
import { join as join15 } from "path";
|
|
4395
4448
|
var LEGACY_STEP_ALIASES = {
|
|
4396
4449
|
github_config: ["github_branches", "github_instance_files", "github_settings"]
|
|
4397
4450
|
};
|
|
@@ -4400,14 +4453,14 @@ function hasCompleted(session, step) {
|
|
|
4400
4453
|
return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
|
|
4401
4454
|
}
|
|
4402
4455
|
function sessionsDir() {
|
|
4403
|
-
return process.env["BIFFO_SESSIONS_DIR"] ??
|
|
4456
|
+
return process.env["BIFFO_SESSIONS_DIR"] ?? join15(homedir(), ".biffo", "sessions");
|
|
4404
4457
|
}
|
|
4405
4458
|
function sessionPath(projectName) {
|
|
4406
|
-
return
|
|
4459
|
+
return join15(sessionsDir(), `${projectName}.json`);
|
|
4407
4460
|
}
|
|
4408
4461
|
function loadSession(projectName) {
|
|
4409
4462
|
const path = sessionPath(projectName);
|
|
4410
|
-
if (!
|
|
4463
|
+
if (!existsSync14(path)) return null;
|
|
4411
4464
|
try {
|
|
4412
4465
|
return JSON.parse(readFileSync9(path, "utf8"));
|
|
4413
4466
|
} catch {
|
|
@@ -4416,23 +4469,23 @@ function loadSession(projectName) {
|
|
|
4416
4469
|
}
|
|
4417
4470
|
function findLatestSession() {
|
|
4418
4471
|
const dir = sessionsDir();
|
|
4419
|
-
if (!
|
|
4472
|
+
if (!existsSync14(dir)) return null;
|
|
4420
4473
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
4421
4474
|
if (files.length === 0) return null;
|
|
4422
4475
|
const sorted = files.map((f) => {
|
|
4423
|
-
const fullPath =
|
|
4424
|
-
const mtime =
|
|
4476
|
+
const fullPath = join15(dir, f);
|
|
4477
|
+
const mtime = existsSync14(fullPath) ? statSync2(fullPath).mtimeMs : -1;
|
|
4425
4478
|
return { f, mtime };
|
|
4426
4479
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
4427
4480
|
try {
|
|
4428
|
-
return JSON.parse(readFileSync9(
|
|
4481
|
+
return JSON.parse(readFileSync9(join15(dir, sorted[0].f), "utf8"));
|
|
4429
4482
|
} catch {
|
|
4430
4483
|
return null;
|
|
4431
4484
|
}
|
|
4432
4485
|
}
|
|
4433
4486
|
function saveSession(session) {
|
|
4434
4487
|
const dir = sessionsDir();
|
|
4435
|
-
if (!
|
|
4488
|
+
if (!existsSync14(dir)) mkdirSync4(dir, { recursive: true });
|
|
4436
4489
|
const name = session.config.project?.name ?? "unknown";
|
|
4437
4490
|
const prior = loadSession(name);
|
|
4438
4491
|
if (prior) {
|
|
@@ -4454,19 +4507,19 @@ function markStepComplete(session, step) {
|
|
|
4454
4507
|
}
|
|
4455
4508
|
function deleteSession(projectName) {
|
|
4456
4509
|
const path = sessionPath(projectName);
|
|
4457
|
-
if (
|
|
4510
|
+
if (existsSync14(path)) rmSync6(path);
|
|
4458
4511
|
}
|
|
4459
4512
|
function projectsDir() {
|
|
4460
|
-
return process.env["BIFFO_PROJECTS_DIR"] ??
|
|
4513
|
+
return process.env["BIFFO_PROJECTS_DIR"] ?? join15(homedir(), ".biffo", "projects");
|
|
4461
4514
|
}
|
|
4462
4515
|
function saveProjectConfig(config) {
|
|
4463
4516
|
const dir = projectsDir();
|
|
4464
|
-
if (!
|
|
4465
|
-
writeFileSync5(
|
|
4517
|
+
if (!existsSync14(dir)) mkdirSync4(dir, { recursive: true });
|
|
4518
|
+
writeFileSync5(join15(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
|
|
4466
4519
|
}
|
|
4467
4520
|
function loadProjectConfig(name) {
|
|
4468
|
-
const path =
|
|
4469
|
-
if (!
|
|
4521
|
+
const path = join15(projectsDir(), `${name}.json`);
|
|
4522
|
+
if (!existsSync14(path)) return null;
|
|
4470
4523
|
try {
|
|
4471
4524
|
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(path, "utf8")));
|
|
4472
4525
|
return result.success ? result.data : null;
|
|
@@ -4475,15 +4528,15 @@ function loadProjectConfig(name) {
|
|
|
4475
4528
|
}
|
|
4476
4529
|
}
|
|
4477
4530
|
function deleteProjectConfig(name) {
|
|
4478
|
-
const path =
|
|
4479
|
-
if (
|
|
4531
|
+
const path = join15(projectsDir(), `${name}.json`);
|
|
4532
|
+
if (existsSync14(path)) rmSync6(path);
|
|
4480
4533
|
}
|
|
4481
4534
|
function listProjectConfigs() {
|
|
4482
4535
|
const dir = projectsDir();
|
|
4483
|
-
if (!
|
|
4536
|
+
if (!existsSync14(dir)) return [];
|
|
4484
4537
|
return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
|
|
4485
4538
|
try {
|
|
4486
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(
|
|
4539
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(join15(dir, f), "utf8")));
|
|
4487
4540
|
return result.success ? [result.data] : [];
|
|
4488
4541
|
} catch {
|
|
4489
4542
|
return [];
|
|
@@ -4572,7 +4625,7 @@ async function resolveConfig(options) {
|
|
|
4572
4625
|
return cfg;
|
|
4573
4626
|
}
|
|
4574
4627
|
const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
|
|
4575
|
-
if (
|
|
4628
|
+
if (existsSync15(localConfigPath)) {
|
|
4576
4629
|
const raw = JSON.parse(readFileSync10(localConfigPath, "utf8"));
|
|
4577
4630
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
4578
4631
|
if (result.success) return result.data;
|
|
@@ -4619,8 +4672,8 @@ async function resolveConfig(options) {
|
|
|
4619
4672
|
|
|
4620
4673
|
// src/commands/data-import.ts
|
|
4621
4674
|
import { execSync as execSync3 } from "child_process";
|
|
4622
|
-
import { cpSync, existsSync as
|
|
4623
|
-
import { join as
|
|
4675
|
+
import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
|
|
4676
|
+
import { join as join16, resolve as resolve5 } from "path";
|
|
4624
4677
|
import chalk6 from "chalk";
|
|
4625
4678
|
import { Command as Command6 } from "commander";
|
|
4626
4679
|
import inquirer2 from "inquirer";
|
|
@@ -4660,23 +4713,23 @@ async function runDataImport(name, options, deps) {
|
|
|
4660
4713
|
`Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
|
|
4661
4714
|
);
|
|
4662
4715
|
}
|
|
4663
|
-
const servicesDir =
|
|
4664
|
-
if (!
|
|
4716
|
+
const servicesDir = join16(options.cwd, "services");
|
|
4717
|
+
if (!existsSync16(servicesDir)) {
|
|
4665
4718
|
throw new Error(
|
|
4666
4719
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
4667
4720
|
);
|
|
4668
4721
|
}
|
|
4669
|
-
const targetDir =
|
|
4670
|
-
if (
|
|
4722
|
+
const targetDir = join16(options.cwd, "db", "imports", name);
|
|
4723
|
+
if (existsSync16(targetDir)) {
|
|
4671
4724
|
throw new Error(
|
|
4672
4725
|
`DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
|
|
4673
4726
|
);
|
|
4674
4727
|
}
|
|
4675
|
-
const isLocalDir =
|
|
4728
|
+
const isLocalDir = existsSync16(options.source) && statSync3(options.source).isDirectory();
|
|
4676
4729
|
let sourceDir;
|
|
4677
4730
|
let cleanupClone = null;
|
|
4678
4731
|
if (isLocalDir) {
|
|
4679
|
-
sourceDir = options.path ?
|
|
4732
|
+
sourceDir = options.path ? join16(options.source, options.path) : options.source;
|
|
4680
4733
|
} else {
|
|
4681
4734
|
const token = options.token ?? await resolveDdlImportToken();
|
|
4682
4735
|
log.info(`Cloning ${options.source}...`);
|
|
@@ -4684,10 +4737,10 @@ async function runDataImport(name, options, deps) {
|
|
|
4684
4737
|
cleanupClone = () => {
|
|
4685
4738
|
deps.git.cleanup(tmpDir);
|
|
4686
4739
|
};
|
|
4687
|
-
sourceDir = options.path ?
|
|
4740
|
+
sourceDir = options.path ? join16(tmpDir, options.path) : tmpDir;
|
|
4688
4741
|
}
|
|
4689
4742
|
try {
|
|
4690
|
-
if (!
|
|
4743
|
+
if (!existsSync16(sourceDir)) {
|
|
4691
4744
|
throw new Error(`Source directory does not exist: ${sourceDir}`);
|
|
4692
4745
|
}
|
|
4693
4746
|
const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
|
|
@@ -4712,7 +4765,7 @@ async function runDataImport(name, options, deps) {
|
|
|
4712
4765
|
}
|
|
4713
4766
|
mkdirSync5(targetDir, { recursive: true });
|
|
4714
4767
|
for (const file of sqlFiles) {
|
|
4715
|
-
cpSync(
|
|
4768
|
+
cpSync(join16(sourceDir, file), join16(targetDir, file));
|
|
4716
4769
|
}
|
|
4717
4770
|
log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
|
|
4718
4771
|
const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
|
|
@@ -4764,8 +4817,8 @@ function printDryRun(name, sqlFiles) {
|
|
|
4764
4817
|
}
|
|
4765
4818
|
|
|
4766
4819
|
// src/commands/data-list.ts
|
|
4767
|
-
import { existsSync as
|
|
4768
|
-
import { join as
|
|
4820
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6 } from "fs";
|
|
4821
|
+
import { join as join17, resolve as resolve6 } from "path";
|
|
4769
4822
|
import chalk7 from "chalk";
|
|
4770
4823
|
import { Command as Command7 } from "commander";
|
|
4771
4824
|
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) => {
|
|
@@ -4778,15 +4831,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
|
|
|
4778
4831
|
}
|
|
4779
4832
|
});
|
|
4780
4833
|
async function runDataList(options) {
|
|
4781
|
-
const importsDir =
|
|
4782
|
-
if (!
|
|
4834
|
+
const importsDir = join17(options.cwd, "db", "imports");
|
|
4835
|
+
if (!existsSync17(importsDir)) {
|
|
4783
4836
|
console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
|
|
4784
4837
|
return;
|
|
4785
4838
|
}
|
|
4786
4839
|
const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
4787
4840
|
const imports = [];
|
|
4788
4841
|
for (const name of candidates) {
|
|
4789
|
-
const fileCount = readdirSync6(
|
|
4842
|
+
const fileCount = readdirSync6(join17(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
|
|
4790
4843
|
if (fileCount > 0) imports.push({ name, fileCount });
|
|
4791
4844
|
}
|
|
4792
4845
|
if (imports.length === 0) {
|
|
@@ -4816,7 +4869,7 @@ dataCommand.addCommand(dataListCommand);
|
|
|
4816
4869
|
|
|
4817
4870
|
// src/commands/deploy.ts
|
|
4818
4871
|
import { execSync as execSync4 } from "child_process";
|
|
4819
|
-
import { existsSync as
|
|
4872
|
+
import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
|
|
4820
4873
|
import { resolve as resolve7 } from "path";
|
|
4821
4874
|
import chalk8 from "chalk";
|
|
4822
4875
|
import { Command as Command9 } from "commander";
|
|
@@ -5193,7 +5246,7 @@ async function resolveConfig2(options) {
|
|
|
5193
5246
|
return cfg;
|
|
5194
5247
|
}
|
|
5195
5248
|
const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
|
|
5196
|
-
if (
|
|
5249
|
+
if (existsSync18(localConfigPath)) {
|
|
5197
5250
|
const raw = JSON.parse(readFileSync11(localConfigPath, "utf8"));
|
|
5198
5251
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
5199
5252
|
if (result.success) return result.data;
|
|
@@ -5757,8 +5810,8 @@ import { Command as Command12 } from "commander";
|
|
|
5757
5810
|
import inquirer5 from "inquirer";
|
|
5758
5811
|
|
|
5759
5812
|
// src/lib/build-freshness.ts
|
|
5760
|
-
import { existsSync as
|
|
5761
|
-
import { dirname as dirname5, join as
|
|
5813
|
+
import { existsSync as existsSync19, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
|
|
5814
|
+
import { dirname as dirname5, join as join18, relative as relative2, sep as sep2 } from "path";
|
|
5762
5815
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5763
5816
|
var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
|
|
5764
5817
|
function checkBuildFreshness(options = {}) {
|
|
@@ -5772,7 +5825,7 @@ function checkBuildFreshness(options = {}) {
|
|
|
5772
5825
|
if (!packageRoot) {
|
|
5773
5826
|
return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
|
|
5774
5827
|
}
|
|
5775
|
-
const distDir =
|
|
5828
|
+
const distDir = join18(packageRoot, "dist");
|
|
5776
5829
|
if (!isInside(distDir, moduleDir)) {
|
|
5777
5830
|
return {
|
|
5778
5831
|
status: "skipped",
|
|
@@ -5780,16 +5833,16 @@ function checkBuildFreshness(options = {}) {
|
|
|
5780
5833
|
newerSources: []
|
|
5781
5834
|
};
|
|
5782
5835
|
}
|
|
5783
|
-
const srcDir =
|
|
5784
|
-
if (!
|
|
5836
|
+
const srcDir = join18(packageRoot, "src");
|
|
5837
|
+
if (!existsSync19(srcDir)) {
|
|
5785
5838
|
return {
|
|
5786
5839
|
status: "skipped",
|
|
5787
5840
|
reason: "no src/ alongside dist/ \u2014 this is a shipped package",
|
|
5788
5841
|
newerSources: []
|
|
5789
5842
|
};
|
|
5790
5843
|
}
|
|
5791
|
-
const entry =
|
|
5792
|
-
if (!
|
|
5844
|
+
const entry = join18(distDir, "index.js");
|
|
5845
|
+
if (!existsSync19(entry)) {
|
|
5793
5846
|
return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
|
|
5794
5847
|
}
|
|
5795
5848
|
const builtAt = statSync4(entry).mtimeMs;
|
|
@@ -5833,7 +5886,7 @@ function collectSourceFiles(srcDir) {
|
|
|
5833
5886
|
const found = [];
|
|
5834
5887
|
const walk = (dir) => {
|
|
5835
5888
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
5836
|
-
const full =
|
|
5889
|
+
const full = join18(dir, entry.name);
|
|
5837
5890
|
if (entry.isDirectory()) {
|
|
5838
5891
|
if (entry.name === "node_modules") continue;
|
|
5839
5892
|
walk(full);
|
|
@@ -5852,7 +5905,7 @@ function collectSourceFiles(srcDir) {
|
|
|
5852
5905
|
function findPackageRoot(from) {
|
|
5853
5906
|
let dir = from;
|
|
5854
5907
|
for (; ; ) {
|
|
5855
|
-
if (
|
|
5908
|
+
if (existsSync19(join18(dir, "package.json"))) return dir;
|
|
5856
5909
|
const parent = dirname5(dir);
|
|
5857
5910
|
if (parent === dir) return null;
|
|
5858
5911
|
dir = parent;
|
|
@@ -5866,9 +5919,9 @@ function isInside(parent, child) {
|
|
|
5866
5919
|
|
|
5867
5920
|
// src/lib/credentials.ts
|
|
5868
5921
|
import { execSync as execSync6 } from "child_process";
|
|
5869
|
-
import { existsSync as
|
|
5922
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
|
|
5870
5923
|
import { homedir as homedir2 } from "os";
|
|
5871
|
-
import { join as
|
|
5924
|
+
import { join as join19 } from "path";
|
|
5872
5925
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
|
|
5873
5926
|
import chalk10 from "chalk";
|
|
5874
5927
|
import inquirer4 from "inquirer";
|
|
@@ -6047,10 +6100,10 @@ async function verifySelectedAwsCredentials(profile, region) {
|
|
|
6047
6100
|
return sts.send(new GetCallerIdentityCommand2({}));
|
|
6048
6101
|
}
|
|
6049
6102
|
function discoverAwsProfiles() {
|
|
6050
|
-
const files = [
|
|
6103
|
+
const files = [join19(homedir2(), ".aws", "credentials"), join19(homedir2(), ".aws", "config")];
|
|
6051
6104
|
const profiles = /* @__PURE__ */ new Set();
|
|
6052
6105
|
for (const file of files) {
|
|
6053
|
-
if (!
|
|
6106
|
+
if (!existsSync20(file)) continue;
|
|
6054
6107
|
const content = readFileSync13(file, "utf8");
|
|
6055
6108
|
for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
|
|
6056
6109
|
const section = match[1]?.trim();
|
|
@@ -6141,7 +6194,7 @@ var SiblingConfigSchema = z6.object({
|
|
|
6141
6194
|
|
|
6142
6195
|
// src/lib/sibling-session.ts
|
|
6143
6196
|
import {
|
|
6144
|
-
existsSync as
|
|
6197
|
+
existsSync as existsSync21,
|
|
6145
6198
|
mkdirSync as mkdirSync6,
|
|
6146
6199
|
readdirSync as readdirSync8,
|
|
6147
6200
|
readFileSync as readFileSync14,
|
|
@@ -6150,16 +6203,16 @@ import {
|
|
|
6150
6203
|
writeFileSync as writeFileSync6
|
|
6151
6204
|
} from "fs";
|
|
6152
6205
|
import { homedir as homedir3 } from "os";
|
|
6153
|
-
import { join as
|
|
6206
|
+
import { join as join20 } from "path";
|
|
6154
6207
|
function sessionsDir2() {
|
|
6155
|
-
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ??
|
|
6208
|
+
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join20(homedir3(), ".biffo", "sibling-sessions");
|
|
6156
6209
|
}
|
|
6157
6210
|
function sessionPath2(projectName) {
|
|
6158
|
-
return
|
|
6211
|
+
return join20(sessionsDir2(), `${projectName}.json`);
|
|
6159
6212
|
}
|
|
6160
6213
|
function loadSiblingSession(projectName) {
|
|
6161
6214
|
const path = sessionPath2(projectName);
|
|
6162
|
-
if (!
|
|
6215
|
+
if (!existsSync21(path)) return null;
|
|
6163
6216
|
try {
|
|
6164
6217
|
return JSON.parse(readFileSync14(path, "utf8"));
|
|
6165
6218
|
} catch {
|
|
@@ -6168,7 +6221,7 @@ function loadSiblingSession(projectName) {
|
|
|
6168
6221
|
}
|
|
6169
6222
|
function saveSiblingSession(session) {
|
|
6170
6223
|
const dir = sessionsDir2();
|
|
6171
|
-
if (!
|
|
6224
|
+
if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true });
|
|
6172
6225
|
const name = session.config.project?.name ?? "unknown";
|
|
6173
6226
|
const prior = loadSiblingSession(name);
|
|
6174
6227
|
if (prior) {
|
|
@@ -6190,30 +6243,30 @@ function markSiblingStepComplete(session, step) {
|
|
|
6190
6243
|
}
|
|
6191
6244
|
function deleteSiblingSession(projectName) {
|
|
6192
6245
|
const path = sessionPath2(projectName);
|
|
6193
|
-
if (
|
|
6246
|
+
if (existsSync21(path)) rmSync7(path);
|
|
6194
6247
|
}
|
|
6195
6248
|
|
|
6196
6249
|
// src/commands/sibling-create.ts
|
|
6197
|
-
import { cpSync as cpSync2, existsSync as
|
|
6250
|
+
import { cpSync as cpSync2, existsSync as existsSync22, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
6198
6251
|
import { tmpdir as tmpdir4 } from "os";
|
|
6199
|
-
import { dirname as dirname6, join as
|
|
6252
|
+
import { dirname as dirname6, join as join22, resolve as resolve9 } from "path";
|
|
6200
6253
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6201
6254
|
import chalk11 from "chalk";
|
|
6202
6255
|
import { Command as Command11 } from "commander";
|
|
6203
6256
|
|
|
6204
6257
|
// src/lib/skeleton-dotfiles.ts
|
|
6205
6258
|
import { readdirSync as readdirSync9, renameSync } from "fs";
|
|
6206
|
-
import { join as
|
|
6259
|
+
import { join as join21 } from "path";
|
|
6207
6260
|
var PACKAGED_GITIGNORE = "_gitignore";
|
|
6208
6261
|
var REAL_GITIGNORE = ".gitignore";
|
|
6209
6262
|
function restorePackagedDotfiles(dir) {
|
|
6210
6263
|
const restored = [];
|
|
6211
6264
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
6212
|
-
const full =
|
|
6265
|
+
const full = join21(dir, entry.name);
|
|
6213
6266
|
if (entry.isDirectory()) {
|
|
6214
6267
|
restored.push(...restorePackagedDotfiles(full));
|
|
6215
6268
|
} else if (entry.name === PACKAGED_GITIGNORE) {
|
|
6216
|
-
const target =
|
|
6269
|
+
const target = join21(dir, REAL_GITIGNORE);
|
|
6217
6270
|
renameSync(full, target);
|
|
6218
6271
|
restored.push(target);
|
|
6219
6272
|
}
|
|
@@ -6258,7 +6311,7 @@ async function runSiblingCreateCommand(name, options) {
|
|
|
6258
6311
|
printDryRun2(config, coreConfig, options.templateRoot);
|
|
6259
6312
|
return;
|
|
6260
6313
|
}
|
|
6261
|
-
if (!
|
|
6314
|
+
if (!existsSync22(options.templateRoot)) {
|
|
6262
6315
|
throw new Error(`Sibling template not found at ${options.templateRoot}`);
|
|
6263
6316
|
}
|
|
6264
6317
|
let session = null;
|
|
@@ -6548,7 +6601,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
|
|
|
6548
6601
|
return coreIdentity;
|
|
6549
6602
|
}
|
|
6550
6603
|
async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
|
|
6551
|
-
const workDir = mkdtempSync4(
|
|
6604
|
+
const workDir = mkdtempSync4(join22(tmpdir4(), `biffo-sibling-${config.project.name}-`));
|
|
6552
6605
|
try {
|
|
6553
6606
|
writeSiblingTemplate(skeletonRoot, workDir, config, {
|
|
6554
6607
|
coreProjectName: coreConfig.project.name,
|
|
@@ -6573,13 +6626,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
|
|
|
6573
6626
|
}
|
|
6574
6627
|
}
|
|
6575
6628
|
function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
6576
|
-
if (!
|
|
6629
|
+
if (!existsSync22(templateRoot)) {
|
|
6577
6630
|
throw new Error(`Sibling template not found at ${templateRoot}`);
|
|
6578
6631
|
}
|
|
6579
6632
|
cpSync2(templateRoot, targetDir, { recursive: true });
|
|
6580
6633
|
restorePackagedDotfiles(targetDir);
|
|
6581
6634
|
writeFileSync7(
|
|
6582
|
-
|
|
6635
|
+
join22(targetDir, "biffo.sibling.json"),
|
|
6583
6636
|
JSON.stringify(
|
|
6584
6637
|
{
|
|
6585
6638
|
name: config.project.name,
|
|
@@ -6598,11 +6651,11 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
|
6598
6651
|
) + "\n"
|
|
6599
6652
|
);
|
|
6600
6653
|
writeFileSync7(
|
|
6601
|
-
|
|
6654
|
+
join22(targetDir, ".biffo-shared-version"),
|
|
6602
6655
|
`core-v${context.templateVersion.replace(/^core-v/, "")}
|
|
6603
6656
|
`
|
|
6604
6657
|
);
|
|
6605
|
-
const envPath =
|
|
6658
|
+
const envPath = join22(targetDir, "apps", "frontend", ".env.example");
|
|
6606
6659
|
try {
|
|
6607
6660
|
const path = basePathFor(context.pathPrefix);
|
|
6608
6661
|
const content = readFileSync15(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}`);
|
|
@@ -6671,7 +6724,7 @@ function assertGitIdentity(identity) {
|
|
|
6671
6724
|
);
|
|
6672
6725
|
}
|
|
6673
6726
|
function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
|
|
6674
|
-
const cdnVarsPath =
|
|
6727
|
+
const cdnVarsPath = join22(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
|
|
6675
6728
|
let declaresSiblingOrigins = false;
|
|
6676
6729
|
try {
|
|
6677
6730
|
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync15(cdnVarsPath, "utf8"));
|
|
@@ -6684,7 +6737,7 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
|
|
|
6684
6737
|
);
|
|
6685
6738
|
}
|
|
6686
6739
|
if (!isRootPathPrefix(pathPrefix)) return;
|
|
6687
|
-
const cdnMainPath =
|
|
6740
|
+
const cdnMainPath = join22(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
|
|
6688
6741
|
let supportsRoot = false;
|
|
6689
6742
|
try {
|
|
6690
6743
|
supportsRoot = /root_sibling_registered/.test(readFileSync15(cdnMainPath, "utf8"));
|
|
@@ -6717,8 +6770,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
|
|
|
6717
6770
|
for (const env of config.environments) {
|
|
6718
6771
|
const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
|
|
6719
6772
|
const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
|
|
6720
|
-
const relativePath =
|
|
6721
|
-
const filePath =
|
|
6773
|
+
const relativePath = join22("infra", "environments", env, "siblings.auto.tfvars.json");
|
|
6774
|
+
const filePath = join22(cloneDir, relativePath);
|
|
6722
6775
|
const existing = readExistingSiblingOrigins(filePath);
|
|
6723
6776
|
const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
|
|
6724
6777
|
name,
|
|
@@ -6798,8 +6851,8 @@ function defaultSiblingTemplateRoot() {
|
|
|
6798
6851
|
const start = dirname6(fileURLToPath4(import.meta.url));
|
|
6799
6852
|
let dir = start;
|
|
6800
6853
|
for (; ; ) {
|
|
6801
|
-
const candidate =
|
|
6802
|
-
if (
|
|
6854
|
+
const candidate = join22(dir, "_skeletons", "sibling-template");
|
|
6855
|
+
if (existsSync22(candidate)) return candidate;
|
|
6803
6856
|
const parent = dirname6(dir);
|
|
6804
6857
|
if (parent === dir) break;
|
|
6805
6858
|
dir = parent;
|
|
@@ -7268,8 +7321,8 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
7268
7321
|
import { Command as Command20 } from "commander";
|
|
7269
7322
|
|
|
7270
7323
|
// src/commands/plugin-create.ts
|
|
7271
|
-
import { existsSync as
|
|
7272
|
-
import { dirname as dirname8, join as
|
|
7324
|
+
import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
|
|
7325
|
+
import { dirname as dirname8, join as join25, resolve as resolve11 } from "path";
|
|
7273
7326
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7274
7327
|
import chalk13 from "chalk";
|
|
7275
7328
|
import { Command as Command13 } from "commander";
|
|
@@ -7333,21 +7386,21 @@ function workflowCheckContexts(workflow) {
|
|
|
7333
7386
|
}
|
|
7334
7387
|
|
|
7335
7388
|
// src/lib/plugin-locations.ts
|
|
7336
|
-
import { existsSync as
|
|
7337
|
-
import { join as
|
|
7389
|
+
import { existsSync as existsSync23, readdirSync as readdirSync10 } from "fs";
|
|
7390
|
+
import { join as join23 } from "path";
|
|
7338
7391
|
var FIRST_PARTY_PLUGINS_DIR = "_plugins";
|
|
7339
7392
|
var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
|
|
7340
7393
|
function pluginDir(name, channel) {
|
|
7341
7394
|
return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
|
|
7342
7395
|
}
|
|
7343
7396
|
function scanDir(absDir, relDir, channel) {
|
|
7344
|
-
if (!
|
|
7397
|
+
if (!existsSync23(absDir)) return [];
|
|
7345
7398
|
const found = [];
|
|
7346
7399
|
for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
|
|
7347
7400
|
if (!entry.isDirectory()) continue;
|
|
7348
7401
|
if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
|
|
7349
|
-
const manifestPath =
|
|
7350
|
-
if (!
|
|
7402
|
+
const manifestPath = join23(absDir, entry.name, PLUGIN_MANIFEST_FILE);
|
|
7403
|
+
if (!existsSync23(manifestPath)) continue;
|
|
7351
7404
|
found.push({
|
|
7352
7405
|
dirName: entry.name,
|
|
7353
7406
|
relDir: `${relDir}/${entry.name}`,
|
|
@@ -7358,11 +7411,11 @@ function scanDir(absDir, relDir, channel) {
|
|
|
7358
7411
|
return found;
|
|
7359
7412
|
}
|
|
7360
7413
|
function findInstalledPlugins(cwd) {
|
|
7361
|
-
const servicesDir =
|
|
7414
|
+
const servicesDir = join23(cwd, "services");
|
|
7362
7415
|
return [
|
|
7363
7416
|
...scanDir(servicesDir, "services", "third-party"),
|
|
7364
7417
|
...scanDir(
|
|
7365
|
-
|
|
7418
|
+
join23(servicesDir, FIRST_PARTY_PLUGINS_DIR),
|
|
7366
7419
|
`services/${FIRST_PARTY_PLUGINS_DIR}`,
|
|
7367
7420
|
"first-party"
|
|
7368
7421
|
)
|
|
@@ -7565,13 +7618,13 @@ function validateManifest(raw) {
|
|
|
7565
7618
|
// src/lib/plugin-scaffold.ts
|
|
7566
7619
|
import {
|
|
7567
7620
|
copyFileSync,
|
|
7568
|
-
existsSync as
|
|
7621
|
+
existsSync as existsSync24,
|
|
7569
7622
|
mkdirSync as mkdirSync8,
|
|
7570
7623
|
readFileSync as readFileSync17,
|
|
7571
7624
|
readdirSync as readdirSync11,
|
|
7572
7625
|
writeFileSync as writeFileSync8
|
|
7573
7626
|
} from "fs";
|
|
7574
|
-
import { dirname as dirname7, join as
|
|
7627
|
+
import { dirname as dirname7, join as join24 } from "path";
|
|
7575
7628
|
var STANDALONE_ONLY_ENTRIES = {
|
|
7576
7629
|
".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
|
|
7577
7630
|
"registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
|
|
@@ -7625,10 +7678,10 @@ function applySubstitutions(text, names) {
|
|
|
7625
7678
|
var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
|
|
7626
7679
|
function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
7627
7680
|
const layout = options.layout ?? "in-tree";
|
|
7628
|
-
if (!
|
|
7681
|
+
if (!existsSync24(skeletonRoot)) {
|
|
7629
7682
|
throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
|
|
7630
7683
|
}
|
|
7631
|
-
if (!
|
|
7684
|
+
if (!existsSync24(join24(skeletonRoot, "terraform"))) {
|
|
7632
7685
|
throw new Error(
|
|
7633
7686
|
`Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
|
|
7634
7687
|
);
|
|
@@ -7636,7 +7689,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
|
7636
7689
|
const skipped = [];
|
|
7637
7690
|
const files = [];
|
|
7638
7691
|
const walk = (relDir) => {
|
|
7639
|
-
const absDir =
|
|
7692
|
+
const absDir = join24(skeletonRoot, relDir);
|
|
7640
7693
|
for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
|
|
7641
7694
|
(a, b) => a.name.localeCompare(b.name)
|
|
7642
7695
|
)) {
|
|
@@ -7651,14 +7704,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
|
7651
7704
|
continue;
|
|
7652
7705
|
}
|
|
7653
7706
|
const destRel = applySubstitutions(relPath, names);
|
|
7654
|
-
const destPath =
|
|
7707
|
+
const destPath = join24(destDir, destRel);
|
|
7655
7708
|
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
7656
7709
|
if (BINARY_EXTENSIONS.test(entry.name)) {
|
|
7657
|
-
copyFileSync(
|
|
7710
|
+
copyFileSync(join24(skeletonRoot, relPath), destPath);
|
|
7658
7711
|
} else {
|
|
7659
7712
|
writeFileSync8(
|
|
7660
7713
|
destPath,
|
|
7661
|
-
applySubstitutions(readFileSync17(
|
|
7714
|
+
applySubstitutions(readFileSync17(join24(skeletonRoot, relPath), "utf8"), names)
|
|
7662
7715
|
);
|
|
7663
7716
|
}
|
|
7664
7717
|
files.push(destRel);
|
|
@@ -7675,8 +7728,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
|
|
|
7675
7728
|
function findSkeletonRoot(startDir, skeleton) {
|
|
7676
7729
|
let dir = startDir;
|
|
7677
7730
|
for (; ; ) {
|
|
7678
|
-
const candidate =
|
|
7679
|
-
if (
|
|
7731
|
+
const candidate = join24(dir, "_skeletons", skeleton);
|
|
7732
|
+
if (existsSync24(candidate)) return candidate;
|
|
7680
7733
|
const parent = dirname7(dir);
|
|
7681
7734
|
if (parent === dir) return null;
|
|
7682
7735
|
dir = parent;
|
|
@@ -7744,7 +7797,7 @@ async function runPluginCreate(name, options, deps) {
|
|
|
7744
7797
|
reportBranchProtectionSummary();
|
|
7745
7798
|
return;
|
|
7746
7799
|
}
|
|
7747
|
-
const isInstance =
|
|
7800
|
+
const isInstance = existsSync25(join25(options.cwd, INSTANCE_CORE_FILE));
|
|
7748
7801
|
if (options.firstParty && isInstance) {
|
|
7749
7802
|
throw new Error(
|
|
7750
7803
|
`--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.`
|
|
@@ -7752,19 +7805,19 @@ async function runPluginCreate(name, options, deps) {
|
|
|
7752
7805
|
}
|
|
7753
7806
|
const channel = options.firstParty ? "first-party" : "third-party";
|
|
7754
7807
|
const relDir = pluginDir(names.slug, channel);
|
|
7755
|
-
const destDir =
|
|
7756
|
-
const servicesDir =
|
|
7757
|
-
if (!
|
|
7808
|
+
const destDir = join25(options.cwd, relDir);
|
|
7809
|
+
const servicesDir = join25(options.cwd, "services");
|
|
7810
|
+
if (!existsSync25(servicesDir)) {
|
|
7758
7811
|
throw new Error(
|
|
7759
7812
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
7760
7813
|
);
|
|
7761
7814
|
}
|
|
7762
|
-
if (
|
|
7815
|
+
if (existsSync25(destDir)) {
|
|
7763
7816
|
throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
|
|
7764
7817
|
}
|
|
7765
7818
|
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
7766
|
-
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ??
|
|
7767
|
-
if (!
|
|
7819
|
+
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join25(options.cwd, "_skeletons", "plugin-template");
|
|
7820
|
+
if (!existsSync25(skeletonRoot)) {
|
|
7768
7821
|
throw new Error(
|
|
7769
7822
|
`Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
|
|
7770
7823
|
);
|
|
@@ -7779,7 +7832,7 @@ async function runPluginCreate(name, options, deps) {
|
|
|
7779
7832
|
for (const { entry, reason } of skipped) {
|
|
7780
7833
|
log.info(`Skipped ${entry} \u2014 ${reason}`);
|
|
7781
7834
|
}
|
|
7782
|
-
const manifestPath =
|
|
7835
|
+
const manifestPath = join25(destDir, "biffo.plugin.json");
|
|
7783
7836
|
const manifest = validateManifest(JSON.parse(readFileSync18(manifestPath, "utf8")));
|
|
7784
7837
|
if (manifest.name !== names.slug) {
|
|
7785
7838
|
throw new Error(
|
|
@@ -7803,8 +7856,8 @@ async function runPluginCreate(name, options, deps) {
|
|
|
7803
7856
|
printNextSteps(names, relDir, channel);
|
|
7804
7857
|
}
|
|
7805
7858
|
async function runStandaloneCreate(names, options, deps) {
|
|
7806
|
-
const destDir =
|
|
7807
|
-
if (
|
|
7859
|
+
const destDir = join25(options.cwd, names.dist);
|
|
7860
|
+
if (existsSync25(destDir)) {
|
|
7808
7861
|
throw new Error(`${names.dist}/ already exists. Choose a different name, or remove it first.`);
|
|
7809
7862
|
}
|
|
7810
7863
|
const skeletonRoot = resolveSkeletonRoot(options);
|
|
@@ -7822,7 +7875,7 @@ async function runStandaloneCreate(names, options, deps) {
|
|
|
7822
7875
|
restorePackagedDotfiles(destDir);
|
|
7823
7876
|
log.success(`Scaffolded ${String(files.length)} file(s) into ${names.dist}/`);
|
|
7824
7877
|
const manifest = validateManifest(
|
|
7825
|
-
JSON.parse(readFileSync18(
|
|
7878
|
+
JSON.parse(readFileSync18(join25(destDir, "biffo.plugin.json"), "utf8"))
|
|
7826
7879
|
);
|
|
7827
7880
|
if (manifest.name !== names.slug) {
|
|
7828
7881
|
throw new Error(
|
|
@@ -7861,8 +7914,8 @@ async function createAndPushStandaloneRepo(org, names, destDir, options, deps) {
|
|
|
7861
7914
|
await deps.git.push(destDir, "dev", { token });
|
|
7862
7915
|
log.success(`Pushed dev to ${org}/${names.dist}`);
|
|
7863
7916
|
await github.setDefaultBranch(org, names.dist, "dev");
|
|
7864
|
-
const ciPath =
|
|
7865
|
-
const contexts =
|
|
7917
|
+
const ciPath = join25(destDir, ".github", "workflows", "ci.yml");
|
|
7918
|
+
const contexts = existsSync25(ciPath) ? workflowCheckContexts(readFileSync18(ciPath, "utf8")) : [];
|
|
7866
7919
|
if (contexts.length === 0) {
|
|
7867
7920
|
log.warn(
|
|
7868
7921
|
`Could not determine required status checks from ${ciPath} \u2014 skipping branch protection. Configure it manually on dev once you know the CI job names.`
|
|
@@ -7891,7 +7944,7 @@ async function registerInRegistrySources(names, cloneUrl, token, deps) {
|
|
|
7891
7944
|
let dir;
|
|
7892
7945
|
try {
|
|
7893
7946
|
dir = await deps.git.cloneForEditing(REGISTRY_REPO, "biffo-registry", token);
|
|
7894
|
-
const path =
|
|
7947
|
+
const path = join25(dir, "sources.json");
|
|
7895
7948
|
const file = JSON.parse(readFileSync18(path, "utf8"));
|
|
7896
7949
|
const next = addSource(file, {
|
|
7897
7950
|
name: names.slug,
|
|
@@ -8000,8 +8053,8 @@ function printStandaloneNextSteps(names, minor) {
|
|
|
8000
8053
|
}
|
|
8001
8054
|
function resolveSkeletonRoot(options) {
|
|
8002
8055
|
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
8003
|
-
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ??
|
|
8004
|
-
if (!
|
|
8056
|
+
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join25(options.cwd, "_skeletons", "plugin-template");
|
|
8057
|
+
if (!existsSync25(skeletonRoot)) {
|
|
8005
8058
|
throw new Error(
|
|
8006
8059
|
`Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
|
|
8007
8060
|
);
|
|
@@ -8176,14 +8229,14 @@ function printEntry(entry) {
|
|
|
8176
8229
|
}
|
|
8177
8230
|
|
|
8178
8231
|
// src/commands/plugin-install.ts
|
|
8179
|
-
import { cpSync as cpSync3, existsSync as
|
|
8180
|
-
import { basename, join as
|
|
8232
|
+
import { cpSync as cpSync3, existsSync as existsSync27, mkdirSync as mkdirSync9, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
|
|
8233
|
+
import { basename, join as join28, relative as relative3, resolve as resolve12 } from "path";
|
|
8181
8234
|
import chalk15 from "chalk";
|
|
8182
8235
|
import { Command as Command15 } from "commander";
|
|
8183
8236
|
|
|
8184
8237
|
// src/adapters/plugin-migrations/index.ts
|
|
8185
8238
|
import { execa as execa4 } from "execa";
|
|
8186
|
-
import { join as
|
|
8239
|
+
import { join as join26 } from "path";
|
|
8187
8240
|
var PluginMigrationsAdapter = class {
|
|
8188
8241
|
/**
|
|
8189
8242
|
* Generates migration file(s) for `pluginNames` (every discovered
|
|
@@ -8192,22 +8245,22 @@ var PluginMigrationsAdapter = class {
|
|
|
8192
8245
|
* or declared no tables.
|
|
8193
8246
|
*/
|
|
8194
8247
|
async generate(cwd, pluginNames) {
|
|
8195
|
-
const scriptPath =
|
|
8248
|
+
const scriptPath = join26(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
|
|
8196
8249
|
const args = [
|
|
8197
8250
|
"run",
|
|
8198
8251
|
"python",
|
|
8199
8252
|
scriptPath,
|
|
8200
8253
|
"--services-root",
|
|
8201
|
-
|
|
8254
|
+
join26(cwd, "services"),
|
|
8202
8255
|
"--versions-dir",
|
|
8203
|
-
|
|
8256
|
+
join26(cwd, "services", "api", "migrations", "versions")
|
|
8204
8257
|
];
|
|
8205
8258
|
for (const name of pluginNames ?? []) {
|
|
8206
8259
|
args.push("--plugin", name);
|
|
8207
8260
|
}
|
|
8208
8261
|
let result;
|
|
8209
8262
|
try {
|
|
8210
|
-
result = await execa4("uv", args, { cwd:
|
|
8263
|
+
result = await execa4("uv", args, { cwd: join26(cwd, "services", "api") });
|
|
8211
8264
|
} catch (err) {
|
|
8212
8265
|
const cause = err;
|
|
8213
8266
|
if (cause.code === "ENOENT") {
|
|
@@ -8224,8 +8277,8 @@ var PluginMigrationsAdapter = class {
|
|
|
8224
8277
|
};
|
|
8225
8278
|
|
|
8226
8279
|
// src/lib/plugin-workspace-sources.ts
|
|
8227
|
-
import { existsSync as
|
|
8228
|
-
import { join as
|
|
8280
|
+
import { existsSync as existsSync26, readdirSync as readdirSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
|
|
8281
|
+
import { join as join27 } from "path";
|
|
8229
8282
|
function readTomlStringArray(text, key) {
|
|
8230
8283
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
8231
8284
|
if (!open) return [];
|
|
@@ -8269,8 +8322,8 @@ function readDependencyNames(text) {
|
|
|
8269
8322
|
return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
|
|
8270
8323
|
}
|
|
8271
8324
|
function workspaceMemberNames(instanceRoot) {
|
|
8272
|
-
const rootPyproject =
|
|
8273
|
-
if (!
|
|
8325
|
+
const rootPyproject = join27(instanceRoot, "pyproject.toml");
|
|
8326
|
+
if (!existsSync26(rootPyproject)) return /* @__PURE__ */ new Set();
|
|
8274
8327
|
const text = readFileSync19(rootPyproject, "utf8");
|
|
8275
8328
|
const members = readTomlStringArray(text, "members");
|
|
8276
8329
|
const excluded = new Set(readTomlStringArray(text, "exclude"));
|
|
@@ -8280,7 +8333,7 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8280
8333
|
const base = member.slice(0, -2);
|
|
8281
8334
|
let entries;
|
|
8282
8335
|
try {
|
|
8283
|
-
entries = readdirSync12(
|
|
8336
|
+
entries = readdirSync12(join27(instanceRoot, base), { withFileTypes: true });
|
|
8284
8337
|
} catch {
|
|
8285
8338
|
continue;
|
|
8286
8339
|
}
|
|
@@ -8294,8 +8347,8 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8294
8347
|
}
|
|
8295
8348
|
const names = /* @__PURE__ */ new Set();
|
|
8296
8349
|
for (const dir of dirs) {
|
|
8297
|
-
const pp =
|
|
8298
|
-
if (!
|
|
8350
|
+
const pp = join27(instanceRoot, dir, "pyproject.toml");
|
|
8351
|
+
if (!existsSync26(pp)) continue;
|
|
8299
8352
|
const name = readProjectName(readFileSync19(pp, "utf8"));
|
|
8300
8353
|
if (name) names.add(name);
|
|
8301
8354
|
}
|
|
@@ -8307,7 +8360,7 @@ function existingWorkspaceSources(text) {
|
|
|
8307
8360
|
);
|
|
8308
8361
|
}
|
|
8309
8362
|
function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
|
|
8310
|
-
if (!
|
|
8363
|
+
if (!existsSync26(pluginPyprojectPath) || memberNames.size === 0) return [];
|
|
8311
8364
|
const text = readFileSync19(pluginPyprojectPath, "utf8");
|
|
8312
8365
|
const already = existingWorkspaceSources(text);
|
|
8313
8366
|
const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
|
|
@@ -8374,14 +8427,14 @@ var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
|
8374
8427
|
".terraform"
|
|
8375
8428
|
]);
|
|
8376
8429
|
function resolveLocalPlugin(localPath) {
|
|
8377
|
-
if (!
|
|
8430
|
+
if (!existsSync27(localPath)) {
|
|
8378
8431
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
8379
8432
|
}
|
|
8380
8433
|
if (!statSync6(localPath).isDirectory()) {
|
|
8381
8434
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
8382
8435
|
}
|
|
8383
|
-
const manifestPath =
|
|
8384
|
-
if (!
|
|
8436
|
+
const manifestPath = join28(localPath, "biffo.plugin.json");
|
|
8437
|
+
if (!existsSync27(manifestPath)) {
|
|
8385
8438
|
throw new Error(
|
|
8386
8439
|
`${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>\`.)`
|
|
8387
8440
|
);
|
|
@@ -8407,8 +8460,8 @@ function parsePluginTarget(target) {
|
|
|
8407
8460
|
async function cloneAndValidatePlugin(entry, git) {
|
|
8408
8461
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
8409
8462
|
try {
|
|
8410
|
-
const manifestPath =
|
|
8411
|
-
if (!
|
|
8463
|
+
const manifestPath = join28(tmpDir, "biffo.plugin.json");
|
|
8464
|
+
if (!existsSync27(manifestPath)) {
|
|
8412
8465
|
throw new Error(
|
|
8413
8466
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
8414
8467
|
);
|
|
@@ -8436,8 +8489,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8436
8489
|
`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\`).`
|
|
8437
8490
|
);
|
|
8438
8491
|
}
|
|
8439
|
-
const servicesDir =
|
|
8440
|
-
if (!
|
|
8492
|
+
const servicesDir = join28(options.cwd, "services");
|
|
8493
|
+
if (!existsSync27(servicesDir)) {
|
|
8441
8494
|
throw new Error(
|
|
8442
8495
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8443
8496
|
);
|
|
@@ -8455,10 +8508,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8455
8508
|
}
|
|
8456
8509
|
const pluginName = entry ? entry.name : source.name;
|
|
8457
8510
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
8458
|
-
const targetDir =
|
|
8459
|
-
const modulesDir =
|
|
8511
|
+
const targetDir = join28(options.cwd, relTargetDir);
|
|
8512
|
+
const modulesDir = join28(options.cwd, "modules", "plugins", pluginName);
|
|
8460
8513
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
8461
|
-
if (
|
|
8514
|
+
if (existsSync27(targetDir) && !inTreeSource) {
|
|
8462
8515
|
throw new Error(
|
|
8463
8516
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
8464
8517
|
);
|
|
@@ -8500,8 +8553,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8500
8553
|
});
|
|
8501
8554
|
log.success(`Installed plugin source at ${relTargetDir}/`);
|
|
8502
8555
|
}
|
|
8503
|
-
const pluginPyproject =
|
|
8504
|
-
if (
|
|
8556
|
+
const pluginPyproject = join28(targetDir, "pyproject.toml");
|
|
8557
|
+
if (existsSync27(pluginPyproject)) {
|
|
8505
8558
|
const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(options.cwd));
|
|
8506
8559
|
if (sourced.length > 0) {
|
|
8507
8560
|
log.info(
|
|
@@ -8510,8 +8563,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8510
8563
|
}
|
|
8511
8564
|
}
|
|
8512
8565
|
const stagePaths = [relTargetDir];
|
|
8513
|
-
const tfSourceDir =
|
|
8514
|
-
if (
|
|
8566
|
+
const tfSourceDir = join28(targetDir, "terraform");
|
|
8567
|
+
if (existsSync27(tfSourceDir)) {
|
|
8515
8568
|
mkdirSync9(modulesDir, { recursive: true });
|
|
8516
8569
|
cpSync3(tfSourceDir, modulesDir, { recursive: true });
|
|
8517
8570
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
@@ -8605,8 +8658,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
|
|
|
8605
8658
|
}
|
|
8606
8659
|
|
|
8607
8660
|
// src/commands/plugin-list.ts
|
|
8608
|
-
import { existsSync as
|
|
8609
|
-
import { join as
|
|
8661
|
+
import { existsSync as existsSync28, readFileSync as readFileSync21 } from "fs";
|
|
8662
|
+
import { join as join29, resolve as resolve13 } from "path";
|
|
8610
8663
|
import chalk16 from "chalk";
|
|
8611
8664
|
import { Command as Command16 } from "commander";
|
|
8612
8665
|
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) => {
|
|
@@ -8619,8 +8672,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
8619
8672
|
}
|
|
8620
8673
|
});
|
|
8621
8674
|
async function runPluginList(options) {
|
|
8622
|
-
const servicesDir =
|
|
8623
|
-
if (!
|
|
8675
|
+
const servicesDir = join29(options.cwd, "services");
|
|
8676
|
+
if (!existsSync28(servicesDir)) {
|
|
8624
8677
|
throw new Error(
|
|
8625
8678
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8626
8679
|
);
|
|
@@ -8665,8 +8718,8 @@ async function runPluginList(options) {
|
|
|
8665
8718
|
}
|
|
8666
8719
|
|
|
8667
8720
|
// src/commands/plugin-sync-migrations.ts
|
|
8668
|
-
import { existsSync as
|
|
8669
|
-
import { join as
|
|
8721
|
+
import { existsSync as existsSync29 } from "fs";
|
|
8722
|
+
import { join as join30, relative as relative4, resolve as resolve14 } from "path";
|
|
8670
8723
|
import chalk17 from "chalk";
|
|
8671
8724
|
import { Command as Command17 } from "commander";
|
|
8672
8725
|
var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
|
|
@@ -8687,11 +8740,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
|
|
|
8687
8740
|
}
|
|
8688
8741
|
);
|
|
8689
8742
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
8690
|
-
const servicesDir =
|
|
8691
|
-
if (!
|
|
8743
|
+
const servicesDir = join30(options.cwd, "services");
|
|
8744
|
+
if (!existsSync29(servicesDir)) {
|
|
8692
8745
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
8693
8746
|
}
|
|
8694
|
-
if (name && !
|
|
8747
|
+
if (name && !existsSync29(join30(servicesDir, name, "biffo.plugin.json"))) {
|
|
8695
8748
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
8696
8749
|
}
|
|
8697
8750
|
if (options.dryRun) {
|
|
@@ -8727,8 +8780,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
8727
8780
|
}
|
|
8728
8781
|
|
|
8729
8782
|
// src/commands/plugin-uninstall.ts
|
|
8730
|
-
import { existsSync as
|
|
8731
|
-
import { join as
|
|
8783
|
+
import { existsSync as existsSync30, readFileSync as readFileSync22, rmSync as rmSync8 } from "fs";
|
|
8784
|
+
import { join as join31, resolve as resolve15 } from "path";
|
|
8732
8785
|
import chalk18 from "chalk";
|
|
8733
8786
|
import { Command as Command18 } from "commander";
|
|
8734
8787
|
import inquirer6 from "inquirer";
|
|
@@ -8760,16 +8813,16 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
8760
8813
|
if (!NAME_PATTERN2.test(name)) {
|
|
8761
8814
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
8762
8815
|
}
|
|
8763
|
-
const servicesDir =
|
|
8764
|
-
if (!
|
|
8816
|
+
const servicesDir = join31(options.cwd, "services");
|
|
8817
|
+
if (!existsSync30(servicesDir)) {
|
|
8765
8818
|
throw new Error(
|
|
8766
8819
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8767
8820
|
);
|
|
8768
8821
|
}
|
|
8769
|
-
const targetDir =
|
|
8770
|
-
if (!
|
|
8771
|
-
const firstParty =
|
|
8772
|
-
if (
|
|
8822
|
+
const targetDir = join31(servicesDir, name);
|
|
8823
|
+
if (!existsSync30(targetDir)) {
|
|
8824
|
+
const firstParty = join31(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
8825
|
+
if (existsSync30(firstParty)) {
|
|
8773
8826
|
throw new Error(
|
|
8774
8827
|
`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.`
|
|
8775
8828
|
);
|
|
@@ -8777,9 +8830,9 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
8777
8830
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
8778
8831
|
}
|
|
8779
8832
|
const version = readInstalledVersion(targetDir);
|
|
8780
|
-
const modulesDir =
|
|
8833
|
+
const modulesDir = join31(options.cwd, "modules", "plugins", name);
|
|
8781
8834
|
const stagePaths = [`services/${name}`];
|
|
8782
|
-
if (
|
|
8835
|
+
if (existsSync30(modulesDir)) {
|
|
8783
8836
|
stagePaths.push(`modules/plugins/${name}`);
|
|
8784
8837
|
}
|
|
8785
8838
|
if (options.dryRun) {
|
|
@@ -8801,7 +8854,7 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
8801
8854
|
}
|
|
8802
8855
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
8803
8856
|
log.success(`Removed services/${name}/`);
|
|
8804
|
-
if (
|
|
8857
|
+
if (existsSync30(modulesDir)) {
|
|
8805
8858
|
rmSync8(modulesDir, { recursive: true, force: true });
|
|
8806
8859
|
log.success(`Removed modules/plugins/${name}/`);
|
|
8807
8860
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -8838,8 +8891,8 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
8838
8891
|
}
|
|
8839
8892
|
}
|
|
8840
8893
|
function readInstalledVersion(targetDir) {
|
|
8841
|
-
const manifestPath =
|
|
8842
|
-
if (!
|
|
8894
|
+
const manifestPath = join31(targetDir, "biffo.plugin.json");
|
|
8895
|
+
if (!existsSync30(manifestPath)) return void 0;
|
|
8843
8896
|
try {
|
|
8844
8897
|
return validateManifest(JSON.parse(readFileSync22(manifestPath, "utf8"))).version;
|
|
8845
8898
|
} catch {
|
|
@@ -8874,8 +8927,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
8874
8927
|
}
|
|
8875
8928
|
|
|
8876
8929
|
// src/commands/plugin-upgrade.ts
|
|
8877
|
-
import { cpSync as cpSync4, existsSync as
|
|
8878
|
-
import { join as
|
|
8930
|
+
import { cpSync as cpSync4, existsSync as existsSync31, mkdirSync as mkdirSync10, readFileSync as readFileSync23, rmSync as rmSync9 } from "fs";
|
|
8931
|
+
import { join as join32, relative as relative5, resolve as resolve16 } from "path";
|
|
8879
8932
|
import chalk19 from "chalk";
|
|
8880
8933
|
import { Command as Command19 } from "commander";
|
|
8881
8934
|
import inquirer7 from "inquirer";
|
|
@@ -8900,14 +8953,14 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
|
|
|
8900
8953
|
});
|
|
8901
8954
|
async function runPluginUpgrade(target, options, deps) {
|
|
8902
8955
|
const { name, minor } = parsePluginTarget(target);
|
|
8903
|
-
const servicesDir =
|
|
8904
|
-
if (!
|
|
8956
|
+
const servicesDir = join32(options.cwd, "services");
|
|
8957
|
+
if (!existsSync31(servicesDir)) {
|
|
8905
8958
|
throw new Error(
|
|
8906
8959
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8907
8960
|
);
|
|
8908
8961
|
}
|
|
8909
|
-
const targetDir =
|
|
8910
|
-
if (!
|
|
8962
|
+
const targetDir = join32(servicesDir, name);
|
|
8963
|
+
if (!existsSync31(targetDir)) {
|
|
8911
8964
|
throw new Error(
|
|
8912
8965
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
8913
8966
|
);
|
|
@@ -8921,7 +8974,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
8921
8974
|
`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.`
|
|
8922
8975
|
);
|
|
8923
8976
|
}
|
|
8924
|
-
const modulesDir =
|
|
8977
|
+
const modulesDir = join32(options.cwd, "modules", "plugins", entry.name);
|
|
8925
8978
|
if (options.dryRun) {
|
|
8926
8979
|
printDryRun6(entry, currentVersion);
|
|
8927
8980
|
return;
|
|
@@ -8954,11 +9007,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
8954
9007
|
cpSync4(tmpDir, targetDir, { recursive: true });
|
|
8955
9008
|
log.success(`Upgraded plugin source at services/${entry.name}/`);
|
|
8956
9009
|
const stagePaths = [`services/${entry.name}`];
|
|
8957
|
-
if (
|
|
9010
|
+
if (existsSync31(modulesDir)) {
|
|
8958
9011
|
rmSync9(modulesDir, { recursive: true, force: true });
|
|
8959
9012
|
}
|
|
8960
|
-
const tfSourceDir =
|
|
8961
|
-
if (
|
|
9013
|
+
const tfSourceDir = join32(targetDir, "terraform");
|
|
9014
|
+
if (existsSync31(tfSourceDir)) {
|
|
8962
9015
|
mkdirSync10(modulesDir, { recursive: true });
|
|
8963
9016
|
cpSync4(tfSourceDir, modulesDir, { recursive: true });
|
|
8964
9017
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
@@ -8992,8 +9045,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
8992
9045
|
}
|
|
8993
9046
|
}
|
|
8994
9047
|
function readInstalledVersion2(targetDir) {
|
|
8995
|
-
const manifestPath =
|
|
8996
|
-
if (!
|
|
9048
|
+
const manifestPath = join32(targetDir, "biffo.plugin.json");
|
|
9049
|
+
if (!existsSync31(manifestPath)) return void 0;
|
|
8997
9050
|
try {
|
|
8998
9051
|
return validateManifest(JSON.parse(readFileSync23(manifestPath, "utf8"))).version;
|
|
8999
9052
|
} catch {
|
|
@@ -9037,7 +9090,7 @@ pluginCommand.addCommand(pluginInfoCommand);
|
|
|
9037
9090
|
import { Command as Command22 } from "commander";
|
|
9038
9091
|
|
|
9039
9092
|
// src/commands/sibling-check-identity.ts
|
|
9040
|
-
import { existsSync as
|
|
9093
|
+
import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
|
|
9041
9094
|
import { resolve as resolve17 } from "path";
|
|
9042
9095
|
import chalk20 from "chalk";
|
|
9043
9096
|
import { Command as Command21 } from "commander";
|
|
@@ -9251,7 +9304,7 @@ async function resolveConfig4(options) {
|
|
|
9251
9304
|
return cfg;
|
|
9252
9305
|
}
|
|
9253
9306
|
const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
|
|
9254
|
-
if (
|
|
9307
|
+
if (existsSync32(localConfigPath)) {
|
|
9255
9308
|
const raw = JSON.parse(readFileSync24(localConfigPath, "utf8"));
|
|
9256
9309
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
9257
9310
|
if (result.success) return result.data;
|
|
@@ -9298,19 +9351,19 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
|
9298
9351
|
import { Command as Command23 } from "commander";
|
|
9299
9352
|
|
|
9300
9353
|
// src/scripts/check-adr-numbering.ts
|
|
9301
|
-
import { existsSync as
|
|
9302
|
-
import { join as
|
|
9354
|
+
import { existsSync as existsSync34 } from "fs";
|
|
9355
|
+
import { join as join34 } from "path";
|
|
9303
9356
|
import { execa as execa5 } from "execa";
|
|
9304
9357
|
|
|
9305
9358
|
// src/lib/adr-numbering-guard.ts
|
|
9306
|
-
import { existsSync as
|
|
9307
|
-
import { join as
|
|
9359
|
+
import { existsSync as existsSync33, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
|
|
9360
|
+
import { join as join33 } from "path";
|
|
9308
9361
|
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
9309
9362
|
var ALLOWLIST_FILENAME = ".numbering-allowlist";
|
|
9310
9363
|
var TEMPLATE_ADR_RESERVED_UPTO = "0099";
|
|
9311
9364
|
function readAdrNumberingAllowlist(adrDir) {
|
|
9312
|
-
const path =
|
|
9313
|
-
if (!
|
|
9365
|
+
const path = join33(adrDir, ALLOWLIST_FILENAME);
|
|
9366
|
+
if (!existsSync33(path)) return /* @__PURE__ */ new Set();
|
|
9314
9367
|
const numbers = /* @__PURE__ */ new Set();
|
|
9315
9368
|
for (const rawLine of readFileSync25(path, "utf8").split("\n")) {
|
|
9316
9369
|
const line = rawLine.split("#")[0].trim();
|
|
@@ -9320,7 +9373,7 @@ function readAdrNumberingAllowlist(adrDir) {
|
|
|
9320
9373
|
}
|
|
9321
9374
|
function adrNumbersIn(adrDir) {
|
|
9322
9375
|
const claims = /* @__PURE__ */ new Map();
|
|
9323
|
-
if (!
|
|
9376
|
+
if (!existsSync33(adrDir)) return claims;
|
|
9324
9377
|
for (const entry of readdirSync13(adrDir).sort()) {
|
|
9325
9378
|
const match = ADR_FILENAME.exec(entry);
|
|
9326
9379
|
if (!match) continue;
|
|
@@ -9375,8 +9428,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
|
|
|
9375
9428
|
// src/scripts/check-adr-numbering.ts
|
|
9376
9429
|
async function runAdrNumberingCheck() {
|
|
9377
9430
|
const root = (await execa5("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
9378
|
-
const adrDir =
|
|
9379
|
-
if (!
|
|
9431
|
+
const adrDir = join34(root, "docs", "ADR");
|
|
9432
|
+
if (!existsSync34(adrDir)) {
|
|
9380
9433
|
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
9381
9434
|
return;
|
|
9382
9435
|
}
|
|
@@ -9704,8 +9757,8 @@ async function runOwnershipCheck(argv) {
|
|
|
9704
9757
|
const { stdout } = await execa7("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
9705
9758
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
9706
9759
|
if (messageFile) {
|
|
9707
|
-
const { readFileSync: readFileSync28, existsSync:
|
|
9708
|
-
if (
|
|
9760
|
+
const { readFileSync: readFileSync28, existsSync: existsSync40 } = await import("fs");
|
|
9761
|
+
if (existsSync40(messageFile)) commitMessage = readFileSync28(messageFile, "utf8");
|
|
9709
9762
|
}
|
|
9710
9763
|
} else {
|
|
9711
9764
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -9806,33 +9859,33 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
9806
9859
|
}
|
|
9807
9860
|
|
|
9808
9861
|
// src/scripts/check-plugin-collisions.ts
|
|
9809
|
-
import { existsSync as
|
|
9810
|
-
import { join as
|
|
9862
|
+
import { existsSync as existsSync36 } from "fs";
|
|
9863
|
+
import { join as join36 } from "path";
|
|
9811
9864
|
import { execa as execa8 } from "execa";
|
|
9812
9865
|
|
|
9813
9866
|
// src/lib/plugin-collision-guard.ts
|
|
9814
|
-
import { existsSync as
|
|
9815
|
-
import { join as
|
|
9867
|
+
import { existsSync as existsSync35, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
|
|
9868
|
+
import { join as join35 } from "path";
|
|
9816
9869
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
9817
9870
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
9818
9871
|
function subdirectories(dir) {
|
|
9819
|
-
if (!
|
|
9872
|
+
if (!existsSync35(dir)) return [];
|
|
9820
9873
|
return readdirSync14(dir).filter((entry) => {
|
|
9821
9874
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
9822
9875
|
try {
|
|
9823
|
-
return statSync7(
|
|
9876
|
+
return statSync7(join35(dir, entry)).isDirectory();
|
|
9824
9877
|
} catch {
|
|
9825
9878
|
return false;
|
|
9826
9879
|
}
|
|
9827
9880
|
});
|
|
9828
9881
|
}
|
|
9829
9882
|
function regularPackagesOf(pluginDir2) {
|
|
9830
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
9883
|
+
return subdirectories(pluginDir2).filter((name) => existsSync35(join35(pluginDir2, name, "__init__.py"))).sort();
|
|
9831
9884
|
}
|
|
9832
9885
|
function bareTestModulesOf(pluginDir2) {
|
|
9833
|
-
const testsDir =
|
|
9834
|
-
if (!
|
|
9835
|
-
if (
|
|
9886
|
+
const testsDir = join35(pluginDir2, "tests");
|
|
9887
|
+
if (!existsSync35(testsDir)) return [];
|
|
9888
|
+
if (existsSync35(join35(testsDir, "__init__.py"))) return [];
|
|
9836
9889
|
return readdirSync14(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
9837
9890
|
}
|
|
9838
9891
|
function findCollisions(servicesDir, pluginDirs) {
|
|
@@ -9841,7 +9894,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
9841
9894
|
const gather = (kind, namesOf) => {
|
|
9842
9895
|
const claims = /* @__PURE__ */ new Map();
|
|
9843
9896
|
for (const plugin of plugins) {
|
|
9844
|
-
for (const name of namesOf(
|
|
9897
|
+
for (const name of namesOf(join35(servicesDir, plugin))) {
|
|
9845
9898
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
9846
9899
|
}
|
|
9847
9900
|
}
|
|
@@ -9879,8 +9932,8 @@ function formatCollisions(collisions) {
|
|
|
9879
9932
|
// src/scripts/check-plugin-collisions.ts
|
|
9880
9933
|
async function runPluginCollisionCheck() {
|
|
9881
9934
|
const root = (await execa8("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
9882
|
-
const servicesDir =
|
|
9883
|
-
if (!
|
|
9935
|
+
const servicesDir = join36(root, "services");
|
|
9936
|
+
if (!existsSync36(servicesDir)) {
|
|
9884
9937
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
9885
9938
|
return;
|
|
9886
9939
|
}
|
|
@@ -9900,8 +9953,8 @@ async function runPluginCollisionCheck() {
|
|
|
9900
9953
|
import { execa as execa9 } from "execa";
|
|
9901
9954
|
|
|
9902
9955
|
// src/lib/plugin-terraform-guard.ts
|
|
9903
|
-
import { existsSync as
|
|
9904
|
-
import { dirname as dirname9, join as
|
|
9956
|
+
import { existsSync as existsSync37, readFileSync as readFileSync26, readdirSync as readdirSync15 } from "fs";
|
|
9957
|
+
import { dirname as dirname9, join as join37, relative as relative6, sep as sep3 } from "path";
|
|
9905
9958
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
9906
9959
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
9907
9960
|
function findPluginManifests(root) {
|
|
@@ -9916,9 +9969,9 @@ function findPluginManifests(root) {
|
|
|
9916
9969
|
for (const entry of entries) {
|
|
9917
9970
|
if (entry.isDirectory()) {
|
|
9918
9971
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
9919
|
-
walk(
|
|
9972
|
+
walk(join37(dir, entry.name));
|
|
9920
9973
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
9921
|
-
found.push(relative6(root,
|
|
9974
|
+
found.push(relative6(root, join37(dir, entry.name)).split(sep3).join("/"));
|
|
9922
9975
|
}
|
|
9923
9976
|
}
|
|
9924
9977
|
};
|
|
@@ -9943,14 +9996,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
9943
9996
|
}
|
|
9944
9997
|
function checkPluginTerraform(root) {
|
|
9945
9998
|
const violations = [];
|
|
9946
|
-
const coreManifest =
|
|
9999
|
+
const coreManifest = existsSync37(join37(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
9947
10000
|
for (const manifest of findPluginManifests(root)) {
|
|
9948
10001
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
9949
|
-
const absManifest =
|
|
10002
|
+
const absManifest = join37(root, manifest);
|
|
9950
10003
|
const subscriptions = readSubscriptions(absManifest);
|
|
9951
10004
|
if (subscriptions === null) continue;
|
|
9952
10005
|
const pluginDir2 = dirname9(absManifest);
|
|
9953
|
-
if (
|
|
10006
|
+
if (existsSync37(join37(pluginDir2, "terraform"))) continue;
|
|
9954
10007
|
const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
|
|
9955
10008
|
violations.push({
|
|
9956
10009
|
manifest,
|
|
@@ -10146,8 +10199,8 @@ function rawArgsAfter(subcommand) {
|
|
|
10146
10199
|
}
|
|
10147
10200
|
|
|
10148
10201
|
// src/commands/doctor.ts
|
|
10149
|
-
import { existsSync as
|
|
10150
|
-
import { join as
|
|
10202
|
+
import { existsSync as existsSync38, readFileSync as readFileSync27 } from "fs";
|
|
10203
|
+
import { join as join38, resolve as resolve18 } from "path";
|
|
10151
10204
|
import chalk21 from "chalk";
|
|
10152
10205
|
import { Command as Command24 } from "commander";
|
|
10153
10206
|
|
|
@@ -10322,8 +10375,8 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
10322
10375
|
return runDoctorChecks(facts);
|
|
10323
10376
|
}
|
|
10324
10377
|
function readLocalCoreVersion(cwd) {
|
|
10325
|
-
const path =
|
|
10326
|
-
if (!
|
|
10378
|
+
const path = join38(cwd, INSTANCE_CORE_FILE);
|
|
10379
|
+
if (!existsSync38(path)) return null;
|
|
10327
10380
|
try {
|
|
10328
10381
|
return parseCoreRecord(readFileSync27(path, "utf8"));
|
|
10329
10382
|
} catch {
|
|
@@ -10340,8 +10393,8 @@ function parseCoreRecord(contents) {
|
|
|
10340
10393
|
}
|
|
10341
10394
|
}
|
|
10342
10395
|
function readFossil(cwd) {
|
|
10343
|
-
const path =
|
|
10344
|
-
if (!
|
|
10396
|
+
const path = join38(cwd, CORE_VERSION_FILE);
|
|
10397
|
+
if (!existsSync38(path)) return null;
|
|
10345
10398
|
try {
|
|
10346
10399
|
const value = readFileSync27(path, "utf8").trim();
|
|
10347
10400
|
return value === "" ? null : value;
|
|
@@ -10792,13 +10845,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
10792
10845
|
import { Command as Command26 } from "commander";
|
|
10793
10846
|
|
|
10794
10847
|
// src/lib/packaged-scripts.ts
|
|
10795
|
-
import { existsSync as
|
|
10796
|
-
import { dirname as dirname10, join as
|
|
10848
|
+
import { existsSync as existsSync39 } from "fs";
|
|
10849
|
+
import { dirname as dirname10, join as join39 } from "path";
|
|
10797
10850
|
function findPackagedScript(startDir, relativePath) {
|
|
10798
10851
|
let dir = startDir;
|
|
10799
10852
|
for (; ; ) {
|
|
10800
|
-
const candidate =
|
|
10801
|
-
if (
|
|
10853
|
+
const candidate = join39(dir, relativePath);
|
|
10854
|
+
if (existsSync39(candidate)) return candidate;
|
|
10802
10855
|
const parent = dirname10(dir);
|
|
10803
10856
|
if (parent === dir) return null;
|
|
10804
10857
|
dir = parent;
|