@remixhq/core 0.1.2 → 0.1.4
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/api.d.ts +2 -0
- package/dist/api.js +1 -1
- package/dist/chunk-EW4PWFHB.js +46 -0
- package/dist/chunk-R44EOUS4.js +288 -0
- package/dist/collab.d.ts +42 -3
- package/dist/collab.js +350 -33
- package/dist/index.js +1 -1
- package/dist/repo.d.ts +1 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -406,6 +406,8 @@ type ApiClient = {
|
|
|
406
406
|
getChangeStep(appId: string, changeStepId: string): Promise<Json>;
|
|
407
407
|
getChangeStepDiff(appId: string, changeStepId: string): Promise<Json>;
|
|
408
408
|
listMergeRequests(params?: {
|
|
409
|
+
queue?: string;
|
|
410
|
+
appId?: string;
|
|
409
411
|
sourceAppId?: string;
|
|
410
412
|
targetAppId?: string;
|
|
411
413
|
status?: string | string[];
|
package/dist/api.js
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RemixError
|
|
3
|
+
} from "./chunk-YZ34ICNN.js";
|
|
4
|
+
|
|
5
|
+
// src/config/model.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var DEFAULT_API_URL = "http://localhost:8080";
|
|
8
|
+
var DEFAULT_SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
|
|
9
|
+
var DEFAULT_SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
|
|
10
|
+
function isValidUrl(value) {
|
|
11
|
+
try {
|
|
12
|
+
new URL(value);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
var urlSchema = z.string().min(1).transform((s) => s.trim()).refine((s) => isValidUrl(s), "Invalid URL.").transform((s) => s.replace(/\/+$/, ""));
|
|
19
|
+
var configSchema = z.object({
|
|
20
|
+
apiUrl: urlSchema,
|
|
21
|
+
supabaseUrl: urlSchema,
|
|
22
|
+
supabaseAnonKey: z.string().min(1)
|
|
23
|
+
});
|
|
24
|
+
var cachedConfig = null;
|
|
25
|
+
async function resolveConfig(_opts) {
|
|
26
|
+
if (cachedConfig) return cachedConfig;
|
|
27
|
+
const cfgRaw = {
|
|
28
|
+
apiUrl: DEFAULT_API_URL,
|
|
29
|
+
supabaseUrl: DEFAULT_SUPABASE_URL,
|
|
30
|
+
supabaseAnonKey: DEFAULT_SUPABASE_ANON_KEY
|
|
31
|
+
};
|
|
32
|
+
const parsedCfg = configSchema.safeParse(cfgRaw);
|
|
33
|
+
if (!parsedCfg.success) {
|
|
34
|
+
throw new RemixError("Invalid core configuration.", {
|
|
35
|
+
exitCode: 1,
|
|
36
|
+
hint: parsedCfg.error.issues.map((i) => `${i.path.join(".") || "config"}: ${i.message}`).join("; ")
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
cachedConfig = parsedCfg.data;
|
|
40
|
+
return parsedCfg.data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
configSchema,
|
|
45
|
+
resolveConfig
|
|
46
|
+
};
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RemixError
|
|
3
|
+
} from "./chunk-YZ34ICNN.js";
|
|
4
|
+
|
|
5
|
+
// src/api/client.ts
|
|
6
|
+
async function readJsonSafe(res) {
|
|
7
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
8
|
+
if (!ct.toLowerCase().includes("application/json")) return null;
|
|
9
|
+
try {
|
|
10
|
+
return await res.json();
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function createApiClient(config, opts) {
|
|
16
|
+
const apiKey = (opts?.apiKey ?? "").trim();
|
|
17
|
+
const tokenProvider = opts?.tokenProvider;
|
|
18
|
+
const CLIENT_KEY_HEADER = "x-comerge-api-key";
|
|
19
|
+
async function request(path, init) {
|
|
20
|
+
if (!tokenProvider) {
|
|
21
|
+
throw new RemixError("API client is missing a token provider.", {
|
|
22
|
+
exitCode: 1,
|
|
23
|
+
hint: "Configure auth before creating the Remix API client."
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const auth = await tokenProvider();
|
|
27
|
+
const url = new URL(path, config.apiUrl).toString();
|
|
28
|
+
const doFetch = async (bearer) => fetch(url, {
|
|
29
|
+
...init,
|
|
30
|
+
headers: {
|
|
31
|
+
Accept: "application/json",
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
...init?.headers ?? {},
|
|
34
|
+
Authorization: `Bearer ${bearer}`,
|
|
35
|
+
...apiKey ? { [CLIENT_KEY_HEADER]: apiKey } : {}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
let res = await doFetch(auth.token);
|
|
39
|
+
if (res.status === 401 && !auth.fromEnv && auth.session?.refresh_token) {
|
|
40
|
+
const refreshed = await tokenProvider({ forceRefresh: true });
|
|
41
|
+
res = await doFetch(refreshed.token);
|
|
42
|
+
}
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const body = await readJsonSafe(res);
|
|
45
|
+
const msg = (body && typeof body === "object" && body && "message" in body && typeof body.message === "string" ? body.message : null) ?? `Request failed (status ${res.status})`;
|
|
46
|
+
throw new RemixError(msg, { exitCode: 1, hint: body ? JSON.stringify(body, null, 2) : null });
|
|
47
|
+
}
|
|
48
|
+
const json = await readJsonSafe(res);
|
|
49
|
+
return json ?? null;
|
|
50
|
+
}
|
|
51
|
+
async function requestBinary(path, init) {
|
|
52
|
+
if (!tokenProvider) {
|
|
53
|
+
throw new RemixError("API client is missing a token provider.", {
|
|
54
|
+
exitCode: 1,
|
|
55
|
+
hint: "Configure auth before creating the Remix API client."
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const auth = await tokenProvider();
|
|
59
|
+
const url = new URL(path, config.apiUrl).toString();
|
|
60
|
+
const doFetch = async (bearer) => fetch(url, {
|
|
61
|
+
...init,
|
|
62
|
+
headers: {
|
|
63
|
+
Accept: "*/*",
|
|
64
|
+
...init?.headers ?? {},
|
|
65
|
+
Authorization: `Bearer ${bearer}`,
|
|
66
|
+
...apiKey ? { [CLIENT_KEY_HEADER]: apiKey } : {}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
let res = await doFetch(auth.token);
|
|
70
|
+
if (res.status === 401 && !auth.fromEnv && auth.session?.refresh_token) {
|
|
71
|
+
const refreshed = await tokenProvider({ forceRefresh: true });
|
|
72
|
+
res = await doFetch(refreshed.token);
|
|
73
|
+
}
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
const body = await readJsonSafe(res);
|
|
76
|
+
const msg = (body && typeof body === "object" && body && "message" in body && typeof body.message === "string" ? body.message : null) ?? `Request failed (status ${res.status})`;
|
|
77
|
+
throw new RemixError(msg, { exitCode: 1, hint: body ? JSON.stringify(body, null, 2) : null });
|
|
78
|
+
}
|
|
79
|
+
const contentDisposition = res.headers.get("content-disposition") ?? "";
|
|
80
|
+
const fileNameMatch = contentDisposition.match(/filename=\"([^\"]+)\"/i);
|
|
81
|
+
return {
|
|
82
|
+
data: Buffer.from(await res.arrayBuffer()),
|
|
83
|
+
fileName: fileNameMatch?.[1] ?? null,
|
|
84
|
+
contentType: res.headers.get("content-type")
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
getMe: () => request("/v1/me", { method: "GET" }),
|
|
89
|
+
listOrganizations: () => request("/v1/organizations", { method: "GET" }),
|
|
90
|
+
getOrganization: (orgId) => request(`/v1/organizations/${encodeURIComponent(orgId)}`, { method: "GET" }),
|
|
91
|
+
listProjects: (params) => {
|
|
92
|
+
const qs = new URLSearchParams();
|
|
93
|
+
if (params?.organizationId) qs.set("organizationId", params.organizationId);
|
|
94
|
+
if (params?.clientAppId) qs.set("clientAppId", params.clientAppId);
|
|
95
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
96
|
+
return request(`/v1/projects${suffix}`, { method: "GET" });
|
|
97
|
+
},
|
|
98
|
+
getProject: (projectId) => request(`/v1/projects/${encodeURIComponent(projectId)}`, { method: "GET" }),
|
|
99
|
+
resolveProjectBinding: (params) => {
|
|
100
|
+
const qs = new URLSearchParams();
|
|
101
|
+
if (params.repoFingerprint) qs.set("repoFingerprint", params.repoFingerprint);
|
|
102
|
+
if (params.remoteUrl) qs.set("remoteUrl", params.remoteUrl);
|
|
103
|
+
return request(`/v1/projects/bindings/resolve?${qs.toString()}`, { method: "GET" });
|
|
104
|
+
},
|
|
105
|
+
autoEnableDeveloper: () => request("/v1/developer/auto-enable", { method: "POST" }),
|
|
106
|
+
listClientApps: (params) => {
|
|
107
|
+
const qs = params?.orgId ? `?orgId=${encodeURIComponent(params.orgId)}` : "";
|
|
108
|
+
return request(`/v1/developer/client-apps${qs}`, { method: "GET" });
|
|
109
|
+
},
|
|
110
|
+
createClientApp: (payload) => request("/v1/developer/client-apps", { method: "POST", body: JSON.stringify(payload) }),
|
|
111
|
+
createClientAppKey: (clientAppId, payload) => request(`/v1/developer/client-apps/${encodeURIComponent(clientAppId)}/keys`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
body: JSON.stringify(payload ?? {})
|
|
114
|
+
}),
|
|
115
|
+
listApps: (params) => {
|
|
116
|
+
const qs = new URLSearchParams();
|
|
117
|
+
if (params?.projectId) qs.set("projectId", params.projectId);
|
|
118
|
+
if (params?.organizationId) qs.set("organizationId", params.organizationId);
|
|
119
|
+
if (params?.forked) qs.set("forked", params.forked);
|
|
120
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
121
|
+
return request(`/v1/apps${suffix}`, { method: "GET" });
|
|
122
|
+
},
|
|
123
|
+
getApp: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}`, { method: "GET" }),
|
|
124
|
+
getMergeRequest: (mrId) => request(`/v1/merge-requests/${encodeURIComponent(mrId)}`, { method: "GET" }),
|
|
125
|
+
presignImportUpload: (payload) => request("/v1/apps/import/upload/presign", { method: "POST", body: JSON.stringify(payload) }),
|
|
126
|
+
importFromUpload: (payload) => request("/v1/apps/import/upload", { method: "POST", body: JSON.stringify(payload) }),
|
|
127
|
+
presignImportUploadFirstParty: (payload) => request("/v1/apps/import/upload/presign/first-party", { method: "POST", body: JSON.stringify(payload) }),
|
|
128
|
+
importFromUploadFirstParty: (payload) => request("/v1/apps/import/upload/first-party", { method: "POST", body: JSON.stringify(payload) }),
|
|
129
|
+
importFromGithubFirstParty: (payload) => request("/v1/apps/import/github/first-party", { method: "POST", body: JSON.stringify(payload) }),
|
|
130
|
+
forkApp: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/fork`, { method: "POST", body: JSON.stringify(payload ?? {}) }),
|
|
131
|
+
downloadAppBundle: (appId) => requestBinary(`/v1/apps/${encodeURIComponent(appId)}/download.bundle`, { method: "GET" }),
|
|
132
|
+
createChangeStep: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/change-steps`, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
body: JSON.stringify(payload)
|
|
135
|
+
}),
|
|
136
|
+
createCollabTurn: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/collab-turns`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
body: JSON.stringify(payload)
|
|
139
|
+
}),
|
|
140
|
+
listCollabTurns: (appId, params) => {
|
|
141
|
+
const qs = new URLSearchParams();
|
|
142
|
+
if (params?.limit !== void 0) qs.set("limit", String(params.limit));
|
|
143
|
+
if (params?.offset !== void 0) qs.set("offset", String(params.offset));
|
|
144
|
+
if (params?.changeStepId) qs.set("changeStepId", params.changeStepId);
|
|
145
|
+
if (params?.threadId) qs.set("threadId", params.threadId);
|
|
146
|
+
if (params?.createdAfter) qs.set("createdAfter", params.createdAfter);
|
|
147
|
+
if (params?.createdBefore) qs.set("createdBefore", params.createdBefore);
|
|
148
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
149
|
+
return request(`/v1/apps/${encodeURIComponent(appId)}/collab-turns${suffix}`, { method: "GET" });
|
|
150
|
+
},
|
|
151
|
+
getCollabTurn: (appId, collabTurnId) => request(`/v1/apps/${encodeURIComponent(appId)}/collab-turns/${encodeURIComponent(collabTurnId)}`, {
|
|
152
|
+
method: "GET"
|
|
153
|
+
}),
|
|
154
|
+
getAgentMemorySummary: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/agent-memory/summary`, { method: "GET" }),
|
|
155
|
+
listAgentMemoryTimeline: (appId, params) => {
|
|
156
|
+
const qs = new URLSearchParams();
|
|
157
|
+
if (params?.limit !== void 0) qs.set("limit", String(params.limit));
|
|
158
|
+
if (params?.offset !== void 0) qs.set("offset", String(params.offset));
|
|
159
|
+
if (params?.createdAfter) qs.set("createdAfter", params.createdAfter);
|
|
160
|
+
if (params?.createdBefore) qs.set("createdBefore", params.createdBefore);
|
|
161
|
+
if (params?.kinds?.length) {
|
|
162
|
+
for (const kind of params.kinds) qs.append("kinds", kind);
|
|
163
|
+
}
|
|
164
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
165
|
+
return request(`/v1/apps/${encodeURIComponent(appId)}/agent-memory/timeline${suffix}`, { method: "GET" });
|
|
166
|
+
},
|
|
167
|
+
searchAgentMemory: (appId, params) => {
|
|
168
|
+
const qs = new URLSearchParams();
|
|
169
|
+
qs.set("q", params.q);
|
|
170
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
171
|
+
if (params.offset !== void 0) qs.set("offset", String(params.offset));
|
|
172
|
+
if (params.createdAfter) qs.set("createdAfter", params.createdAfter);
|
|
173
|
+
if (params.createdBefore) qs.set("createdBefore", params.createdBefore);
|
|
174
|
+
if (params.kinds?.length) {
|
|
175
|
+
for (const kind of params.kinds) qs.append("kinds", kind);
|
|
176
|
+
}
|
|
177
|
+
return request(`/v1/apps/${encodeURIComponent(appId)}/agent-memory/search?${qs.toString()}`, { method: "GET" });
|
|
178
|
+
},
|
|
179
|
+
getChangeStep: (appId, changeStepId) => request(`/v1/apps/${encodeURIComponent(appId)}/change-steps/${encodeURIComponent(changeStepId)}`, { method: "GET" }),
|
|
180
|
+
getChangeStepDiff: (appId, changeStepId) => request(`/v1/apps/${encodeURIComponent(appId)}/change-steps/${encodeURIComponent(changeStepId)}/diff`, {
|
|
181
|
+
method: "GET"
|
|
182
|
+
}),
|
|
183
|
+
startChangeStepReplay: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/change-steps/replays`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
body: JSON.stringify(payload)
|
|
186
|
+
}),
|
|
187
|
+
getChangeStepReplay: (appId, replayId) => request(`/v1/apps/${encodeURIComponent(appId)}/change-steps/replays/${encodeURIComponent(replayId)}`, {
|
|
188
|
+
method: "GET"
|
|
189
|
+
}),
|
|
190
|
+
getChangeStepReplayDiff: (appId, replayId) => request(`/v1/apps/${encodeURIComponent(appId)}/change-steps/replays/${encodeURIComponent(replayId)}/diff`, {
|
|
191
|
+
method: "GET"
|
|
192
|
+
}),
|
|
193
|
+
listMergeRequests: (params) => {
|
|
194
|
+
const qs = new URLSearchParams();
|
|
195
|
+
if (params?.queue) qs.set("queue", params.queue);
|
|
196
|
+
if (params?.appId) qs.set("appId", params.appId);
|
|
197
|
+
if (params?.sourceAppId) qs.set("sourceAppId", params.sourceAppId);
|
|
198
|
+
if (params?.targetAppId) qs.set("targetAppId", params.targetAppId);
|
|
199
|
+
if (Array.isArray(params?.status)) {
|
|
200
|
+
for (const status of params.status) qs.append("status", status);
|
|
201
|
+
} else if (typeof params?.status === "string") {
|
|
202
|
+
qs.set("status", params.status);
|
|
203
|
+
}
|
|
204
|
+
if (params?.kind) qs.set("kind", params.kind);
|
|
205
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
206
|
+
return request(`/v1/merge-requests${suffix}`, { method: "GET" });
|
|
207
|
+
},
|
|
208
|
+
openMergeRequest: (sourceAppId) => request("/v1/merge-requests", { method: "POST", body: JSON.stringify({ sourceAppId }) }),
|
|
209
|
+
getMergeRequestReview: (mrId) => request(`/v1/merge-requests/${encodeURIComponent(mrId)}/review`, { method: "GET" }),
|
|
210
|
+
updateMergeRequest: (mrId, payload) => request(`/v1/merge-requests/${encodeURIComponent(mrId)}`, { method: "PATCH", body: JSON.stringify(payload) }),
|
|
211
|
+
createOrganizationInvite: (orgId, payload) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations`, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
body: JSON.stringify(payload)
|
|
214
|
+
}),
|
|
215
|
+
createProjectInvite: (projectId, payload) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
body: JSON.stringify(payload)
|
|
218
|
+
}),
|
|
219
|
+
createAppInvite: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations`, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
body: JSON.stringify(payload)
|
|
222
|
+
}),
|
|
223
|
+
listOrganizationInvites: (orgId) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations`, { method: "GET" }),
|
|
224
|
+
listProjectInvites: (projectId) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations`, { method: "GET" }),
|
|
225
|
+
listAppInvites: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations`, { method: "GET" }),
|
|
226
|
+
resendOrganizationInvite: (orgId, inviteId, payload) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations/${encodeURIComponent(inviteId)}/resend`, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify(payload ?? {})
|
|
229
|
+
}),
|
|
230
|
+
resendProjectInvite: (projectId, inviteId, payload) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations/${encodeURIComponent(inviteId)}/resend`, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
body: JSON.stringify(payload ?? {})
|
|
233
|
+
}),
|
|
234
|
+
resendAppInvite: (appId, inviteId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations/${encodeURIComponent(inviteId)}/resend`, {
|
|
235
|
+
method: "POST",
|
|
236
|
+
body: JSON.stringify(payload ?? {})
|
|
237
|
+
}),
|
|
238
|
+
revokeOrganizationInvite: (orgId, inviteId) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations/${encodeURIComponent(inviteId)}`, {
|
|
239
|
+
method: "DELETE"
|
|
240
|
+
}),
|
|
241
|
+
revokeProjectInvite: (projectId, inviteId) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations/${encodeURIComponent(inviteId)}`, {
|
|
242
|
+
method: "DELETE"
|
|
243
|
+
}),
|
|
244
|
+
revokeAppInvite: (appId, inviteId) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations/${encodeURIComponent(inviteId)}`, {
|
|
245
|
+
method: "DELETE"
|
|
246
|
+
}),
|
|
247
|
+
syncUpstreamApp: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/sync-upstream`, {
|
|
248
|
+
method: "POST",
|
|
249
|
+
body: JSON.stringify({})
|
|
250
|
+
}),
|
|
251
|
+
preflightAppReconcile: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/reconcile/preflight`, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
body: JSON.stringify(payload)
|
|
254
|
+
}),
|
|
255
|
+
startAppReconcile: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/reconcile/start`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
body: JSON.stringify(payload)
|
|
258
|
+
}),
|
|
259
|
+
getAppReconcile: (appId, reconcileId) => request(`/v1/apps/${encodeURIComponent(appId)}/reconcile/${encodeURIComponent(reconcileId)}`, { method: "GET" }),
|
|
260
|
+
downloadAppReconcileBundle: (appId, reconcileId) => requestBinary(`/v1/apps/${encodeURIComponent(appId)}/reconcile/${encodeURIComponent(reconcileId)}/download.bundle`, {
|
|
261
|
+
method: "GET"
|
|
262
|
+
}),
|
|
263
|
+
syncLocalApp: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/sync-local`, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
body: JSON.stringify(payload)
|
|
266
|
+
}),
|
|
267
|
+
initiateBundle: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles`, { method: "POST", body: JSON.stringify(payload) }),
|
|
268
|
+
getBundle: (appId, bundleId) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}`, { method: "GET" }),
|
|
269
|
+
getBundleDownloadUrl: (appId, bundleId, options) => request(
|
|
270
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/download?redirect=${options?.redirect ?? false}`,
|
|
271
|
+
{ method: "GET" }
|
|
272
|
+
),
|
|
273
|
+
getBundleAssetsDownloadUrl: (appId, bundleId, options) => {
|
|
274
|
+
const qs = new URLSearchParams({
|
|
275
|
+
redirect: String(options?.redirect ?? false),
|
|
276
|
+
kind: options?.kind ?? "metro-assets"
|
|
277
|
+
});
|
|
278
|
+
return request(
|
|
279
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download?${qs.toString()}`,
|
|
280
|
+
{ method: "GET" }
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export {
|
|
287
|
+
createApiClient
|
|
288
|
+
};
|
package/dist/collab.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
type JsonObject = Record<string, any>;
|
|
2
2
|
type MergeRequestStatus = "open" | "approved" | "rejected" | "merged" | "closed";
|
|
3
|
+
type MergeRequestQueue = "reviewable" | "created_by_me" | "app_reviewable" | "app_outgoing" | "app_related_visible";
|
|
3
4
|
type MergeRequest = {
|
|
4
5
|
id: string;
|
|
5
6
|
sourceAppId: string;
|
|
@@ -63,7 +64,7 @@ type MergeRequestReview = {
|
|
|
63
64
|
};
|
|
64
65
|
type CollabApproveMode = "remote-only" | "sync-target-repo";
|
|
65
66
|
type CollabStatusBlockedReason = "not_git_repo" | "not_bound" | "missing_head" | "detached_head" | "branch_mismatch" | "dirty_worktree" | "metadata_conflict" | "remote_error";
|
|
66
|
-
type CollabStatusRecommendedAction = "init" | "sync" | "reconcile" | "
|
|
67
|
+
type CollabStatusRecommendedAction = "init" | "sync" | "reconcile" | "review_queue" | "no_action";
|
|
67
68
|
type CollabStatus = {
|
|
68
69
|
schemaVersion: 1;
|
|
69
70
|
repo: {
|
|
@@ -144,6 +145,23 @@ type SyncUpstreamResponse = {
|
|
|
144
145
|
status: "up-to-date" | "queued";
|
|
145
146
|
mergeRequestId?: string;
|
|
146
147
|
};
|
|
148
|
+
type CollabRecordingPreflightStatus = "not_git_repo" | "not_bound" | "missing_head" | "branch_mismatch" | "metadata_conflict" | "up_to_date" | "ready_to_fast_forward" | "reconcile_required";
|
|
149
|
+
type CollabRecordingPreflight = {
|
|
150
|
+
status: CollabRecordingPreflightStatus;
|
|
151
|
+
repoRoot: string | null;
|
|
152
|
+
appId: string | null;
|
|
153
|
+
currentBranch: string | null;
|
|
154
|
+
preferredBranch: string | null;
|
|
155
|
+
headCommitHash: string | null;
|
|
156
|
+
worktreeClean: boolean;
|
|
157
|
+
syncStatus: SyncLocalResponse["status"] | null;
|
|
158
|
+
syncTargetCommitHash: string | null;
|
|
159
|
+
syncTargetCommitId: string | null;
|
|
160
|
+
reconcileTargetHeadCommitHash: string | null;
|
|
161
|
+
reconcileTargetHeadCommitId: string | null;
|
|
162
|
+
warnings: string[];
|
|
163
|
+
hint: string | null;
|
|
164
|
+
};
|
|
147
165
|
type ReconcilePreflightResponse = {
|
|
148
166
|
status: "up_to_date" | "ready_to_reconcile" | "metadata_conflict";
|
|
149
167
|
localHeadCommitHash: string;
|
|
@@ -319,6 +337,8 @@ type CollabApiClient = {
|
|
|
319
337
|
}): Promise<unknown>;
|
|
320
338
|
getChangeStep(appId: string, changeStepId: string): Promise<unknown>;
|
|
321
339
|
listMergeRequests(params?: {
|
|
340
|
+
queue?: MergeRequestQueue;
|
|
341
|
+
appId?: string;
|
|
322
342
|
sourceAppId?: string;
|
|
323
343
|
targetAppId?: string;
|
|
324
344
|
status?: string | string[];
|
|
@@ -409,6 +429,12 @@ declare function collabAdd(params: {
|
|
|
409
429
|
};
|
|
410
430
|
}): Promise<JsonObject>;
|
|
411
431
|
|
|
432
|
+
declare function collabRecordingPreflight(params: {
|
|
433
|
+
api: CollabApiClient;
|
|
434
|
+
cwd: string;
|
|
435
|
+
allowBranchMismatch?: boolean;
|
|
436
|
+
}): Promise<CollabRecordingPreflight>;
|
|
437
|
+
|
|
412
438
|
declare function collabRecordTurn(params: {
|
|
413
439
|
api: CollabApiClient;
|
|
414
440
|
cwd: string;
|
|
@@ -435,7 +461,20 @@ declare function collabApprove(params: {
|
|
|
435
461
|
declare function collabInbox(params: {
|
|
436
462
|
api: CollabApiClient;
|
|
437
463
|
}): Promise<{
|
|
438
|
-
mergeRequests:
|
|
464
|
+
mergeRequests: MergeRequest[];
|
|
465
|
+
}>;
|
|
466
|
+
|
|
467
|
+
declare function collabListMergeRequests(params: {
|
|
468
|
+
api: CollabApiClient;
|
|
469
|
+
queue: MergeRequestQueue;
|
|
470
|
+
cwd?: string | null;
|
|
471
|
+
appId?: string | null;
|
|
472
|
+
status?: string | string[];
|
|
473
|
+
kind?: string | null;
|
|
474
|
+
}): Promise<{
|
|
475
|
+
queue: MergeRequestQueue;
|
|
476
|
+
appId: string | null;
|
|
477
|
+
mergeRequests: MergeRequest[];
|
|
439
478
|
}>;
|
|
440
479
|
|
|
441
480
|
declare function collabInit(params: {
|
|
@@ -677,4 +716,4 @@ declare function collabView(params: {
|
|
|
677
716
|
mrId: string;
|
|
678
717
|
}): Promise<MergeRequestReview>;
|
|
679
718
|
|
|
680
|
-
export { type AppReconcileResponse, type CollabApiClient, type CollabApproveMode, type CollabApproveResult, type CollabStatus, type CollabStatusBlockedReason, type CollabStatusRecommendedAction, type CollabTurn, type InvitationScopeType, type JsonObject, type MergeRequest, type MergeRequestReview, type MergeRequestStatus, type ReconcilePreflightResponse, type SyncLocalResponse, type SyncUpstreamResponse, collabAdd, collabApprove, collabInbox, collabInit, collabInvite, collabList, collabReconcile, collabRecordTurn, collabReject, collabRemix, collabRequestMerge, collabStatus, collabSync, collabSyncUpstream, collabView };
|
|
719
|
+
export { type AppReconcileResponse, type CollabApiClient, type CollabApproveMode, type CollabApproveResult, type CollabRecordingPreflight, type CollabRecordingPreflightStatus, type CollabStatus, type CollabStatusBlockedReason, type CollabStatusRecommendedAction, type CollabTurn, type InvitationScopeType, type JsonObject, type MergeRequest, type MergeRequestQueue, type MergeRequestReview, type MergeRequestStatus, type ReconcilePreflightResponse, type SyncLocalResponse, type SyncUpstreamResponse, collabAdd, collabApprove, collabInbox, collabInit, collabInvite, collabList, collabListMergeRequests, collabReconcile, collabRecordTurn, collabRecordingPreflight, collabReject, collabRemix, collabRequestMerge, collabStatus, collabSync, collabSyncUpstream, collabView };
|
package/dist/collab.js
CHANGED
|
@@ -260,6 +260,199 @@ async function pollMergeRequestCompletion(api, mrId, params) {
|
|
|
260
260
|
throw new RemixError("Timed out waiting for merge approval to complete.", { exitCode: 1 });
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
// src/application/collab/recordingPreflight.ts
|
|
264
|
+
async function collabRecordingPreflight(params) {
|
|
265
|
+
let repoRoot;
|
|
266
|
+
try {
|
|
267
|
+
repoRoot = await findGitRoot(params.cwd);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const message = error instanceof Error ? error.message : "Not inside a git repository.";
|
|
270
|
+
return {
|
|
271
|
+
status: "not_git_repo",
|
|
272
|
+
repoRoot: null,
|
|
273
|
+
appId: null,
|
|
274
|
+
currentBranch: null,
|
|
275
|
+
preferredBranch: null,
|
|
276
|
+
headCommitHash: null,
|
|
277
|
+
worktreeClean: false,
|
|
278
|
+
syncStatus: null,
|
|
279
|
+
syncTargetCommitHash: null,
|
|
280
|
+
syncTargetCommitId: null,
|
|
281
|
+
reconcileTargetHeadCommitHash: null,
|
|
282
|
+
reconcileTargetHeadCommitId: null,
|
|
283
|
+
warnings: [],
|
|
284
|
+
hint: message
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const binding = await readCollabBinding(repoRoot);
|
|
288
|
+
if (!binding) {
|
|
289
|
+
return {
|
|
290
|
+
status: "not_bound",
|
|
291
|
+
repoRoot,
|
|
292
|
+
appId: null,
|
|
293
|
+
currentBranch: null,
|
|
294
|
+
preferredBranch: null,
|
|
295
|
+
headCommitHash: null,
|
|
296
|
+
worktreeClean: false,
|
|
297
|
+
syncStatus: null,
|
|
298
|
+
syncTargetCommitHash: null,
|
|
299
|
+
syncTargetCommitId: null,
|
|
300
|
+
reconcileTargetHeadCommitHash: null,
|
|
301
|
+
reconcileTargetHeadCommitId: null,
|
|
302
|
+
warnings: [],
|
|
303
|
+
hint: "Run `remix collab init` first."
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const [currentBranch, headCommitHash, worktreeStatus] = await Promise.all([
|
|
307
|
+
getCurrentBranch(repoRoot),
|
|
308
|
+
getHeadCommitHash(repoRoot),
|
|
309
|
+
getWorktreeStatus(repoRoot)
|
|
310
|
+
]);
|
|
311
|
+
const preferredBranch = binding.preferredBranch ?? null;
|
|
312
|
+
if (!headCommitHash) {
|
|
313
|
+
return {
|
|
314
|
+
status: "missing_head",
|
|
315
|
+
repoRoot,
|
|
316
|
+
appId: binding.currentAppId,
|
|
317
|
+
currentBranch,
|
|
318
|
+
preferredBranch,
|
|
319
|
+
headCommitHash: null,
|
|
320
|
+
worktreeClean: worktreeStatus.isClean,
|
|
321
|
+
syncStatus: null,
|
|
322
|
+
syncTargetCommitHash: null,
|
|
323
|
+
syncTargetCommitId: null,
|
|
324
|
+
reconcileTargetHeadCommitHash: null,
|
|
325
|
+
reconcileTargetHeadCommitId: null,
|
|
326
|
+
warnings: [],
|
|
327
|
+
hint: "Failed to resolve local HEAD commit."
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (!params.allowBranchMismatch && !isPreferredBranchMatch(currentBranch, preferredBranch)) {
|
|
331
|
+
return {
|
|
332
|
+
status: "branch_mismatch",
|
|
333
|
+
repoRoot,
|
|
334
|
+
appId: binding.currentAppId,
|
|
335
|
+
currentBranch,
|
|
336
|
+
preferredBranch,
|
|
337
|
+
headCommitHash,
|
|
338
|
+
worktreeClean: worktreeStatus.isClean,
|
|
339
|
+
syncStatus: null,
|
|
340
|
+
syncTargetCommitHash: null,
|
|
341
|
+
syncTargetCommitId: null,
|
|
342
|
+
reconcileTargetHeadCommitHash: null,
|
|
343
|
+
reconcileTargetHeadCommitId: null,
|
|
344
|
+
warnings: [],
|
|
345
|
+
hint: buildPreferredBranchMismatchHint({
|
|
346
|
+
currentBranch,
|
|
347
|
+
preferredBranch
|
|
348
|
+
})
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const syncResp = await params.api.syncLocalApp(binding.currentAppId, {
|
|
352
|
+
baseCommitHash: headCommitHash,
|
|
353
|
+
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
354
|
+
remoteUrl: binding.remoteUrl ?? void 0,
|
|
355
|
+
defaultBranch: binding.defaultBranch ?? void 0,
|
|
356
|
+
dryRun: true
|
|
357
|
+
});
|
|
358
|
+
const sync = unwrapResponseObject(syncResp, "sync result");
|
|
359
|
+
if (sync.status === "conflict_risk") {
|
|
360
|
+
return {
|
|
361
|
+
status: "metadata_conflict",
|
|
362
|
+
repoRoot,
|
|
363
|
+
appId: binding.currentAppId,
|
|
364
|
+
currentBranch,
|
|
365
|
+
preferredBranch,
|
|
366
|
+
headCommitHash,
|
|
367
|
+
worktreeClean: worktreeStatus.isClean,
|
|
368
|
+
syncStatus: sync.status,
|
|
369
|
+
syncTargetCommitHash: sync.targetCommitHash,
|
|
370
|
+
syncTargetCommitId: sync.targetCommitId,
|
|
371
|
+
reconcileTargetHeadCommitHash: null,
|
|
372
|
+
reconcileTargetHeadCommitId: null,
|
|
373
|
+
warnings: sync.warnings,
|
|
374
|
+
hint: sync.warnings.join("\n") || "Run the command from the correct bound repository."
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
if (sync.status === "up_to_date" || sync.status === "ready_to_fast_forward") {
|
|
378
|
+
return {
|
|
379
|
+
status: sync.status,
|
|
380
|
+
repoRoot,
|
|
381
|
+
appId: binding.currentAppId,
|
|
382
|
+
currentBranch,
|
|
383
|
+
preferredBranch,
|
|
384
|
+
headCommitHash,
|
|
385
|
+
worktreeClean: worktreeStatus.isClean,
|
|
386
|
+
syncStatus: sync.status,
|
|
387
|
+
syncTargetCommitHash: sync.targetCommitHash,
|
|
388
|
+
syncTargetCommitId: sync.targetCommitId,
|
|
389
|
+
reconcileTargetHeadCommitHash: null,
|
|
390
|
+
reconcileTargetHeadCommitId: null,
|
|
391
|
+
warnings: sync.warnings,
|
|
392
|
+
hint: null
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const reconcileResp = await params.api.preflightAppReconcile(binding.currentAppId, {
|
|
396
|
+
localHeadCommitHash: headCommitHash,
|
|
397
|
+
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
398
|
+
remoteUrl: binding.remoteUrl ?? void 0,
|
|
399
|
+
defaultBranch: binding.defaultBranch ?? void 0
|
|
400
|
+
});
|
|
401
|
+
const reconcile = unwrapResponseObject(reconcileResp, "reconcile preflight");
|
|
402
|
+
if (reconcile.status === "metadata_conflict") {
|
|
403
|
+
return {
|
|
404
|
+
status: "metadata_conflict",
|
|
405
|
+
repoRoot,
|
|
406
|
+
appId: binding.currentAppId,
|
|
407
|
+
currentBranch,
|
|
408
|
+
preferredBranch,
|
|
409
|
+
headCommitHash,
|
|
410
|
+
worktreeClean: worktreeStatus.isClean,
|
|
411
|
+
syncStatus: sync.status,
|
|
412
|
+
syncTargetCommitHash: sync.targetCommitHash,
|
|
413
|
+
syncTargetCommitId: sync.targetCommitId,
|
|
414
|
+
reconcileTargetHeadCommitHash: reconcile.targetHeadCommitHash,
|
|
415
|
+
reconcileTargetHeadCommitId: reconcile.targetHeadCommitId,
|
|
416
|
+
warnings: reconcile.warnings,
|
|
417
|
+
hint: reconcile.warnings.join("\n") || "Run the command from the correct bound repository."
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (reconcile.status === "up_to_date") {
|
|
421
|
+
return {
|
|
422
|
+
status: "up_to_date",
|
|
423
|
+
repoRoot,
|
|
424
|
+
appId: binding.currentAppId,
|
|
425
|
+
currentBranch,
|
|
426
|
+
preferredBranch,
|
|
427
|
+
headCommitHash,
|
|
428
|
+
worktreeClean: worktreeStatus.isClean,
|
|
429
|
+
syncStatus: sync.status,
|
|
430
|
+
syncTargetCommitHash: sync.targetCommitHash,
|
|
431
|
+
syncTargetCommitId: sync.targetCommitId,
|
|
432
|
+
reconcileTargetHeadCommitHash: reconcile.targetHeadCommitHash,
|
|
433
|
+
reconcileTargetHeadCommitId: reconcile.targetHeadCommitId,
|
|
434
|
+
warnings: reconcile.warnings,
|
|
435
|
+
hint: null
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
status: "reconcile_required",
|
|
440
|
+
repoRoot,
|
|
441
|
+
appId: binding.currentAppId,
|
|
442
|
+
currentBranch,
|
|
443
|
+
preferredBranch,
|
|
444
|
+
headCommitHash,
|
|
445
|
+
worktreeClean: worktreeStatus.isClean,
|
|
446
|
+
syncStatus: sync.status,
|
|
447
|
+
syncTargetCommitHash: sync.targetCommitHash,
|
|
448
|
+
syncTargetCommitId: sync.targetCommitId,
|
|
449
|
+
reconcileTargetHeadCommitHash: reconcile.targetHeadCommitHash,
|
|
450
|
+
reconcileTargetHeadCommitId: reconcile.targetHeadCommitId,
|
|
451
|
+
warnings: reconcile.warnings,
|
|
452
|
+
hint: reconcile.warnings.join("\n") || "Run `remix collab reconcile` first because the local history is no longer fast-forward compatible with the app."
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
263
456
|
// src/infrastructure/locking/repoMutationLock.ts
|
|
264
457
|
import fs from "fs/promises";
|
|
265
458
|
import os from "os";
|
|
@@ -595,27 +788,43 @@ async function collabSync(params) {
|
|
|
595
788
|
}
|
|
596
789
|
|
|
597
790
|
// src/application/collab/collabAdd.ts
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
791
|
+
function assertSupportedRecordingPreflight(preflight) {
|
|
792
|
+
if (preflight.status === "not_bound") {
|
|
793
|
+
throw new RemixError("Repository is not bound to Remix.", {
|
|
794
|
+
exitCode: 2,
|
|
795
|
+
hint: preflight.hint
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
if (preflight.status === "not_git_repo") {
|
|
799
|
+
throw new RemixError(preflight.hint || "Not inside a git repository.", {
|
|
800
|
+
exitCode: 2,
|
|
801
|
+
hint: preflight.hint
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
if (preflight.status === "missing_head") {
|
|
805
|
+
throw new RemixError("Failed to resolve local HEAD commit.", {
|
|
806
|
+
exitCode: 1,
|
|
807
|
+
hint: preflight.hint
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
if (preflight.status === "branch_mismatch") {
|
|
811
|
+
assertPreferredBranchMatch({
|
|
812
|
+
currentBranch: preflight.currentBranch,
|
|
813
|
+
preferredBranch: preflight.preferredBranch,
|
|
814
|
+
allowBranchMismatch: false,
|
|
815
|
+
operation: "`remix collab add`"
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
if (preflight.status === "metadata_conflict") {
|
|
610
819
|
throw new RemixError("Local repository metadata conflicts with the bound Remix app.", {
|
|
611
820
|
exitCode: 2,
|
|
612
|
-
hint:
|
|
821
|
+
hint: preflight.hint
|
|
613
822
|
});
|
|
614
823
|
}
|
|
615
|
-
if (
|
|
824
|
+
if (preflight.status === "reconcile_required") {
|
|
616
825
|
throw new RemixError("Local repository cannot be fast-forward synced.", {
|
|
617
826
|
exitCode: 2,
|
|
618
|
-
hint:
|
|
827
|
+
hint: preflight.hint
|
|
619
828
|
});
|
|
620
829
|
}
|
|
621
830
|
}
|
|
@@ -635,28 +844,25 @@ async function collabAdd(params) {
|
|
|
635
844
|
const diffSource = params.diffSource ?? (params.diff ? "external" : "worktree");
|
|
636
845
|
const autoSyncEnabled = params.sync !== false;
|
|
637
846
|
const run = async (lockWarnings = []) => {
|
|
638
|
-
const
|
|
847
|
+
const preflight = await collabRecordingPreflight({
|
|
848
|
+
api: params.api,
|
|
849
|
+
cwd: repoRoot,
|
|
850
|
+
allowBranchMismatch: params.allowBranchMismatch
|
|
851
|
+
});
|
|
852
|
+
assertSupportedRecordingPreflight(preflight);
|
|
853
|
+
const branch = preflight.currentBranch;
|
|
639
854
|
assertPreferredBranchMatch({
|
|
640
855
|
currentBranch: branch,
|
|
641
856
|
preferredBranch: binding.preferredBranch,
|
|
642
857
|
allowBranchMismatch: params.allowBranchMismatch,
|
|
643
858
|
operation: "`remix collab add`"
|
|
644
859
|
});
|
|
645
|
-
let headCommitHash =
|
|
860
|
+
let headCommitHash = preflight.headCommitHash;
|
|
646
861
|
if (!headCommitHash) {
|
|
647
862
|
throw new RemixError("Failed to resolve local HEAD commit.", { exitCode: 1 });
|
|
648
863
|
}
|
|
649
864
|
const worktreeStatus = await getWorktreeStatus(repoRoot);
|
|
650
|
-
|
|
651
|
-
api: params.api,
|
|
652
|
-
appId: binding.currentAppId,
|
|
653
|
-
headCommitHash,
|
|
654
|
-
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
655
|
-
remoteUrl: binding.remoteUrl ?? void 0,
|
|
656
|
-
defaultBranch: binding.defaultBranch ?? void 0
|
|
657
|
-
});
|
|
658
|
-
assertSupportedSyncStatus(syncPreview);
|
|
659
|
-
if (syncPreview.status === "ready_to_fast_forward") {
|
|
865
|
+
if (preflight.status === "ready_to_fast_forward") {
|
|
660
866
|
if (!autoSyncEnabled) {
|
|
661
867
|
throw new RemixError("Local repository is stale and `collab add` sync automation is disabled.", {
|
|
662
868
|
exitCode: 2,
|
|
@@ -887,6 +1093,46 @@ async function collabAdd(params) {
|
|
|
887
1093
|
}
|
|
888
1094
|
|
|
889
1095
|
// src/application/collab/collabRecordTurn.ts
|
|
1096
|
+
function assertSupportedRecordingPreflight2(preflight) {
|
|
1097
|
+
if (preflight.status === "not_bound") {
|
|
1098
|
+
throw new RemixError("Repository is not bound to Remix.", {
|
|
1099
|
+
exitCode: 2,
|
|
1100
|
+
hint: preflight.hint
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
if (preflight.status === "not_git_repo") {
|
|
1104
|
+
throw new RemixError(preflight.hint || "Not inside a git repository.", {
|
|
1105
|
+
exitCode: 2,
|
|
1106
|
+
hint: preflight.hint
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
if (preflight.status === "missing_head") {
|
|
1110
|
+
throw new RemixError("Failed to resolve local HEAD commit.", {
|
|
1111
|
+
exitCode: 1,
|
|
1112
|
+
hint: preflight.hint
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
if (preflight.status === "branch_mismatch") {
|
|
1116
|
+
assertPreferredBranchMatch({
|
|
1117
|
+
currentBranch: preflight.currentBranch,
|
|
1118
|
+
preferredBranch: preflight.preferredBranch,
|
|
1119
|
+
allowBranchMismatch: false,
|
|
1120
|
+
operation: "`remix collab record-turn`"
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
if (preflight.status === "metadata_conflict") {
|
|
1124
|
+
throw new RemixError("Local repository metadata conflicts with the bound Remix app.", {
|
|
1125
|
+
exitCode: 2,
|
|
1126
|
+
hint: preflight.hint
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
if (preflight.status === "reconcile_required") {
|
|
1130
|
+
throw new RemixError("Local repository cannot be fast-forward synced.", {
|
|
1131
|
+
exitCode: 2,
|
|
1132
|
+
hint: preflight.hint
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
890
1136
|
async function collabRecordTurn(params) {
|
|
891
1137
|
const repoRoot = await findGitRoot(params.cwd);
|
|
892
1138
|
const binding = await readCollabBinding(repoRoot);
|
|
@@ -900,6 +1146,26 @@ async function collabRecordTurn(params) {
|
|
|
900
1146
|
const assistantResponse = params.assistantResponse.trim();
|
|
901
1147
|
if (!prompt) throw new RemixError("Prompt is required.", { exitCode: 2 });
|
|
902
1148
|
if (!assistantResponse) throw new RemixError("Assistant response is required.", { exitCode: 2 });
|
|
1149
|
+
const preflight = await collabRecordingPreflight({
|
|
1150
|
+
api: params.api,
|
|
1151
|
+
cwd: repoRoot,
|
|
1152
|
+
allowBranchMismatch: params.allowBranchMismatch
|
|
1153
|
+
});
|
|
1154
|
+
assertSupportedRecordingPreflight2(preflight);
|
|
1155
|
+
if (!preflight.worktreeClean) {
|
|
1156
|
+
throw new RemixError("Cannot record a no-diff turn while the worktree has local changes.", {
|
|
1157
|
+
exitCode: 2,
|
|
1158
|
+
hint: "Record the pending code changes as a Remix change step with `remix collab add`, or clean the worktree before retrying `remix collab record-turn`."
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
if (preflight.status === "ready_to_fast_forward") {
|
|
1162
|
+
await collabSync({
|
|
1163
|
+
api: params.api,
|
|
1164
|
+
cwd: repoRoot,
|
|
1165
|
+
dryRun: false,
|
|
1166
|
+
allowBranchMismatch: params.allowBranchMismatch
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
903
1169
|
const branch = await getCurrentBranch(repoRoot);
|
|
904
1170
|
assertPreferredBranchMatch({
|
|
905
1171
|
currentBranch: branch,
|
|
@@ -1011,11 +1277,60 @@ async function collabApprove(params) {
|
|
|
1011
1277
|
};
|
|
1012
1278
|
}
|
|
1013
1279
|
|
|
1280
|
+
// src/application/collab/collabListMergeRequests.ts
|
|
1281
|
+
var APP_SCOPED_QUEUES = /* @__PURE__ */ new Set(["app_reviewable", "app_outgoing", "app_related_visible"]);
|
|
1282
|
+
async function resolveQueueAppId(params) {
|
|
1283
|
+
if (!APP_SCOPED_QUEUES.has(params.queue)) {
|
|
1284
|
+
return params.appId ?? void 0;
|
|
1285
|
+
}
|
|
1286
|
+
if (params.appId?.trim()) {
|
|
1287
|
+
return params.appId.trim();
|
|
1288
|
+
}
|
|
1289
|
+
if (!params.cwd?.trim()) {
|
|
1290
|
+
throw new RemixError("An app-scoped merge-request queue requires either `appId` or `cwd`.", {
|
|
1291
|
+
exitCode: 2,
|
|
1292
|
+
hint: "Pass `appId` explicitly, or run the request from a Remix-bound checkout with `cwd` set."
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
const repoRoot = await findGitRoot(params.cwd);
|
|
1296
|
+
const binding = await readCollabBinding(repoRoot);
|
|
1297
|
+
if (!binding) {
|
|
1298
|
+
throw new RemixError("Repository is not bound to Remix.", {
|
|
1299
|
+
exitCode: 2,
|
|
1300
|
+
hint: "Bind the repository first or pass `appId` explicitly for the app-scoped merge-request queue."
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
return binding.currentAppId;
|
|
1304
|
+
}
|
|
1305
|
+
async function collabListMergeRequests(params) {
|
|
1306
|
+
const appId = await resolveQueueAppId({
|
|
1307
|
+
cwd: params.cwd,
|
|
1308
|
+
appId: params.appId,
|
|
1309
|
+
queue: params.queue
|
|
1310
|
+
});
|
|
1311
|
+
const resp = await params.api.listMergeRequests({
|
|
1312
|
+
queue: params.queue,
|
|
1313
|
+
appId,
|
|
1314
|
+
status: params.status,
|
|
1315
|
+
kind: params.kind ?? "merge"
|
|
1316
|
+
});
|
|
1317
|
+
const mergeRequests = unwrapResponseObject(resp, "merge requests");
|
|
1318
|
+
return {
|
|
1319
|
+
queue: params.queue,
|
|
1320
|
+
appId: appId ?? null,
|
|
1321
|
+
mergeRequests
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1014
1325
|
// src/application/collab/collabInbox.ts
|
|
1015
1326
|
async function collabInbox(params) {
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1327
|
+
const result = await collabListMergeRequests({
|
|
1328
|
+
api: params.api,
|
|
1329
|
+
queue: "reviewable",
|
|
1330
|
+
status: "open",
|
|
1331
|
+
kind: "merge"
|
|
1332
|
+
});
|
|
1333
|
+
return { mergeRequests: result.mergeRequests };
|
|
1019
1334
|
}
|
|
1020
1335
|
|
|
1021
1336
|
// src/application/collab/collabInit.ts
|
|
@@ -1702,8 +2017,8 @@ async function collabStatus(params) {
|
|
|
1702
2017
|
}
|
|
1703
2018
|
const [appResult, incomingResult, outgoingResult, syncResult] = await Promise.allSettled([
|
|
1704
2019
|
params.api.getApp(binding.currentAppId),
|
|
1705
|
-
params.api.listMergeRequests({
|
|
1706
|
-
params.api.listMergeRequests({
|
|
2020
|
+
params.api.listMergeRequests({ queue: "app_reviewable", appId: binding.currentAppId, status: "open", kind: "merge" }),
|
|
2021
|
+
params.api.listMergeRequests({ queue: "app_outgoing", appId: binding.currentAppId, status: "open", kind: "merge" }),
|
|
1707
2022
|
headCommitHash ? params.api.syncLocalApp(binding.currentAppId, {
|
|
1708
2023
|
baseCommitHash: headCommitHash,
|
|
1709
2024
|
repoFingerprint: binding.repoFingerprint ?? void 0,
|
|
@@ -1809,7 +2124,7 @@ async function collabStatus(params) {
|
|
|
1809
2124
|
} else if (status.reconcile.canApply && status.reconcile.status === "ready_to_reconcile") {
|
|
1810
2125
|
status.recommendedAction = "reconcile";
|
|
1811
2126
|
} else if ((status.remote.incomingOpenMergeRequestCount ?? 0) > 0) {
|
|
1812
|
-
status.recommendedAction = "
|
|
2127
|
+
status.recommendedAction = "review_queue";
|
|
1813
2128
|
} else {
|
|
1814
2129
|
status.recommendedAction = "no_action";
|
|
1815
2130
|
}
|
|
@@ -1905,8 +2220,10 @@ export {
|
|
|
1905
2220
|
collabInit,
|
|
1906
2221
|
collabInvite,
|
|
1907
2222
|
collabList,
|
|
2223
|
+
collabListMergeRequests,
|
|
1908
2224
|
collabReconcile,
|
|
1909
2225
|
collabRecordTurn,
|
|
2226
|
+
collabRecordingPreflight,
|
|
1910
2227
|
collabReject,
|
|
1911
2228
|
collabRemix,
|
|
1912
2229
|
collabRequestMerge,
|
package/dist/index.js
CHANGED
package/dist/repo.d.ts
CHANGED
|
@@ -63,4 +63,4 @@ declare function summarizeUnifiedDiff(diff: string): {
|
|
|
63
63
|
deletions: number;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
export { assertRepoSnapshotUnchanged, buildRepoFingerprint, captureRepoSnapshot, cloneGitBundleToDirectory, createBackupBranch, createGitBundle, discardTrackedChanges, ensureCleanWorktree, ensureCommitExists, ensureGitInfoExcludeEntries, fastForwardToCommit, findGitRoot, getAbsoluteGitDir, getCurrentBranch, getDefaultBranch, getGitCommonDir, getGitPath, getHeadCommitHash, getRemoteOriginUrl, getWorkingTreeDiff, getWorkspaceDiff, hardResetToCommit, importGitBundle, listUntrackedFiles, normalizeGitRemote, requireCurrentBranch, summarizeUnifiedDiff, writeTempUnifiedDiffBackup };
|
|
66
|
+
export { type RepoSnapshot, assertRepoSnapshotUnchanged, buildRepoFingerprint, captureRepoSnapshot, cloneGitBundleToDirectory, createBackupBranch, createGitBundle, discardTrackedChanges, ensureCleanWorktree, ensureCommitExists, ensureGitInfoExcludeEntries, fastForwardToCommit, findGitRoot, getAbsoluteGitDir, getCurrentBranch, getDefaultBranch, getGitCommonDir, getGitPath, getHeadCommitHash, getRemoteOriginUrl, getWorkingTreeDiff, getWorkspaceDiff, hardResetToCommit, importGitBundle, listUntrackedFiles, normalizeGitRemote, requireCurrentBranch, summarizeUnifiedDiff, writeTempUnifiedDiffBackup };
|