@remixhq/core 0.1.11 → 0.1.12
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/binding.d.ts +1 -1
- package/dist/binding.js +1 -1
- package/dist/chunk-IXWQWFYT.js +342 -0
- package/dist/collab.d.ts +8 -4
- package/dist/collab.js +479 -158
- package/package.json +1 -1
package/dist/binding.d.ts
CHANGED
package/dist/binding.js
CHANGED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentBranch
|
|
3
|
+
} from "./chunk-RREREIGW.js";
|
|
4
|
+
import {
|
|
5
|
+
RemixError
|
|
6
|
+
} from "./chunk-YZ34ICNN.js";
|
|
7
|
+
|
|
8
|
+
// src/infrastructure/binding/collabBindingStore.ts
|
|
9
|
+
import fs2 from "fs/promises";
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
|
|
12
|
+
// src/shared/fs.ts
|
|
13
|
+
import fs from "fs/promises";
|
|
14
|
+
import path from "path";
|
|
15
|
+
async function reserveDirectory(targetDir) {
|
|
16
|
+
try {
|
|
17
|
+
await fs.mkdir(targetDir);
|
|
18
|
+
return targetDir;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error?.code === "EEXIST") {
|
|
21
|
+
throw new RemixError("Output directory already exists.", {
|
|
22
|
+
exitCode: 2,
|
|
23
|
+
hint: `Choose an empty destination path: ${targetDir}`
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function reserveAvailableDirPath(preferredDir) {
|
|
30
|
+
const parent = path.dirname(preferredDir);
|
|
31
|
+
const base = path.basename(preferredDir);
|
|
32
|
+
for (let i = 1; i <= 1e3; i += 1) {
|
|
33
|
+
const candidate = i === 1 ? preferredDir : path.join(parent, `${base}-${i}`);
|
|
34
|
+
try {
|
|
35
|
+
await fs.mkdir(candidate);
|
|
36
|
+
return candidate;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error?.code === "EEXIST") continue;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
throw new RemixError("No available output directory name.", {
|
|
43
|
+
exitCode: 2,
|
|
44
|
+
hint: `Tried ${base} through ${base}-1000 under ${parent}.`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function writeJsonAtomic(filePath, value) {
|
|
48
|
+
const dir = path.dirname(filePath);
|
|
49
|
+
await fs.mkdir(dir, { recursive: true });
|
|
50
|
+
const tmp = `${filePath}.tmp-${Date.now()}`;
|
|
51
|
+
await fs.writeFile(tmp, `${JSON.stringify(value, null, 2)}
|
|
52
|
+
`, "utf8");
|
|
53
|
+
await fs.rename(tmp, filePath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/infrastructure/binding/collabBindingStore.ts
|
|
57
|
+
function getCollabBindingPath(repoRoot) {
|
|
58
|
+
return path2.join(repoRoot, ".remix", "config.json");
|
|
59
|
+
}
|
|
60
|
+
function buildBindingFileV3(params) {
|
|
61
|
+
return {
|
|
62
|
+
schemaVersion: 3,
|
|
63
|
+
repoFingerprint: params.repoFingerprint,
|
|
64
|
+
remoteUrl: params.remoteUrl,
|
|
65
|
+
defaultBranch: params.defaultBranch,
|
|
66
|
+
branchBindings: params.branchBindings,
|
|
67
|
+
...params.explicitRootBinding ? { explicitRootBinding: params.explicitRootBinding } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function normalizeBranchName(value) {
|
|
71
|
+
const normalized = String(value ?? "").trim();
|
|
72
|
+
return normalized || null;
|
|
73
|
+
}
|
|
74
|
+
function normalizeProjectId(value) {
|
|
75
|
+
const normalized = String(value ?? "").trim();
|
|
76
|
+
return normalized || null;
|
|
77
|
+
}
|
|
78
|
+
function normalizeBranchBinding(value) {
|
|
79
|
+
if (!value?.currentAppId || !value?.upstreamAppId) return null;
|
|
80
|
+
return {
|
|
81
|
+
projectId: normalizeProjectId(value.projectId),
|
|
82
|
+
currentAppId: value.currentAppId,
|
|
83
|
+
upstreamAppId: value.upstreamAppId,
|
|
84
|
+
threadId: value.threadId ?? null,
|
|
85
|
+
laneId: value.laneId ?? null,
|
|
86
|
+
bindingMode: value.bindingMode === "legacy" ? "legacy" : value.bindingMode === "explicit_root" ? "explicit_root" : "lane"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function buildResolvedBinding(params) {
|
|
90
|
+
if (!params.binding) return null;
|
|
91
|
+
return {
|
|
92
|
+
schemaVersion: 3,
|
|
93
|
+
projectId: params.binding.projectId ?? params.fallbackProjectId,
|
|
94
|
+
currentAppId: params.binding.currentAppId,
|
|
95
|
+
upstreamAppId: params.binding.upstreamAppId,
|
|
96
|
+
threadId: params.binding.threadId,
|
|
97
|
+
repoFingerprint: params.repoFingerprint,
|
|
98
|
+
remoteUrl: params.remoteUrl,
|
|
99
|
+
defaultBranch: params.defaultBranch,
|
|
100
|
+
laneId: params.binding.laneId,
|
|
101
|
+
branchName: params.branchName,
|
|
102
|
+
bindingMode: params.binding.bindingMode
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function deriveFallbackProjectId(params) {
|
|
106
|
+
const candidates = [
|
|
107
|
+
params.currentBranch ? params.branchBindings[params.currentBranch]?.projectId ?? null : null,
|
|
108
|
+
params.defaultBranch ? params.branchBindings[params.defaultBranch]?.projectId ?? null : null,
|
|
109
|
+
...Object.values(params.branchBindings).map((binding) => binding.projectId),
|
|
110
|
+
params.legacyProjectId
|
|
111
|
+
];
|
|
112
|
+
for (const candidate of candidates) {
|
|
113
|
+
if (candidate) return candidate;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
async function readCollabBindingState(repoRoot, options) {
|
|
118
|
+
try {
|
|
119
|
+
const persist = options?.persist === true;
|
|
120
|
+
const filePath = getCollabBindingPath(repoRoot);
|
|
121
|
+
const raw = await fs2.readFile(filePath, "utf8");
|
|
122
|
+
const parsed = JSON.parse(raw);
|
|
123
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
124
|
+
const currentBranch = normalizeBranchName(await getCurrentBranch(repoRoot).catch(() => null));
|
|
125
|
+
if (parsed.schemaVersion === 1) {
|
|
126
|
+
if (!parsed.currentAppId || !parsed.upstreamAppId) return null;
|
|
127
|
+
const projectId = normalizeProjectId(parsed.projectId);
|
|
128
|
+
const preferredBranch = normalizeBranchName(parsed.preferredBranch ?? parsed.defaultBranch ?? null);
|
|
129
|
+
const branchKey = preferredBranch ?? currentBranch ?? null;
|
|
130
|
+
const branchBindings2 = branchKey ? {
|
|
131
|
+
[branchKey]: {
|
|
132
|
+
projectId,
|
|
133
|
+
currentAppId: parsed.currentAppId,
|
|
134
|
+
upstreamAppId: parsed.upstreamAppId,
|
|
135
|
+
threadId: parsed.threadId ?? null,
|
|
136
|
+
laneId: null,
|
|
137
|
+
bindingMode: "legacy"
|
|
138
|
+
}
|
|
139
|
+
} : {};
|
|
140
|
+
const migratedFile = buildBindingFileV3({
|
|
141
|
+
repoFingerprint: parsed.repoFingerprint ?? null,
|
|
142
|
+
remoteUrl: parsed.remoteUrl ?? null,
|
|
143
|
+
defaultBranch: parsed.defaultBranch ?? null,
|
|
144
|
+
branchBindings: branchBindings2
|
|
145
|
+
});
|
|
146
|
+
if (persist) {
|
|
147
|
+
try {
|
|
148
|
+
await writeJsonAtomic(filePath, migratedFile);
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
schemaVersion: 3,
|
|
154
|
+
projectId,
|
|
155
|
+
repoFingerprint: migratedFile.repoFingerprint,
|
|
156
|
+
remoteUrl: migratedFile.remoteUrl,
|
|
157
|
+
defaultBranch: migratedFile.defaultBranch,
|
|
158
|
+
currentBranch,
|
|
159
|
+
branchBindings: migratedFile.branchBindings,
|
|
160
|
+
explicitRootBinding: null,
|
|
161
|
+
binding: buildResolvedBinding({
|
|
162
|
+
fallbackProjectId: projectId,
|
|
163
|
+
repoFingerprint: migratedFile.repoFingerprint,
|
|
164
|
+
remoteUrl: migratedFile.remoteUrl,
|
|
165
|
+
defaultBranch: migratedFile.defaultBranch,
|
|
166
|
+
branchName: branchKey,
|
|
167
|
+
binding: branchKey ? migratedFile.branchBindings[branchKey] : null
|
|
168
|
+
})
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (parsed.schemaVersion !== 2 && parsed.schemaVersion !== 3) return null;
|
|
172
|
+
const file = parsed;
|
|
173
|
+
let shouldPersistNormalizedBranchBindings = false;
|
|
174
|
+
const legacyProjectId = normalizeProjectId(file.projectId);
|
|
175
|
+
const branchBindings = Object.fromEntries(
|
|
176
|
+
Object.entries(file.branchBindings ?? {}).map(([branchName, branchBinding]) => {
|
|
177
|
+
const normalized = normalizeBranchBinding(branchBinding);
|
|
178
|
+
const rawProjectId = branchBinding && typeof branchBinding === "object" && "projectId" in branchBinding ? normalizeProjectId(branchBinding.projectId) : null;
|
|
179
|
+
const rawBindingMode = branchBinding && typeof branchBinding === "object" && "bindingMode" in branchBinding ? branchBinding.bindingMode : null;
|
|
180
|
+
const hasLegacyPreferredBranch = Boolean(branchBinding) && typeof branchBinding === "object" && "preferredBranch" in branchBinding;
|
|
181
|
+
let normalizedWithProject = normalized;
|
|
182
|
+
if (normalizedWithProject && !normalizedWithProject.projectId && legacyProjectId) {
|
|
183
|
+
normalizedWithProject = {
|
|
184
|
+
...normalizedWithProject,
|
|
185
|
+
projectId: legacyProjectId
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (normalizedWithProject && (rawBindingMode !== normalizedWithProject.bindingMode || hasLegacyPreferredBranch || rawProjectId !== normalizedWithProject.projectId)) {
|
|
189
|
+
shouldPersistNormalizedBranchBindings = true;
|
|
190
|
+
}
|
|
191
|
+
return [branchName, normalizedWithProject];
|
|
192
|
+
}).filter((entry) => Boolean(entry[1]))
|
|
193
|
+
);
|
|
194
|
+
const legacyExplicitBinding = normalizeBranchBinding(file.explicitBinding ?? null);
|
|
195
|
+
const legacyExplicitBranch = currentBranch ?? normalizeBranchName(file.defaultBranch);
|
|
196
|
+
if (legacyExplicitBinding && legacyExplicitBranch) {
|
|
197
|
+
branchBindings[legacyExplicitBranch] = {
|
|
198
|
+
...legacyExplicitBinding,
|
|
199
|
+
projectId: legacyExplicitBinding.projectId ?? branchBindings[legacyExplicitBranch]?.projectId ?? legacyProjectId,
|
|
200
|
+
bindingMode: "lane"
|
|
201
|
+
};
|
|
202
|
+
shouldPersistNormalizedBranchBindings = true;
|
|
203
|
+
}
|
|
204
|
+
let explicitRootBinding = normalizeBranchBinding(file.explicitRootBinding ?? null);
|
|
205
|
+
if (explicitRootBinding && !explicitRootBinding.projectId && legacyProjectId) {
|
|
206
|
+
explicitRootBinding = {
|
|
207
|
+
...explicitRootBinding,
|
|
208
|
+
projectId: legacyProjectId
|
|
209
|
+
};
|
|
210
|
+
shouldPersistNormalizedBranchBindings = true;
|
|
211
|
+
}
|
|
212
|
+
if (explicitRootBinding && explicitRootBinding.bindingMode !== "explicit_root") {
|
|
213
|
+
explicitRootBinding = {
|
|
214
|
+
...explicitRootBinding,
|
|
215
|
+
bindingMode: "explicit_root"
|
|
216
|
+
};
|
|
217
|
+
shouldPersistNormalizedBranchBindings = true;
|
|
218
|
+
}
|
|
219
|
+
if (persist && ("explicitBinding" in file || "explicitRootBinding" in file || shouldPersistNormalizedBranchBindings || parsed.schemaVersion === 2)) {
|
|
220
|
+
try {
|
|
221
|
+
await writeJsonAtomic(
|
|
222
|
+
filePath,
|
|
223
|
+
buildBindingFileV3({
|
|
224
|
+
repoFingerprint: file.repoFingerprint ?? null,
|
|
225
|
+
remoteUrl: file.remoteUrl ?? null,
|
|
226
|
+
defaultBranch: file.defaultBranch ?? null,
|
|
227
|
+
branchBindings,
|
|
228
|
+
explicitRootBinding
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const resolvedBranch = currentBranch ?? normalizeBranchName(file.defaultBranch);
|
|
235
|
+
const fallbackProjectId = deriveFallbackProjectId({
|
|
236
|
+
branchBindings,
|
|
237
|
+
currentBranch: resolvedBranch,
|
|
238
|
+
defaultBranch: normalizeBranchName(file.defaultBranch),
|
|
239
|
+
legacyProjectId: explicitRootBinding?.projectId ?? legacyProjectId
|
|
240
|
+
});
|
|
241
|
+
const resolvedBinding = buildResolvedBinding({
|
|
242
|
+
fallbackProjectId,
|
|
243
|
+
repoFingerprint: file.repoFingerprint ?? null,
|
|
244
|
+
remoteUrl: file.remoteUrl ?? null,
|
|
245
|
+
defaultBranch: file.defaultBranch ?? null,
|
|
246
|
+
branchName: resolvedBranch,
|
|
247
|
+
binding: resolvedBranch ? branchBindings[resolvedBranch] ?? null : null
|
|
248
|
+
}) ?? (resolvedBranch && resolvedBranch === normalizeBranchName(file.defaultBranch) && explicitRootBinding ? buildResolvedBinding({
|
|
249
|
+
fallbackProjectId,
|
|
250
|
+
repoFingerprint: file.repoFingerprint ?? null,
|
|
251
|
+
remoteUrl: file.remoteUrl ?? null,
|
|
252
|
+
defaultBranch: file.defaultBranch ?? null,
|
|
253
|
+
branchName: normalizeBranchName(file.defaultBranch),
|
|
254
|
+
binding: explicitRootBinding
|
|
255
|
+
}) : null);
|
|
256
|
+
return {
|
|
257
|
+
schemaVersion: parsed.schemaVersion,
|
|
258
|
+
projectId: fallbackProjectId,
|
|
259
|
+
repoFingerprint: file.repoFingerprint ?? null,
|
|
260
|
+
remoteUrl: file.remoteUrl ?? null,
|
|
261
|
+
defaultBranch: file.defaultBranch ?? null,
|
|
262
|
+
currentBranch,
|
|
263
|
+
branchBindings,
|
|
264
|
+
explicitRootBinding: buildResolvedBinding({
|
|
265
|
+
fallbackProjectId,
|
|
266
|
+
repoFingerprint: file.repoFingerprint ?? null,
|
|
267
|
+
remoteUrl: file.remoteUrl ?? null,
|
|
268
|
+
defaultBranch: file.defaultBranch ?? null,
|
|
269
|
+
branchName: normalizeBranchName(file.defaultBranch),
|
|
270
|
+
binding: explicitRootBinding
|
|
271
|
+
}),
|
|
272
|
+
binding: resolvedBinding
|
|
273
|
+
};
|
|
274
|
+
} catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function readCollabBinding(repoRoot) {
|
|
279
|
+
const state = await readCollabBindingState(repoRoot);
|
|
280
|
+
return state?.binding ?? null;
|
|
281
|
+
}
|
|
282
|
+
async function writeCollabBinding(repoRoot, binding) {
|
|
283
|
+
const filePath = getCollabBindingPath(repoRoot);
|
|
284
|
+
const currentBranch = normalizeBranchName(await getCurrentBranch(repoRoot).catch(() => null));
|
|
285
|
+
const branchName = normalizeBranchName(binding.branchName) ?? currentBranch ?? binding.defaultBranch ?? "main";
|
|
286
|
+
const existing = await readCollabBindingState(repoRoot, { persist: true });
|
|
287
|
+
const branchBindings = { ...existing?.branchBindings ?? {} };
|
|
288
|
+
branchBindings[branchName] = {
|
|
289
|
+
projectId: normalizeProjectId(binding.projectId) ?? branchBindings[branchName]?.projectId ?? existing?.projectId ?? null,
|
|
290
|
+
currentAppId: binding.currentAppId,
|
|
291
|
+
upstreamAppId: binding.upstreamAppId,
|
|
292
|
+
threadId: binding.threadId ?? null,
|
|
293
|
+
laneId: binding.laneId ?? null,
|
|
294
|
+
bindingMode: binding.bindingMode ?? "lane"
|
|
295
|
+
};
|
|
296
|
+
const explicitRootBinding = binding.bindingMode === "explicit_root" ? {
|
|
297
|
+
...branchBindings[branchName],
|
|
298
|
+
bindingMode: "explicit_root"
|
|
299
|
+
} : existing?.explicitRootBinding ? {
|
|
300
|
+
projectId: existing.explicitRootBinding.projectId,
|
|
301
|
+
currentAppId: existing.explicitRootBinding.currentAppId,
|
|
302
|
+
upstreamAppId: existing.explicitRootBinding.upstreamAppId,
|
|
303
|
+
threadId: existing.explicitRootBinding.threadId,
|
|
304
|
+
laneId: existing.explicitRootBinding.laneId,
|
|
305
|
+
bindingMode: "explicit_root"
|
|
306
|
+
} : null;
|
|
307
|
+
await writeJsonAtomic(
|
|
308
|
+
filePath,
|
|
309
|
+
buildBindingFileV3({
|
|
310
|
+
repoFingerprint: binding.repoFingerprint ?? null,
|
|
311
|
+
remoteUrl: binding.remoteUrl ?? null,
|
|
312
|
+
defaultBranch: binding.defaultBranch ?? null,
|
|
313
|
+
branchBindings,
|
|
314
|
+
explicitRootBinding
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
return filePath;
|
|
318
|
+
}
|
|
319
|
+
async function writeCollabBindingSnapshot(params) {
|
|
320
|
+
const filePath = getCollabBindingPath(params.repoRoot);
|
|
321
|
+
await writeJsonAtomic(
|
|
322
|
+
filePath,
|
|
323
|
+
buildBindingFileV3({
|
|
324
|
+
repoFingerprint: params.repoFingerprint,
|
|
325
|
+
remoteUrl: params.remoteUrl,
|
|
326
|
+
defaultBranch: params.defaultBranch,
|
|
327
|
+
branchBindings: params.branchBindings,
|
|
328
|
+
explicitRootBinding: params.explicitRootBinding ?? null
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
return filePath;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export {
|
|
335
|
+
reserveDirectory,
|
|
336
|
+
reserveAvailableDirPath,
|
|
337
|
+
getCollabBindingPath,
|
|
338
|
+
readCollabBindingState,
|
|
339
|
+
readCollabBinding,
|
|
340
|
+
writeCollabBinding,
|
|
341
|
+
writeCollabBindingSnapshot
|
|
342
|
+
};
|
package/dist/collab.d.ts
CHANGED
|
@@ -64,8 +64,8 @@ type MergeRequestReview = {
|
|
|
64
64
|
};
|
|
65
65
|
};
|
|
66
66
|
type CollabApproveMode = "remote-only" | "sync-target-repo";
|
|
67
|
-
type CollabStatusBlockedReason = "not_git_repo" | "not_bound" | "branch_binding_missing" | "missing_head" | "detached_head" | "branch_mismatch" | "dirty_worktree" | "metadata_conflict" | "remote_error";
|
|
68
|
-
type CollabStatusRecommendedAction = "init" | "sync" | "reconcile" | "review_queue" | "no_action";
|
|
67
|
+
type CollabStatusBlockedReason = "not_git_repo" | "not_bound" | "branch_binding_missing" | "family_ambiguous" | "missing_head" | "detached_head" | "branch_mismatch" | "dirty_worktree" | "metadata_conflict" | "remote_error";
|
|
68
|
+
type CollabStatusRecommendedAction = "init" | "sync" | "reconcile" | "review_queue" | "choose_family" | "no_action";
|
|
69
69
|
type CollabStatus = {
|
|
70
70
|
schemaVersion: 1;
|
|
71
71
|
repo: {
|
|
@@ -95,7 +95,7 @@ type CollabStatus = {
|
|
|
95
95
|
defaultBranch: string | null;
|
|
96
96
|
laneId: string | null;
|
|
97
97
|
branchName: string | null;
|
|
98
|
-
bindingMode: "legacy" | "lane" | null;
|
|
98
|
+
bindingMode: "legacy" | "lane" | "explicit_root" | null;
|
|
99
99
|
};
|
|
100
100
|
remote: {
|
|
101
101
|
checked: boolean;
|
|
@@ -148,7 +148,7 @@ type SyncUpstreamResponse = {
|
|
|
148
148
|
status: "up-to-date" | "queued";
|
|
149
149
|
mergeRequestId?: string;
|
|
150
150
|
};
|
|
151
|
-
type CollabRecordingPreflightStatus = "not_git_repo" | "not_bound" | "branch_binding_missing" | "missing_head" | "branch_mismatch" | "metadata_conflict" | "up_to_date" | "ready_to_fast_forward" | "reconcile_required";
|
|
151
|
+
type CollabRecordingPreflightStatus = "not_git_repo" | "not_bound" | "branch_binding_missing" | "family_ambiguous" | "missing_head" | "branch_mismatch" | "metadata_conflict" | "up_to_date" | "ready_to_fast_forward" | "reconcile_required";
|
|
152
152
|
type CollabRecordingPreflight = {
|
|
153
153
|
status: CollabRecordingPreflightStatus;
|
|
154
154
|
repoRoot: string | null;
|
|
@@ -686,6 +686,8 @@ declare function collabInit(params: {
|
|
|
686
686
|
upstreamAppId: string;
|
|
687
687
|
bindingPath: string;
|
|
688
688
|
repoRoot: string;
|
|
689
|
+
bindingMode: string;
|
|
690
|
+
createdCanonicalFamily: boolean;
|
|
689
691
|
} | {
|
|
690
692
|
warnings?: string[] | undefined;
|
|
691
693
|
reused: boolean;
|
|
@@ -695,6 +697,8 @@ declare function collabInit(params: {
|
|
|
695
697
|
upstreamAppId: string;
|
|
696
698
|
bindingPath: string;
|
|
697
699
|
repoRoot: string;
|
|
700
|
+
bindingMode: "lane" | "explicit_root";
|
|
701
|
+
createdCanonicalFamily: boolean;
|
|
698
702
|
remoteUrl: string | null;
|
|
699
703
|
defaultBranch: string | null;
|
|
700
704
|
}>;
|
package/dist/collab.js
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
readCollabBindingState,
|
|
5
5
|
reserveAvailableDirPath,
|
|
6
6
|
reserveDirectory,
|
|
7
|
-
writeCollabBinding
|
|
8
|
-
|
|
7
|
+
writeCollabBinding,
|
|
8
|
+
writeCollabBindingSnapshot
|
|
9
|
+
} from "./chunk-IXWQWFYT.js";
|
|
9
10
|
import "./chunk-HZNEDSRS.js";
|
|
10
11
|
import {
|
|
11
12
|
assertRepoSnapshotUnchanged,
|
|
@@ -340,6 +341,10 @@ function normalizeBranchName(value) {
|
|
|
340
341
|
}
|
|
341
342
|
function buildBindingFromLane(state, lane) {
|
|
342
343
|
if (!lane.currentAppId || !lane.upstreamAppId) return null;
|
|
344
|
+
const resolvedBranch = normalizeBranchName(lane.branchName) ?? state.currentBranch ?? null;
|
|
345
|
+
const resolvedDefaultBranch = normalizeBranchName(lane.defaultBranch) ?? normalizeBranchName(state.defaultBranch);
|
|
346
|
+
const explicitRootProjectId = state.explicitRootBinding?.projectId ?? null;
|
|
347
|
+
const bindingMode = explicitRootProjectId && lane.projectId === explicitRootProjectId && resolvedBranch && resolvedBranch === resolvedDefaultBranch ? "explicit_root" : "lane";
|
|
343
348
|
return {
|
|
344
349
|
schemaVersion: 3,
|
|
345
350
|
projectId: lane.projectId ?? state.projectId,
|
|
@@ -350,8 +355,8 @@ function buildBindingFromLane(state, lane) {
|
|
|
350
355
|
remoteUrl: lane.remoteUrl ?? state.remoteUrl ?? null,
|
|
351
356
|
defaultBranch: lane.defaultBranch ?? state.defaultBranch ?? null,
|
|
352
357
|
laneId: lane.laneId ?? null,
|
|
353
|
-
branchName:
|
|
354
|
-
bindingMode
|
|
358
|
+
branchName: resolvedBranch,
|
|
359
|
+
bindingMode
|
|
355
360
|
};
|
|
356
361
|
}
|
|
357
362
|
function shouldPersistRemoteLaneMetadata(localBinding, lane) {
|
|
@@ -380,6 +385,16 @@ async function persistResolvedLane(repoRoot, binding) {
|
|
|
380
385
|
});
|
|
381
386
|
return readCollabBinding(repoRoot);
|
|
382
387
|
}
|
|
388
|
+
function buildAmbiguousResolution(params) {
|
|
389
|
+
return {
|
|
390
|
+
status: "ambiguous_family_selection",
|
|
391
|
+
currentBranch: params.currentBranch,
|
|
392
|
+
projectIds: Array.isArray(params.lane.projectIds) ? params.lane.projectIds.filter((value) => typeof value === "string" && value.trim().length > 0) : [],
|
|
393
|
+
repoFingerprint: params.lane.repoFingerprint ?? params.state.repoFingerprint,
|
|
394
|
+
remoteUrl: params.lane.remoteUrl ?? params.state.remoteUrl,
|
|
395
|
+
defaultBranch: params.lane.defaultBranch ?? params.state.defaultBranch
|
|
396
|
+
};
|
|
397
|
+
}
|
|
383
398
|
async function resolveActiveLaneBinding(params) {
|
|
384
399
|
const state = await readCollabBindingState(params.repoRoot);
|
|
385
400
|
if (!state) {
|
|
@@ -402,13 +417,16 @@ async function resolveActiveLaneBinding(params) {
|
|
|
402
417
|
};
|
|
403
418
|
}
|
|
404
419
|
const laneResp2 = await params.api.resolveProjectLaneBinding({
|
|
405
|
-
projectId: localBinding.projectId ?? state.projectId ?? void 0,
|
|
420
|
+
projectId: state.explicitRootBinding?.projectId ?? (requireRemoteLane ? void 0 : localBinding.projectId ?? state.projectId ?? void 0),
|
|
406
421
|
repoFingerprint: state.repoFingerprint ?? void 0,
|
|
407
422
|
remoteUrl: state.remoteUrl ?? void 0,
|
|
408
423
|
defaultBranch: state.defaultBranch ?? void 0,
|
|
409
424
|
branchName: currentBranch
|
|
410
425
|
});
|
|
411
426
|
const lane2 = unwrapResponseObject(laneResp2, "project lane binding");
|
|
427
|
+
if (lane2.status === "ambiguous_family_selection") {
|
|
428
|
+
return buildAmbiguousResolution({ state, currentBranch, lane: lane2 });
|
|
429
|
+
}
|
|
412
430
|
if (lane2.status === "resolved") {
|
|
413
431
|
const resolvedBranch = normalizeBranchName(lane2.branchName);
|
|
414
432
|
const resolvedProjectId = lane2.projectId ?? state.projectId;
|
|
@@ -451,12 +469,12 @@ async function resolveActiveLaneBinding(params) {
|
|
|
451
469
|
return {
|
|
452
470
|
status: "missing_branch_binding",
|
|
453
471
|
currentBranch,
|
|
454
|
-
projectId: state.projectId,
|
|
455
|
-
repoFingerprint: state.repoFingerprint,
|
|
456
|
-
remoteUrl: state.remoteUrl,
|
|
457
|
-
defaultBranch: state.defaultBranch,
|
|
458
|
-
upstreamAppId: localBinding.upstreamAppId ?? null,
|
|
459
|
-
threadId: localBinding.threadId ?? null
|
|
472
|
+
projectId: lane2.projectId ?? state.projectId,
|
|
473
|
+
repoFingerprint: lane2.repoFingerprint ?? state.repoFingerprint,
|
|
474
|
+
remoteUrl: lane2.remoteUrl ?? state.remoteUrl,
|
|
475
|
+
defaultBranch: lane2.defaultBranch ?? state.defaultBranch,
|
|
476
|
+
upstreamAppId: lane2.upstreamAppId ?? localBinding.upstreamAppId ?? null,
|
|
477
|
+
threadId: lane2.threadId ?? localBinding.threadId ?? null
|
|
460
478
|
};
|
|
461
479
|
}
|
|
462
480
|
return {
|
|
@@ -479,13 +497,16 @@ async function resolveActiveLaneBinding(params) {
|
|
|
479
497
|
};
|
|
480
498
|
}
|
|
481
499
|
const laneResp = await params.api.resolveProjectLaneBinding({
|
|
482
|
-
projectId: state.projectId ?? void 0,
|
|
500
|
+
projectId: state.explicitRootBinding?.projectId ?? state.projectId ?? void 0,
|
|
483
501
|
repoFingerprint: state.repoFingerprint ?? void 0,
|
|
484
502
|
remoteUrl: state.remoteUrl ?? void 0,
|
|
485
503
|
defaultBranch: state.defaultBranch ?? void 0,
|
|
486
504
|
branchName: currentBranch
|
|
487
505
|
});
|
|
488
506
|
const lane = unwrapResponseObject(laneResp, "project lane binding");
|
|
507
|
+
if (lane.status === "ambiguous_family_selection") {
|
|
508
|
+
return buildAmbiguousResolution({ state, currentBranch, lane });
|
|
509
|
+
}
|
|
489
510
|
if (lane.status === "resolved") {
|
|
490
511
|
const binding = buildBindingFromLane(state, lane);
|
|
491
512
|
if (binding) {
|
|
@@ -497,18 +518,15 @@ async function resolveActiveLaneBinding(params) {
|
|
|
497
518
|
};
|
|
498
519
|
}
|
|
499
520
|
}
|
|
500
|
-
if (lane.status === "binding_not_found") {
|
|
501
|
-
return { status: "not_bound", currentBranch };
|
|
502
|
-
}
|
|
503
521
|
return {
|
|
504
522
|
status: "missing_branch_binding",
|
|
505
523
|
currentBranch,
|
|
506
|
-
projectId: lane.projectId ?? state.projectId,
|
|
524
|
+
projectId: lane.projectId ?? state.explicitRootBinding?.projectId ?? state.projectId,
|
|
507
525
|
repoFingerprint: lane.repoFingerprint ?? state.repoFingerprint,
|
|
508
526
|
remoteUrl: lane.remoteUrl ?? state.remoteUrl,
|
|
509
527
|
defaultBranch: lane.defaultBranch ?? state.defaultBranch,
|
|
510
|
-
upstreamAppId: lane.upstreamAppId ?? null,
|
|
511
|
-
threadId: lane.threadId ?? null
|
|
528
|
+
upstreamAppId: lane.upstreamAppId ?? state.explicitRootBinding?.upstreamAppId ?? null,
|
|
529
|
+
threadId: lane.threadId ?? state.explicitRootBinding?.threadId ?? null
|
|
512
530
|
};
|
|
513
531
|
}
|
|
514
532
|
async function ensureActiveLaneBinding(params) {
|
|
@@ -528,6 +546,12 @@ async function ensureActiveLaneBinding(params) {
|
|
|
528
546
|
hint: `Local app ${resolved.binding.currentAppId}; server app ${resolved.resolvedLane.currentAppId ?? "(unknown)"}. Repair the branch binding before running ${params.operation ?? "this command"}.`
|
|
529
547
|
});
|
|
530
548
|
}
|
|
549
|
+
if (resolved.status === "ambiguous_family_selection") {
|
|
550
|
+
throw new RemixError("Multiple canonical Remix families match this repository.", {
|
|
551
|
+
exitCode: 2,
|
|
552
|
+
hint: "This checkout is not specific enough to choose a single family for the current branch. Continue from a checkout already bound to the intended family, or run `remix collab init --force-new` to create a new canonical family."
|
|
553
|
+
});
|
|
554
|
+
}
|
|
531
555
|
if (resolved.status === "not_bound") {
|
|
532
556
|
return null;
|
|
533
557
|
}
|
|
@@ -542,65 +566,6 @@ async function ensureActiveLaneBinding(params) {
|
|
|
542
566
|
hint: `Run \`remix collab init\` on branch ${resolved.currentBranch} before running ${params.operation ?? "this command"}.`
|
|
543
567
|
});
|
|
544
568
|
}
|
|
545
|
-
async function provisionActiveLaneBinding(params) {
|
|
546
|
-
const resolved = await resolveActiveLaneBinding(params);
|
|
547
|
-
if (resolved.status === "resolved") {
|
|
548
|
-
if (resolved.source === "local") {
|
|
549
|
-
return { binding: resolved.binding, warnings: [] };
|
|
550
|
-
}
|
|
551
|
-
const persisted2 = await persistResolvedLane(params.repoRoot, resolved.binding);
|
|
552
|
-
return { binding: persisted2, warnings: [] };
|
|
553
|
-
}
|
|
554
|
-
if (resolved.status === "binding_conflict") {
|
|
555
|
-
throw new RemixError("Current branch binding conflicts with the server-resolved Remix lane.", {
|
|
556
|
-
exitCode: 2,
|
|
557
|
-
hint: `Local app ${resolved.binding.currentAppId}; server app ${resolved.resolvedLane.currentAppId ?? "(unknown)"}. Repair the branch binding before running ${params.operation}.`
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
if (resolved.status === "not_bound") {
|
|
561
|
-
return { binding: null, warnings: [] };
|
|
562
|
-
}
|
|
563
|
-
if (!resolved.currentBranch) {
|
|
564
|
-
throw new RemixError("Current branch is not yet bound to a Remix lane.", {
|
|
565
|
-
exitCode: 2,
|
|
566
|
-
hint: `Switch to a named branch before running ${params.operation}.`
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
let lane;
|
|
570
|
-
try {
|
|
571
|
-
const laneResp = await params.api.ensureProjectLaneBinding({
|
|
572
|
-
projectId: resolved.projectId ?? void 0,
|
|
573
|
-
repoFingerprint: resolved.repoFingerprint ?? void 0,
|
|
574
|
-
remoteUrl: resolved.remoteUrl ?? void 0,
|
|
575
|
-
defaultBranch: resolved.defaultBranch ?? void 0,
|
|
576
|
-
branchName: resolved.currentBranch
|
|
577
|
-
});
|
|
578
|
-
lane = unwrapResponseObject(laneResp, "project lane binding");
|
|
579
|
-
} catch (error) {
|
|
580
|
-
throw new RemixError(`Failed to provision a Remix lane for branch ${resolved.currentBranch}.`, {
|
|
581
|
-
exitCode: error instanceof RemixError ? error.exitCode : 1,
|
|
582
|
-
hint: formatCliErrorDetail(error) || `Remix could not create or recover the branch lane required before ${params.operation}.`
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
const state = await readCollabBindingState(params.repoRoot);
|
|
586
|
-
if (!state) {
|
|
587
|
-
return { binding: null, warnings: [] };
|
|
588
|
-
}
|
|
589
|
-
const binding = buildBindingFromLane(state, lane);
|
|
590
|
-
if (!binding) {
|
|
591
|
-
throw new RemixError(`Failed to provision a Remix lane for branch ${resolved.currentBranch}.`, {
|
|
592
|
-
exitCode: 1,
|
|
593
|
-
hint: "The server returned incomplete lane binding metadata."
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
const persisted = await persistResolvedLane(params.repoRoot, binding);
|
|
597
|
-
return {
|
|
598
|
-
binding: persisted,
|
|
599
|
-
warnings: [
|
|
600
|
-
lane.created ? `Provisioned Remix lane for branch ${resolved.currentBranch}.` : `Recovered existing Remix lane binding for branch ${resolved.currentBranch}.`
|
|
601
|
-
]
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
569
|
|
|
605
570
|
// src/application/collab/recordingPreflight.ts
|
|
606
571
|
async function collabRecordingPreflight(params) {
|
|
@@ -663,6 +628,24 @@ async function collabRecordingPreflight(params) {
|
|
|
663
628
|
hint: `Current branch ${bindingResolution.currentBranch ?? "(detached)"} is not yet bound to a Remix lane.`
|
|
664
629
|
};
|
|
665
630
|
}
|
|
631
|
+
if (bindingResolution.status === "ambiguous_family_selection") {
|
|
632
|
+
return {
|
|
633
|
+
status: "family_ambiguous",
|
|
634
|
+
repoRoot,
|
|
635
|
+
appId: null,
|
|
636
|
+
currentBranch: bindingResolution.currentBranch,
|
|
637
|
+
branchName: bindingResolution.currentBranch,
|
|
638
|
+
headCommitHash: null,
|
|
639
|
+
worktreeClean: false,
|
|
640
|
+
syncStatus: null,
|
|
641
|
+
syncTargetCommitHash: null,
|
|
642
|
+
syncTargetCommitId: null,
|
|
643
|
+
reconcileTargetHeadCommitHash: null,
|
|
644
|
+
reconcileTargetHeadCommitId: null,
|
|
645
|
+
warnings: [],
|
|
646
|
+
hint: "Multiple canonical Remix families match this repository. Continue from a checkout already bound to the intended family, or run `remix collab init --force-new` to create a new canonical family."
|
|
647
|
+
};
|
|
648
|
+
}
|
|
666
649
|
if (bindingResolution.status === "binding_conflict") {
|
|
667
650
|
return {
|
|
668
651
|
status: "metadata_conflict",
|
|
@@ -1184,6 +1167,12 @@ function assertSupportedRecordingPreflight(preflight) {
|
|
|
1184
1167
|
hint: preflight.hint
|
|
1185
1168
|
});
|
|
1186
1169
|
}
|
|
1170
|
+
if (preflight.status === "family_ambiguous") {
|
|
1171
|
+
throw new RemixError("Multiple canonical Remix families match this repository.", {
|
|
1172
|
+
exitCode: 2,
|
|
1173
|
+
hint: preflight.hint
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1187
1176
|
if (preflight.status === "not_git_repo") {
|
|
1188
1177
|
throw new RemixError(preflight.hint || "Not inside a git repository.", {
|
|
1189
1178
|
exitCode: 2,
|
|
@@ -1501,6 +1490,12 @@ function assertSupportedRecordingPreflight2(preflight) {
|
|
|
1501
1490
|
hint: preflight.hint
|
|
1502
1491
|
});
|
|
1503
1492
|
}
|
|
1493
|
+
if (preflight.status === "family_ambiguous") {
|
|
1494
|
+
throw new RemixError("Multiple canonical Remix families match this repository.", {
|
|
1495
|
+
exitCode: 2,
|
|
1496
|
+
hint: preflight.hint
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1504
1499
|
if (preflight.status === "not_git_repo") {
|
|
1505
1500
|
throw new RemixError(preflight.hint || "Not inside a git repository.", {
|
|
1506
1501
|
exitCode: 2,
|
|
@@ -2009,6 +2004,12 @@ async function resolveQueueAppId(params) {
|
|
|
2009
2004
|
hint: `Local app ${bindingResolution.binding.currentAppId}; server app ${bindingResolution.resolvedLane.currentAppId ?? "(unknown)"}.`
|
|
2010
2005
|
});
|
|
2011
2006
|
}
|
|
2007
|
+
if (bindingResolution.status === "ambiguous_family_selection") {
|
|
2008
|
+
throw new RemixError("Multiple canonical Remix families match this repository.", {
|
|
2009
|
+
exitCode: 2,
|
|
2010
|
+
hint: "This checkout does not identify a single canonical family. Continue from a checkout already bound to the intended family, or pass `appId` explicitly for the queue request."
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2012
2013
|
return bindingResolution.binding.currentAppId;
|
|
2013
2014
|
}
|
|
2014
2015
|
async function collabListMergeRequests(params) {
|
|
@@ -2055,6 +2056,12 @@ async function resolveScopeTarget(params) {
|
|
|
2055
2056
|
hint: `Local app ${bindingResolution.binding.currentAppId}; server app ${bindingResolution.resolvedLane.currentAppId ?? "(unknown)"}.`
|
|
2056
2057
|
});
|
|
2057
2058
|
}
|
|
2059
|
+
if (bindingResolution.status === "ambiguous_family_selection") {
|
|
2060
|
+
throw new RemixError("Multiple canonical Remix families match this repository and no explicit target id was provided.", {
|
|
2061
|
+
exitCode: 2,
|
|
2062
|
+
hint: "This checkout does not identify a single canonical family. Continue from a checkout already bound to the intended family, or pass `targetId` explicitly."
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2058
2065
|
const binding = bindingResolution.binding;
|
|
2059
2066
|
if (params.scope === "project") {
|
|
2060
2067
|
if (!binding.projectId) {
|
|
@@ -2185,6 +2192,85 @@ ${text}`.trim() || null
|
|
|
2185
2192
|
}
|
|
2186
2193
|
|
|
2187
2194
|
// src/application/collab/collabInit.ts
|
|
2195
|
+
function requireResolvedLaneBinding(lane, params) {
|
|
2196
|
+
if (lane.status === "resolved" && lane.currentAppId && lane.upstreamAppId) {
|
|
2197
|
+
return lane;
|
|
2198
|
+
}
|
|
2199
|
+
const branchLabel = params.branchName ?? "the current branch";
|
|
2200
|
+
const laneStatus = String(lane.status ?? "");
|
|
2201
|
+
throw new RemixError(`Failed to resolve a Remix lane for ${branchLabel}.`, {
|
|
2202
|
+
exitCode: 1,
|
|
2203
|
+
hint: laneStatus === "binding_not_found" ? `Run ${params.operation} again after the repository has been initialized.` : laneStatus === "ambiguous_family_selection" ? "Multiple canonical Remix families match this repository. Continue from a checkout already bound to the intended family, or rerun with `--force-new` to create a new family." : `Remix did not return complete lane metadata for ${branchLabel}.`
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
function resolveProjectBindingResult(response) {
|
|
2207
|
+
const payload = response?.responseObject;
|
|
2208
|
+
if (!payload || typeof payload !== "object") {
|
|
2209
|
+
return { status: "not_found" };
|
|
2210
|
+
}
|
|
2211
|
+
if (payload.status === "ambiguous_family_selection") {
|
|
2212
|
+
const projectIds = Array.isArray(payload.projectIds) ? payload.projectIds.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
|
|
2213
|
+
return {
|
|
2214
|
+
status: "ambiguous_family_selection",
|
|
2215
|
+
candidateCount: typeof payload.candidateCount === "number" && Number.isFinite(payload.candidateCount) ? payload.candidateCount : projectIds.length,
|
|
2216
|
+
projectIds
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
if (payload.projectId && payload.appId) {
|
|
2220
|
+
return {
|
|
2221
|
+
status: "resolved",
|
|
2222
|
+
projectId: String(payload.projectId),
|
|
2223
|
+
appId: String(payload.appId),
|
|
2224
|
+
upstreamAppId: String(payload.upstreamAppId ?? payload.appId),
|
|
2225
|
+
threadId: payload.threadId ? String(payload.threadId) : null
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
return { status: "not_found" };
|
|
2229
|
+
}
|
|
2230
|
+
function throwAmbiguousFamilyError(params) {
|
|
2231
|
+
const familyCount = params.candidateCount || params.projectIds.length;
|
|
2232
|
+
const projectHint = params.projectIds.length > 0 ? ` Matching project ids: ${params.projectIds.join(", ")}.` : "";
|
|
2233
|
+
throw new RemixError("Multiple canonical Remix families already match this repository.", {
|
|
2234
|
+
exitCode: 2,
|
|
2235
|
+
hint: `Plain \`remix collab init\` cannot safely choose among ${familyCount} matching canonical families for repo fingerprint ${params.repoFingerprint}${params.remoteUrl ? ` (${params.remoteUrl})` : ""}.${projectHint} Run \`remix collab init --force-new\` to create a new canonical family, or continue from a checkout already bound to the intended family.`
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
async function resolveOrEnsureLaneBinding(params) {
|
|
2239
|
+
const resolvePayload = {
|
|
2240
|
+
projectId: params.projectId ?? void 0,
|
|
2241
|
+
repoFingerprint: params.repoFingerprint,
|
|
2242
|
+
remoteUrl: params.remoteUrl ?? void 0,
|
|
2243
|
+
defaultBranch: params.defaultBranch ?? void 0,
|
|
2244
|
+
branchName: params.branchName
|
|
2245
|
+
};
|
|
2246
|
+
let lane = unwrapResponseObject(
|
|
2247
|
+
await params.api.resolveProjectLaneBinding(resolvePayload),
|
|
2248
|
+
"project lane binding"
|
|
2249
|
+
);
|
|
2250
|
+
if (lane.status !== "resolved") {
|
|
2251
|
+
lane = unwrapResponseObject(
|
|
2252
|
+
await params.api.ensureProjectLaneBinding({
|
|
2253
|
+
...resolvePayload,
|
|
2254
|
+
seedAppId: params.seedAppId ?? void 0
|
|
2255
|
+
}),
|
|
2256
|
+
"project lane binding"
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
return requireResolvedLaneBinding(lane, {
|
|
2260
|
+
branchName: params.branchName,
|
|
2261
|
+
operation: params.operation
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
function branchBindingFromLane(lane, mode, fallback) {
|
|
2265
|
+
return {
|
|
2266
|
+
projectId: lane.projectId ?? fallback.projectId,
|
|
2267
|
+
currentAppId: lane.currentAppId ?? fallback.currentAppId,
|
|
2268
|
+
upstreamAppId: lane.upstreamAppId ?? fallback.upstreamAppId,
|
|
2269
|
+
threadId: lane.threadId ?? fallback.threadId,
|
|
2270
|
+
laneId: lane.laneId ?? null,
|
|
2271
|
+
bindingMode: mode
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2188
2274
|
async function collabInit(params) {
|
|
2189
2275
|
return withRepoMutationLock(
|
|
2190
2276
|
{
|
|
@@ -2206,72 +2292,207 @@ async function collabInit(params) {
|
|
|
2206
2292
|
const branchName = currentBranch ?? defaultBranch ?? null;
|
|
2207
2293
|
const repoFingerprint = await buildRepoFingerprint({ gitRoot: repoRoot, remoteUrl, defaultBranch });
|
|
2208
2294
|
const repoSnapshot = await captureRepoSnapshot(repoRoot);
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2295
|
+
const localBindingState = await readCollabBindingState(repoRoot, { persist: true });
|
|
2296
|
+
if (!params.forceNew && localBindingState?.explicitRootBinding && branchName) {
|
|
2297
|
+
const explicitRoot = localBindingState.explicitRootBinding;
|
|
2298
|
+
const explicitProjectId = explicitRoot.projectId ?? localBindingState.projectId;
|
|
2299
|
+
let canonicalLane2 = null;
|
|
2300
|
+
let boundProjectId2 = explicitProjectId;
|
|
2301
|
+
let boundCurrentAppId2 = explicitRoot.currentAppId;
|
|
2302
|
+
let boundUpstreamAppId2 = explicitRoot.upstreamAppId;
|
|
2303
|
+
let boundThreadId2 = explicitRoot.threadId;
|
|
2304
|
+
let boundLaneId2 = explicitRoot.laneId;
|
|
2305
|
+
if (defaultBranch && branchName !== defaultBranch) {
|
|
2306
|
+
canonicalLane2 = await resolveOrEnsureLaneBinding({
|
|
2307
|
+
api: params.api,
|
|
2308
|
+
projectId: explicitProjectId ?? void 0,
|
|
2309
|
+
repoFingerprint,
|
|
2310
|
+
remoteUrl,
|
|
2311
|
+
defaultBranch,
|
|
2312
|
+
branchName: defaultBranch,
|
|
2313
|
+
operation: "`remix collab init`"
|
|
2220
2314
|
});
|
|
2315
|
+
const lane = await resolveOrEnsureLaneBinding({
|
|
2316
|
+
api: params.api,
|
|
2317
|
+
projectId: canonicalLane2.projectId ?? explicitProjectId ?? void 0,
|
|
2318
|
+
repoFingerprint,
|
|
2319
|
+
remoteUrl,
|
|
2320
|
+
defaultBranch,
|
|
2321
|
+
branchName,
|
|
2322
|
+
operation: "`remix collab init`"
|
|
2323
|
+
});
|
|
2324
|
+
boundProjectId2 = lane.projectId ?? boundProjectId2;
|
|
2325
|
+
boundCurrentAppId2 = lane.currentAppId ?? boundCurrentAppId2;
|
|
2326
|
+
boundUpstreamAppId2 = lane.upstreamAppId ?? boundUpstreamAppId2;
|
|
2327
|
+
boundThreadId2 = lane.threadId ?? boundThreadId2;
|
|
2328
|
+
boundLaneId2 = lane.laneId ?? null;
|
|
2329
|
+
} else {
|
|
2330
|
+
canonicalLane2 = await resolveOrEnsureLaneBinding({
|
|
2331
|
+
api: params.api,
|
|
2332
|
+
projectId: explicitProjectId ?? void 0,
|
|
2333
|
+
repoFingerprint,
|
|
2334
|
+
remoteUrl,
|
|
2335
|
+
defaultBranch,
|
|
2336
|
+
branchName,
|
|
2337
|
+
operation: "`remix collab init`"
|
|
2338
|
+
});
|
|
2339
|
+
boundProjectId2 = canonicalLane2.projectId ?? boundProjectId2;
|
|
2340
|
+
boundCurrentAppId2 = canonicalLane2.currentAppId ?? boundCurrentAppId2;
|
|
2341
|
+
boundUpstreamAppId2 = canonicalLane2.upstreamAppId ?? boundUpstreamAppId2;
|
|
2342
|
+
boundThreadId2 = canonicalLane2.threadId ?? boundThreadId2;
|
|
2343
|
+
boundLaneId2 = canonicalLane2.laneId ?? null;
|
|
2221
2344
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2345
|
+
const readyApp = await pollAppReady(params.api, boundCurrentAppId2);
|
|
2346
|
+
boundProjectId2 = String(readyApp.projectId ?? boundProjectId2);
|
|
2347
|
+
boundThreadId2 = readyApp.threadId ? String(readyApp.threadId) : boundThreadId2;
|
|
2348
|
+
await assertRepoSnapshotUnchanged(repoRoot, repoSnapshot, {
|
|
2349
|
+
operation: "`remix collab init`",
|
|
2350
|
+
recoveryHint: "The repository changed while the local binding was being initialized. Review the local changes and rerun `remix collab init`."
|
|
2228
2351
|
});
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2352
|
+
await writeCollabBinding(repoRoot, {
|
|
2353
|
+
projectId: canonicalLane2?.projectId ?? explicitProjectId ?? null,
|
|
2354
|
+
currentAppId: canonicalLane2?.currentAppId ?? explicitRoot.currentAppId,
|
|
2355
|
+
upstreamAppId: canonicalLane2?.upstreamAppId ?? canonicalLane2?.currentAppId ?? explicitRoot.upstreamAppId ?? explicitRoot.currentAppId,
|
|
2356
|
+
threadId: canonicalLane2?.threadId ?? explicitRoot.threadId,
|
|
2357
|
+
repoFingerprint: canonicalLane2?.repoFingerprint ?? explicitRoot.repoFingerprint ?? repoFingerprint,
|
|
2358
|
+
remoteUrl: canonicalLane2?.remoteUrl ?? explicitRoot.remoteUrl ?? remoteUrl,
|
|
2359
|
+
defaultBranch: canonicalLane2?.defaultBranch ?? explicitRoot.defaultBranch ?? defaultBranch ?? null,
|
|
2360
|
+
laneId: canonicalLane2?.laneId ?? explicitRoot.laneId,
|
|
2361
|
+
branchName: defaultBranch,
|
|
2362
|
+
bindingMode: "explicit_root"
|
|
2363
|
+
});
|
|
2364
|
+
if (defaultBranch && branchName !== defaultBranch) {
|
|
2365
|
+
await writeCollabBinding(repoRoot, {
|
|
2366
|
+
projectId: boundProjectId2,
|
|
2367
|
+
currentAppId: boundCurrentAppId2,
|
|
2368
|
+
upstreamAppId: boundUpstreamAppId2,
|
|
2369
|
+
threadId: boundThreadId2,
|
|
2244
2370
|
repoFingerprint,
|
|
2245
2371
|
remoteUrl,
|
|
2246
2372
|
defaultBranch: defaultBranch ?? null,
|
|
2247
|
-
laneId:
|
|
2373
|
+
laneId: boundLaneId2,
|
|
2248
2374
|
branchName,
|
|
2249
2375
|
bindingMode: "lane"
|
|
2250
2376
|
});
|
|
2377
|
+
}
|
|
2378
|
+
return {
|
|
2379
|
+
reused: true,
|
|
2380
|
+
projectId: boundProjectId2 ?? explicitProjectId ?? "",
|
|
2381
|
+
appId: boundCurrentAppId2,
|
|
2382
|
+
dashboardUrl: buildDashboardAppUrl(boundCurrentAppId2),
|
|
2383
|
+
upstreamAppId: boundUpstreamAppId2,
|
|
2384
|
+
bindingPath: path5.join(repoRoot, ".remix", "config.json"),
|
|
2385
|
+
repoRoot,
|
|
2386
|
+
bindingMode: defaultBranch && branchName !== defaultBranch ? "lane" : "explicit_root",
|
|
2387
|
+
createdCanonicalFamily: false,
|
|
2388
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
if (!params.forceNew) {
|
|
2392
|
+
const bindingResolution = resolveProjectBindingResult(
|
|
2393
|
+
await params.api.resolveProjectBinding({
|
|
2394
|
+
repoFingerprint,
|
|
2395
|
+
remoteUrl: remoteUrl ?? void 0,
|
|
2396
|
+
branchName: branchName ?? void 0
|
|
2397
|
+
})
|
|
2398
|
+
);
|
|
2399
|
+
if (bindingResolution.status === "ambiguous_family_selection") {
|
|
2400
|
+
throwAmbiguousFamilyError({
|
|
2401
|
+
repoFingerprint,
|
|
2402
|
+
remoteUrl,
|
|
2403
|
+
projectIds: bindingResolution.projectIds,
|
|
2404
|
+
candidateCount: bindingResolution.candidateCount
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
if (bindingResolution.status === "resolved") {
|
|
2408
|
+
const initialProjectId = bindingResolution.projectId;
|
|
2409
|
+
const initialCurrentAppId = bindingResolution.appId;
|
|
2410
|
+
const initialUpstreamAppId = bindingResolution.upstreamAppId;
|
|
2411
|
+
const initialThreadId = bindingResolution.threadId;
|
|
2251
2412
|
let boundProjectId2 = initialProjectId;
|
|
2252
2413
|
let boundCurrentAppId2 = initialCurrentAppId;
|
|
2253
2414
|
let boundUpstreamAppId2 = initialUpstreamAppId;
|
|
2254
2415
|
let boundThreadId2 = initialThreadId;
|
|
2255
|
-
let
|
|
2416
|
+
let boundLaneId2 = null;
|
|
2417
|
+
let canonicalLane2 = null;
|
|
2256
2418
|
if (branchName) {
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2419
|
+
if (defaultBranch && branchName !== defaultBranch) {
|
|
2420
|
+
canonicalLane2 = await resolveOrEnsureLaneBinding({
|
|
2421
|
+
api: params.api,
|
|
2422
|
+
repoFingerprint,
|
|
2423
|
+
remoteUrl,
|
|
2424
|
+
defaultBranch,
|
|
2425
|
+
branchName: defaultBranch,
|
|
2426
|
+
seedAppId: initialCurrentAppId,
|
|
2427
|
+
operation: "`remix collab init`"
|
|
2428
|
+
});
|
|
2429
|
+
const lane = await resolveOrEnsureLaneBinding({
|
|
2430
|
+
api: params.api,
|
|
2431
|
+
projectId: canonicalLane2.projectId ?? void 0,
|
|
2432
|
+
repoFingerprint,
|
|
2433
|
+
remoteUrl,
|
|
2434
|
+
defaultBranch,
|
|
2435
|
+
branchName,
|
|
2436
|
+
operation: "`remix collab init`"
|
|
2437
|
+
});
|
|
2438
|
+
boundProjectId2 = lane.projectId ?? boundProjectId2;
|
|
2439
|
+
boundCurrentAppId2 = lane.currentAppId ?? boundCurrentAppId2;
|
|
2440
|
+
boundUpstreamAppId2 = lane.upstreamAppId ?? boundUpstreamAppId2;
|
|
2441
|
+
boundThreadId2 = lane.threadId ?? boundThreadId2;
|
|
2442
|
+
boundLaneId2 = lane.laneId ?? null;
|
|
2443
|
+
} else {
|
|
2444
|
+
const lane = await resolveOrEnsureLaneBinding({
|
|
2445
|
+
api: params.api,
|
|
2446
|
+
repoFingerprint,
|
|
2447
|
+
remoteUrl,
|
|
2448
|
+
defaultBranch,
|
|
2449
|
+
branchName,
|
|
2450
|
+
seedAppId: initialCurrentAppId,
|
|
2451
|
+
operation: "`remix collab init`"
|
|
2452
|
+
});
|
|
2453
|
+
canonicalLane2 = lane;
|
|
2454
|
+
boundProjectId2 = lane.projectId ?? boundProjectId2;
|
|
2455
|
+
boundCurrentAppId2 = lane.currentAppId ?? boundCurrentAppId2;
|
|
2456
|
+
boundUpstreamAppId2 = lane.upstreamAppId ?? boundUpstreamAppId2;
|
|
2457
|
+
boundThreadId2 = lane.threadId ?? boundThreadId2;
|
|
2458
|
+
boundLaneId2 = lane.laneId ?? null;
|
|
2267
2459
|
}
|
|
2268
|
-
finalWarnings = [...finalWarnings, ...provisioned.warnings];
|
|
2269
2460
|
}
|
|
2270
2461
|
if (boundCurrentAppId2) {
|
|
2271
2462
|
const readyApp = await pollAppReady(params.api, boundCurrentAppId2);
|
|
2272
2463
|
boundProjectId2 = String(readyApp.projectId ?? boundProjectId2);
|
|
2273
2464
|
boundThreadId2 = readyApp.threadId ? String(readyApp.threadId) : boundThreadId2;
|
|
2274
2465
|
}
|
|
2466
|
+
await assertRepoSnapshotUnchanged(repoRoot, repoSnapshot, {
|
|
2467
|
+
operation: "`remix collab init`",
|
|
2468
|
+
recoveryHint: "The repository changed while the local binding was being initialized. Review the local changes and rerun `remix collab init`."
|
|
2469
|
+
});
|
|
2470
|
+
if (canonicalLane2 && defaultBranch && branchName && branchName !== defaultBranch) {
|
|
2471
|
+
await writeCollabBinding(repoRoot, {
|
|
2472
|
+
projectId: canonicalLane2.projectId ?? null,
|
|
2473
|
+
currentAppId: canonicalLane2.currentAppId ?? boundCurrentAppId2,
|
|
2474
|
+
upstreamAppId: canonicalLane2.upstreamAppId ?? canonicalLane2.currentAppId ?? boundCurrentAppId2,
|
|
2475
|
+
threadId: canonicalLane2.threadId ?? null,
|
|
2476
|
+
repoFingerprint: canonicalLane2.repoFingerprint ?? repoFingerprint,
|
|
2477
|
+
remoteUrl: canonicalLane2.remoteUrl ?? remoteUrl,
|
|
2478
|
+
defaultBranch: canonicalLane2.defaultBranch ?? defaultBranch,
|
|
2479
|
+
laneId: canonicalLane2.laneId ?? null,
|
|
2480
|
+
branchName: defaultBranch,
|
|
2481
|
+
bindingMode: "lane"
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
const bindingPath2 = await writeCollabBinding(repoRoot, {
|
|
2485
|
+
projectId: boundProjectId2,
|
|
2486
|
+
currentAppId: boundCurrentAppId2,
|
|
2487
|
+
upstreamAppId: boundUpstreamAppId2,
|
|
2488
|
+
threadId: boundThreadId2,
|
|
2489
|
+
repoFingerprint,
|
|
2490
|
+
remoteUrl,
|
|
2491
|
+
defaultBranch: defaultBranch ?? null,
|
|
2492
|
+
laneId: boundLaneId2,
|
|
2493
|
+
branchName,
|
|
2494
|
+
bindingMode: "lane"
|
|
2495
|
+
});
|
|
2275
2496
|
return {
|
|
2276
2497
|
reused: true,
|
|
2277
2498
|
projectId: boundProjectId2,
|
|
@@ -2280,7 +2501,9 @@ async function collabInit(params) {
|
|
|
2280
2501
|
upstreamAppId: boundUpstreamAppId2,
|
|
2281
2502
|
bindingPath: bindingPath2,
|
|
2282
2503
|
repoRoot,
|
|
2283
|
-
|
|
2504
|
+
bindingMode: "lane",
|
|
2505
|
+
createdCanonicalFamily: false,
|
|
2506
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
2284
2507
|
};
|
|
2285
2508
|
}
|
|
2286
2509
|
}
|
|
@@ -2307,7 +2530,7 @@ async function collabInit(params) {
|
|
|
2307
2530
|
path: params.path?.trim() || void 0,
|
|
2308
2531
|
platform: "generic",
|
|
2309
2532
|
isPublic: false,
|
|
2310
|
-
branch: currentBranch ?? void 0,
|
|
2533
|
+
branch: defaultBranch && branchName && branchName !== defaultBranch ? defaultBranch : currentBranch ?? void 0,
|
|
2311
2534
|
remoteUrl: remoteUrl ?? void 0,
|
|
2312
2535
|
defaultBranch: defaultBranch ?? void 0,
|
|
2313
2536
|
repoFingerprint,
|
|
@@ -2320,28 +2543,51 @@ async function collabInit(params) {
|
|
|
2320
2543
|
let boundUpstreamAppId = String(app.id);
|
|
2321
2544
|
let boundThreadId = app.threadId ? String(app.threadId) : null;
|
|
2322
2545
|
let boundLaneId = null;
|
|
2546
|
+
let canonicalLane = null;
|
|
2323
2547
|
if (branchName) {
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2548
|
+
if (defaultBranch && branchName !== defaultBranch) {
|
|
2549
|
+
canonicalLane = await resolveOrEnsureLaneBinding({
|
|
2550
|
+
api: params.api,
|
|
2551
|
+
projectId: boundProjectId,
|
|
2552
|
+
repoFingerprint,
|
|
2553
|
+
remoteUrl,
|
|
2554
|
+
defaultBranch,
|
|
2555
|
+
branchName: defaultBranch,
|
|
2556
|
+
seedAppId: String(app.id),
|
|
2557
|
+
operation: "`remix collab init`"
|
|
2558
|
+
});
|
|
2559
|
+
const lane = await resolveOrEnsureLaneBinding({
|
|
2560
|
+
api: params.api,
|
|
2561
|
+
projectId: canonicalLane.projectId ?? boundProjectId,
|
|
2562
|
+
repoFingerprint,
|
|
2563
|
+
remoteUrl,
|
|
2564
|
+
defaultBranch,
|
|
2565
|
+
branchName,
|
|
2566
|
+
operation: "`remix collab init`"
|
|
2567
|
+
});
|
|
2568
|
+
boundProjectId = lane.projectId ?? boundProjectId;
|
|
2569
|
+
boundCurrentAppId = lane.currentAppId ?? boundCurrentAppId;
|
|
2570
|
+
boundUpstreamAppId = lane.upstreamAppId ?? boundUpstreamAppId;
|
|
2571
|
+
boundThreadId = lane.threadId ?? boundThreadId;
|
|
2572
|
+
boundLaneId = lane.laneId ?? null;
|
|
2573
|
+
} else {
|
|
2574
|
+
const lane = await resolveOrEnsureLaneBinding({
|
|
2575
|
+
api: params.api,
|
|
2576
|
+
projectId: boundProjectId,
|
|
2577
|
+
repoFingerprint,
|
|
2578
|
+
remoteUrl,
|
|
2579
|
+
defaultBranch,
|
|
2580
|
+
branchName,
|
|
2581
|
+
seedAppId: String(app.id),
|
|
2582
|
+
operation: "`remix collab init`"
|
|
2583
|
+
});
|
|
2584
|
+
canonicalLane = lane;
|
|
2585
|
+
boundProjectId = lane.projectId ?? boundProjectId;
|
|
2586
|
+
boundCurrentAppId = lane.currentAppId ?? boundCurrentAppId;
|
|
2587
|
+
boundUpstreamAppId = lane.upstreamAppId ?? boundUpstreamAppId;
|
|
2588
|
+
boundThreadId = lane.threadId ?? boundThreadId;
|
|
2589
|
+
boundLaneId = lane.laneId ?? null;
|
|
2590
|
+
}
|
|
2345
2591
|
}
|
|
2346
2592
|
if (boundCurrentAppId) {
|
|
2347
2593
|
const readyApp = await pollAppReady(params.api, boundCurrentAppId);
|
|
@@ -2352,18 +2598,64 @@ async function collabInit(params) {
|
|
|
2352
2598
|
operation: "`remix collab init`",
|
|
2353
2599
|
recoveryHint: "The repository changed before the Remix binding was written. Review the local changes and rerun `remix collab init`."
|
|
2354
2600
|
});
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2601
|
+
const bindingMode = params.forceNew && (!defaultBranch || branchName === defaultBranch) ? "explicit_root" : "lane";
|
|
2602
|
+
let bindingPath;
|
|
2603
|
+
if (params.forceNew && defaultBranch && canonicalLane) {
|
|
2604
|
+
const canonicalBinding = branchBindingFromLane(canonicalLane, "explicit_root", {
|
|
2605
|
+
projectId: canonicalLane.projectId ?? boundProjectId,
|
|
2606
|
+
currentAppId: canonicalLane.currentAppId ?? boundCurrentAppId,
|
|
2607
|
+
upstreamAppId: canonicalLane.upstreamAppId ?? canonicalLane.currentAppId ?? boundCurrentAppId,
|
|
2608
|
+
threadId: canonicalLane.threadId ?? boundThreadId
|
|
2609
|
+
});
|
|
2610
|
+
const branchBindings = {
|
|
2611
|
+
[defaultBranch]: canonicalBinding
|
|
2612
|
+
};
|
|
2613
|
+
if (branchName && branchName !== defaultBranch) {
|
|
2614
|
+
branchBindings[branchName] = {
|
|
2615
|
+
projectId: boundProjectId,
|
|
2616
|
+
currentAppId: boundCurrentAppId,
|
|
2617
|
+
upstreamAppId: boundUpstreamAppId,
|
|
2618
|
+
threadId: boundThreadId,
|
|
2619
|
+
laneId: boundLaneId,
|
|
2620
|
+
bindingMode: "lane"
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
bindingPath = await writeCollabBindingSnapshot({
|
|
2624
|
+
repoRoot,
|
|
2625
|
+
repoFingerprint,
|
|
2626
|
+
remoteUrl,
|
|
2627
|
+
defaultBranch,
|
|
2628
|
+
branchBindings,
|
|
2629
|
+
explicitRootBinding: canonicalBinding
|
|
2630
|
+
});
|
|
2631
|
+
} else {
|
|
2632
|
+
if (canonicalLane && defaultBranch && branchName && branchName !== defaultBranch) {
|
|
2633
|
+
await writeCollabBinding(repoRoot, {
|
|
2634
|
+
projectId: canonicalLane.projectId ?? null,
|
|
2635
|
+
currentAppId: canonicalLane.currentAppId ?? boundCurrentAppId,
|
|
2636
|
+
upstreamAppId: canonicalLane.upstreamAppId ?? canonicalLane.currentAppId ?? boundCurrentAppId,
|
|
2637
|
+
threadId: canonicalLane.threadId ?? null,
|
|
2638
|
+
repoFingerprint: canonicalLane.repoFingerprint ?? repoFingerprint,
|
|
2639
|
+
remoteUrl: canonicalLane.remoteUrl ?? remoteUrl,
|
|
2640
|
+
defaultBranch: canonicalLane.defaultBranch ?? defaultBranch,
|
|
2641
|
+
laneId: canonicalLane.laneId ?? null,
|
|
2642
|
+
branchName: defaultBranch,
|
|
2643
|
+
bindingMode: params.forceNew ? "explicit_root" : "lane"
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
bindingPath = await writeCollabBinding(repoRoot, {
|
|
2647
|
+
projectId: boundProjectId,
|
|
2648
|
+
currentAppId: boundCurrentAppId,
|
|
2649
|
+
upstreamAppId: boundUpstreamAppId,
|
|
2650
|
+
threadId: boundThreadId,
|
|
2651
|
+
repoFingerprint,
|
|
2652
|
+
remoteUrl,
|
|
2653
|
+
defaultBranch: defaultBranch ?? null,
|
|
2654
|
+
laneId: boundLaneId,
|
|
2655
|
+
branchName,
|
|
2656
|
+
bindingMode
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2367
2659
|
return {
|
|
2368
2660
|
reused: false,
|
|
2369
2661
|
projectId: boundProjectId,
|
|
@@ -2372,6 +2664,8 @@ async function collabInit(params) {
|
|
|
2372
2664
|
upstreamAppId: boundUpstreamAppId,
|
|
2373
2665
|
bindingPath,
|
|
2374
2666
|
repoRoot,
|
|
2667
|
+
bindingMode,
|
|
2668
|
+
createdCanonicalFamily: Boolean(params.forceNew),
|
|
2375
2669
|
remoteUrl,
|
|
2376
2670
|
defaultBranch,
|
|
2377
2671
|
...warnings.length > 0 ? { warnings } : {}
|
|
@@ -2831,7 +3125,32 @@ async function collabStatus(params) {
|
|
|
2831
3125
|
addBlockedReason(status.sync, "branch_binding_missing");
|
|
2832
3126
|
addBlockedReason(status.reconcile, "branch_binding_missing");
|
|
2833
3127
|
addWarning(status, `Current branch ${bindingResolution.currentBranch ?? "(detached)"} is not yet bound to a Remix lane.`);
|
|
2834
|
-
status.recommendedAction = "
|
|
3128
|
+
status.recommendedAction = "init";
|
|
3129
|
+
return status;
|
|
3130
|
+
}
|
|
3131
|
+
if (bindingResolution.status === "ambiguous_family_selection") {
|
|
3132
|
+
status.binding = {
|
|
3133
|
+
isBound: true,
|
|
3134
|
+
path: getCollabBindingPath(repoRoot),
|
|
3135
|
+
projectId: null,
|
|
3136
|
+
currentAppId: null,
|
|
3137
|
+
upstreamAppId: null,
|
|
3138
|
+
isRemix: null,
|
|
3139
|
+
threadId: null,
|
|
3140
|
+
repoFingerprint: bindingResolution.repoFingerprint,
|
|
3141
|
+
remoteUrl: bindingResolution.remoteUrl,
|
|
3142
|
+
defaultBranch: bindingResolution.defaultBranch,
|
|
3143
|
+
laneId: null,
|
|
3144
|
+
branchName: bindingResolution.currentBranch,
|
|
3145
|
+
bindingMode: null
|
|
3146
|
+
};
|
|
3147
|
+
addBlockedReason(status.sync, "family_ambiguous");
|
|
3148
|
+
addBlockedReason(status.reconcile, "family_ambiguous");
|
|
3149
|
+
addWarning(
|
|
3150
|
+
status,
|
|
3151
|
+
`Multiple canonical Remix families match ${bindingResolution.currentBranch ?? "the current branch"}. Switch to a checkout already bound to the intended family or run \`remix collab init --force-new\`.`
|
|
3152
|
+
);
|
|
3153
|
+
status.recommendedAction = "choose_family";
|
|
2835
3154
|
return status;
|
|
2836
3155
|
}
|
|
2837
3156
|
const binding = bindingResolution.binding;
|
|
@@ -2983,6 +3302,8 @@ async function collabStatus(params) {
|
|
|
2983
3302
|
status.recommendedAction = "reconcile";
|
|
2984
3303
|
} else if ((status.remote.incomingOpenMergeRequestCount ?? 0) > 0) {
|
|
2985
3304
|
status.recommendedAction = "review_queue";
|
|
3305
|
+
} else if (status.sync.blockedReasons.includes("family_ambiguous") || status.reconcile.blockedReasons.includes("family_ambiguous")) {
|
|
3306
|
+
status.recommendedAction = "choose_family";
|
|
2986
3307
|
} else {
|
|
2987
3308
|
status.recommendedAction = "no_action";
|
|
2988
3309
|
}
|