@remixhq/core 0.1.5 → 0.1.7
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 +16 -0
- package/dist/api.js +1 -1
- package/dist/chunk-4276ARDF.js +303 -0
- package/dist/chunk-ZAQZKEH4.js +46 -0
- package/dist/collab.d.ts +88 -1
- package/dist/collab.js +263 -149
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ type MergeRequest = {
|
|
|
11
11
|
sourceTipCommitId: string | null;
|
|
12
12
|
targetAppId: string;
|
|
13
13
|
targetCommitId: string | null;
|
|
14
|
+
kind: "merge" | "sync";
|
|
14
15
|
status: MergeRequestStatus;
|
|
15
16
|
title: string | null;
|
|
16
17
|
description: string | null;
|
|
@@ -203,6 +204,9 @@ type ReconcilePreflightResponse = {
|
|
|
203
204
|
targetHeadCommitHash: string;
|
|
204
205
|
warnings: string[];
|
|
205
206
|
};
|
|
207
|
+
type OrganizationMemberRole = "owner" | "admin" | "member" | "viewer";
|
|
208
|
+
type ProjectMemberRole = "owner" | "maintainer" | "editor" | "viewer";
|
|
209
|
+
type AppMemberRole = "owner" | "maintainer" | "editor" | "viewer";
|
|
206
210
|
type AppReconcileResponse = {
|
|
207
211
|
id: string;
|
|
208
212
|
appId: string;
|
|
@@ -435,6 +439,18 @@ type ApiClient = {
|
|
|
435
439
|
role?: string;
|
|
436
440
|
ttlDays?: number;
|
|
437
441
|
}): Promise<Json>;
|
|
442
|
+
listOrganizationMembers(orgId: string): Promise<Json>;
|
|
443
|
+
updateOrganizationMember(orgId: string, userId: string, payload: {
|
|
444
|
+
role: OrganizationMemberRole;
|
|
445
|
+
}): Promise<Json>;
|
|
446
|
+
listProjectMembers(projectId: string): Promise<Json>;
|
|
447
|
+
updateProjectMember(projectId: string, userId: string, payload: {
|
|
448
|
+
role: ProjectMemberRole;
|
|
449
|
+
}): Promise<Json>;
|
|
450
|
+
listAppMembers(appId: string): Promise<Json>;
|
|
451
|
+
updateAppMember(appId: string, userId: string, payload: {
|
|
452
|
+
role: AppMemberRole;
|
|
453
|
+
}): Promise<Json>;
|
|
438
454
|
listOrganizationInvites(orgId: string): Promise<Json>;
|
|
439
455
|
listProjectInvites(projectId: string): Promise<Json>;
|
|
440
456
|
listAppInvites(appId: string): Promise<Json>;
|
package/dist/api.js
CHANGED
|
@@ -0,0 +1,303 @@
|
|
|
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
|
+
listOrganizationMembers: (orgId) => request(`/v1/organizations/${encodeURIComponent(orgId)}/members`, { method: "GET" }),
|
|
224
|
+
updateOrganizationMember: (orgId, userId, payload) => request(`/v1/organizations/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`, {
|
|
225
|
+
method: "PATCH",
|
|
226
|
+
body: JSON.stringify(payload)
|
|
227
|
+
}),
|
|
228
|
+
listProjectMembers: (projectId) => request(`/v1/projects/${encodeURIComponent(projectId)}/members`, { method: "GET" }),
|
|
229
|
+
updateProjectMember: (projectId, userId, payload) => request(`/v1/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`, {
|
|
230
|
+
method: "PATCH",
|
|
231
|
+
body: JSON.stringify(payload)
|
|
232
|
+
}),
|
|
233
|
+
listAppMembers: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/members`, { method: "GET" }),
|
|
234
|
+
updateAppMember: (appId, userId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/members/${encodeURIComponent(userId)}`, {
|
|
235
|
+
method: "PATCH",
|
|
236
|
+
body: JSON.stringify(payload)
|
|
237
|
+
}),
|
|
238
|
+
listOrganizationInvites: (orgId) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations`, { method: "GET" }),
|
|
239
|
+
listProjectInvites: (projectId) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations`, { method: "GET" }),
|
|
240
|
+
listAppInvites: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations`, { method: "GET" }),
|
|
241
|
+
resendOrganizationInvite: (orgId, inviteId, payload) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations/${encodeURIComponent(inviteId)}/resend`, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
body: JSON.stringify(payload ?? {})
|
|
244
|
+
}),
|
|
245
|
+
resendProjectInvite: (projectId, inviteId, payload) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations/${encodeURIComponent(inviteId)}/resend`, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
body: JSON.stringify(payload ?? {})
|
|
248
|
+
}),
|
|
249
|
+
resendAppInvite: (appId, inviteId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations/${encodeURIComponent(inviteId)}/resend`, {
|
|
250
|
+
method: "POST",
|
|
251
|
+
body: JSON.stringify(payload ?? {})
|
|
252
|
+
}),
|
|
253
|
+
revokeOrganizationInvite: (orgId, inviteId) => request(`/v1/organizations/${encodeURIComponent(orgId)}/invitations/${encodeURIComponent(inviteId)}`, {
|
|
254
|
+
method: "DELETE"
|
|
255
|
+
}),
|
|
256
|
+
revokeProjectInvite: (projectId, inviteId) => request(`/v1/projects/${encodeURIComponent(projectId)}/invitations/${encodeURIComponent(inviteId)}`, {
|
|
257
|
+
method: "DELETE"
|
|
258
|
+
}),
|
|
259
|
+
revokeAppInvite: (appId, inviteId) => request(`/v1/apps/${encodeURIComponent(appId)}/invitations/${encodeURIComponent(inviteId)}`, {
|
|
260
|
+
method: "DELETE"
|
|
261
|
+
}),
|
|
262
|
+
syncUpstreamApp: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}/sync-upstream`, {
|
|
263
|
+
method: "POST",
|
|
264
|
+
body: JSON.stringify({})
|
|
265
|
+
}),
|
|
266
|
+
preflightAppReconcile: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/reconcile/preflight`, {
|
|
267
|
+
method: "POST",
|
|
268
|
+
body: JSON.stringify(payload)
|
|
269
|
+
}),
|
|
270
|
+
startAppReconcile: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/reconcile/start`, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
body: JSON.stringify(payload)
|
|
273
|
+
}),
|
|
274
|
+
getAppReconcile: (appId, reconcileId) => request(`/v1/apps/${encodeURIComponent(appId)}/reconcile/${encodeURIComponent(reconcileId)}`, { method: "GET" }),
|
|
275
|
+
downloadAppReconcileBundle: (appId, reconcileId) => requestBinary(`/v1/apps/${encodeURIComponent(appId)}/reconcile/${encodeURIComponent(reconcileId)}/download.bundle`, {
|
|
276
|
+
method: "GET"
|
|
277
|
+
}),
|
|
278
|
+
syncLocalApp: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/sync-local`, {
|
|
279
|
+
method: "POST",
|
|
280
|
+
body: JSON.stringify(payload)
|
|
281
|
+
}),
|
|
282
|
+
initiateBundle: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles`, { method: "POST", body: JSON.stringify(payload) }),
|
|
283
|
+
getBundle: (appId, bundleId) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}`, { method: "GET" }),
|
|
284
|
+
getBundleDownloadUrl: (appId, bundleId, options) => request(
|
|
285
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/download?redirect=${options?.redirect ?? false}`,
|
|
286
|
+
{ method: "GET" }
|
|
287
|
+
),
|
|
288
|
+
getBundleAssetsDownloadUrl: (appId, bundleId, options) => {
|
|
289
|
+
const qs = new URLSearchParams({
|
|
290
|
+
redirect: String(options?.redirect ?? false),
|
|
291
|
+
kind: options?.kind ?? "metro-assets"
|
|
292
|
+
});
|
|
293
|
+
return request(
|
|
294
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download?${qs.toString()}`,
|
|
295
|
+
{ method: "GET" }
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export {
|
|
302
|
+
createApiClient
|
|
303
|
+
};
|
|
@@ -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://192.168.8.242: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
|
+
};
|
package/dist/collab.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ type MergeRequest = {
|
|
|
8
8
|
sourceTipCommitId: string | null;
|
|
9
9
|
targetAppId: string;
|
|
10
10
|
targetCommitId: string | null;
|
|
11
|
+
kind: "merge" | "sync";
|
|
11
12
|
status: MergeRequestStatus;
|
|
12
13
|
title: string | null;
|
|
13
14
|
description: string | null;
|
|
@@ -241,6 +242,38 @@ type CollabTurn = {
|
|
|
241
242
|
assistantResponse: string | null;
|
|
242
243
|
};
|
|
243
244
|
type InvitationScopeType = "organization" | "project" | "app";
|
|
245
|
+
type MembershipScopeType = InvitationScopeType;
|
|
246
|
+
type OrganizationMemberRole = "owner" | "admin" | "member" | "viewer";
|
|
247
|
+
type ProjectMemberRole = "owner" | "maintainer" | "editor" | "viewer";
|
|
248
|
+
type AppMemberRole = "owner" | "maintainer" | "editor" | "viewer";
|
|
249
|
+
type OrganizationMember = {
|
|
250
|
+
id: string;
|
|
251
|
+
userId: string;
|
|
252
|
+
organizationId: string;
|
|
253
|
+
role: OrganizationMemberRole;
|
|
254
|
+
invitedBy: string | null;
|
|
255
|
+
createdAt: string;
|
|
256
|
+
updatedAt: string;
|
|
257
|
+
};
|
|
258
|
+
type ProjectMember = {
|
|
259
|
+
id: string;
|
|
260
|
+
userId: string;
|
|
261
|
+
projectId: string;
|
|
262
|
+
role: ProjectMemberRole;
|
|
263
|
+
invitedBy: string | null;
|
|
264
|
+
createdAt: string;
|
|
265
|
+
updatedAt: string;
|
|
266
|
+
};
|
|
267
|
+
type AppMember = {
|
|
268
|
+
id: string;
|
|
269
|
+
userId: string;
|
|
270
|
+
appId: string;
|
|
271
|
+
role: AppMemberRole;
|
|
272
|
+
invitedBy: string | null;
|
|
273
|
+
createdAt: string;
|
|
274
|
+
updatedAt: string;
|
|
275
|
+
};
|
|
276
|
+
type CollabMember = OrganizationMember | ProjectMember | AppMember;
|
|
244
277
|
type CollabApiClient = {
|
|
245
278
|
resolveProjectBinding(params: {
|
|
246
279
|
repoFingerprint?: string;
|
|
@@ -366,6 +399,18 @@ type CollabApiClient = {
|
|
|
366
399
|
role?: string;
|
|
367
400
|
ttlDays?: number;
|
|
368
401
|
}): Promise<unknown>;
|
|
402
|
+
listOrganizationMembers(orgId: string): Promise<unknown>;
|
|
403
|
+
updateOrganizationMember(orgId: string, userId: string, payload: {
|
|
404
|
+
role: OrganizationMemberRole;
|
|
405
|
+
}): Promise<unknown>;
|
|
406
|
+
listProjectMembers(projectId: string): Promise<unknown>;
|
|
407
|
+
updateProjectMember(projectId: string, userId: string, payload: {
|
|
408
|
+
role: ProjectMemberRole;
|
|
409
|
+
}): Promise<unknown>;
|
|
410
|
+
listAppMembers(appId: string): Promise<unknown>;
|
|
411
|
+
updateAppMember(appId: string, userId: string, payload: {
|
|
412
|
+
role: AppMemberRole;
|
|
413
|
+
}): Promise<unknown>;
|
|
369
414
|
listOrganizationInvites(orgId: string): Promise<unknown>;
|
|
370
415
|
listProjectInvites(projectId: string): Promise<unknown>;
|
|
371
416
|
listAppInvites(appId: string): Promise<unknown>;
|
|
@@ -458,6 +503,20 @@ declare function collabApprove(params: {
|
|
|
458
503
|
allowBranchMismatch?: boolean;
|
|
459
504
|
}): Promise<CollabApproveResult>;
|
|
460
505
|
|
|
506
|
+
declare function collabCheckout(params: {
|
|
507
|
+
api: CollabApiClient;
|
|
508
|
+
cwd: string;
|
|
509
|
+
appId?: string | null;
|
|
510
|
+
outputDir?: string | null;
|
|
511
|
+
}): Promise<{
|
|
512
|
+
appId: string;
|
|
513
|
+
dashboardUrl: string;
|
|
514
|
+
projectId: string;
|
|
515
|
+
upstreamAppId: string;
|
|
516
|
+
bindingPath: string;
|
|
517
|
+
repoRoot: string;
|
|
518
|
+
}>;
|
|
519
|
+
|
|
461
520
|
declare function collabListMergeRequests(params: {
|
|
462
521
|
api: CollabApiClient;
|
|
463
522
|
queue: MergeRequestQueue;
|
|
@@ -471,6 +530,31 @@ declare function collabListMergeRequests(params: {
|
|
|
471
530
|
mergeRequests: MergeRequest[];
|
|
472
531
|
}>;
|
|
473
532
|
|
|
533
|
+
declare function getMemberRolesForScope(scope: InvitationScopeType): readonly string[];
|
|
534
|
+
declare function validateMemberRole(scope: InvitationScopeType, role: string): string;
|
|
535
|
+
declare function collabListMembers(params: {
|
|
536
|
+
api: CollabApiClient;
|
|
537
|
+
cwd: string;
|
|
538
|
+
scope: InvitationScopeType;
|
|
539
|
+
targetId?: string | null;
|
|
540
|
+
}): Promise<{
|
|
541
|
+
scopeType: InvitationScopeType;
|
|
542
|
+
targetId: string;
|
|
543
|
+
members: CollabMember[];
|
|
544
|
+
}>;
|
|
545
|
+
declare function collabUpdateMemberRole(params: {
|
|
546
|
+
api: CollabApiClient;
|
|
547
|
+
cwd: string;
|
|
548
|
+
scope: InvitationScopeType;
|
|
549
|
+
targetId?: string | null;
|
|
550
|
+
userId: string;
|
|
551
|
+
role: string;
|
|
552
|
+
}): Promise<{
|
|
553
|
+
scopeType: InvitationScopeType;
|
|
554
|
+
targetId: string;
|
|
555
|
+
member: CollabMember;
|
|
556
|
+
}>;
|
|
557
|
+
|
|
474
558
|
declare function collabInit(params: {
|
|
475
559
|
api: CollabApiClient;
|
|
476
560
|
cwd: string;
|
|
@@ -482,6 +566,7 @@ declare function collabInit(params: {
|
|
|
482
566
|
reused: boolean;
|
|
483
567
|
projectId: string;
|
|
484
568
|
appId: string;
|
|
569
|
+
dashboardUrl: string;
|
|
485
570
|
upstreamAppId: string;
|
|
486
571
|
bindingPath: string;
|
|
487
572
|
repoRoot: string;
|
|
@@ -490,6 +575,7 @@ declare function collabInit(params: {
|
|
|
490
575
|
reused: boolean;
|
|
491
576
|
projectId: string;
|
|
492
577
|
appId: string;
|
|
578
|
+
dashboardUrl: string;
|
|
493
579
|
upstreamAppId: string;
|
|
494
580
|
bindingPath: string;
|
|
495
581
|
repoRoot: string;
|
|
@@ -595,6 +681,7 @@ declare function collabRemix(params: {
|
|
|
595
681
|
outputDir?: string | null;
|
|
596
682
|
}): Promise<{
|
|
597
683
|
appId: string;
|
|
684
|
+
dashboardUrl: string;
|
|
598
685
|
projectId: string;
|
|
599
686
|
upstreamAppId: string;
|
|
600
687
|
bindingPath: string;
|
|
@@ -710,4 +797,4 @@ declare function collabView(params: {
|
|
|
710
797
|
mrId: string;
|
|
711
798
|
}): Promise<MergeRequestReview>;
|
|
712
799
|
|
|
713
|
-
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, collabInit, collabInvite, collabList, collabListMergeRequests, collabReconcile, collabRecordTurn, collabRecordingPreflight, collabReject, collabRemix, collabRequestMerge, collabStatus, collabSync, collabSyncUpstream, collabView };
|
|
800
|
+
export { type AppMember, type AppMemberRole, type AppReconcileResponse, type CollabApiClient, type CollabApproveMode, type CollabApproveResult, type CollabMember, type CollabRecordingPreflight, type CollabRecordingPreflightStatus, type CollabStatus, type CollabStatusBlockedReason, type CollabStatusRecommendedAction, type CollabTurn, type InvitationScopeType, type JsonObject, type MembershipScopeType, type MergeRequest, type MergeRequestQueue, type MergeRequestReview, type MergeRequestStatus, type OrganizationMember, type OrganizationMemberRole, type ProjectMember, type ProjectMemberRole, type ReconcilePreflightResponse, type SyncLocalResponse, type SyncUpstreamResponse, collabAdd, collabApprove, collabCheckout, collabInit, collabInvite, collabList, collabListMembers, collabListMergeRequests, collabReconcile, collabRecordTurn, collabRecordingPreflight, collabReject, collabRemix, collabRequestMerge, collabStatus, collabSync, collabSyncUpstream, collabUpdateMemberRole, collabView, getMemberRolesForScope, validateMemberRole };
|
package/dist/collab.js
CHANGED
|
@@ -116,6 +116,9 @@ function sanitizeCheckoutDirName(value) {
|
|
|
116
116
|
const sanitized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
117
117
|
return sanitized || "remix-remix";
|
|
118
118
|
}
|
|
119
|
+
function buildDashboardAppUrl(appId) {
|
|
120
|
+
return `https://dashboard.remix.one/apps/${encodeURIComponent(appId)}`;
|
|
121
|
+
}
|
|
119
122
|
async function pollAppReady(api, appId) {
|
|
120
123
|
const started = Date.now();
|
|
121
124
|
let delay = 2e3;
|
|
@@ -1277,6 +1280,163 @@ async function collabApprove(params) {
|
|
|
1277
1280
|
};
|
|
1278
1281
|
}
|
|
1279
1282
|
|
|
1283
|
+
// src/application/collab/checkoutWorkspace.ts
|
|
1284
|
+
import fs4 from "fs/promises";
|
|
1285
|
+
import os3 from "os";
|
|
1286
|
+
import path4 from "path";
|
|
1287
|
+
async function pathExists(targetPath) {
|
|
1288
|
+
try {
|
|
1289
|
+
await fs4.access(targetPath);
|
|
1290
|
+
return true;
|
|
1291
|
+
} catch {
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
async function statIsDirectory(targetPath) {
|
|
1296
|
+
const stats = await fs4.stat(targetPath).catch(() => null);
|
|
1297
|
+
return Boolean(stats?.isDirectory());
|
|
1298
|
+
}
|
|
1299
|
+
async function findContainingGitRoot(startPath) {
|
|
1300
|
+
let current = path4.resolve(startPath);
|
|
1301
|
+
while (true) {
|
|
1302
|
+
if (await pathExists(path4.join(current, ".git"))) return current;
|
|
1303
|
+
const parent = path4.dirname(current);
|
|
1304
|
+
if (parent === current) return null;
|
|
1305
|
+
current = parent;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function isSubpath(parentPath, candidatePath) {
|
|
1309
|
+
const relative = path4.relative(parentPath, candidatePath);
|
|
1310
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
1311
|
+
}
|
|
1312
|
+
function buildPreferredCheckoutBranch(appId) {
|
|
1313
|
+
const normalized = appId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1314
|
+
return `remix/remix/${normalized || "app"}`;
|
|
1315
|
+
}
|
|
1316
|
+
async function resolveCheckoutDestination(params) {
|
|
1317
|
+
if (params.outputDir?.trim()) {
|
|
1318
|
+
const preferredRepoRoot = path4.resolve(params.outputDir.trim());
|
|
1319
|
+
const parentDir2 = path4.dirname(preferredRepoRoot);
|
|
1320
|
+
if (!await statIsDirectory(parentDir2)) {
|
|
1321
|
+
throw new RemixError("Remix output parent directory does not exist.", {
|
|
1322
|
+
exitCode: 2,
|
|
1323
|
+
hint: `Create the directory first: ${parentDir2}`
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
return {
|
|
1327
|
+
preferredRepoRoot,
|
|
1328
|
+
parentDir: parentDir2,
|
|
1329
|
+
explicitOutputDir: true
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
const parentDir = path4.resolve(params.cwd);
|
|
1333
|
+
if (!await statIsDirectory(parentDir)) {
|
|
1334
|
+
throw new RemixError("Remix output parent directory does not exist.", {
|
|
1335
|
+
exitCode: 2,
|
|
1336
|
+
hint: `Create the directory first: ${parentDir}`
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
return {
|
|
1340
|
+
preferredRepoRoot: path4.join(parentDir, params.defaultDirName),
|
|
1341
|
+
parentDir,
|
|
1342
|
+
explicitOutputDir: false
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
async function assertSafeCheckoutDestination(params) {
|
|
1346
|
+
const callerGitRoot = await findContainingGitRoot(params.cwd);
|
|
1347
|
+
if (callerGitRoot && isSubpath(callerGitRoot, params.repoRoot)) {
|
|
1348
|
+
throw new RemixError("Refusing to create a remix checkout inside an existing git repository.", {
|
|
1349
|
+
exitCode: 2,
|
|
1350
|
+
hint: params.explicitOutputDir ? `Choose a destination outside ${callerGitRoot}.` : `Pass --output-dir outside ${callerGitRoot}.`
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
const parentGitRoot = await findContainingGitRoot(params.parentDir);
|
|
1354
|
+
if (parentGitRoot && isSubpath(parentGitRoot, params.repoRoot)) {
|
|
1355
|
+
throw new RemixError("Refusing to create a remix checkout inside an existing git repository.", {
|
|
1356
|
+
exitCode: 2,
|
|
1357
|
+
hint: params.explicitOutputDir ? `Choose a destination outside ${parentGitRoot}.` : `Pass --output-dir outside ${parentGitRoot}.`
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
async function materializeAppCheckout(params) {
|
|
1362
|
+
const destination = await resolveCheckoutDestination({
|
|
1363
|
+
cwd: params.cwd,
|
|
1364
|
+
outputDir: params.outputDir ?? null,
|
|
1365
|
+
defaultDirName: params.defaultDirName
|
|
1366
|
+
});
|
|
1367
|
+
await assertSafeCheckoutDestination({
|
|
1368
|
+
cwd: params.cwd,
|
|
1369
|
+
repoRoot: destination.preferredRepoRoot,
|
|
1370
|
+
parentDir: destination.parentDir,
|
|
1371
|
+
explicitOutputDir: destination.explicitOutputDir
|
|
1372
|
+
});
|
|
1373
|
+
const repoRoot = destination.explicitOutputDir ? await reserveDirectory(destination.preferredRepoRoot) : await reserveAvailableDirPath(destination.preferredRepoRoot);
|
|
1374
|
+
const bundleTempDir = await fs4.mkdtemp(path4.join(os3.tmpdir(), "remix-checkout-"));
|
|
1375
|
+
const bundlePath = path4.join(bundleTempDir, "repository.bundle");
|
|
1376
|
+
try {
|
|
1377
|
+
const bundle = await params.api.downloadAppBundle(params.appId);
|
|
1378
|
+
await fs4.writeFile(bundlePath, bundle.data);
|
|
1379
|
+
await cloneGitBundleToDirectory(bundlePath, repoRoot);
|
|
1380
|
+
await checkoutLocalBranch(repoRoot, buildPreferredCheckoutBranch(params.appId));
|
|
1381
|
+
await ensureGitInfoExcludeEntries(repoRoot, [".remix/"]);
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
await fs4.rm(repoRoot, { recursive: true, force: true }).catch(() => {
|
|
1384
|
+
});
|
|
1385
|
+
throw err;
|
|
1386
|
+
} finally {
|
|
1387
|
+
await fs4.rm(bundleTempDir, { recursive: true, force: true });
|
|
1388
|
+
}
|
|
1389
|
+
const remoteUrl = normalizeGitRemote(await getRemoteOriginUrl(repoRoot));
|
|
1390
|
+
const defaultBranch = await getDefaultBranch(repoRoot) ?? await getCurrentBranch(repoRoot) ?? null;
|
|
1391
|
+
const preferredBranch = await getCurrentBranch(repoRoot) ?? buildPreferredCheckoutBranch(params.appId);
|
|
1392
|
+
const repoFingerprint = remoteUrl ? await buildRepoFingerprint({ gitRoot: repoRoot, remoteUrl, defaultBranch }) : null;
|
|
1393
|
+
return {
|
|
1394
|
+
repoRoot,
|
|
1395
|
+
remoteUrl,
|
|
1396
|
+
defaultBranch,
|
|
1397
|
+
preferredBranch,
|
|
1398
|
+
repoFingerprint
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/application/collab/collabCheckout.ts
|
|
1403
|
+
async function collabCheckout(params) {
|
|
1404
|
+
const appId = params.appId?.trim() || null;
|
|
1405
|
+
if (!appId) {
|
|
1406
|
+
throw new RemixError("No app selected.", {
|
|
1407
|
+
exitCode: 2,
|
|
1408
|
+
hint: "Pass the app id to checkout."
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
const app = await pollAppReady(params.api, appId);
|
|
1412
|
+
const checkout = await materializeAppCheckout({
|
|
1413
|
+
api: params.api,
|
|
1414
|
+
cwd: params.cwd,
|
|
1415
|
+
appId: String(app.id),
|
|
1416
|
+
outputDir: params.outputDir ?? null,
|
|
1417
|
+
defaultDirName: sanitizeCheckoutDirName(String(app.name || app.id))
|
|
1418
|
+
});
|
|
1419
|
+
const upstreamAppId = String(app.forkedFromAppId ?? app.id);
|
|
1420
|
+
const bindingPath = await writeCollabBinding(checkout.repoRoot, {
|
|
1421
|
+
projectId: String(app.projectId),
|
|
1422
|
+
currentAppId: String(app.id),
|
|
1423
|
+
upstreamAppId,
|
|
1424
|
+
threadId: app.threadId ? String(app.threadId) : null,
|
|
1425
|
+
repoFingerprint: checkout.repoFingerprint,
|
|
1426
|
+
remoteUrl: checkout.remoteUrl,
|
|
1427
|
+
defaultBranch: checkout.defaultBranch,
|
|
1428
|
+
preferredBranch: checkout.preferredBranch
|
|
1429
|
+
});
|
|
1430
|
+
return {
|
|
1431
|
+
appId: String(app.id),
|
|
1432
|
+
dashboardUrl: buildDashboardAppUrl(String(app.id)),
|
|
1433
|
+
projectId: String(app.projectId),
|
|
1434
|
+
upstreamAppId,
|
|
1435
|
+
bindingPath,
|
|
1436
|
+
repoRoot: checkout.repoRoot
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1280
1440
|
// src/application/collab/collabListMergeRequests.ts
|
|
1281
1441
|
var APP_SCOPED_QUEUES = /* @__PURE__ */ new Set(["app_reviewable", "app_outgoing", "app_related_visible"]);
|
|
1282
1442
|
async function resolveQueueAppId(params) {
|
|
@@ -1322,17 +1482,81 @@ async function collabListMergeRequests(params) {
|
|
|
1322
1482
|
};
|
|
1323
1483
|
}
|
|
1324
1484
|
|
|
1485
|
+
// src/application/collab/resolveScopeTarget.ts
|
|
1486
|
+
async function resolveScopeTarget(params) {
|
|
1487
|
+
if (params.targetId?.trim()) return params.targetId.trim();
|
|
1488
|
+
const repoRoot = await findGitRoot(params.cwd);
|
|
1489
|
+
const binding = await readCollabBinding(repoRoot);
|
|
1490
|
+
if (!binding) {
|
|
1491
|
+
throw new RemixError("Repository is not bound to Remix and no explicit target id was provided.", { exitCode: 2 });
|
|
1492
|
+
}
|
|
1493
|
+
if (params.scope === "project") return binding.projectId;
|
|
1494
|
+
if (params.scope === "app") return binding.currentAppId;
|
|
1495
|
+
const project = unwrapResponseObject(await params.api.getProject(binding.projectId), "project");
|
|
1496
|
+
const organizationId = typeof project.organizationId === "string" ? project.organizationId : null;
|
|
1497
|
+
if (!organizationId) {
|
|
1498
|
+
throw new RemixError("Could not resolve the organization for the current repository binding.", { exitCode: 2 });
|
|
1499
|
+
}
|
|
1500
|
+
return organizationId;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// src/application/collab/collabMembers.ts
|
|
1504
|
+
var ORGANIZATION_MEMBER_ROLES = ["owner", "admin", "member", "viewer"];
|
|
1505
|
+
var WORKFLOW_MEMBER_ROLES = ["owner", "maintainer", "editor", "viewer"];
|
|
1506
|
+
function getMemberRolesForScope(scope) {
|
|
1507
|
+
return scope === "organization" ? ORGANIZATION_MEMBER_ROLES : WORKFLOW_MEMBER_ROLES;
|
|
1508
|
+
}
|
|
1509
|
+
function validateMemberRole(scope, role) {
|
|
1510
|
+
const normalized = role.trim().toLowerCase();
|
|
1511
|
+
if (getMemberRolesForScope(scope).includes(normalized)) return normalized;
|
|
1512
|
+
throw new RemixError(`Invalid ${scope} member role.`, {
|
|
1513
|
+
exitCode: 2,
|
|
1514
|
+
hint: `Allowed roles: ${getMemberRolesForScope(scope).join(", ")}`
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
async function collabListMembers(params) {
|
|
1518
|
+
const targetId = await resolveScopeTarget({
|
|
1519
|
+
api: params.api,
|
|
1520
|
+
cwd: params.cwd,
|
|
1521
|
+
scope: params.scope,
|
|
1522
|
+
targetId: params.targetId
|
|
1523
|
+
});
|
|
1524
|
+
const resp = params.scope === "organization" ? await params.api.listOrganizationMembers(targetId) : params.scope === "project" ? await params.api.listProjectMembers(targetId) : await params.api.listAppMembers(targetId);
|
|
1525
|
+
const members = params.scope === "organization" ? unwrapResponseObject(resp, "members") : params.scope === "project" ? unwrapResponseObject(resp, "members") : unwrapResponseObject(resp, "members");
|
|
1526
|
+
return {
|
|
1527
|
+
scopeType: params.scope,
|
|
1528
|
+
targetId,
|
|
1529
|
+
members
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
async function collabUpdateMemberRole(params) {
|
|
1533
|
+
const targetId = await resolveScopeTarget({
|
|
1534
|
+
api: params.api,
|
|
1535
|
+
cwd: params.cwd,
|
|
1536
|
+
scope: params.scope,
|
|
1537
|
+
targetId: params.targetId
|
|
1538
|
+
});
|
|
1539
|
+
const role = validateMemberRole(params.scope, params.role);
|
|
1540
|
+
const resp = params.scope === "organization" ? await params.api.updateOrganizationMember(targetId, params.userId, { role }) : params.scope === "project" ? await params.api.updateProjectMember(targetId, params.userId, { role }) : await params.api.updateAppMember(targetId, params.userId, { role });
|
|
1541
|
+
const member = params.scope === "organization" ? unwrapResponseObject(resp, "member") : params.scope === "project" ? unwrapResponseObject(resp, "member") : unwrapResponseObject(resp, "member");
|
|
1542
|
+
return {
|
|
1543
|
+
scopeType: params.scope,
|
|
1544
|
+
targetId,
|
|
1545
|
+
member
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1325
1549
|
// src/application/collab/collabInit.ts
|
|
1326
|
-
import
|
|
1327
|
-
import
|
|
1550
|
+
import fs7 from "fs/promises";
|
|
1551
|
+
import path5 from "path";
|
|
1328
1552
|
|
|
1329
1553
|
// src/shared/hash.ts
|
|
1330
1554
|
import crypto from "crypto";
|
|
1331
|
-
import
|
|
1555
|
+
import fs5 from "fs";
|
|
1332
1556
|
async function sha256FileHex(filePath) {
|
|
1333
1557
|
const hash = crypto.createHash("sha256");
|
|
1334
1558
|
await new Promise((resolve, reject) => {
|
|
1335
|
-
const stream =
|
|
1559
|
+
const stream = fs5.createReadStream(filePath);
|
|
1336
1560
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
1337
1561
|
stream.on("error", reject);
|
|
1338
1562
|
stream.on("end", () => resolve());
|
|
@@ -1341,15 +1565,15 @@ async function sha256FileHex(filePath) {
|
|
|
1341
1565
|
}
|
|
1342
1566
|
|
|
1343
1567
|
// src/shared/upload.ts
|
|
1344
|
-
import
|
|
1568
|
+
import fs6 from "fs";
|
|
1345
1569
|
import { PassThrough } from "stream";
|
|
1346
1570
|
async function uploadPresigned(params) {
|
|
1347
|
-
const stats = await
|
|
1571
|
+
const stats = await fs6.promises.stat(params.filePath).catch(() => null);
|
|
1348
1572
|
if (!stats || !stats.isFile()) {
|
|
1349
1573
|
throw new RemixError("Upload file not found.", { exitCode: 2 });
|
|
1350
1574
|
}
|
|
1351
1575
|
const totalBytes = stats.size;
|
|
1352
|
-
const fileStream =
|
|
1576
|
+
const fileStream = fs6.createReadStream(params.filePath);
|
|
1353
1577
|
const pass = new PassThrough();
|
|
1354
1578
|
let sentBytes = 0;
|
|
1355
1579
|
fileStream.on("data", (chunk) => {
|
|
@@ -1421,6 +1645,7 @@ async function collabInit(params) {
|
|
|
1421
1645
|
reused: true,
|
|
1422
1646
|
projectId: String(existing.projectId),
|
|
1423
1647
|
appId: String(existing.appId),
|
|
1648
|
+
dashboardUrl: buildDashboardAppUrl(String(existing.appId)),
|
|
1424
1649
|
upstreamAppId: String(existing.upstreamAppId ?? existing.appId),
|
|
1425
1650
|
bindingPath: bindingPath2,
|
|
1426
1651
|
repoRoot,
|
|
@@ -1430,7 +1655,7 @@ async function collabInit(params) {
|
|
|
1430
1655
|
}
|
|
1431
1656
|
const { bundlePath, headCommitHash } = await createGitBundle(repoRoot, "repository.bundle");
|
|
1432
1657
|
const bundleSha = await sha256FileHex(bundlePath);
|
|
1433
|
-
const bundleSize = (await
|
|
1658
|
+
const bundleSize = (await fs7.stat(bundlePath)).size;
|
|
1434
1659
|
const presignResp = await params.api.presignImportUploadFirstParty({
|
|
1435
1660
|
file: {
|
|
1436
1661
|
name: "repository.bundle",
|
|
@@ -1447,7 +1672,7 @@ async function collabInit(params) {
|
|
|
1447
1672
|
});
|
|
1448
1673
|
const importResp = await params.api.importFromUploadFirstParty({
|
|
1449
1674
|
uploadId: String(presign.uploadId),
|
|
1450
|
-
appName: params.appName?.trim() ||
|
|
1675
|
+
appName: params.appName?.trim() || path5.basename(repoRoot),
|
|
1451
1676
|
path: params.path?.trim() || void 0,
|
|
1452
1677
|
platform: "generic",
|
|
1453
1678
|
isPublic: false,
|
|
@@ -1476,6 +1701,7 @@ async function collabInit(params) {
|
|
|
1476
1701
|
reused: false,
|
|
1477
1702
|
projectId: String(app.projectId),
|
|
1478
1703
|
appId: String(app.id),
|
|
1704
|
+
dashboardUrl: buildDashboardAppUrl(String(app.id)),
|
|
1479
1705
|
upstreamAppId: String(app.id),
|
|
1480
1706
|
bindingPath,
|
|
1481
1707
|
repoRoot,
|
|
@@ -1489,22 +1715,6 @@ async function collabInit(params) {
|
|
|
1489
1715
|
}
|
|
1490
1716
|
|
|
1491
1717
|
// src/application/collab/collabInvite.ts
|
|
1492
|
-
async function resolveScopeTarget(params) {
|
|
1493
|
-
if (params.targetId?.trim()) return params.targetId.trim();
|
|
1494
|
-
const repoRoot = await findGitRoot(params.cwd);
|
|
1495
|
-
const binding = await readCollabBinding(repoRoot);
|
|
1496
|
-
if (!binding) {
|
|
1497
|
-
throw new RemixError("Repository is not bound to Remix and no explicit target id was provided.", { exitCode: 2 });
|
|
1498
|
-
}
|
|
1499
|
-
if (params.scope === "project") return binding.projectId;
|
|
1500
|
-
if (params.scope === "app") return binding.currentAppId;
|
|
1501
|
-
const project = unwrapResponseObject(await params.api.getProject(binding.projectId), "project");
|
|
1502
|
-
const organizationId = typeof project.organizationId === "string" ? project.organizationId : null;
|
|
1503
|
-
if (!organizationId) {
|
|
1504
|
-
throw new RemixError("Could not resolve the organization for the current repository binding.", { exitCode: 2 });
|
|
1505
|
-
}
|
|
1506
|
-
return organizationId;
|
|
1507
|
-
}
|
|
1508
1718
|
async function collabInvite(params) {
|
|
1509
1719
|
const scope = params.scope ?? "project";
|
|
1510
1720
|
const targetId = await resolveScopeTarget({
|
|
@@ -1535,9 +1745,9 @@ async function collabList(params) {
|
|
|
1535
1745
|
}
|
|
1536
1746
|
|
|
1537
1747
|
// src/application/collab/collabReconcile.ts
|
|
1538
|
-
import
|
|
1539
|
-
import
|
|
1540
|
-
import
|
|
1748
|
+
import fs8 from "fs/promises";
|
|
1749
|
+
import os4 from "os";
|
|
1750
|
+
import path6 from "path";
|
|
1541
1751
|
async function collabReconcile(params) {
|
|
1542
1752
|
const repoRoot = await findGitRoot(params.cwd);
|
|
1543
1753
|
const binding = await readCollabBinding(repoRoot);
|
|
@@ -1608,13 +1818,13 @@ async function collabReconcile(params) {
|
|
|
1608
1818
|
return previewResult;
|
|
1609
1819
|
}
|
|
1610
1820
|
const { bundlePath, headCommitHash: bundledHeadCommitHash } = await createGitBundle(repoRoot, "reconcile-local.bundle");
|
|
1611
|
-
const bundleTempDir =
|
|
1821
|
+
const bundleTempDir = path6.dirname(bundlePath);
|
|
1612
1822
|
try {
|
|
1613
|
-
const bundleStat = await
|
|
1823
|
+
const bundleStat = await fs8.stat(bundlePath);
|
|
1614
1824
|
const checksumSha256 = await sha256FileHex(bundlePath);
|
|
1615
1825
|
const presignResp = await params.api.presignImportUploadFirstParty({
|
|
1616
1826
|
file: {
|
|
1617
|
-
name:
|
|
1827
|
+
name: path6.basename(bundlePath),
|
|
1618
1828
|
mimeType: "application/x-git-bundle",
|
|
1619
1829
|
size: bundleStat.size,
|
|
1620
1830
|
checksumSha256
|
|
@@ -1673,10 +1883,10 @@ async function collabReconcile(params) {
|
|
|
1673
1883
|
});
|
|
1674
1884
|
await hardResetToCommit(lockedRepoRoot, mergeBaseCommitHash, "`remix collab reconcile`");
|
|
1675
1885
|
const bundleResp = await params.api.downloadAppReconcileBundle(binding.currentAppId, reconcile.id);
|
|
1676
|
-
const resultTempDir = await
|
|
1677
|
-
const resultBundlePath =
|
|
1886
|
+
const resultTempDir = await fs8.mkdtemp(path6.join(os4.tmpdir(), "remix-reconcile-"));
|
|
1887
|
+
const resultBundlePath = path6.join(resultTempDir, bundleResp.fileName ?? "reconcile-result.bundle");
|
|
1678
1888
|
try {
|
|
1679
|
-
await
|
|
1889
|
+
await fs8.writeFile(resultBundlePath, bundleResp.data);
|
|
1680
1890
|
await importGitBundle(lockedRepoRoot, resultBundlePath, resultBundleRef);
|
|
1681
1891
|
await ensureCommitExists(lockedRepoRoot, reconciledHeadCommitHash);
|
|
1682
1892
|
const localCommitHash = await fastForwardToCommit(lockedRepoRoot, reconciledHeadCommitHash);
|
|
@@ -1697,12 +1907,12 @@ async function collabReconcile(params) {
|
|
|
1697
1907
|
warnings: Array.from(/* @__PURE__ */ new Set([...previewResult.warnings, ...warnings]))
|
|
1698
1908
|
};
|
|
1699
1909
|
} finally {
|
|
1700
|
-
await
|
|
1910
|
+
await fs8.rm(resultTempDir, { recursive: true, force: true });
|
|
1701
1911
|
}
|
|
1702
1912
|
}
|
|
1703
1913
|
);
|
|
1704
1914
|
} finally {
|
|
1705
|
-
await
|
|
1915
|
+
await fs8.rm(bundleTempDir, { recursive: true, force: true });
|
|
1706
1916
|
}
|
|
1707
1917
|
}
|
|
1708
1918
|
|
|
@@ -1713,83 +1923,6 @@ async function collabReject(params) {
|
|
|
1713
1923
|
}
|
|
1714
1924
|
|
|
1715
1925
|
// src/application/collab/collabRemix.ts
|
|
1716
|
-
import fs8 from "fs/promises";
|
|
1717
|
-
import os4 from "os";
|
|
1718
|
-
import path6 from "path";
|
|
1719
|
-
async function pathExists(targetPath) {
|
|
1720
|
-
try {
|
|
1721
|
-
await fs8.access(targetPath);
|
|
1722
|
-
return true;
|
|
1723
|
-
} catch {
|
|
1724
|
-
return false;
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
async function statIsDirectory(targetPath) {
|
|
1728
|
-
const stats = await fs8.stat(targetPath).catch(() => null);
|
|
1729
|
-
return Boolean(stats?.isDirectory());
|
|
1730
|
-
}
|
|
1731
|
-
async function findContainingGitRoot(startPath) {
|
|
1732
|
-
let current = path6.resolve(startPath);
|
|
1733
|
-
while (true) {
|
|
1734
|
-
if (await pathExists(path6.join(current, ".git"))) return current;
|
|
1735
|
-
const parent = path6.dirname(current);
|
|
1736
|
-
if (parent === current) return null;
|
|
1737
|
-
current = parent;
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
function isSubpath(parentPath, candidatePath) {
|
|
1741
|
-
const relative = path6.relative(parentPath, candidatePath);
|
|
1742
|
-
return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
|
|
1743
|
-
}
|
|
1744
|
-
function buildPreferredRemixBranch(appId) {
|
|
1745
|
-
const normalized = appId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1746
|
-
return `remix/remix/${normalized || "app"}`;
|
|
1747
|
-
}
|
|
1748
|
-
async function resolveRemixDestination(params) {
|
|
1749
|
-
if (params.outputDir?.trim()) {
|
|
1750
|
-
const preferredRepoRoot = path6.resolve(params.outputDir.trim());
|
|
1751
|
-
const parentDir2 = path6.dirname(preferredRepoRoot);
|
|
1752
|
-
if (!await statIsDirectory(parentDir2)) {
|
|
1753
|
-
throw new RemixError("Remix output parent directory does not exist.", {
|
|
1754
|
-
exitCode: 2,
|
|
1755
|
-
hint: `Create the directory first: ${parentDir2}`
|
|
1756
|
-
});
|
|
1757
|
-
}
|
|
1758
|
-
return {
|
|
1759
|
-
preferredRepoRoot,
|
|
1760
|
-
parentDir: parentDir2,
|
|
1761
|
-
explicitOutputDir: true
|
|
1762
|
-
};
|
|
1763
|
-
}
|
|
1764
|
-
const parentDir = path6.resolve(params.cwd);
|
|
1765
|
-
if (!await statIsDirectory(parentDir)) {
|
|
1766
|
-
throw new RemixError("Remix output parent directory does not exist.", {
|
|
1767
|
-
exitCode: 2,
|
|
1768
|
-
hint: `Create the directory first: ${parentDir}`
|
|
1769
|
-
});
|
|
1770
|
-
}
|
|
1771
|
-
return {
|
|
1772
|
-
preferredRepoRoot: path6.join(parentDir, params.defaultDirName),
|
|
1773
|
-
parentDir,
|
|
1774
|
-
explicitOutputDir: false
|
|
1775
|
-
};
|
|
1776
|
-
}
|
|
1777
|
-
async function assertSafeRemixDestination(params) {
|
|
1778
|
-
const callerGitRoot = await findContainingGitRoot(params.cwd);
|
|
1779
|
-
if (callerGitRoot && isSubpath(callerGitRoot, params.repoRoot)) {
|
|
1780
|
-
throw new RemixError("Refusing to create a remix checkout inside an existing git repository.", {
|
|
1781
|
-
exitCode: 2,
|
|
1782
|
-
hint: params.explicitOutputDir ? `Choose a destination outside ${callerGitRoot}.` : `Pass --output-dir outside ${callerGitRoot}.`
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1785
|
-
const parentGitRoot = await findContainingGitRoot(params.parentDir);
|
|
1786
|
-
if (parentGitRoot && isSubpath(parentGitRoot, params.repoRoot)) {
|
|
1787
|
-
throw new RemixError("Refusing to create a remix checkout inside an existing git repository.", {
|
|
1788
|
-
exitCode: 2,
|
|
1789
|
-
hint: params.explicitOutputDir ? `Choose a destination outside ${parentGitRoot}.` : `Pass --output-dir outside ${parentGitRoot}.`
|
|
1790
|
-
});
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
1926
|
async function collabRemix(params) {
|
|
1794
1927
|
const sourceAppId = params.appId?.trim() || null;
|
|
1795
1928
|
if (!sourceAppId) {
|
|
@@ -1801,54 +1934,30 @@ async function collabRemix(params) {
|
|
|
1801
1934
|
const forkResp = await params.api.forkApp(sourceAppId, { name: params.name?.trim() || void 0, platform: "generic" });
|
|
1802
1935
|
const forked = unwrapResponseObject(forkResp, "fork");
|
|
1803
1936
|
const app = await pollAppReady(params.api, String(forked.id));
|
|
1804
|
-
const
|
|
1805
|
-
|
|
1937
|
+
const checkout = await materializeAppCheckout({
|
|
1938
|
+
api: params.api,
|
|
1806
1939
|
cwd: params.cwd,
|
|
1940
|
+
appId: String(app.id),
|
|
1807
1941
|
outputDir: params.outputDir ?? null,
|
|
1808
|
-
defaultDirName:
|
|
1809
|
-
});
|
|
1810
|
-
await assertSafeRemixDestination({
|
|
1811
|
-
cwd: params.cwd,
|
|
1812
|
-
repoRoot: destination.preferredRepoRoot,
|
|
1813
|
-
parentDir: destination.parentDir,
|
|
1814
|
-
explicitOutputDir: destination.explicitOutputDir
|
|
1942
|
+
defaultDirName: sanitizeCheckoutDirName(String(params.name?.trim() || app.name || app.id))
|
|
1815
1943
|
});
|
|
1816
|
-
const
|
|
1817
|
-
const bundleTempDir = await fs8.mkdtemp(path6.join(os4.tmpdir(), "remix-remix-"));
|
|
1818
|
-
const bundlePath = path6.join(bundleTempDir, "repository.bundle");
|
|
1819
|
-
try {
|
|
1820
|
-
const bundle = await params.api.downloadAppBundle(String(app.id));
|
|
1821
|
-
await fs8.writeFile(bundlePath, bundle.data);
|
|
1822
|
-
await cloneGitBundleToDirectory(bundlePath, repoRoot);
|
|
1823
|
-
await checkoutLocalBranch(repoRoot, buildPreferredRemixBranch(String(app.id)));
|
|
1824
|
-
await ensureGitInfoExcludeEntries(repoRoot, [".remix/"]);
|
|
1825
|
-
} catch (err) {
|
|
1826
|
-
await fs8.rm(repoRoot, { recursive: true, force: true }).catch(() => {
|
|
1827
|
-
});
|
|
1828
|
-
throw err;
|
|
1829
|
-
} finally {
|
|
1830
|
-
await fs8.rm(bundleTempDir, { recursive: true, force: true });
|
|
1831
|
-
}
|
|
1832
|
-
const remoteUrl = normalizeGitRemote(await getRemoteOriginUrl(repoRoot));
|
|
1833
|
-
const defaultBranch = await getDefaultBranch(repoRoot) ?? await getCurrentBranch(repoRoot) ?? null;
|
|
1834
|
-
const preferredBranch = await getCurrentBranch(repoRoot) ?? buildPreferredRemixBranch(String(app.id));
|
|
1835
|
-
const repoFingerprint = remoteUrl ? await buildRepoFingerprint({ gitRoot: repoRoot, remoteUrl, defaultBranch }) : null;
|
|
1836
|
-
const bindingPath = await writeCollabBinding(repoRoot, {
|
|
1944
|
+
const bindingPath = await writeCollabBinding(checkout.repoRoot, {
|
|
1837
1945
|
projectId: String(app.projectId),
|
|
1838
1946
|
currentAppId: String(app.id),
|
|
1839
1947
|
upstreamAppId: String(app.forkedFromAppId ?? sourceAppId),
|
|
1840
1948
|
threadId: app.threadId ? String(app.threadId) : null,
|
|
1841
|
-
repoFingerprint,
|
|
1842
|
-
remoteUrl,
|
|
1843
|
-
defaultBranch,
|
|
1844
|
-
preferredBranch
|
|
1949
|
+
repoFingerprint: checkout.repoFingerprint,
|
|
1950
|
+
remoteUrl: checkout.remoteUrl,
|
|
1951
|
+
defaultBranch: checkout.defaultBranch,
|
|
1952
|
+
preferredBranch: checkout.preferredBranch
|
|
1845
1953
|
});
|
|
1846
1954
|
return {
|
|
1847
1955
|
appId: String(app.id),
|
|
1956
|
+
dashboardUrl: buildDashboardAppUrl(String(app.id)),
|
|
1848
1957
|
projectId: String(app.projectId),
|
|
1849
1958
|
upstreamAppId: String(app.forkedFromAppId ?? sourceAppId),
|
|
1850
1959
|
bindingPath,
|
|
1851
|
-
repoRoot
|
|
1960
|
+
repoRoot: checkout.repoRoot
|
|
1852
1961
|
};
|
|
1853
1962
|
}
|
|
1854
1963
|
|
|
@@ -2205,9 +2314,11 @@ async function collabView(params) {
|
|
|
2205
2314
|
export {
|
|
2206
2315
|
collabAdd,
|
|
2207
2316
|
collabApprove,
|
|
2317
|
+
collabCheckout,
|
|
2208
2318
|
collabInit,
|
|
2209
2319
|
collabInvite,
|
|
2210
2320
|
collabList,
|
|
2321
|
+
collabListMembers,
|
|
2211
2322
|
collabListMergeRequests,
|
|
2212
2323
|
collabReconcile,
|
|
2213
2324
|
collabRecordTurn,
|
|
@@ -2218,5 +2329,8 @@ export {
|
|
|
2218
2329
|
collabStatus,
|
|
2219
2330
|
collabSync,
|
|
2220
2331
|
collabSyncUpstream,
|
|
2221
|
-
|
|
2332
|
+
collabUpdateMemberRole,
|
|
2333
|
+
collabView,
|
|
2334
|
+
getMemberRolesForScope,
|
|
2335
|
+
validateMemberRole
|
|
2222
2336
|
};
|
package/dist/index.js
CHANGED