@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.
Files changed (2) hide show
  1. package/dist/index.js +93 -7
  2. 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 writeFileSync9 } from "fs";
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
- writeFileSync9(pluginPyprojectPath, updated);
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
- severity: "error",
9147
- detail: `The primary checkout 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.`,
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.167.0",
3
+ "version": "0.168.0",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",