@teambit/ci 1.0.523 → 1.0.525
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/ci.docs.mdx +26 -13
- package/commands/sync.cmd.ts +2 -2
- package/dist/ci.docs.mdx +26 -13
- package/dist/commands/sync.cmd.js +2 -2
- package/dist/commands/sync.cmd.js.map +1 -1
- package/dist/{preview-1787260228106.js → preview-1787325829408.js} +1 -1
- package/dist/sync/heal-missing-main-files.d.ts +36 -0
- package/dist/sync/heal-missing-main-files.js +180 -0
- package/dist/sync/heal-missing-main-files.js.map +1 -0
- package/dist/sync/lane-sync-executor.d.ts +37 -19
- package/dist/sync/lane-sync-executor.js +102 -42
- package/dist/sync/lane-sync-executor.js.map +1 -1
- package/dist/sync/lane-sync-executor.spec.js +67 -9
- package/dist/sync/lane-sync-executor.spec.js.map +1 -1
- package/dist/sync/main-sync-executor.d.ts +3 -0
- package/dist/sync/main-sync-executor.js +23 -4
- package/dist/sync/main-sync-executor.js.map +1 -1
- package/dist/sync/sync-orchestrator.js +1 -0
- package/dist/sync/sync-orchestrator.js.map +1 -1
- package/package.json +22 -20
- package/sync/heal-missing-main-files.ts +153 -0
- package/sync/lane-sync-executor.spec.ts +80 -10
- package/sync/lane-sync-executor.ts +123 -46
- package/sync/main-sync-executor.ts +38 -3
- package/sync/sync-orchestrator.ts +1 -0
|
@@ -19,6 +19,7 @@ exports.isProtectedBranch = isProtectedBranch;
|
|
|
19
19
|
exports.laneHeadFingerprint = laneHeadFingerprint;
|
|
20
20
|
exports.laneSummaryComponents = laneSummaryComponents;
|
|
21
21
|
exports.laneSyncPrBody = laneSyncPrBody;
|
|
22
|
+
exports.ownLaneComponents = ownLaneComponents;
|
|
22
23
|
exports.racedAdoptionPushSummary = racedAdoptionPushSummary;
|
|
23
24
|
exports.racedLedgerPushSummary = racedLedgerPushSummary;
|
|
24
25
|
exports.runSummaryCommentBody = runSummaryCommentBody;
|
|
@@ -173,15 +174,25 @@ function laneComponentIds(components) {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
/**
|
|
176
|
-
* The lane's components that do not belong to `defaultScope`. A lane may span scopes
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
177
|
+
* The lane's components that do not belong to `defaultScope`. A lane may span scopes; this
|
|
178
|
+
* repository reconciles only its own-scope slice (see `ownLaneComponents`), and the foreign entries
|
|
179
|
+
* ride along on the lane untouched — sourced in their own scopes' repositories and reachable here
|
|
180
|
+
* only as package dependencies. Soft-deleted components and `updateDependents` entries are
|
|
181
|
+
* deliberately not read — the reconciler never materializes or exports them either.
|
|
180
182
|
*/
|
|
181
183
|
function foreignLaneComponents(components, defaultScope) {
|
|
182
184
|
return components.filter(comp => comp.id.scope !== defaultScope).map(comp => comp.id.toStringWithoutVersion());
|
|
183
185
|
}
|
|
184
186
|
|
|
187
|
+
/**
|
|
188
|
+
* The slice of the lane this repository mirrors: the components that belong to `defaultScope`.
|
|
189
|
+
* Every reconciliation act — fingerprint, materialize, snap, export — operates on this slice, so a
|
|
190
|
+
* foreign component's head moving neither wakes the mirror nor reads as divergence.
|
|
191
|
+
*/
|
|
192
|
+
function ownLaneComponents(components, defaultScope) {
|
|
193
|
+
return components.filter(comp => comp.id.scope === defaultScope);
|
|
194
|
+
}
|
|
195
|
+
|
|
185
196
|
/** How many foreign component ids a cross-scope message names before it summarizes the rest. */
|
|
186
197
|
const MAX_LISTED_FOREIGN_COMPONENTS = 5;
|
|
187
198
|
|
|
@@ -201,30 +212,34 @@ function capEntries(entries, overflowPrefix = '', max = MAX_LISTED_COMPONENTS) {
|
|
|
201
212
|
return [...entries.slice(0, max), `${overflowPrefix}…and ${entries.length - max} more`];
|
|
202
213
|
}
|
|
203
214
|
|
|
204
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* The shared clause describing a lane with nothing of this repository's scope on it, used by the
|
|
217
|
+
* skip/refusal/halt outcomes. Only such a lane is refused: a lane that merely INCLUDES foreign
|
|
218
|
+
* components is reconciled over its own-scope slice.
|
|
219
|
+
*/
|
|
205
220
|
function crossScopeDescription(foreignIds, defaultScope) {
|
|
206
221
|
// The scope is everything before the FIRST '/': a component id is `<scope>/<namespace…>/<name>`.
|
|
207
222
|
const scopes = [...new Set(foreignIds.map(id => id.split('/', 1)[0]))].sort();
|
|
208
223
|
const sample = capEntries(foreignIds, '', MAX_LISTED_FOREIGN_COMPONENTS).join(', ');
|
|
209
|
-
return `
|
|
224
|
+
return `every component is from scope(s) ${scopes.join(', ')} (this repo maps scope ${defaultScope}); ` + `foreign components: ${sample}`;
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
/**
|
|
213
|
-
* A
|
|
214
|
-
* lane must not turn every scheduled run permanently red.
|
|
228
|
+
* A lane with no own-scope components that was merely enumerated: a skip, and the run stays green — a
|
|
229
|
+
* standing, valid lane must not turn every scheduled run permanently red.
|
|
215
230
|
*/
|
|
216
231
|
function crossScopeSkipSummary(laneName, foreignIds, defaultScope) {
|
|
217
|
-
return `${laneName} -> skipped (
|
|
232
|
+
return `${laneName} -> skipped (nothing to mirror: ${crossScopeDescription(foreignIds, defaultScope)} — ` + `no branch created; see the docs' Cross-scope lanes section)`;
|
|
218
233
|
}
|
|
219
234
|
|
|
220
235
|
/**
|
|
221
|
-
* A
|
|
222
|
-
* halt — no PR is labelled, no branch is written. `existingBranch`
|
|
223
|
-
* when a branch of that name already exists.
|
|
236
|
+
* A lane with no own-scope components that the user named explicitly: exits non-zero with the
|
|
237
|
+
* explanation. A refusal, not a halt — no PR is labelled, no branch is written. `existingBranch`
|
|
238
|
+
* keeps the closing sentence truthful when a branch of that name already exists.
|
|
224
239
|
*/
|
|
225
240
|
function crossScopeRefusal(foreignIds, defaultScope, existingBranch) {
|
|
226
241
|
const outcome = existingBranch ? `Nothing was written; the existing branch ${existingBranch} was left untouched` : 'No branch was created and nothing was written';
|
|
227
|
-
return `
|
|
242
|
+
return `nothing to mirror: ${crossScopeDescription(foreignIds, defaultScope)} — this repository reconciles ` + `only its own scope's slice of a lane, and this lane has none. See the docs' Cross-scope lanes ` + `section. ${outcome}`;
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
/**
|
|
@@ -252,11 +267,12 @@ Do NOT run the usual "bit lane import" resolution steps on this branch — they
|
|
|
252
267
|
}
|
|
253
268
|
|
|
254
269
|
/**
|
|
255
|
-
* A lane
|
|
256
|
-
* cross-scope shape that is a genuine halt: the pair is mid-flight and
|
|
270
|
+
* A lane whose own-scope slice emptied out after this repository had already mirrored it onto a
|
|
271
|
+
* branch — the one cross-scope shape that is a genuine halt: the pair is mid-flight, and an empty
|
|
272
|
+
* slice fingerprints as a constant, which would read as converged forever over a stale mirror.
|
|
257
273
|
*/
|
|
258
274
|
function crossScopeMidFlightHaltReason(branch, foreignIds, defaultScope) {
|
|
259
|
-
return `
|
|
275
|
+
return `every ${defaultScope} component left the lane after it was mirrored onto ${branch}: ` + `${crossScopeDescription(foreignIds, defaultScope)}; the branch and the lane can no longer be ` + `reconciled automatically — see the docs' Cross-scope lanes section`;
|
|
260
276
|
}
|
|
261
277
|
|
|
262
278
|
/**
|
|
@@ -379,7 +395,12 @@ class LaneSyncExecutor {
|
|
|
379
395
|
return summary;
|
|
380
396
|
}
|
|
381
397
|
const remoteLane = await this.getRemoteLane(target);
|
|
382
|
-
|
|
398
|
+
|
|
399
|
+
// The slice of the lane this repository reconciles. Everything below — fingerprint, materialize,
|
|
400
|
+
// snap, export — acts on it alone: foreign components stay on the lane untouched and reach this
|
|
401
|
+
// repository only as package dependencies, so a foreign head moving is invisible here.
|
|
402
|
+
const ownComponents = remoteLane ? ownLaneComponents(remoteLane.components, defaultScope) : [];
|
|
403
|
+
const laneHead = remoteLane ? laneHeadFingerprint(ownComponents) : undefined;
|
|
383
404
|
const branchExists = await (0, _gitOps().branchExistsOnRemote)(branch);
|
|
384
405
|
const branchState = branchExists ? await (0, _syncState().readBranchSyncState)(branch, defaultBranch, defaultScope) : {
|
|
385
406
|
stateCommit: undefined,
|
|
@@ -424,11 +445,13 @@ class LaneSyncExecutor {
|
|
|
424
445
|
});
|
|
425
446
|
}
|
|
426
447
|
|
|
427
|
-
// A
|
|
428
|
-
//
|
|
429
|
-
//
|
|
448
|
+
// A lane with NO own-scope components cannot be reconciled here — there is nothing to mirror, and
|
|
449
|
+
// an empty slice fingerprints as a constant, which would read as converged forever. A lane that
|
|
450
|
+
// merely INCLUDES foreign components proceeds: it is reconciled over its own-scope slice. The
|
|
451
|
+
// check lives in the executor because every trigger funnels through here, and it needs the lane's
|
|
452
|
+
// content.
|
|
430
453
|
const foreign = remoteLane ? foreignLaneComponents(remoteLane.components, defaultScope) : [];
|
|
431
|
-
if (foreign.length) {
|
|
454
|
+
if (foreign.length && !ownComponents.length) {
|
|
432
455
|
return this.crossScopeOutcome({
|
|
433
456
|
laneName,
|
|
434
457
|
laneIdStr,
|
|
@@ -443,14 +466,19 @@ class LaneSyncExecutor {
|
|
|
443
466
|
dryRun
|
|
444
467
|
});
|
|
445
468
|
}
|
|
469
|
+
if (foreign.length) {
|
|
470
|
+
logger.console((0, _cli().formatWarningSummary)(`${laneName}: cross-scope lane — mirroring its ${ownComponents.length} ${defaultScope} component(s); ` + `${foreign.length} component(s) from other scope(s) stay on the lane as package dependencies ` + `(${capEntries(foreign, '', MAX_LISTED_FOREIGN_COMPONENTS).join(', ')})`));
|
|
471
|
+
}
|
|
446
472
|
// Attribution to THIS lane is required: the claim was computed for whichever lane the branch's
|
|
447
473
|
// `.bitmap` names, and a claim on someone else's behalf licenses nothing here.
|
|
448
474
|
const laneIsGone = branchExists && !laneHead;
|
|
449
475
|
const ownership = laneIsGone && mirroredLane === laneIdStr ? claim : 'inherited-or-none';
|
|
450
476
|
|
|
451
477
|
// What the branch reflects, read off its own `.bitmap` — only when that `.bitmap` names THIS lane;
|
|
452
|
-
// otherwise the planner's `!lastSyncedHead` rows handle "no state of ours".
|
|
453
|
-
|
|
478
|
+
// otherwise the planner's `!lastSyncedHead` rows handle "no state of ours". Read over the OWN
|
|
479
|
+
// slice's ids, the same set `laneHead` fingerprints — a foreign component is never in `.bitmap`
|
|
480
|
+
// and counting it would read the pair as permanently diverged.
|
|
481
|
+
const lastSyncedHead = remoteLane && branchState.bitmap && mirroredLane === laneIdStr ? (0, _syncState().branchStateFingerprint)(branchState.bitmap, laneComponentIds(ownComponents)) : undefined;
|
|
454
482
|
|
|
455
483
|
// `isSyncAuthoredMessage`, NOT `hasSyncMarker`: this feeds a branch deletion, and a message that
|
|
456
484
|
// merely quotes the marker must not read as one we wrote.
|
|
@@ -548,10 +576,12 @@ class LaneSyncExecutor {
|
|
|
548
576
|
}
|
|
549
577
|
|
|
550
578
|
/**
|
|
551
|
-
* What a
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
579
|
+
* What a lane with NO own-scope components means for this repository (a lane that merely includes
|
|
580
|
+
* foreign components never reaches here — it is reconciled over its own-scope slice): a mid-flight
|
|
581
|
+
* halt when the branch is this lane's live mirror (checked first — the problem is the state of the
|
|
582
|
+
* pair, not the phrasing of the request); a refusal when the user named the lane explicitly;
|
|
583
|
+
* otherwise an enumerated skip that stays green — one standing foreign lane must not fail every
|
|
584
|
+
* scheduled run forever.
|
|
555
585
|
*/
|
|
556
586
|
async crossScopeOutcome({
|
|
557
587
|
laneName,
|
|
@@ -696,11 +726,15 @@ class LaneSyncExecutor {
|
|
|
696
726
|
// moved in between must re-plan (merge-diverged owns that shape): comparing the branch's files
|
|
697
727
|
// with the newer lane would misread the lane's own advance as branch work, and the export would
|
|
698
728
|
// put the older content on top of a concurrent developer's push. The materialize above just
|
|
699
|
-
// fetched, so this read and the status below see the same lane.
|
|
729
|
+
// fetched, so this read and the status below see the same lane. Own-slice fingerprints on both
|
|
730
|
+
// sides: a foreign head moving concurrently is not this pair's divergence.
|
|
700
731
|
if (preExportLane) {
|
|
701
732
|
const laneNow = await this.getRemoteLane(target);
|
|
702
|
-
const
|
|
703
|
-
|
|
733
|
+
const {
|
|
734
|
+
defaultScope
|
|
735
|
+
} = this.deps;
|
|
736
|
+
const movedTo = laneNow ? laneHeadFingerprint(ownLaneComponents(laneNow.components, defaultScope)) : undefined;
|
|
737
|
+
if (movedTo !== laneHeadFingerprint(ownLaneComponents(preExportLane.components, defaultScope))) {
|
|
704
738
|
return `${laneName} -> noop (lane ${laneIdStr} moved while this run was planning; the next run re-plans)`;
|
|
705
739
|
}
|
|
706
740
|
}
|
|
@@ -855,11 +889,15 @@ class LaneSyncExecutor {
|
|
|
855
889
|
|
|
856
890
|
// Same freshness rule as the export probe: the plan's inputs must still describe the world.
|
|
857
891
|
// A lane that moved since planning re-plans — the next run merges the newer head — rather
|
|
858
|
-
// than proceeding on a divergence assessment the planner never made.
|
|
892
|
+
// than proceeding on a divergence assessment the planner never made. Own-slice on both sides,
|
|
893
|
+
// like every fingerprint here.
|
|
859
894
|
if (preExportLane) {
|
|
860
895
|
const laneNow = await this.getRemoteLane(target);
|
|
861
|
-
const
|
|
862
|
-
|
|
896
|
+
const {
|
|
897
|
+
defaultScope
|
|
898
|
+
} = this.deps;
|
|
899
|
+
const movedTo = laneNow ? laneHeadFingerprint(ownLaneComponents(laneNow.components, defaultScope)) : undefined;
|
|
900
|
+
if (movedTo !== laneHeadFingerprint(ownLaneComponents(preExportLane.components, defaultScope))) {
|
|
863
901
|
return `${laneName} -> noop (lane ${laneIdStr} moved while this run was planning; the next run re-plans)`;
|
|
864
902
|
}
|
|
865
903
|
}
|
|
@@ -982,7 +1020,8 @@ class LaneSyncExecutor {
|
|
|
982
1020
|
if (!remoteLane) return {
|
|
983
1021
|
status: 'unreadable'
|
|
984
1022
|
};
|
|
985
|
-
|
|
1023
|
+
// Own-slice, like the planner's `laneHead`: the ledger records what THIS repository mirrors.
|
|
1024
|
+
const laneHead = laneHeadFingerprint(ownLaneComponents(remoteLane.components, this.deps.defaultScope));
|
|
986
1025
|
const pushResult = await this.commitAllAndPush(branch, (0, _syncState().buildSyncCommitMessage)(laneIdStr, laneHead, opts));
|
|
987
1026
|
if (pushResult.raced) return {
|
|
988
1027
|
status: 'raced'
|
|
@@ -1031,7 +1070,11 @@ class LaneSyncExecutor {
|
|
|
1031
1070
|
mergeStrategy,
|
|
1032
1071
|
promptMergeOptions: false,
|
|
1033
1072
|
skipNpmInstall: true,
|
|
1034
|
-
workspaceOnly: false
|
|
1073
|
+
workspaceOnly: false,
|
|
1074
|
+
// `workspaceOnly: false` also ADDS lane components the workspace lacks; on a cross-scope lane
|
|
1075
|
+
// that must stop at the own-scope slice — a foreign component merged in here would ride the
|
|
1076
|
+
// sync commit into this repository.
|
|
1077
|
+
restrictToScopes: [this.deps.defaultScope]
|
|
1035
1078
|
});
|
|
1036
1079
|
let conflictedFileCount = 0;
|
|
1037
1080
|
const conflicts = [];
|
|
@@ -1336,6 +1379,10 @@ class LaneSyncExecutor {
|
|
|
1336
1379
|
// opposite of `bit ci pr`, where the git checkout is the source of truth and `switchToLane`'s
|
|
1337
1380
|
// `workspaceOnly` default keeps the switch from writing lane-only components.
|
|
1338
1381
|
workspaceOnly: false,
|
|
1382
|
+
// A cross-scope lane's foreign components must never be written into this repository — they are
|
|
1383
|
+
// sourced elsewhere and reach this workspace only as package dependencies. The lane object
|
|
1384
|
+
// itself is still fetched and saved whole, so a later export preserves the foreign entries.
|
|
1385
|
+
restrictToScopes: [this.deps.defaultScope],
|
|
1339
1386
|
// Follows the direction: in adopt mode the missing-component retry must not write the lane's
|
|
1340
1387
|
// files over the branch's — the ledger commit would then push the lane's content onto the branch.
|
|
1341
1388
|
writeAdoptedFiles: theirsWins
|
|
@@ -1429,7 +1476,8 @@ class LaneSyncExecutor {
|
|
|
1429
1476
|
branch,
|
|
1430
1477
|
branchTipSha,
|
|
1431
1478
|
laneHead,
|
|
1432
|
-
|
|
1479
|
+
// Own-slice only: a foreign head that moved concurrently was not snapped by this export.
|
|
1480
|
+
changed: ownLaneComponents(changedLaneComponents(preComponents, postComponents), this.deps.defaultScope)
|
|
1433
1481
|
});
|
|
1434
1482
|
await gitHost.upsertComment(pr.number, RUN_SUMMARY_MARKER, body);
|
|
1435
1483
|
} catch (e) {
|
|
@@ -1542,7 +1590,8 @@ class LaneSyncExecutor {
|
|
|
1542
1590
|
laneUrl,
|
|
1543
1591
|
branch,
|
|
1544
1592
|
laneHead,
|
|
1545
|
-
components: remoteLane.components
|
|
1593
|
+
components: remoteLane.components,
|
|
1594
|
+
defaultScope: this.deps.defaultScope
|
|
1546
1595
|
});
|
|
1547
1596
|
try {
|
|
1548
1597
|
const created = await gitHost.createPr({
|
|
@@ -1595,18 +1644,29 @@ function isConflictFileStatus(status) {
|
|
|
1595
1644
|
}
|
|
1596
1645
|
|
|
1597
1646
|
/**
|
|
1598
|
-
* The body of the pull request that mirrors a lane onto a branch.
|
|
1599
|
-
*
|
|
1647
|
+
* The body of the pull request that mirrors a lane onto a branch. On a cross-scope lane only the
|
|
1648
|
+
* own-scope slice is mirrored, and the foreign components get their own section — the branch's diff
|
|
1649
|
+
* cannot show them, and a reviewer must know the lane carries more than this repository sees. Every
|
|
1650
|
+
* component list is capped so the body stays inside the git host's size limit however large the lane
|
|
1651
|
+
* is; the totals are not.
|
|
1600
1652
|
*/
|
|
1601
1653
|
function laneSyncPrBody({
|
|
1602
1654
|
laneIdStr,
|
|
1603
1655
|
laneUrl,
|
|
1604
1656
|
branch,
|
|
1605
1657
|
laneHead,
|
|
1606
|
-
components
|
|
1658
|
+
components,
|
|
1659
|
+
defaultScope
|
|
1607
1660
|
}) {
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1661
|
+
const own = ownLaneComponents(components, defaultScope);
|
|
1662
|
+
const foreignIds = foreignLaneComponents(components, defaultScope);
|
|
1663
|
+
const listed = capEntries(own.map(comp => `- \`${comp.id.toStringWithoutVersion()}\` @ \`${comp.head.slice(0, 9)}\``), '- ').join('\n');
|
|
1664
|
+
const lines = [`Mirrors the Bit lane [\`${laneIdStr}\`](${laneUrl}) onto \`${branch}\`.`, '', `- lane: ${laneUrl}`, `- lane head: \`${laneHead}\``, '', foreignIds.length ? `Components mirrored from the lane (${own.length} of ${components.length}):` : `Components on the lane (${components.length}):`, listed || '_none_'];
|
|
1665
|
+
if (foreignIds.length) {
|
|
1666
|
+
lines.push('', `Also on this lane, from other scopes (${foreignIds.length}) — not mirrored into this repository; ` + `this branch consumes them as package dependencies at their lane versions:`, capEntries(foreignIds.map(id => `- \`${id}\``), '- ').join('\n'));
|
|
1667
|
+
}
|
|
1668
|
+
lines.push('', 'This PR is maintained by `bit ci sync` — push to the branch to send changes back to the lane.');
|
|
1669
|
+
return lines.join('\n');
|
|
1610
1670
|
}
|
|
1611
1671
|
|
|
1612
1672
|
/**
|