@remixhq/core 0.1.6 → 0.1.8
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 +15 -0
- package/dist/api.js +1 -1
- package/dist/chunk-4276ARDF.js +303 -0
- package/dist/chunk-CJFGQE7D.js +46 -0
- package/dist/chunk-RREREIGW.js +710 -0
- package/dist/collab.d.ts +87 -1
- package/dist/collab.js +264 -150
- package/dist/index.js +1 -1
- package/dist/repo.js +1 -1
- package/package.json +1 -1
package/dist/collab.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
summarizeUnifiedDiff,
|
|
37
37
|
validateUnifiedDiff,
|
|
38
38
|
writeTempUnifiedDiffBackup
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-RREREIGW.js";
|
|
40
40
|
import {
|
|
41
41
|
REMIX_ERROR_CODES
|
|
42
42
|
} from "./chunk-GC2MOT3U.js";
|
|
@@ -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
package/dist/repo.js
CHANGED