@bakers-build/addon-herdr 0.1.2-next.93 → 0.2.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/src/shared.d.ts +19 -0
- package/dist/src/shared.js +72 -0
- package/dist/src/workspace.d.ts +2 -17
- package/dist/src/workspace.js +8 -75
- package/package.json +6 -5
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { renderTemplate } from './config.js';
|
|
2
|
+
export { restoreWritable } from './fs.js';
|
|
3
|
+
export { type LaunchScript, type LaunchWatchOptions, watchLaunch, writeLaunchScript, } from './launch-script.js';
|
|
4
|
+
export { truncateOpenTabTitle, validateBranch } from './workspace.js';
|
|
5
|
+
/** Runs `git <args>`; resolves with stdout, rejects on non-zero exit. */
|
|
6
|
+
export type BaseRefGitExec = (args: string[]) => Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve (and fetch) the base ref a NEW worktree branch is created from, so a
|
|
9
|
+
* run starts from the pushed remote state, not the host's local branch.
|
|
10
|
+
*
|
|
11
|
+
* A ticket's `override` (its `origin/<base_branch>`) wins only when it resolves
|
|
12
|
+
* on the remote after a fetch; otherwise the configured `baseBranch`, else the
|
|
13
|
+
* upstream of the repo's HEAD (e.g. `origin/develop`). A repo with no upstream
|
|
14
|
+
* yields `undefined`. Fetch failures are non-fatal (offline / unknown remote).
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveBaseRef(root: string, execGit: BaseRefGitExec, opts: {
|
|
17
|
+
baseBranch?: string;
|
|
18
|
+
override?: string;
|
|
19
|
+
}): Promise<string | undefined>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// The herdr pieces a sibling host addon (`@bakers-build/addon-orca`) reuses
|
|
2
|
+
// rather than copies: the 0600 launch wrapper + its watch, the label template,
|
|
3
|
+
// the Drupal write-bit restore and the worktree base-ref resolution.
|
|
4
|
+
export { renderTemplate } from './config.js';
|
|
5
|
+
export { restoreWritable } from './fs.js';
|
|
6
|
+
export { watchLaunch, writeLaunchScript, } from './launch-script.js';
|
|
7
|
+
export { truncateOpenTabTitle, validateBranch } from './workspace.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resolve (and fetch) the base ref a NEW worktree branch is created from, so a
|
|
10
|
+
* run starts from the pushed remote state, not the host's local branch.
|
|
11
|
+
*
|
|
12
|
+
* A ticket's `override` (its `origin/<base_branch>`) wins only when it resolves
|
|
13
|
+
* on the remote after a fetch; otherwise the configured `baseBranch`, else the
|
|
14
|
+
* upstream of the repo's HEAD (e.g. `origin/develop`). A repo with no upstream
|
|
15
|
+
* yields `undefined`. Fetch failures are non-fatal (offline / unknown remote).
|
|
16
|
+
*/
|
|
17
|
+
export async function resolveBaseRef(root, execGit, opts) {
|
|
18
|
+
const fetch = async (ref) => {
|
|
19
|
+
const slash = ref.indexOf('/');
|
|
20
|
+
if (slash <= 0)
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
await execGit([
|
|
24
|
+
'-C',
|
|
25
|
+
root,
|
|
26
|
+
'fetch',
|
|
27
|
+
ref.slice(0, slash),
|
|
28
|
+
ref.slice(slash + 1),
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Non-fatal: use whatever the local tracking ref already points at.
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
if (opts.override !== undefined) {
|
|
36
|
+
await fetch(opts.override);
|
|
37
|
+
try {
|
|
38
|
+
await execGit([
|
|
39
|
+
'-C',
|
|
40
|
+
root,
|
|
41
|
+
'rev-parse',
|
|
42
|
+
'--verify',
|
|
43
|
+
'--quiet',
|
|
44
|
+
`${opts.override}^{commit}`,
|
|
45
|
+
]);
|
|
46
|
+
return opts.override;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Not on the remote — fall back to the default base below.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let ref = opts.baseBranch;
|
|
53
|
+
if (!ref) {
|
|
54
|
+
try {
|
|
55
|
+
ref =
|
|
56
|
+
(await execGit([
|
|
57
|
+
'-C',
|
|
58
|
+
root,
|
|
59
|
+
'rev-parse',
|
|
60
|
+
'--abbrev-ref',
|
|
61
|
+
'--symbolic-full-name',
|
|
62
|
+
'@{u}',
|
|
63
|
+
])).trim() || undefined;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (ref !== undefined)
|
|
70
|
+
await fetch(ref);
|
|
71
|
+
return ref;
|
|
72
|
+
}
|
package/dist/src/workspace.d.ts
CHANGED
|
@@ -89,6 +89,8 @@ export interface HerdrWorkspaceOptions {
|
|
|
89
89
|
/** Optional logger for the best-effort open-layout path (defaults to noop). */
|
|
90
90
|
logger?: ConductorLogger;
|
|
91
91
|
}
|
|
92
|
+
/** Git-safe branch validation: safe ref chars only, no leading dot, no `..`. */
|
|
93
|
+
export declare function validateBranch(branch: string): void;
|
|
92
94
|
export declare class HerdrWorkspace implements BbWorkspace {
|
|
93
95
|
private readonly options;
|
|
94
96
|
private readonly execHerdr;
|
|
@@ -106,23 +108,6 @@ export declare class HerdrWorkspace implements BbWorkspace {
|
|
|
106
108
|
/** Herdr-backed intent-level agent host for `bakers ui` (BB-190). */
|
|
107
109
|
readonly agentHost: AgentLaunchHost;
|
|
108
110
|
constructor(options: HerdrWorkspaceOptions);
|
|
109
|
-
/**
|
|
110
|
-
* Resolve the base ref for a new worktree branch. Prefers the configured
|
|
111
|
-
* `baseBranch`; otherwise the remote tracking branch of the repo's HEAD
|
|
112
|
-
* (e.g. `origin/develop`). Best-effort: a repo with no upstream returns
|
|
113
|
-
* `undefined`, leaving herdr to fall back to the parent workspace HEAD.
|
|
114
|
-
*/
|
|
115
|
-
private resolveBaseRef;
|
|
116
|
-
/**
|
|
117
|
-
* Fetch the base ref's remote branch so the new worktree starts from the
|
|
118
|
-
* latest pushed state. Best-effort: offline / unknown remote is non-fatal.
|
|
119
|
-
*/
|
|
120
|
-
private fetchBaseRef;
|
|
121
|
-
/**
|
|
122
|
-
* Verify a base-ref candidate resolves on the remote (after fetching it).
|
|
123
|
-
* Returns the ref when it exists, else undefined so the caller falls back.
|
|
124
|
-
*/
|
|
125
|
-
private verifyRemoteRef;
|
|
126
111
|
/**
|
|
127
112
|
* Ensure the branch's worktree + herdr workspace exist.
|
|
128
113
|
*
|
package/dist/src/workspace.js
CHANGED
|
@@ -5,6 +5,7 @@ import { herdrAgentHost } from './agents.js';
|
|
|
5
5
|
import { renderTemplate } from './config.js';
|
|
6
6
|
import { applyPaneLayout, isRecord, parseJson } from './pane-layout.js';
|
|
7
7
|
import { deriveHerdrRoot } from './root-anchor.js';
|
|
8
|
+
import { resolveBaseRef } from './shared.js';
|
|
8
9
|
/** Max `{title}` length substituted into the overview **tab** label (BB-374). */
|
|
9
10
|
export const OPEN_TAB_TITLE_MAX = 30;
|
|
10
11
|
/** Cap overview-tab `{title}` and append `...` when truncated. */
|
|
@@ -94,7 +95,7 @@ function parseWorktreeRootPane(output) {
|
|
|
94
95
|
return tabId !== undefined ? { paneId, tabId } : { paneId };
|
|
95
96
|
}
|
|
96
97
|
/** Git-safe branch validation: safe ref chars only, no leading dot, no `..`. */
|
|
97
|
-
function validateBranch(branch) {
|
|
98
|
+
export function validateBranch(branch) {
|
|
98
99
|
if (!/^[A-Za-z0-9._/-]+$/.test(branch) ||
|
|
99
100
|
branch.startsWith('.') ||
|
|
100
101
|
branch.includes('..')) {
|
|
@@ -128,70 +129,6 @@ export class HerdrWorkspace {
|
|
|
128
129
|
this.logger = options.logger ?? noopLogger;
|
|
129
130
|
this.agentHost = herdrAgentHost(this.execHerdr);
|
|
130
131
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Resolve the base ref for a new worktree branch. Prefers the configured
|
|
133
|
-
* `baseBranch`; otherwise the remote tracking branch of the repo's HEAD
|
|
134
|
-
* (e.g. `origin/develop`). Best-effort: a repo with no upstream returns
|
|
135
|
-
* `undefined`, leaving herdr to fall back to the parent workspace HEAD.
|
|
136
|
-
*/
|
|
137
|
-
async resolveBaseRef() {
|
|
138
|
-
if (this.options.baseBranch) {
|
|
139
|
-
return this.options.baseBranch;
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
const ref = (await this.execGit([
|
|
143
|
-
'-C',
|
|
144
|
-
this.root,
|
|
145
|
-
'rev-parse',
|
|
146
|
-
'--abbrev-ref',
|
|
147
|
-
'--symbolic-full-name',
|
|
148
|
-
'@{u}',
|
|
149
|
-
])).trim();
|
|
150
|
-
return ref || undefined;
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Fetch the base ref's remote branch so the new worktree starts from the
|
|
158
|
-
* latest pushed state. Best-effort: offline / unknown remote is non-fatal.
|
|
159
|
-
*/
|
|
160
|
-
async fetchBaseRef(baseRef) {
|
|
161
|
-
const slash = baseRef.indexOf('/');
|
|
162
|
-
if (slash <= 0) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const remote = baseRef.slice(0, slash);
|
|
166
|
-
const branch = baseRef.slice(slash + 1);
|
|
167
|
-
try {
|
|
168
|
-
await this.execGit(['-C', this.root, 'fetch', remote, branch]);
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
// Non-fatal: use whatever the local tracking ref already points at.
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Verify a base-ref candidate resolves on the remote (after fetching it).
|
|
176
|
-
* Returns the ref when it exists, else undefined so the caller falls back.
|
|
177
|
-
*/
|
|
178
|
-
async verifyRemoteRef(ref) {
|
|
179
|
-
await this.fetchBaseRef(ref);
|
|
180
|
-
try {
|
|
181
|
-
await this.execGit([
|
|
182
|
-
'-C',
|
|
183
|
-
this.root,
|
|
184
|
-
'rev-parse',
|
|
185
|
-
'--verify',
|
|
186
|
-
'--quiet',
|
|
187
|
-
`${ref}^{commit}`,
|
|
188
|
-
]);
|
|
189
|
-
return ref;
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
132
|
/**
|
|
196
133
|
* Ensure the branch's worktree + herdr workspace exist.
|
|
197
134
|
*
|
|
@@ -235,16 +172,12 @@ export class HerdrWorkspace {
|
|
|
235
172
|
// host's local checked-out branch. A ticket passes its `origin/<base_branch>`
|
|
236
173
|
// as the override; use it only when it resolves on the remote, else fall
|
|
237
174
|
// back to the default base.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (baseRef !== undefined) {
|
|
245
|
-
await this.fetchBaseRef(baseRef);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
175
|
+
const baseRef = await resolveBaseRef(this.root, this.execGit, {
|
|
176
|
+
...(this.options.baseBranch
|
|
177
|
+
? { baseBranch: this.options.baseBranch }
|
|
178
|
+
: {}),
|
|
179
|
+
...(baseRefOverride !== undefined ? { override: baseRefOverride } : {}),
|
|
180
|
+
});
|
|
248
181
|
const createOutput = await this.execHerdr([
|
|
249
182
|
'worktree',
|
|
250
183
|
'create',
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakers-build/addon-herdr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "bakers.build herdr integration: spawn executor + per-ticket worktree workspace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/src/index.js",
|
|
9
|
-
"./preset": "./dist/src/preset.js"
|
|
9
|
+
"./preset": "./dist/src/preset.js",
|
|
10
|
+
"./shared": "./dist/src/shared.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist/src"
|
|
@@ -20,10 +21,10 @@
|
|
|
20
21
|
"directory": "bakers-cli/addons/herdr"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@bakers-build/addon-workspace-git": "^0.
|
|
24
|
+
"@bakers-build/addon-workspace-git": "^0.2.0"
|
|
24
25
|
},
|
|
25
26
|
"peerDependencies": {
|
|
26
|
-
"@bakers-build/conductor": "^0.
|
|
27
|
-
"@bakers-build/core": "^0.
|
|
27
|
+
"@bakers-build/conductor": "^0.2.0",
|
|
28
|
+
"@bakers-build/core": "^0.2.0"
|
|
28
29
|
}
|
|
29
30
|
}
|