@teambit/ci 1.0.485 → 1.0.487

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 (97) hide show
  1. package/ci.docs.mdx +344 -83
  2. package/commands/sync.cmd.ts +77 -0
  3. package/dist/ci.docs.mdx +344 -83
  4. package/dist/ci.main.runtime.d.ts +67 -6
  5. package/dist/ci.main.runtime.js +152 -25
  6. package/dist/ci.main.runtime.js.map +1 -1
  7. package/dist/commands/sync.cmd.d.ts +34 -0
  8. package/dist/commands/sync.cmd.js +57 -0
  9. package/dist/commands/sync.cmd.js.map +1 -0
  10. package/dist/git.d.ts +11 -1
  11. package/dist/git.js +11 -1
  12. package/dist/git.js.map +1 -1
  13. package/dist/index.d.ts +4 -0
  14. package/dist/index.js +26 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/{preview-1785858957533.js → preview-1785883495880.js} +1 -1
  17. package/dist/sync/git-host-provider.d.ts +47 -0
  18. package/dist/sync/git-host-provider.js +54 -0
  19. package/dist/sync/git-host-provider.js.map +1 -0
  20. package/dist/sync/git-host-provider.spec.d.ts +1 -0
  21. package/dist/sync/git-host-provider.spec.js +171 -0
  22. package/dist/sync/git-host-provider.spec.js.map +1 -0
  23. package/dist/sync/git-ops.d.ts +171 -0
  24. package/dist/sync/git-ops.js +354 -0
  25. package/dist/sync/git-ops.js.map +1 -0
  26. package/dist/sync/git-ops.spec.d.ts +1 -0
  27. package/dist/sync/git-ops.spec.js +476 -0
  28. package/dist/sync/git-ops.spec.js.map +1 -0
  29. package/dist/sync/github-client.d.ts +79 -0
  30. package/dist/sync/github-client.js +198 -0
  31. package/dist/sync/github-client.js.map +1 -0
  32. package/dist/sync/github-client.spec.d.ts +1 -0
  33. package/dist/sync/github-client.spec.js +205 -0
  34. package/dist/sync/github-client.spec.js.map +1 -0
  35. package/dist/sync/init-scaffold.d.ts +40 -0
  36. package/dist/sync/init-scaffold.js +326 -0
  37. package/dist/sync/init-scaffold.js.map +1 -0
  38. package/dist/sync/init-scaffold.spec.d.ts +1 -0
  39. package/dist/sync/init-scaffold.spec.js +169 -0
  40. package/dist/sync/init-scaffold.spec.js.map +1 -0
  41. package/dist/sync/lane-sync-executor.d.ts +256 -0
  42. package/dist/sync/lane-sync-executor.js +1249 -0
  43. package/dist/sync/lane-sync-executor.js.map +1 -0
  44. package/dist/sync/lane-sync-executor.spec.d.ts +1 -0
  45. package/dist/sync/lane-sync-executor.spec.js +416 -0
  46. package/dist/sync/lane-sync-executor.spec.js.map +1 -0
  47. package/dist/sync/main-sync-executor.d.ts +62 -0
  48. package/dist/sync/main-sync-executor.js +305 -0
  49. package/dist/sync/main-sync-executor.js.map +1 -0
  50. package/dist/sync/sync-config.d.ts +83 -0
  51. package/dist/sync/sync-config.js +187 -0
  52. package/dist/sync/sync-config.js.map +1 -0
  53. package/dist/sync/sync-config.spec.d.ts +1 -0
  54. package/dist/sync/sync-config.spec.js +254 -0
  55. package/dist/sync/sync-config.spec.js.map +1 -0
  56. package/dist/sync/sync-orchestrator.d.ts +76 -0
  57. package/dist/sync/sync-orchestrator.js +396 -0
  58. package/dist/sync/sync-orchestrator.js.map +1 -0
  59. package/dist/sync/sync-orchestrator.spec.d.ts +1 -0
  60. package/dist/sync/sync-orchestrator.spec.js +155 -0
  61. package/dist/sync/sync-orchestrator.spec.js.map +1 -0
  62. package/dist/sync/sync-planner.d.ts +60 -0
  63. package/dist/sync/sync-planner.js +114 -0
  64. package/dist/sync/sync-planner.js.map +1 -0
  65. package/dist/sync/sync-planner.spec.d.ts +1 -0
  66. package/dist/sync/sync-planner.spec.js +210 -0
  67. package/dist/sync/sync-planner.spec.js.map +1 -0
  68. package/dist/sync/sync-state.d.ts +94 -0
  69. package/dist/sync/sync-state.js +178 -0
  70. package/dist/sync/sync-state.js.map +1 -0
  71. package/dist/sync/sync-state.spec.d.ts +1 -0
  72. package/dist/sync/sync-state.spec.js +271 -0
  73. package/dist/sync/sync-state.spec.js.map +1 -0
  74. package/dist/sync/workspace-lane.d.ts +13 -0
  75. package/dist/sync/workspace-lane.js +27 -0
  76. package/dist/sync/workspace-lane.js.map +1 -0
  77. package/package.json +20 -15
  78. package/sync/git-host-provider.spec.ts +197 -0
  79. package/sync/git-host-provider.ts +89 -0
  80. package/sync/git-ops.spec.ts +506 -0
  81. package/sync/git-ops.ts +334 -0
  82. package/sync/github-client.spec.ts +174 -0
  83. package/sync/github-client.ts +209 -0
  84. package/sync/init-scaffold.spec.ts +142 -0
  85. package/sync/init-scaffold.ts +340 -0
  86. package/sync/lane-sync-executor.spec.ts +389 -0
  87. package/sync/lane-sync-executor.ts +1363 -0
  88. package/sync/main-sync-executor.ts +321 -0
  89. package/sync/sync-config.spec.ts +283 -0
  90. package/sync/sync-config.ts +214 -0
  91. package/sync/sync-orchestrator.spec.ts +120 -0
  92. package/sync/sync-orchestrator.ts +386 -0
  93. package/sync/sync-planner.spec.ts +126 -0
  94. package/sync/sync-planner.ts +99 -0
  95. package/sync/sync-state.spec.ts +263 -0
  96. package/sync/sync-state.ts +176 -0
  97. package/sync/workspace-lane.ts +20 -0
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+
3
+ function _chai() {
4
+ const data = require("chai");
5
+ _chai = function () {
6
+ return data;
7
+ };
8
+ return data;
9
+ }
10
+ function _laneSyncExecutor() {
11
+ const data = require("./lane-sync-executor");
12
+ _laneSyncExecutor = function () {
13
+ return data;
14
+ };
15
+ return data;
16
+ }
17
+ function _syncConfig() {
18
+ const data = require("./sync-config");
19
+ _syncConfig = function () {
20
+ return data;
21
+ };
22
+ return data;
23
+ }
24
+ /** A stand-in for `LaneData`'s component entry; the helpers only read the id string, scope and head. */
25
+ function comp(id, head) {
26
+ return {
27
+ id: {
28
+ toStringWithoutVersion: () => id,
29
+ scope: id.split('/', 1)[0]
30
+ },
31
+ head
32
+ };
33
+ }
34
+ describe('laneHeadFingerprint', () => {
35
+ const a = comp('acme.shop/comp1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1');
36
+ const b = comp('acme.shop/comp2', 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2');
37
+ it('is a single 40-hex token, so it survives being written into a commit trailer as an annotation', () => {
38
+ const fingerprint = (0, _laneSyncExecutor().laneHeadFingerprint)([a, b]);
39
+ (0, _chai().expect)(fingerprint).to.match(/^[0-9a-f]{40}$/);
40
+ });
41
+ it('is stable under reordering: the remote listing order must not look like the lane moved', () => {
42
+ (0, _chai().expect)((0, _laneSyncExecutor().laneHeadFingerprint)([a, b])).to.equal((0, _laneSyncExecutor().laneHeadFingerprint)([b, a]));
43
+ });
44
+ it('changes when a component head changes', () => {
45
+ const moved = comp('acme.shop/comp2', 'cccccccccccccccccccccccccccccccccccccc33');
46
+ (0, _chai().expect)((0, _laneSyncExecutor().laneHeadFingerprint)([a, moved])).to.not.equal((0, _laneSyncExecutor().laneHeadFingerprint)([a, b]));
47
+ });
48
+ it('changes when a component is added or removed', () => {
49
+ (0, _chai().expect)((0, _laneSyncExecutor().laneHeadFingerprint)([a])).to.not.equal((0, _laneSyncExecutor().laneHeadFingerprint)([a, b]));
50
+ (0, _chai().expect)((0, _laneSyncExecutor().laneHeadFingerprint)([])).to.not.equal((0, _laneSyncExecutor().laneHeadFingerprint)([a]));
51
+ });
52
+ it('does not depend on the lane object, only on its content', () => {
53
+ (0, _chai().expect)((0, _laneSyncExecutor().laneHeadFingerprint)([a, b])).to.equal((0, _laneSyncExecutor().laneHeadFingerprint)([comp('acme.shop/comp1', a.head), b]));
54
+ });
55
+ });
56
+ describe('foreignLaneComponents', () => {
57
+ const DEFAULT_SCOPE = 'acme.shop';
58
+ const ours = comp('acme.shop/comp1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1');
59
+ const theirs = comp('other.scope/comp2', 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2');
60
+ it('is empty for a lane whose components are all in the repository scope', () => {
61
+ (0, _chai().expect)((0, _laneSyncExecutor().foreignLaneComponents)([ours], DEFAULT_SCOPE)).to.deep.equal([]);
62
+ (0, _chai().expect)((0, _laneSyncExecutor().foreignLaneComponents)([], DEFAULT_SCOPE)).to.deep.equal([]);
63
+ });
64
+ it('names every component outside the repository scope', () => {
65
+ (0, _chai().expect)((0, _laneSyncExecutor().foreignLaneComponents)([ours, theirs], DEFAULT_SCOPE)).to.deep.equal(['other.scope/comp2']);
66
+ });
67
+ it('compares the scope, not the id prefix: a namespaced component of our scope is ours', () => {
68
+ // `acme.shop/ui/button` must not read as scope `acme.shop/ui`.
69
+ const namespaced = comp('acme.shop/ui/button', 'ccccccccccccccccccccccccccccccccccccc333');
70
+ (0, _chai().expect)((0, _laneSyncExecutor().foreignLaneComponents)([namespaced], DEFAULT_SCOPE)).to.deep.equal([]);
71
+ });
72
+ it('treats a lane hosted elsewhere but filled with our components as ours (hosting != content)', () => {
73
+ (0, _chai().expect)((0, _laneSyncExecutor().foreignLaneComponents)([ours], DEFAULT_SCOPE)).to.deep.equal([]);
74
+ });
75
+ });
76
+ describe('crossScopeDescription', () => {
77
+ const DEFAULT_SCOPE = 'acme.shop';
78
+ it('names the foreign scopes and this repository scope', () => {
79
+ const description = (0, _laneSyncExecutor().crossScopeDescription)(['other.scope/comp2', 'third.scope/comp3'], DEFAULT_SCOPE);
80
+ (0, _chai().expect)(description).to.include('components from scope(s) other.scope, third.scope');
81
+ (0, _chai().expect)(description).to.include(`(this repo maps scope ${DEFAULT_SCOPE})`);
82
+ (0, _chai().expect)(description).to.include('foreign components: other.scope/comp2, third.scope/comp3');
83
+ });
84
+ it('lists at most five foreign components and summarizes the rest', () => {
85
+ const ids = Array.from({
86
+ length: 8
87
+ }, (_, index) => `other.scope/comp${index}`);
88
+ const description = (0, _laneSyncExecutor().crossScopeDescription)(ids, DEFAULT_SCOPE);
89
+ (0, _chai().expect)(description).to.include('other.scope/comp4');
90
+ (0, _chai().expect)(description).to.not.include('other.scope/comp5');
91
+ (0, _chai().expect)(description).to.include('…and 3 more');
92
+ });
93
+ it('does not append a count when everything is listed', () => {
94
+ (0, _chai().expect)((0, _laneSyncExecutor().crossScopeDescription)(['other.scope/comp1'], DEFAULT_SCOPE)).to.not.include('more');
95
+ });
96
+ });
97
+
98
+ // GitHub rejects a PR whose body exceeds 65,536 characters, whole-request; a lane has no size limit.
99
+ describe('laneSyncPrBody', () => {
100
+ const LANE_HEAD = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1';
101
+ function body(componentCount) {
102
+ return (0, _laneSyncExecutor().laneSyncPrBody)({
103
+ laneIdStr: 'acme.shop/my-lane',
104
+ laneUrl: 'https://bit.cloud/acme/shop/~lane/my-lane',
105
+ branch: 'my-lane',
106
+ laneHead: LANE_HEAD,
107
+ components: Array.from({
108
+ length: componentCount
109
+ }, (_, index) => comp(`acme.shop/namespace/component-with-a-realistic-name-${index}`, `${'0'.repeat(39)}${index % 10}`))
110
+ });
111
+ }
112
+ it('lists at most twenty components and summarizes the rest, keeping the exact total', () => {
113
+ const rendered = body(100);
114
+ (0, _chai().expect)(rendered).to.include('component-with-a-realistic-name-19');
115
+ (0, _chai().expect)(rendered).to.not.include('component-with-a-realistic-name-20');
116
+ (0, _chai().expect)(rendered).to.include('and 80 more');
117
+ (0, _chai().expect)(rendered).to.include('Components on the lane (100):');
118
+ (0, _chai().expect)(rendered).to.include(`lane head: \`${LANE_HEAD}\``);
119
+ });
120
+ it('stays far inside a git host body limit for a lane of any size', () => {
121
+ (0, _chai().expect)(body(100).length).to.be.below(60000);
122
+ (0, _chai().expect)(body(5000).length).to.be.below(60000);
123
+ });
124
+ it('lists a small lane in full, with no summary line', () => {
125
+ const rendered = body(3);
126
+ (0, _chai().expect)(rendered).to.include('component-with-a-realistic-name-2');
127
+ (0, _chai().expect)(rendered).to.not.include('more');
128
+ (0, _chai().expect)(rendered).to.include('Components on the lane (3):');
129
+ });
130
+ it('says so explicitly when the lane has no components', () => {
131
+ (0, _chai().expect)(body(0)).to.include('_none_');
132
+ });
133
+ });
134
+ describe('cross-scope outcome messages', () => {
135
+ const DEFAULT_SCOPE = 'acme.shop';
136
+ const FOREIGN = ['other.scope/comp2'];
137
+ it('an enumerated lane is SKIPPED, in the vocabulary of a healthy run', () => {
138
+ const summary = (0, _laneSyncExecutor().crossScopeSkipSummary)('my-lane', FOREIGN, DEFAULT_SCOPE);
139
+ (0, _chai().expect)(summary).to.match(/^my-lane -> skipped \(cross-scope lane: /);
140
+ (0, _chai().expect)(summary).to.include('no branch created');
141
+ // A HALTED/REFUSED marker in this line would flip the exit code of a healthy repository.
142
+ (0, _chai().expect)(summary).to.not.include('HALTED');
143
+ (0, _chai().expect)(summary).to.not.include('REFUSED');
144
+ });
145
+ it('an explicitly requested lane is REFUSED, with the reason and the "nothing was written" promise', () => {
146
+ const refusal = (0, _laneSyncExecutor().crossScopeRefusal)(FOREIGN, DEFAULT_SCOPE);
147
+ (0, _chai().expect)(refusal).to.include('cross-scope lane: components from scope(s) other.scope');
148
+ (0, _chai().expect)(refusal).to.include("not supported yet — see the docs' Cross-scope lanes section");
149
+ (0, _chai().expect)(refusal).to.include('No branch was created and nothing was written');
150
+ });
151
+ it('the refusal does not claim it created no branch when a branch already exists', () => {
152
+ const refusal = (0, _laneSyncExecutor().crossScopeRefusal)(FOREIGN, DEFAULT_SCOPE, 'my-lane');
153
+ (0, _chai().expect)(refusal).to.include('Nothing was written; the existing branch my-lane was left untouched');
154
+ (0, _chai().expect)(refusal).to.not.include('No branch was created');
155
+ });
156
+ it('a lane that became cross-scope mid-flight names the branch it can no longer converge with', () => {
157
+ const reason = (0, _laneSyncExecutor().crossScopeMidFlightHaltReason)('my-lane', FOREIGN, DEFAULT_SCOPE);
158
+ (0, _chai().expect)(reason).to.include('lane became cross-scope after it was mirrored onto my-lane');
159
+ (0, _chai().expect)(reason).to.include('can no longer be reconciled automatically');
160
+ });
161
+ });
162
+ describe('branchMirrorsOtherLaneReason', () => {
163
+ it('names the branch, the lane that owns it, and the lane that was refused', () => {
164
+ const reason = (0, _laneSyncExecutor().branchMirrorsOtherLaneReason)('release', 'other.scope/release', 'acme.shop/release');
165
+ (0, _chai().expect)(reason).to.include('branch release mirrors lane other.scope/release');
166
+ (0, _chai().expect)(reason).to.include('refusing to plan for acme.shop/release');
167
+ (0, _chai().expect)(reason).to.include("overwrite the other lane's mirror");
168
+ });
169
+ it('the PR comment tells the branch owner their lane is fine, and not to run the usual steps', () => {
170
+ const note = (0, _laneSyncExecutor().branchMirrorsOtherLaneNote)('other.scope/release', 'acme.shop/release');
171
+ (0, _chai().expect)(note).to.include('belongs to lane `other.scope/release`');
172
+ (0, _chai().expect)(note).to.include('nothing is wrong with it');
173
+ (0, _chai().expect)(note).to.include('acme.shop/release');
174
+ (0, _chai().expect)(note).to.match(/Do NOT run the usual "bit lane import" resolution steps/);
175
+ (0, _chai().expect)(note).to.include('rename one of the two lanes');
176
+ (0, _chai().expect)(note).to.include('`branches`');
177
+ });
178
+ });
179
+
180
+ // The executor is real; `getDefaultBranchName` throwing is the seam — the first dependency
181
+ // `reconcileLane` awaits after the fetch, so the throw reaches the outer catch with no git/network work.
182
+ describe('syncLane outer catch under --dry-run', () => {
183
+ function throwingExecutor(gitHostCalls) {
184
+ const noopLogger = {
185
+ console: () => {},
186
+ consoleWarning: () => {},
187
+ error: () => {},
188
+ debug: () => {}
189
+ };
190
+ const executor = new (_laneSyncExecutor().LaneSyncExecutor)({
191
+ lanes: {},
192
+ ci: {
193
+ getDefaultBranchName: async () => {
194
+ throw new Error('boom');
195
+ }
196
+ },
197
+ logger: noopLogger,
198
+ gitHost: {
199
+ name: 'stub',
200
+ findPrByBranch: async () => {
201
+ gitHostCalls.push('findPrByBranch');
202
+ return {
203
+ number: 7,
204
+ htmlUrl: 'https://example.test/pr/7',
205
+ labels: []
206
+ };
207
+ },
208
+ addLabel: async () => {
209
+ gitHostCalls.push('addLabel');
210
+ },
211
+ comment: async () => {
212
+ gitHostCalls.push('comment');
213
+ }
214
+ },
215
+ cfg: (0, _syncConfig().resolveSyncConfig)({}),
216
+ defaultScope: 'acme.shop'
217
+ });
218
+ // Mark the fetch done so the spec never touches a repository or the network.
219
+ executor.fetched = true;
220
+ return executor;
221
+ }
222
+ it('reports the halt without reading, labelling or commenting on the PR', async () => {
223
+ const gitHostCalls = [];
224
+ const summary = await throwingExecutor(gitHostCalls).syncLane({
225
+ hostScope: 'acme.shop',
226
+ name: 'my-lane'
227
+ }, {
228
+ dryRun: true
229
+ });
230
+ (0, _chai().expect)(summary).to.equal('HALTED my-lane -> unexpected error: boom');
231
+ (0, _chai().expect)(gitHostCalls).to.deep.equal([]);
232
+ });
233
+ it('is non-vacuous: without --dry-run the same failure does read, label and comment the PR', async () => {
234
+ const gitHostCalls = [];
235
+ const summary = await throwingExecutor(gitHostCalls).syncLane({
236
+ hostScope: 'acme.shop',
237
+ name: 'my-lane'
238
+ });
239
+ (0, _chai().expect)(summary).to.equal('HALTED my-lane -> unexpected error: boom');
240
+ (0, _chai().expect)(gitHostCalls).to.deep.equal(['findPrByBranch', 'addLabel', 'comment']);
241
+ });
242
+
243
+ // '-hostile' is a legal bit lane name (the charset allows a leading '-') that can never be a branch.
244
+ it('a lane whose name cannot map to a branch halts report-only instead of throwing', async () => {
245
+ const gitHostCalls = [];
246
+ const summary = await throwingExecutor(gitHostCalls).syncLane({
247
+ hostScope: 'acme.shop',
248
+ name: '-hostile'
249
+ });
250
+ (0, _chai().expect)(summary).to.match(/^HALTED -hostile -> unexpected error: .*not a valid git branch name/);
251
+ (0, _chai().expect)(gitHostCalls).to.deep.equal([]);
252
+ });
253
+ });
254
+
255
+ // bit's lane-name charset admits `$`, `-`, `_` and `!`, so an unquoted runbook shell-expands when pasted.
256
+ describe('haltCommentBody', () => {
257
+ const LANE_NAME = 'fix-$home-and-!bang';
258
+ it('single-quotes every value it interpolates into a command', () => {
259
+ const body = (0, _laneSyncExecutor().haltCommentBody)({
260
+ reason: 'merge conflicts in: acme.shop/comp1',
261
+ branch: LANE_NAME,
262
+ laneId: `acme.shop/${LANE_NAME}`
263
+ });
264
+ (0, _chai().expect)(body).to.include(`git fetch origin && git checkout '${LANE_NAME}'`);
265
+ (0, _chai().expect)(body).to.include(`bit lane import 'acme.shop/${LANE_NAME}'`);
266
+ (0, _chai().expect)(body).to.include(`git push origin '${LANE_NAME}'`);
267
+ // the reason is prose, not a command, and the runbook still reads as one
268
+ (0, _chai().expect)(body).to.include('merge conflicts in: acme.shop/comp1');
269
+ (0, _chai().expect)(body).to.include('Remove the `bit-sync-conflict` label to resume syncing.');
270
+ });
271
+
272
+ // A configured `branches` override only has to be a valid git ref, and git accepts a quote in one.
273
+ it('escapes a value that itself contains a quote, rather than ending the quoting early', () => {
274
+ const body = (0, _laneSyncExecutor().haltCommentBody)({
275
+ reason: 'x',
276
+ branch: "it's-a-branch",
277
+ laneId: 'acme.shop/lane'
278
+ });
279
+ (0, _chai().expect)(body).to.include(`git checkout 'it'\\''s-a-branch'`);
280
+ });
281
+ it('leaves an overriding note alone: it carries no commands to quote', () => {
282
+ const body = (0, _laneSyncExecutor().haltCommentBody)({
283
+ reason: 'x',
284
+ branch: LANE_NAME,
285
+ laneId: 'acme.shop/lane',
286
+ note: 'nothing to do'
287
+ });
288
+ (0, _chai().expect)(body).to.include('nothing to do');
289
+ (0, _chai().expect)(body).to.not.include('git checkout');
290
+ });
291
+ });
292
+
293
+ // `summarizeSync` decides the exit code by scanning for the prefix and nothing else, so a prefix-less
294
+ // line makes a dry run report success on a plan the real run would have halted on.
295
+ describe('dryRunSummaryLine', () => {
296
+ it('carries the HALTED prefix and the reason for a planned halt, so the run exits non-zero', () => {
297
+ const line = (0, _laneSyncExecutor().dryRunSummaryLine)('my-lane', {
298
+ type: 'halt',
299
+ reason: 'cannot tell which side is newer'
300
+ });
301
+ (0, _chai().expect)(line).to.equal('HALTED my-lane -> cannot tell which side is newer');
302
+ });
303
+ it('leaves every other planned action unprefixed — those runs are healthy', () => {
304
+ ['import-lane', 'export-branch', 'merge-diverged'].forEach(type => {
305
+ const line = (0, _laneSyncExecutor().dryRunSummaryLine)('my-lane', {
306
+ type
307
+ });
308
+ (0, _chai().expect)(line, type).to.equal(`my-lane -> ${type}`);
309
+ (0, _chai().expect)(line, type).to.not.contain('HALTED');
310
+ (0, _chai().expect)(line, type).to.not.contain('REFUSED');
311
+ });
312
+ (0, _chai().expect)((0, _laneSyncExecutor().dryRunSummaryLine)('my-lane', {
313
+ type: 'noop',
314
+ reason: 'converged'
315
+ })).to.equal('my-lane -> noop');
316
+ (0, _chai().expect)((0, _laneSyncExecutor().dryRunSummaryLine)('my-lane', {
317
+ type: 'close-pr',
318
+ deleteBranch: true
319
+ })).to.equal('my-lane -> close-pr');
320
+ });
321
+ });
322
+
323
+ // The deletion decision is computed from refs fetched once per run; on an `--all` run the branch can
324
+ // advance while earlier lanes are reconciled. Both seams are stubbed, so no repository is touched.
325
+ describe('close-pr re-verifies the tip before retiring a branch', () => {
326
+ const EVIDENCE_SHA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1';
327
+ const MOVED_SHA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2';
328
+ function retirer(currentTip, pushError) {
329
+ const warnings = [];
330
+ const pushes = [];
331
+ const executor = new (_laneSyncExecutor().LaneSyncExecutor)({
332
+ lanes: {},
333
+ ci: {},
334
+ logger: {
335
+ console: () => {},
336
+ consoleWarning: msg => warnings.push(msg),
337
+ error: () => {}
338
+ },
339
+ gitHost: undefined,
340
+ cfg: (0, _syncConfig().resolveSyncConfig)({}),
341
+ defaultScope: 'acme.shop'
342
+ });
343
+ executor.currentBranchTip = async () => currentTip;
344
+ executor.pushBranchDeletion = async (branch, sha) => {
345
+ pushes.push([branch, sha]);
346
+ if (pushError) throw pushError;
347
+ };
348
+ const closePr = () => executor.executeClosePr({
349
+ laneName: 'my-lane',
350
+ laneIdStr: 'acme.shop/my-lane',
351
+ branch: 'my-lane',
352
+ defaultBranch: 'main',
353
+ deleteBranch: true,
354
+ expectedTipSha: EVIDENCE_SHA
355
+ });
356
+ return {
357
+ closePr,
358
+ warnings,
359
+ pushes
360
+ };
361
+ }
362
+ it('deletes at the evidence sha while the branch is still there', async () => {
363
+ const {
364
+ closePr,
365
+ pushes
366
+ } = retirer(EVIDENCE_SHA);
367
+ (0, _chai().expect)(await closePr()).to.equal('my-lane -> close-pr (no open PR, branch my-lane deleted)');
368
+ (0, _chai().expect)(pushes).to.deep.equal([['my-lane', EVIDENCE_SHA]]);
369
+ });
370
+ it('KEEPS the branch when the tip moved during the run, pushing nothing at all', async () => {
371
+ const {
372
+ closePr,
373
+ warnings,
374
+ pushes
375
+ } = retirer(MOVED_SHA);
376
+ const summary = await closePr();
377
+ (0, _chai().expect)(summary).to.contain('branch my-lane kept: its tip advanced after the ownership evidence was read');
378
+ (0, _chai().expect)(pushes).to.deep.equal([]);
379
+ (0, _chai().expect)(warnings.join('\n')).to.contain(`its tip is now ${MOVED_SHA} rather than the ${EVIDENCE_SHA}`);
380
+ });
381
+ it('keeps the branch when the tip cannot be re-read at all, rather than trusting the stale answer', async () => {
382
+ const {
383
+ closePr,
384
+ pushes
385
+ } = retirer(undefined);
386
+ (0, _chai().expect)(await closePr()).to.contain('branch my-lane kept: its tip advanced');
387
+ (0, _chai().expect)(pushes).to.deep.equal([]);
388
+ });
389
+
390
+ // The re-read and the push are not atomic; the server gets the last word, and it is not a crash.
391
+ it('reports a lease refusal as the same kept outcome', async () => {
392
+ const {
393
+ closePr,
394
+ warnings
395
+ } = retirer(EVIDENCE_SHA, new Error('! [rejected] (delete) -> my-lane (stale info)\nerror: failed to push some refs'));
396
+ (0, _chai().expect)(await closePr()).to.contain('branch my-lane kept: its tip advanced');
397
+ (0, _chai().expect)(warnings.join('\n')).to.contain('the remote refused the lease');
398
+ });
399
+ it('an unrelated push failure still reads as "left in place", not as a race', async () => {
400
+ const {
401
+ closePr
402
+ } = retirer(EVIDENCE_SHA, new Error('remote: GH006: Protected branch update failed'));
403
+ (0, _chai().expect)(await closePr()).to.equal('my-lane -> close-pr (no open PR, branch my-lane left in place)');
404
+ });
405
+ });
406
+ describe('isProtectedBranch', () => {
407
+ it('refuses the default branch and the main sync branch', () => {
408
+ (0, _chai().expect)((0, _laneSyncExecutor().isProtectedBranch)('develop', 'develop', 'bit-sync/main')).to.equal(true);
409
+ (0, _chai().expect)((0, _laneSyncExecutor().isProtectedBranch)('bit-sync/main', 'develop', 'bit-sync/main')).to.equal(true);
410
+ });
411
+ it('permits an ordinary lane branch', () => {
412
+ (0, _chai().expect)((0, _laneSyncExecutor().isProtectedBranch)('my-lane', 'develop', 'bit-sync/main')).to.equal(false);
413
+ });
414
+ });
415
+
416
+ //# sourceMappingURL=lane-sync-executor.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_chai","data","require","_laneSyncExecutor","_syncConfig","comp","id","head","toStringWithoutVersion","scope","split","describe","a","b","it","fingerprint","laneHeadFingerprint","expect","to","match","equal","moved","not","DEFAULT_SCOPE","ours","theirs","foreignLaneComponents","deep","namespaced","description","crossScopeDescription","include","ids","Array","from","length","_","index","LANE_HEAD","body","componentCount","laneSyncPrBody","laneIdStr","laneUrl","branch","laneHead","components","repeat","rendered","be","below","FOREIGN","summary","crossScopeSkipSummary","refusal","crossScopeRefusal","reason","crossScopeMidFlightHaltReason","branchMirrorsOtherLaneReason","note","branchMirrorsOtherLaneNote","throwingExecutor","gitHostCalls","noopLogger","console","consoleWarning","error","debug","executor","LaneSyncExecutor","lanes","ci","getDefaultBranchName","Error","logger","gitHost","name","findPrByBranch","push","number","htmlUrl","labels","addLabel","comment","cfg","resolveSyncConfig","defaultScope","fetched","syncLane","hostScope","dryRun","LANE_NAME","haltCommentBody","laneId","line","dryRunSummaryLine","type","forEach","contain","deleteBranch","EVIDENCE_SHA","MOVED_SHA","retirer","currentTip","pushError","warnings","pushes","msg","undefined","currentBranchTip","pushBranchDeletion","sha","closePr","executeClosePr","laneName","defaultBranch","expectedTipSha","join","isProtectedBranch"],"sources":["lane-sync-executor.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport {\n branchMirrorsOtherLaneNote,\n branchMirrorsOtherLaneReason,\n crossScopeDescription,\n crossScopeMidFlightHaltReason,\n crossScopeRefusal,\n crossScopeSkipSummary,\n dryRunSummaryLine,\n foreignLaneComponents,\n haltCommentBody,\n isProtectedBranch,\n laneHeadFingerprint,\n LaneSyncExecutor,\n laneSyncPrBody,\n} from './lane-sync-executor';\nimport { resolveSyncConfig } from './sync-config';\n\ntype LaneComponents = Parameters<typeof laneHeadFingerprint>[0];\n\n/** A stand-in for `LaneData`'s component entry; the helpers only read the id string, scope and head. */\nfunction comp(id: string, head: string): LaneComponents[number] {\n return {\n id: { toStringWithoutVersion: () => id, scope: id.split('/', 1)[0] },\n head,\n } as unknown as LaneComponents[number];\n}\n\ndescribe('laneHeadFingerprint', () => {\n const a = comp('acme.shop/comp1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1');\n const b = comp('acme.shop/comp2', 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2');\n\n it('is a single 40-hex token, so it survives being written into a commit trailer as an annotation', () => {\n const fingerprint = laneHeadFingerprint([a, b]);\n expect(fingerprint).to.match(/^[0-9a-f]{40}$/);\n });\n\n it('is stable under reordering: the remote listing order must not look like the lane moved', () => {\n expect(laneHeadFingerprint([a, b])).to.equal(laneHeadFingerprint([b, a]));\n });\n\n it('changes when a component head changes', () => {\n const moved = comp('acme.shop/comp2', 'cccccccccccccccccccccccccccccccccccccc33');\n expect(laneHeadFingerprint([a, moved])).to.not.equal(laneHeadFingerprint([a, b]));\n });\n\n it('changes when a component is added or removed', () => {\n expect(laneHeadFingerprint([a])).to.not.equal(laneHeadFingerprint([a, b]));\n expect(laneHeadFingerprint([])).to.not.equal(laneHeadFingerprint([a]));\n });\n\n it('does not depend on the lane object, only on its content', () => {\n expect(laneHeadFingerprint([a, b])).to.equal(laneHeadFingerprint([comp('acme.shop/comp1', a.head), b]));\n });\n});\n\ndescribe('foreignLaneComponents', () => {\n const DEFAULT_SCOPE = 'acme.shop';\n const ours = comp('acme.shop/comp1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1');\n const theirs = comp('other.scope/comp2', 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2');\n\n it('is empty for a lane whose components are all in the repository scope', () => {\n expect(foreignLaneComponents([ours], DEFAULT_SCOPE)).to.deep.equal([]);\n expect(foreignLaneComponents([], DEFAULT_SCOPE)).to.deep.equal([]);\n });\n\n it('names every component outside the repository scope', () => {\n expect(foreignLaneComponents([ours, theirs], DEFAULT_SCOPE)).to.deep.equal(['other.scope/comp2']);\n });\n\n it('compares the scope, not the id prefix: a namespaced component of our scope is ours', () => {\n // `acme.shop/ui/button` must not read as scope `acme.shop/ui`.\n const namespaced = comp('acme.shop/ui/button', 'ccccccccccccccccccccccccccccccccccccc333');\n expect(foreignLaneComponents([namespaced], DEFAULT_SCOPE)).to.deep.equal([]);\n });\n\n it('treats a lane hosted elsewhere but filled with our components as ours (hosting != content)', () => {\n expect(foreignLaneComponents([ours], DEFAULT_SCOPE)).to.deep.equal([]);\n });\n});\n\ndescribe('crossScopeDescription', () => {\n const DEFAULT_SCOPE = 'acme.shop';\n\n it('names the foreign scopes and this repository scope', () => {\n const description = crossScopeDescription(['other.scope/comp2', 'third.scope/comp3'], DEFAULT_SCOPE);\n expect(description).to.include('components from scope(s) other.scope, third.scope');\n expect(description).to.include(`(this repo maps scope ${DEFAULT_SCOPE})`);\n expect(description).to.include('foreign components: other.scope/comp2, third.scope/comp3');\n });\n\n it('lists at most five foreign components and summarizes the rest', () => {\n const ids = Array.from({ length: 8 }, (_, index) => `other.scope/comp${index}`);\n const description = crossScopeDescription(ids, DEFAULT_SCOPE);\n expect(description).to.include('other.scope/comp4');\n expect(description).to.not.include('other.scope/comp5');\n expect(description).to.include('…and 3 more');\n });\n\n it('does not append a count when everything is listed', () => {\n expect(crossScopeDescription(['other.scope/comp1'], DEFAULT_SCOPE)).to.not.include('more');\n });\n});\n\n// GitHub rejects a PR whose body exceeds 65,536 characters, whole-request; a lane has no size limit.\ndescribe('laneSyncPrBody', () => {\n const LANE_HEAD = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1';\n\n function body(componentCount: number) {\n return laneSyncPrBody({\n laneIdStr: 'acme.shop/my-lane',\n laneUrl: 'https://bit.cloud/acme/shop/~lane/my-lane',\n branch: 'my-lane',\n laneHead: LANE_HEAD,\n components: Array.from({ length: componentCount }, (_, index) =>\n comp(`acme.shop/namespace/component-with-a-realistic-name-${index}`, `${'0'.repeat(39)}${index % 10}`)\n ),\n });\n }\n\n it('lists at most twenty components and summarizes the rest, keeping the exact total', () => {\n const rendered = body(100);\n expect(rendered).to.include('component-with-a-realistic-name-19');\n expect(rendered).to.not.include('component-with-a-realistic-name-20');\n expect(rendered).to.include('and 80 more');\n expect(rendered).to.include('Components on the lane (100):');\n expect(rendered).to.include(`lane head: \\`${LANE_HEAD}\\``);\n });\n\n it('stays far inside a git host body limit for a lane of any size', () => {\n expect(body(100).length).to.be.below(60000);\n expect(body(5000).length).to.be.below(60000);\n });\n\n it('lists a small lane in full, with no summary line', () => {\n const rendered = body(3);\n expect(rendered).to.include('component-with-a-realistic-name-2');\n expect(rendered).to.not.include('more');\n expect(rendered).to.include('Components on the lane (3):');\n });\n\n it('says so explicitly when the lane has no components', () => {\n expect(body(0)).to.include('_none_');\n });\n});\n\ndescribe('cross-scope outcome messages', () => {\n const DEFAULT_SCOPE = 'acme.shop';\n const FOREIGN = ['other.scope/comp2'];\n\n it('an enumerated lane is SKIPPED, in the vocabulary of a healthy run', () => {\n const summary = crossScopeSkipSummary('my-lane', FOREIGN, DEFAULT_SCOPE);\n expect(summary).to.match(/^my-lane -> skipped \\(cross-scope lane: /);\n expect(summary).to.include('no branch created');\n // A HALTED/REFUSED marker in this line would flip the exit code of a healthy repository.\n expect(summary).to.not.include('HALTED');\n expect(summary).to.not.include('REFUSED');\n });\n\n it('an explicitly requested lane is REFUSED, with the reason and the \"nothing was written\" promise', () => {\n const refusal = crossScopeRefusal(FOREIGN, DEFAULT_SCOPE);\n expect(refusal).to.include('cross-scope lane: components from scope(s) other.scope');\n expect(refusal).to.include(\"not supported yet — see the docs' Cross-scope lanes section\");\n expect(refusal).to.include('No branch was created and nothing was written');\n });\n\n it('the refusal does not claim it created no branch when a branch already exists', () => {\n const refusal = crossScopeRefusal(FOREIGN, DEFAULT_SCOPE, 'my-lane');\n expect(refusal).to.include('Nothing was written; the existing branch my-lane was left untouched');\n expect(refusal).to.not.include('No branch was created');\n });\n\n it('a lane that became cross-scope mid-flight names the branch it can no longer converge with', () => {\n const reason = crossScopeMidFlightHaltReason('my-lane', FOREIGN, DEFAULT_SCOPE);\n expect(reason).to.include('lane became cross-scope after it was mirrored onto my-lane');\n expect(reason).to.include('can no longer be reconciled automatically');\n });\n});\n\ndescribe('branchMirrorsOtherLaneReason', () => {\n it('names the branch, the lane that owns it, and the lane that was refused', () => {\n const reason = branchMirrorsOtherLaneReason('release', 'other.scope/release', 'acme.shop/release');\n expect(reason).to.include('branch release mirrors lane other.scope/release');\n expect(reason).to.include('refusing to plan for acme.shop/release');\n expect(reason).to.include(\"overwrite the other lane's mirror\");\n });\n\n it('the PR comment tells the branch owner their lane is fine, and not to run the usual steps', () => {\n const note = branchMirrorsOtherLaneNote('other.scope/release', 'acme.shop/release');\n expect(note).to.include('belongs to lane `other.scope/release`');\n expect(note).to.include('nothing is wrong with it');\n expect(note).to.include('acme.shop/release');\n expect(note).to.match(/Do NOT run the usual \"bit lane import\" resolution steps/);\n expect(note).to.include('rename one of the two lanes');\n expect(note).to.include('`branches`');\n });\n});\n\n// The executor is real; `getDefaultBranchName` throwing is the seam — the first dependency\n// `reconcileLane` awaits after the fetch, so the throw reaches the outer catch with no git/network work.\ndescribe('syncLane outer catch under --dry-run', () => {\n function throwingExecutor(gitHostCalls: string[]): LaneSyncExecutor {\n const noopLogger = { console: () => {}, consoleWarning: () => {}, error: () => {}, debug: () => {} };\n const executor = new LaneSyncExecutor({\n lanes: {} as any,\n ci: {\n getDefaultBranchName: async () => {\n throw new Error('boom');\n },\n } as any,\n logger: noopLogger as any,\n gitHost: {\n name: 'stub',\n findPrByBranch: async () => {\n gitHostCalls.push('findPrByBranch');\n return { number: 7, htmlUrl: 'https://example.test/pr/7', labels: [] };\n },\n addLabel: async () => {\n gitHostCalls.push('addLabel');\n },\n comment: async () => {\n gitHostCalls.push('comment');\n },\n } as any,\n cfg: resolveSyncConfig({}),\n defaultScope: 'acme.shop',\n });\n // Mark the fetch done so the spec never touches a repository or the network.\n (executor as any).fetched = true;\n return executor;\n }\n\n it('reports the halt without reading, labelling or commenting on the PR', async () => {\n const gitHostCalls: string[] = [];\n const summary = await throwingExecutor(gitHostCalls).syncLane(\n { hostScope: 'acme.shop', name: 'my-lane' },\n { dryRun: true }\n );\n expect(summary).to.equal('HALTED my-lane -> unexpected error: boom');\n expect(gitHostCalls).to.deep.equal([]);\n });\n\n it('is non-vacuous: without --dry-run the same failure does read, label and comment the PR', async () => {\n const gitHostCalls: string[] = [];\n const summary = await throwingExecutor(gitHostCalls).syncLane({ hostScope: 'acme.shop', name: 'my-lane' });\n expect(summary).to.equal('HALTED my-lane -> unexpected error: boom');\n expect(gitHostCalls).to.deep.equal(['findPrByBranch', 'addLabel', 'comment']);\n });\n\n // '-hostile' is a legal bit lane name (the charset allows a leading '-') that can never be a branch.\n it('a lane whose name cannot map to a branch halts report-only instead of throwing', async () => {\n const gitHostCalls: string[] = [];\n const summary = await throwingExecutor(gitHostCalls).syncLane({ hostScope: 'acme.shop', name: '-hostile' });\n expect(summary).to.match(/^HALTED -hostile -> unexpected error: .*not a valid git branch name/);\n expect(gitHostCalls).to.deep.equal([]);\n });\n});\n\n// bit's lane-name charset admits `$`, `-`, `_` and `!`, so an unquoted runbook shell-expands when pasted.\ndescribe('haltCommentBody', () => {\n const LANE_NAME = 'fix-$home-and-!bang';\n\n it('single-quotes every value it interpolates into a command', () => {\n const body = haltCommentBody({\n reason: 'merge conflicts in: acme.shop/comp1',\n branch: LANE_NAME,\n laneId: `acme.shop/${LANE_NAME}`,\n });\n expect(body).to.include(`git fetch origin && git checkout '${LANE_NAME}'`);\n expect(body).to.include(`bit lane import 'acme.shop/${LANE_NAME}'`);\n expect(body).to.include(`git push origin '${LANE_NAME}'`);\n // the reason is prose, not a command, and the runbook still reads as one\n expect(body).to.include('merge conflicts in: acme.shop/comp1');\n expect(body).to.include('Remove the `bit-sync-conflict` label to resume syncing.');\n });\n\n // A configured `branches` override only has to be a valid git ref, and git accepts a quote in one.\n it('escapes a value that itself contains a quote, rather than ending the quoting early', () => {\n const body = haltCommentBody({ reason: 'x', branch: \"it's-a-branch\", laneId: 'acme.shop/lane' });\n expect(body).to.include(`git checkout 'it'\\\\''s-a-branch'`);\n });\n\n it('leaves an overriding note alone: it carries no commands to quote', () => {\n const body = haltCommentBody({ reason: 'x', branch: LANE_NAME, laneId: 'acme.shop/lane', note: 'nothing to do' });\n expect(body).to.include('nothing to do');\n expect(body).to.not.include('git checkout');\n });\n});\n\n// `summarizeSync` decides the exit code by scanning for the prefix and nothing else, so a prefix-less\n// line makes a dry run report success on a plan the real run would have halted on.\ndescribe('dryRunSummaryLine', () => {\n it('carries the HALTED prefix and the reason for a planned halt, so the run exits non-zero', () => {\n const line = dryRunSummaryLine('my-lane', { type: 'halt', reason: 'cannot tell which side is newer' });\n expect(line).to.equal('HALTED my-lane -> cannot tell which side is newer');\n });\n\n it('leaves every other planned action unprefixed — those runs are healthy', () => {\n (['import-lane', 'export-branch', 'merge-diverged'] as const).forEach((type) => {\n const line = dryRunSummaryLine('my-lane', { type });\n expect(line, type).to.equal(`my-lane -> ${type}`);\n expect(line, type).to.not.contain('HALTED');\n expect(line, type).to.not.contain('REFUSED');\n });\n expect(dryRunSummaryLine('my-lane', { type: 'noop', reason: 'converged' })).to.equal('my-lane -> noop');\n expect(dryRunSummaryLine('my-lane', { type: 'close-pr', deleteBranch: true })).to.equal('my-lane -> close-pr');\n });\n});\n\n// The deletion decision is computed from refs fetched once per run; on an `--all` run the branch can\n// advance while earlier lanes are reconciled. Both seams are stubbed, so no repository is touched.\ndescribe('close-pr re-verifies the tip before retiring a branch', () => {\n const EVIDENCE_SHA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1';\n const MOVED_SHA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2';\n\n function retirer(currentTip: string | undefined, pushError?: Error) {\n const warnings: string[] = [];\n const pushes: string[][] = [];\n const executor = new LaneSyncExecutor({\n lanes: {} as any,\n ci: {} as any,\n logger: { console: () => {}, consoleWarning: (msg: string) => warnings.push(msg), error: () => {} } as any,\n gitHost: undefined,\n cfg: resolveSyncConfig({}),\n defaultScope: 'acme.shop',\n });\n (executor as any).currentBranchTip = async () => currentTip;\n (executor as any).pushBranchDeletion = async (branch: string, sha: string) => {\n pushes.push([branch, sha]);\n if (pushError) throw pushError;\n };\n const closePr = () =>\n (executor as any).executeClosePr({\n laneName: 'my-lane',\n laneIdStr: 'acme.shop/my-lane',\n branch: 'my-lane',\n defaultBranch: 'main',\n deleteBranch: true,\n expectedTipSha: EVIDENCE_SHA,\n }) as Promise<string>;\n return { closePr, warnings, pushes };\n }\n\n it('deletes at the evidence sha while the branch is still there', async () => {\n const { closePr, pushes } = retirer(EVIDENCE_SHA);\n expect(await closePr()).to.equal('my-lane -> close-pr (no open PR, branch my-lane deleted)');\n expect(pushes).to.deep.equal([['my-lane', EVIDENCE_SHA]]);\n });\n\n it('KEEPS the branch when the tip moved during the run, pushing nothing at all', async () => {\n const { closePr, warnings, pushes } = retirer(MOVED_SHA);\n const summary = await closePr();\n expect(summary).to.contain('branch my-lane kept: its tip advanced after the ownership evidence was read');\n expect(pushes).to.deep.equal([]);\n expect(warnings.join('\\n')).to.contain(`its tip is now ${MOVED_SHA} rather than the ${EVIDENCE_SHA}`);\n });\n\n it('keeps the branch when the tip cannot be re-read at all, rather than trusting the stale answer', async () => {\n const { closePr, pushes } = retirer(undefined);\n expect(await closePr()).to.contain('branch my-lane kept: its tip advanced');\n expect(pushes).to.deep.equal([]);\n });\n\n // The re-read and the push are not atomic; the server gets the last word, and it is not a crash.\n it('reports a lease refusal as the same kept outcome', async () => {\n const { closePr, warnings } = retirer(\n EVIDENCE_SHA,\n new Error('! [rejected] (delete) -> my-lane (stale info)\\nerror: failed to push some refs')\n );\n expect(await closePr()).to.contain('branch my-lane kept: its tip advanced');\n expect(warnings.join('\\n')).to.contain('the remote refused the lease');\n });\n\n it('an unrelated push failure still reads as \"left in place\", not as a race', async () => {\n const { closePr } = retirer(EVIDENCE_SHA, new Error('remote: GH006: Protected branch update failed'));\n expect(await closePr()).to.equal('my-lane -> close-pr (no open PR, branch my-lane left in place)');\n });\n});\n\ndescribe('isProtectedBranch', () => {\n it('refuses the default branch and the main sync branch', () => {\n expect(isProtectedBranch('develop', 'develop', 'bit-sync/main')).to.equal(true);\n expect(isProtectedBranch('bit-sync/main', 'develop', 'bit-sync/main')).to.equal(true);\n });\n\n it('permits an ordinary lane branch', () => {\n expect(isProtectedBranch('my-lane', 'develop', 'bit-sync/main')).to.equal(false);\n });\n});\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,62 @@
1
+ import type { Logger } from '@teambit/logger';
2
+ import type { LanesMain } from '@teambit/lanes';
3
+ import type { CheckoutMain } from '@teambit/checkout';
4
+ import type { CiMain } from '../ci.main.runtime';
5
+ import type { CiSyncConfig } from './sync-config';
6
+ import type { GitHostProvider } from './git-host-provider';
7
+ export type MainSyncDeps = {
8
+ checkout: CheckoutMain;
9
+ lanes: LanesMain;
10
+ /** for reloadWorkspaceFromDisk + switchToLaneForSync */
11
+ ci: CiMain;
12
+ logger: Logger;
13
+ /** undefined => PR operations are logged and skipped; the git half of the sync still runs. */
14
+ gitHost?: GitHostProvider;
15
+ cfg: Required<CiSyncConfig>;
16
+ defaultBranch: string;
17
+ defaultScope: string;
18
+ };
19
+ export declare class MainSyncExecutor {
20
+ private deps;
21
+ constructor(deps: MainSyncDeps);
22
+ /**
23
+ * Returns a summary line (HALT-prefixed on failure) and, like `syncLane`, does not throw — a
24
+ * main-sync failure must not erase the lane results collected before it.
25
+ */
26
+ syncMain(opts?: {
27
+ dryRun?: boolean;
28
+ }): Promise<string>;
29
+ /**
30
+ * Put the working tree on the sync branch at a pristine copy of `startPoint`. Pristine is
31
+ * load-bearing for a diff-based reconciler: any stray modification would be indistinguishable from
32
+ * main-scope drift. The reload makes `checkout head` read this branch's `.bitmap`.
33
+ */
34
+ private resetToStartPoint;
35
+ /**
36
+ * Merge the default branch into the sync branch so drift is computed against the repository's
37
+ * current state. Returns a reason string on failure; a conflict needs a human (or deleting the sync
38
+ * branch, which the next run re-forks).
39
+ */
40
+ private catchUpWithDefaultBranch;
41
+ /**
42
+ * The "nothing to do" line — plus the open sync PR, if any: converged means the SYNC branch matches
43
+ * the scope, and the default branch may still differ until that PR merges.
44
+ */
45
+ private convergedSummary;
46
+ /**
47
+ * The files `bit checkout head` changed, i.e. the drift. Excludes `.bit`/`node_modules` — the same
48
+ * set the commit paths refuse to touch, keeping "counts as drift" and "gets committed" identical.
49
+ */
50
+ private driftFiles;
51
+ /**
52
+ * Make sure the pushed branch has an open PR. A git-host failure only warns: the branch is pushed,
53
+ * which is the load-bearing half, and the next run retries the PR.
54
+ */
55
+ private ensureSyncPr;
56
+ /**
57
+ * Leave git on the default branch and bit on main. Mirrors `LaneSyncExecutor.restoreWorkspace`:
58
+ * best-effort and warn-only so a restore hiccup can't throw out of a `finally` and mask the real
59
+ * error; the clean discards untracked files `checkout head` wrote on the converged/dry-run paths.
60
+ */
61
+ private restoreWorkspace;
62
+ }