@webappwiz/arbor 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -246,6 +246,10 @@ export default defineConfig({
246
246
  });
247
247
  ```
248
248
 
249
+ `trunk` is the one key worth leaving out: unset, arbor takes the branch
250
+ `refs/remotes/origin/HEAD` points at, so a repo on `master` needs no config at
251
+ all, and falls back to `main` when there is no such ref to read.
252
+
249
253
  The hooks are named for the git events they sit around: `postCheckout` runs
250
254
  once, when `add` checks the worktree out; `postRewrite` runs after every rebase
251
255
  `merge` does; `preMerge` runs after that, and is the last thing between the
package/git.d.ts CHANGED
@@ -25,6 +25,11 @@ export declare class Git {
25
25
  run(cwd: string, ...args: string[]): Promise<GitResult>;
26
26
  out(cwd: string, ...args: string[]): Promise<string>;
27
27
  branchExists(branch: string): Promise<boolean>;
28
+ /**
29
+ * The branch `origin/HEAD` points at, or null when the repo has no such ref:
30
+ * no remote, or a clone that never fetched it.
31
+ */
32
+ defaultBranch(): Promise<string | null>;
28
33
  currentBranch(cwd: string): Promise<string>;
29
34
  head(cwd: string): Promise<string>;
30
35
  shortHead(cwd: string): Promise<string>;
package/index.js CHANGED
@@ -83,9 +83,8 @@ async function add({
83
83
  }
84
84
  const added = await service.add(task, { base });
85
85
  if (added.code !== 0) {
86
- fail("usage", `git worktree add failed: ${added.stderr}`, {
87
- task
88
- });
86
+ const missingTrunk = base === config.trunk && !await service.git.branchExists(base);
87
+ fail("usage", missingTrunk ? `trunk '${base}' is not a branch in this repo: set \`trunk\` in arbor.config.ts` : `git worktree add failed: ${added.stderr}`, { task });
89
88
  }
90
89
  const worktree = await (await service.find(task)).take({ base });
91
90
  const info = `${await service.git.commonDir()}/info`;
@@ -698,6 +697,10 @@ class Git {
698
697
  const { code } = await this.run(this.root, "rev-parse", "--verify", "--quiet", `refs/heads/${branch}`);
699
698
  return code === 0;
700
699
  }
700
+ async defaultBranch() {
701
+ const { code, stdout } = await this.run(this.root, "symbolic-ref", "--short", "refs/remotes/origin/HEAD");
702
+ return code === 0 ? stdout.replace(/^origin\//, "") || null : null;
703
+ }
701
704
  currentBranch(cwd) {
702
705
  return this.out(cwd, "rev-parse", "--abbrev-ref", "HEAD");
703
706
  }
@@ -832,11 +835,13 @@ class Journal {
832
835
  import { basename, resolve } from "node:path";
833
836
  import { NodeFs as NodeFs5 } from "webappwiz/system";
834
837
  async function loadConfig(root, opts = {}) {
835
- return { ...defaults(root), ...await file(opts.fs ?? new NodeFs5, root) };
838
+ const found = await file(opts.fs ?? new NodeFs5, root);
839
+ const trunk = found.trunk ?? await (opts.git ?? new Git(root)).defaultBranch() ?? "main";
840
+ return { ...defaults(root, trunk), ...found };
836
841
  }
837
- function defaults(root) {
842
+ function defaults(root, trunk) {
838
843
  return {
839
- trunk: "main",
844
+ trunk,
840
845
  worktreeRoot: resolve(root, "..", `${basename(root)}-arbor`),
841
846
  postCheckout: null,
842
847
  postRewrite: null,
@@ -852,7 +857,9 @@ async function file(fs, root) {
852
857
  if (!await fs.exists(path2)) {
853
858
  return {};
854
859
  }
855
- const mod = await import(path2);
860
+ const mod = await import(path2).catch((cause) => {
861
+ throw new Error(`could not load ${path2}: ${cause}. Its imports may be stale: try \`bun install\` in ${root}.`, { cause });
862
+ });
856
863
  return mod.default ?? {};
857
864
  }
858
865
 
@@ -1150,8 +1157,8 @@ function repository(at) {
1150
1157
  }
1151
1158
  const gitDir = stdout.trim();
1152
1159
  const arborDir = `${gitDir}/arbor`;
1153
- const config = await loadConfig(dirname(gitDir), { fs });
1154
1160
  const git = new Git(dirname(gitDir), { ps, fs });
1161
+ const config = await loadConfig(dirname(gitDir), { fs, git });
1155
1162
  const service = new WorktreeService(git, config, arborDir, { fs, ps });
1156
1163
  await service.init();
1157
1164
  await next({
package/load-config.d.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  import { type Fs } from "webappwiz/system";
2
2
  import type { Config } from "./config.js";
3
+ import { Git } from "./git.js";
3
4
  export interface LoadConfigOptions {
4
5
  /** What `arbor.config.ts` is looked for through; the real one by default. */
5
6
  fs?: Fs;
7
+ /** What the trunk is detected through; the repo's own git by default. */
8
+ git?: Git;
6
9
  }
10
+ /**
11
+ * The settings arbor runs a repo with: what `arbor.config.ts` names, then the
12
+ * branch `origin/HEAD` points at for the trunk, then arbor's own defaults.
13
+ * Throws when the config file is there but cannot be imported.
14
+ */
7
15
  export declare function loadConfig(root: string, opts?: LoadConfigOptions): Promise<Config>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webappwiz/arbor",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Runs several AI coding agents on one repository at once, each in its own git worktree",
5
5
  "license": "MIT",
6
6
  "author": "Jared Johnson",
@@ -17,7 +17,7 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "webappwiz": "^0.0.1"
20
+ "webappwiz": "^0.0.2"
21
21
  },
22
22
  "main": "./index.js",
23
23
  "types": "./index.d.ts",