@biffo/cli 0.167.0 → 0.168.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 +93 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -948,6 +948,27 @@ var GitAdapter = class {
|
|
|
948
948
|
async fetchPrune(cwd, remote = "origin") {
|
|
949
949
|
await execa2("git", ["fetch", "--quiet", "--prune", remote], { cwd, reject: false });
|
|
950
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Is `cwd` the primary checkout, rather than a linked worktree?
|
|
953
|
+
*
|
|
954
|
+
* The distinction decides whether being off the integration branch is a
|
|
955
|
+
* defect or the mandated state: AGENTS.md §1 requires all work to happen in a
|
|
956
|
+
* worktree on its own branch, while §2 requires the primary to stay on `dev`.
|
|
957
|
+
* Reporting the former as a problem is a false positive in the one place
|
|
958
|
+
* everybody works.
|
|
959
|
+
*
|
|
960
|
+
* A linked worktree's git dir points inside `.git/worktrees/<name>`, while the
|
|
961
|
+
* common dir is the shared `.git`. They are equal only in the primary.
|
|
962
|
+
*/
|
|
963
|
+
async isPrimaryWorktree(cwd) {
|
|
964
|
+
const opts = { cwd, reject: false };
|
|
965
|
+
const [dir, common] = await Promise.all([
|
|
966
|
+
execa2("git", ["rev-parse", "--absolute-git-dir"], opts),
|
|
967
|
+
execa2("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], opts)
|
|
968
|
+
]);
|
|
969
|
+
if (dir.exitCode !== 0 || common.exitCode !== 0) return true;
|
|
970
|
+
return dir.stdout.trim() === common.stdout.trim();
|
|
971
|
+
}
|
|
951
972
|
/**
|
|
952
973
|
* Worktrees other than the primary, with the branch each is on (#797).
|
|
953
974
|
*
|
|
@@ -6498,12 +6519,26 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
6498
6519
|
import { Command as Command20 } from "commander";
|
|
6499
6520
|
|
|
6500
6521
|
// src/commands/plugin-create.ts
|
|
6501
|
-
import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
|
|
6522
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
|
|
6502
6523
|
import { dirname as dirname8, join as join23, resolve as resolve11 } from "path";
|
|
6503
6524
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
6504
6525
|
import chalk13 from "chalk";
|
|
6505
6526
|
import { Command as Command13 } from "commander";
|
|
6506
6527
|
|
|
6528
|
+
// src/lib/registry-sources.ts
|
|
6529
|
+
function manifestUrlFor(repoUrl, branch = "dev") {
|
|
6530
|
+
const slug = repoUrl.replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
|
|
6531
|
+
return `https://raw.githubusercontent.com/${slug}/${branch}/biffo.plugin.json`;
|
|
6532
|
+
}
|
|
6533
|
+
function addSource(file, source) {
|
|
6534
|
+
if (file.sources.some((s) => s.name === source.name)) return null;
|
|
6535
|
+
return { ...file, sources: [...file.sources, source] };
|
|
6536
|
+
}
|
|
6537
|
+
function serialiseSources(file) {
|
|
6538
|
+
return `${JSON.stringify(file, null, 2)}
|
|
6539
|
+
`;
|
|
6540
|
+
}
|
|
6541
|
+
|
|
6507
6542
|
// src/lib/workflow-check-contexts.ts
|
|
6508
6543
|
function unquote(value) {
|
|
6509
6544
|
const trimmed = value.trim();
|
|
@@ -6912,7 +6947,7 @@ var pluginCreateCommand = new Command13("create").description("Scaffold a new pl
|
|
|
6912
6947
|
).option(
|
|
6913
6948
|
"--runner-label <label>",
|
|
6914
6949
|
"With --org, the RUNNER_LABEL the new repo\u2019s CI should run on (defaults to mirroring this checkout\u2019s)"
|
|
6915
|
-
).option(
|
|
6950
|
+
).option("--no-register", "With --org, skip adding the plugin to the registry\u2019s sources.json").option(
|
|
6916
6951
|
"--skeleton <path>",
|
|
6917
6952
|
"Path to the plugin skeleton (defaults to _skeletons/plugin-template)"
|
|
6918
6953
|
).option("--dry-run", "Print planned changes without modifying the repo").option("--no-commit", "Scaffold the files but leave them uncommitted").option("--cwd <path>", "Project root to scaffold into (defaults to the current directory)").action(
|
|
@@ -6926,6 +6961,7 @@ var pluginCreateCommand = new Command13("create").description("Scaffold a new pl
|
|
|
6926
6961
|
standalone: options.standalone ?? false,
|
|
6927
6962
|
...options.org ? { org: options.org } : {},
|
|
6928
6963
|
...options.runnerLabel ? { runnerLabel: options.runnerLabel } : {},
|
|
6964
|
+
register: options.register !== false,
|
|
6929
6965
|
...options.skeleton ? { skeletonRoot: resolve11(options.skeleton) } : {},
|
|
6930
6966
|
dryRun: options.dryRun ?? false,
|
|
6931
6967
|
commit: options.commit !== false,
|
|
@@ -7080,8 +7116,41 @@ async function createAndPushStandaloneRepo(org, names, destDir, options, deps) {
|
|
|
7080
7116
|
} else {
|
|
7081
7117
|
await github.protectSingleBranch(org, names.dist, "dev", contexts);
|
|
7082
7118
|
}
|
|
7119
|
+
if (options.register !== false) {
|
|
7120
|
+
await registerInRegistrySources(names, cloneUrl, token, deps);
|
|
7121
|
+
}
|
|
7083
7122
|
printStandaloneRemoteNextSteps(names, org);
|
|
7084
7123
|
}
|
|
7124
|
+
var REGISTRY_REPO = "https://github.com/keiranholloway/biffo-plugins-registry";
|
|
7125
|
+
async function registerInRegistrySources(names, cloneUrl, token, deps) {
|
|
7126
|
+
let dir;
|
|
7127
|
+
try {
|
|
7128
|
+
dir = await deps.git.cloneForEditing(REGISTRY_REPO, "biffo-registry", token);
|
|
7129
|
+
const path = join23(dir, "sources.json");
|
|
7130
|
+
const file = JSON.parse(readFileSync17(path, "utf8"));
|
|
7131
|
+
const next = addSource(file, {
|
|
7132
|
+
name: names.slug,
|
|
7133
|
+
repo: cloneUrl.replace(/\.git$/, ""),
|
|
7134
|
+
manifest: manifestUrlFor(cloneUrl),
|
|
7135
|
+
tags: []
|
|
7136
|
+
});
|
|
7137
|
+
if (next === null) {
|
|
7138
|
+
log.info(`${names.slug} is already listed in the registry's sources.json`);
|
|
7139
|
+
return;
|
|
7140
|
+
}
|
|
7141
|
+
writeFileSync9(path, serialiseSources(next));
|
|
7142
|
+
await deps.git.add(dir, ["sources.json"]);
|
|
7143
|
+
await deps.git.commit(dir, `feat(registry): track ${names.slug} in sources.json`);
|
|
7144
|
+
await deps.git.push(dir, "main", { token });
|
|
7145
|
+
log.success(`Registered ${names.slug} in the plugin registry's sources.json`);
|
|
7146
|
+
} catch (err) {
|
|
7147
|
+
log.warn(
|
|
7148
|
+
`Could not add ${names.slug} to the registry's sources.json: ${err.message}. Until it is listed there (or REGISTRY_PUBLISH_TOKEN is set on the new repo), the plugin will not appear in the portal's plugin store.`
|
|
7149
|
+
);
|
|
7150
|
+
} finally {
|
|
7151
|
+
if (dir !== void 0) deps.git.cleanup(dir);
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7085
7154
|
async function propagateRunnerLabel(org, repo, options, deps, github) {
|
|
7086
7155
|
try {
|
|
7087
7156
|
let label = options.runnerLabel?.trim();
|
|
@@ -7390,7 +7459,7 @@ var PluginMigrationsAdapter = class {
|
|
|
7390
7459
|
};
|
|
7391
7460
|
|
|
7392
7461
|
// src/lib/plugin-workspace-sources.ts
|
|
7393
|
-
import { existsSync as existsSync24, readdirSync as readdirSync12, readFileSync as readFileSync18, writeFileSync as
|
|
7462
|
+
import { existsSync as existsSync24, readdirSync as readdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
7394
7463
|
import { join as join25 } from "path";
|
|
7395
7464
|
function readTomlStringArray(text, key) {
|
|
7396
7465
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
@@ -7494,7 +7563,7 @@ ${lines.join("\n")}${text.slice(insertAt)}`;
|
|
|
7494
7563
|
${lines.join("\n")}
|
|
7495
7564
|
`;
|
|
7496
7565
|
}
|
|
7497
|
-
|
|
7566
|
+
writeFileSync10(pluginPyprojectPath, updated);
|
|
7498
7567
|
return toAdd;
|
|
7499
7568
|
}
|
|
7500
7569
|
|
|
@@ -9131,7 +9200,7 @@ function checkCheckoutCurrency(facts) {
|
|
|
9131
9200
|
});
|
|
9132
9201
|
return findings;
|
|
9133
9202
|
}
|
|
9134
|
-
if (facts.currentBranch !== facts.integrationBranch) {
|
|
9203
|
+
if (facts.isPrimary && facts.currentBranch !== facts.integrationBranch) {
|
|
9135
9204
|
findings.push({
|
|
9136
9205
|
check: "checkout-off-integration",
|
|
9137
9206
|
severity: "error",
|
|
@@ -9139,12 +9208,24 @@ function checkCheckoutCurrency(facts) {
|
|
|
9139
9208
|
remedy: `git switch ${facts.integrationBranch} && git pull (do the work in a worktree instead)`
|
|
9140
9209
|
});
|
|
9141
9210
|
}
|
|
9211
|
+
if (facts.isPrimary && facts.isDirty) {
|
|
9212
|
+
findings.push({
|
|
9213
|
+
check: "checkout-dirty",
|
|
9214
|
+
severity: "warn",
|
|
9215
|
+
detail: "The primary checkout has uncommitted changes, so what it contains is neither the integration branch nor anything reviewed. Editing the primary directly is what AGENTS.md \xA71 exists to prevent; work belongs in a worktree.",
|
|
9216
|
+
remedy: 'git stash push -m "<what this is>" or commit it on a branch, then work in a worktree'
|
|
9217
|
+
});
|
|
9218
|
+
}
|
|
9142
9219
|
if (facts.hasUpstream && facts.behind > 0) {
|
|
9143
9220
|
const diverged = facts.ahead > 0 ? `, and ${String(facts.ahead)} ahead (diverged)` : "";
|
|
9221
|
+
const where = facts.isPrimary ? "The primary checkout" : "This worktree";
|
|
9144
9222
|
findings.push({
|
|
9145
9223
|
check: "checkout-behind",
|
|
9146
|
-
|
|
9147
|
-
|
|
9224
|
+
// In a worktree, behind-its-own-upstream means someone else pushed to the
|
|
9225
|
+
// branch — worth knowing, but not a reason to distrust everything read
|
|
9226
|
+
// from it the way a stale primary is.
|
|
9227
|
+
severity: facts.isPrimary ? "error" : "warn",
|
|
9228
|
+
detail: `${where} is ${String(facts.behind)} commit(s) behind its upstream${diverged}. Every file read from it may be stale, including the ones that look like authoritative state.`,
|
|
9148
9229
|
remedy: "git pull --ff-only"
|
|
9149
9230
|
});
|
|
9150
9231
|
}
|
|
@@ -9153,6 +9234,7 @@ function checkCheckoutCurrency(facts) {
|
|
|
9153
9234
|
function checkCoreVersionCurrency(facts) {
|
|
9154
9235
|
if (facts.localCoreVersion === null || facts.remoteCoreVersion === null) return [];
|
|
9155
9236
|
if (facts.localCoreVersion === facts.remoteCoreVersion) return [];
|
|
9237
|
+
if (!facts.isPrimary) return [];
|
|
9156
9238
|
return [
|
|
9157
9239
|
{
|
|
9158
9240
|
check: "core-version-stale",
|
|
@@ -9247,6 +9329,8 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
9247
9329
|
}
|
|
9248
9330
|
if (options.fetch) await git.fetchPrune(options.cwd);
|
|
9249
9331
|
const currentBranch = await git.currentBranch(options.cwd);
|
|
9332
|
+
const isPrimary = await git.isPrimaryWorktree(options.cwd);
|
|
9333
|
+
const isDirty = await git.hasUncommittedChanges(options.cwd);
|
|
9250
9334
|
const { ahead, behind, hasUpstream } = await git.aheadBehind(options.cwd);
|
|
9251
9335
|
const branches = await git.listBranchRefs(options.cwd);
|
|
9252
9336
|
const worktreePaths = await git.listWorktrees(options.cwd);
|
|
@@ -9258,10 +9342,12 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
9258
9342
|
);
|
|
9259
9343
|
const facts = {
|
|
9260
9344
|
currentBranch,
|
|
9345
|
+
isPrimary,
|
|
9261
9346
|
integrationBranch: INTEGRATION_BRANCH,
|
|
9262
9347
|
ahead,
|
|
9263
9348
|
behind,
|
|
9264
9349
|
hasUpstream,
|
|
9350
|
+
isDirty,
|
|
9265
9351
|
localCoreVersion: readLocalCoreVersion(options.cwd),
|
|
9266
9352
|
remoteCoreVersion: parseCoreRecord(
|
|
9267
9353
|
await git.showFileAtRef(options.cwd, `origin/${INTEGRATION_BRANCH}`, INSTANCE_CORE_FILE)
|