@remixhq/core 0.1.33 → 0.1.34

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.
Files changed (2) hide show
  1. package/dist/collab.js +144 -2
  2. package/package.json +1 -1
package/dist/collab.js CHANGED
@@ -509,6 +509,13 @@ import path3 from "path";
509
509
  function getBaselinePath(params) {
510
510
  return path3.join(getBaselinesRoot(), `${buildLaneStateKey(params)}.json`);
511
511
  }
512
+ function normalizeNullableString(value) {
513
+ const normalized = String(value ?? "").trim();
514
+ return normalized || null;
515
+ }
516
+ function isSameBaselineIdentity(left, right) {
517
+ return left.repoRoot === right.repoRoot && (left.repoFingerprint ?? null) === (right.repoFingerprint ?? null) && (left.laneId ?? null) === (right.laneId ?? null) && left.currentAppId === right.currentAppId && (left.branchName ?? null) === (right.branchName ?? null);
518
+ }
512
519
  async function readLocalBaseline(params) {
513
520
  try {
514
521
  const raw = await fs2.readFile(getBaselinePath(params), "utf8");
@@ -558,6 +565,105 @@ async function writeLocalBaseline(baseline) {
558
565
  await writeJsonAtomic(getBaselinePath(baseline), normalized);
559
566
  return normalized;
560
567
  }
568
+ async function readAllLocalBaselines() {
569
+ let entries;
570
+ try {
571
+ entries = await fs2.readdir(getBaselinesRoot());
572
+ } catch (error) {
573
+ if (error?.code === "ENOENT") return [];
574
+ throw error;
575
+ }
576
+ const baselines = [];
577
+ for (const entry of entries) {
578
+ if (!entry.endsWith(".json")) continue;
579
+ try {
580
+ const raw = await fs2.readFile(path3.join(getBaselinesRoot(), entry), "utf8");
581
+ const parsed = JSON.parse(raw);
582
+ if (!parsed || typeof parsed !== "object") continue;
583
+ if (![1, 2].includes(Number(parsed.schemaVersion)) || typeof parsed.key !== "string" || typeof parsed.repoRoot !== "string") {
584
+ continue;
585
+ }
586
+ baselines.push({
587
+ schemaVersion: Number(parsed.schemaVersion) === 2 ? 2 : 1,
588
+ key: parsed.key,
589
+ repoRoot: parsed.repoRoot,
590
+ repoFingerprint: parsed.repoFingerprint ?? null,
591
+ laneId: parsed.laneId ?? null,
592
+ currentAppId: String(parsed.currentAppId ?? ""),
593
+ branchName: parsed.branchName ?? null,
594
+ lastSnapshotId: parsed.lastSnapshotId ?? null,
595
+ lastSnapshotHash: parsed.lastSnapshotHash ?? null,
596
+ lastServerRevisionId: parsed.lastServerRevisionId ?? null,
597
+ lastServerTreeHash: parsed.lastServerTreeHash ?? null,
598
+ lastServerHeadHash: parsed.lastServerHeadHash ?? null,
599
+ lastSeenLocalCommitHash: parsed.lastSeenLocalCommitHash ?? null,
600
+ updatedAt: String(parsed.updatedAt ?? "")
601
+ });
602
+ } catch {
603
+ continue;
604
+ }
605
+ }
606
+ return baselines;
607
+ }
608
+ async function migrateLocalBaselineForLaneChange(params) {
609
+ if ((params.fromLaneId ?? null) === (params.toLaneId ?? null)) {
610
+ return { status: "same_lane" };
611
+ }
612
+ const target = await readLocalBaseline({
613
+ repoRoot: params.repoRoot,
614
+ repoFingerprint: params.repoFingerprint,
615
+ laneId: params.toLaneId
616
+ });
617
+ if (target) {
618
+ return { status: "target_exists" };
619
+ }
620
+ const source = await readLocalBaseline({
621
+ repoRoot: params.repoRoot,
622
+ repoFingerprint: params.repoFingerprint,
623
+ laneId: params.fromLaneId
624
+ });
625
+ if (!source) {
626
+ return { status: "source_missing" };
627
+ }
628
+ const expectedSource = {
629
+ repoRoot: params.repoRoot,
630
+ repoFingerprint: params.repoFingerprint,
631
+ laneId: params.fromLaneId,
632
+ currentAppId: params.currentAppId,
633
+ branchName: params.branchName
634
+ };
635
+ const actualSource = {
636
+ repoRoot: source.repoRoot,
637
+ repoFingerprint: source.repoFingerprint,
638
+ laneId: source.laneId,
639
+ currentAppId: source.currentAppId,
640
+ branchName: source.branchName
641
+ };
642
+ if (!isSameBaselineIdentity(actualSource, expectedSource)) {
643
+ return { status: "source_missing" };
644
+ }
645
+ const baseline = await writeLocalBaseline({
646
+ ...source,
647
+ laneId: params.toLaneId
648
+ });
649
+ return { status: "migrated", baseline };
650
+ }
651
+ async function findCompatibleLocalBaseline(params) {
652
+ const targetKey = buildLaneStateKey(params);
653
+ const branchName = normalizeNullableString(params.branchName);
654
+ const candidates = (await readAllLocalBaselines()).filter((baseline) => {
655
+ if (baseline.key === targetKey) return false;
656
+ if (baseline.repoRoot !== params.repoRoot) return false;
657
+ if ((baseline.repoFingerprint ?? null) !== (params.repoFingerprint ?? null)) return false;
658
+ if (baseline.currentAppId !== params.currentAppId) return false;
659
+ return true;
660
+ });
661
+ const branchMatches = candidates.filter((baseline) => normalizeNullableString(baseline.branchName) === branchName);
662
+ const compatible = branchMatches.length > 0 ? branchMatches : candidates.filter((baseline) => normalizeNullableString(baseline.branchName) === null);
663
+ if (compatible.length === 0) return { status: "none", candidateCount: 0 };
664
+ if (compatible.length > 1) return { status: "ambiguous", candidateCount: compatible.length };
665
+ return { status: "found", baseline: compatible[0] };
666
+ }
561
667
  async function clearLocalBaseline(params) {
562
668
  try {
563
669
  await fs2.unlink(getBaselinePath(params));
@@ -1500,6 +1606,7 @@ function resolveLaneLookupProjectId(params) {
1500
1606
  return params.explicitRootProjectId ?? (params.requireRemoteLane ? void 0 : localProjectId ?? params.fallbackProjectId ?? void 0);
1501
1607
  }
1502
1608
  async function persistResolvedLane(repoRoot, binding) {
1609
+ const previousBinding = await readCollabBinding(repoRoot);
1503
1610
  await writeCollabBinding(repoRoot, {
1504
1611
  projectId: binding.projectId,
1505
1612
  currentAppId: binding.currentAppId,
@@ -1512,6 +1619,16 @@ async function persistResolvedLane(repoRoot, binding) {
1512
1619
  branchName: binding.branchName,
1513
1620
  bindingMode: binding.bindingMode
1514
1621
  });
1622
+ if (previousBinding && (previousBinding.laneId ?? null) !== (binding.laneId ?? null)) {
1623
+ await migrateLocalBaselineForLaneChange({
1624
+ repoRoot,
1625
+ repoFingerprint: binding.repoFingerprint,
1626
+ currentAppId: binding.currentAppId,
1627
+ branchName: binding.branchName,
1628
+ fromLaneId: previousBinding.laneId,
1629
+ toLaneId: binding.laneId
1630
+ });
1631
+ }
1515
1632
  return readCollabBinding(repoRoot);
1516
1633
  }
1517
1634
  function buildAmbiguousResolution(params) {
@@ -1911,11 +2028,36 @@ async function collabDetectRepoState(params) {
1911
2028
  return detected;
1912
2029
  }
1913
2030
  try {
1914
- const baseline = await readLocalBaseline({
2031
+ let baseline = await readLocalBaseline({
1915
2032
  repoFingerprint: binding.repoFingerprint,
1916
2033
  laneId: binding.laneId,
1917
2034
  repoRoot
1918
2035
  });
2036
+ let ambiguousBaselineCandidateCount = null;
2037
+ if (!baseline) {
2038
+ const compatibleBaseline = await findCompatibleLocalBaseline({
2039
+ repoRoot,
2040
+ repoFingerprint: binding.repoFingerprint,
2041
+ laneId: binding.laneId,
2042
+ currentAppId: binding.currentAppId,
2043
+ branchName: binding.branchName
2044
+ });
2045
+ if (compatibleBaseline.status === "found") {
2046
+ baseline = await writeLocalBaseline({
2047
+ ...compatibleBaseline.baseline,
2048
+ repoRoot,
2049
+ repoFingerprint: binding.repoFingerprint,
2050
+ laneId: binding.laneId,
2051
+ currentAppId: binding.currentAppId,
2052
+ branchName: binding.branchName
2053
+ });
2054
+ detected.warnings.push(
2055
+ `Recovered local Remix baseline after lane id changed from ${compatibleBaseline.baseline.laneId ?? "null"} to ${binding.laneId ?? "null"} for app ${binding.currentAppId} on branch ${binding.branchName ?? "(unknown)"}.`
2056
+ );
2057
+ } else if (compatibleBaseline.status === "ambiguous") {
2058
+ ambiguousBaselineCandidateCount = compatibleBaseline.candidateCount;
2059
+ }
2060
+ }
1919
2061
  const hasFullBaseline = Boolean(baseline?.lastSnapshotHash && baseline?.lastServerHeadHash);
1920
2062
  const metadataIdentityPromise = hasFullBaseline ? getAppDeltaCached(params.api, binding.currentAppId, {
1921
2063
  baseHeadHash: baseline.lastServerHeadHash,
@@ -1988,7 +2130,7 @@ async function collabDetectRepoState(params) {
1988
2130
  }
1989
2131
  }
1990
2132
  detected.repoState = "external_local_base_changed";
1991
- detected.hint = "No local Remix revision baseline exists for this lane yet. Run `remix collab init` or sync this lane to seed the baseline.";
2133
+ detected.hint = ambiguousBaselineCandidateCount !== null ? `No local Remix revision baseline exists for this lane, and multiple compatible local baselines were found (${ambiguousBaselineCandidateCount}). Run \`remix collab status\` or sync this lane to seed an unambiguous baseline.` : "No local Remix revision baseline exists for this lane yet. Run `remix collab init` or sync this lane to seed the baseline.";
1992
2134
  return detected;
1993
2135
  }
1994
2136
  const localHeadMovedSinceBaseline = Boolean(baseline.lastSeenLocalCommitHash) && localCommitHash !== baseline.lastSeenLocalCommitHash;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixhq/core",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Remix core library",
5
5
  "homepage": "https://github.com/RemixDotOne/remix-core",
6
6
  "license": "MIT",