@teambit/ci 1.0.484 → 1.0.486

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-1785846648967.js → preview-1785864931304.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 +21 -16
  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,305 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.MainSyncExecutor = void 0;
7
+ function _chalk() {
8
+ const data = _interopRequireDefault(require("chalk"));
9
+ _chalk = function () {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
14
+ function _cli() {
15
+ const data = require("@teambit/cli");
16
+ _cli = function () {
17
+ return data;
18
+ };
19
+ return data;
20
+ }
21
+ function _git() {
22
+ const data = require("../git");
23
+ _git = function () {
24
+ return data;
25
+ };
26
+ return data;
27
+ }
28
+ function _syncState() {
29
+ const data = require("./sync-state");
30
+ _syncState = function () {
31
+ return data;
32
+ };
33
+ return data;
34
+ }
35
+ function _workspaceLane() {
36
+ const data = require("./workspace-lane");
37
+ _workspaceLane = function () {
38
+ return data;
39
+ };
40
+ return data;
41
+ }
42
+ function _laneSyncExecutor() {
43
+ const data = require("./lane-sync-executor");
44
+ _laneSyncExecutor = function () {
45
+ return data;
46
+ };
47
+ return data;
48
+ }
49
+ function _gitOps() {
50
+ const data = require("./git-ops");
51
+ _gitOps = function () {
52
+ return data;
53
+ };
54
+ return data;
55
+ }
56
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
57
+ /**
58
+ * Reconcile the main scope with the repository's default branch. Stateless: `bit checkout head`
59
+ * writes the latest exported versions, so an empty `git status` IS convergence and any diff IS the
60
+ * drift. Under 'pr' the convergence is proposed via `mainSyncBranch`; under 'direct-push' it lands on
61
+ * the default branch with a plain push. Nothing is ever force-pushed in either mode.
62
+ */
63
+ /** the "nothing to do" line, shared by both modes */
64
+ const CONVERGED_SUMMARY = 'main -> converged (checkout head produced no changes)';
65
+ class MainSyncExecutor {
66
+ constructor(deps) {
67
+ this.deps = deps;
68
+ }
69
+
70
+ /**
71
+ * Returns a summary line (HALT-prefixed on failure) and, like `syncLane`, does not throw — a
72
+ * main-sync failure must not erase the lane results collected before it.
73
+ */
74
+ async syncMain(opts = {}) {
75
+ const {
76
+ cfg,
77
+ defaultBranch,
78
+ defaultScope,
79
+ logger
80
+ } = this.deps;
81
+ const directPush = cfg.mainSync === 'direct-push';
82
+ // In direct-push mode the sync branch (and its PR) is not consulted, moved or deleted.
83
+ const branch = directPush ? defaultBranch : cfg.mainSyncBranch;
84
+ if (cfg.autoMergeMainSyncPr && !directPush) {
85
+ // Enabling auto-merge is host-specific and not part of the GitHostProvider contract.
86
+ logger.consoleWarning(`sync.autoMergeMainSyncPr is enabled in the config, but enabling auto-merge is not implemented yet — ` + `the sync PR is opened without it. Use a repository auto-merge rule instead.`);
87
+ }
88
+ try {
89
+ await (0, _gitOps().fetchRemoteHeads)();
90
+ // Direct-push always starts from `origin/<defaultBranch>` — the very tip the plain push must
91
+ // fast-forward. In 'pr' mode an existing sync branch is reused so its PR history survives.
92
+ const syncBranchExists = directPush ? false : await (0, _gitOps().branchExistsOnRemote)(branch);
93
+ const startPoint = syncBranchExists ? `origin/${branch}` : `origin/${defaultBranch}`;
94
+ logger.console(_chalk().default.blue(directPush ? `main -> checking the main scope against ${defaultBranch} (direct-push)` : `main -> checking the main scope against ${defaultBranch} (sync branch ${branch})`));
95
+ await this.resetToStartPoint(branch, startPoint);
96
+ if (syncBranchExists) {
97
+ // Catch up with the default branch: the PR must stay mergeable, and `checkout head` on a stale
98
+ // tree would re-commit pre-merge content over the default branch's changes. A merge — never a
99
+ // rebase, never a force-push — is the only non-destructive way to move a branch with an open PR.
100
+ const catchUpErr = await this.catchUpWithDefaultBranch(branch);
101
+ if (catchUpErr) return `${_laneSyncExecutor().HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;
102
+ }
103
+
104
+ // `checkout head` resolves versions against the CURRENT lane; a lane pointer on this branch
105
+ // would make the "drift" the lane's content. Refuse rather than open a wildly wrong PR.
106
+ // `.bitmap`-derived read: the scope-object read answers "main" on a fresh runner and the guard
107
+ // would silently stop refusing exactly where it ships (see `workspace-lane.ts`).
108
+ const currentLane = (0, _workspaceLane().currentLaneIdStr)(this.deps.lanes);
109
+ if (currentLane) {
110
+ return `${_laneSyncExecutor().HALT_SUMMARY_PREFIX} main -> the .bitmap on ${startPoint} points at lane ` + `"${currentLane}" rather than main, so the main-scope drift cannot be computed`;
111
+ }
112
+
113
+ // `checkoutByCLIValues` rather than `checkout`: it imports current objects first (so `head`
114
+ // resolves to the remote scope's versions) and persists `.bitmap` on destroy.
115
+ // `includeNewFromScope`: a component exported to the scope's main but never in this repo's
116
+ // `.bitmap` would otherwise be invisible to every sync run forever.
117
+ // `mergeStrategy: 'theirs'` is required: unexported source drift makes components "modified",
118
+ // and with no strategy the checkout throws. 'theirs' materializes the exported truth — any
119
+ // reversion of unexported drift is visible in the PR diff. 'ours' is forbidden: it would advance
120
+ // `.bitmap` while keeping the old files, a commit asserting a state the tree does not have.
121
+ const results = await this.deps.checkout.checkoutByCLIValues('', {
122
+ head: true,
123
+ skipNpmInstall: true,
124
+ includeNewFromScope: true,
125
+ mergeStrategy: 'theirs'
126
+ });
127
+ const newFromScope = results.newFromScope ?? [];
128
+ if (newFromScope.length) {
129
+ logger.console(_chalk().default.blue(`Added ${newFromScope.length} component(s) that exist on ${defaultScope}'s main but not in this ` + `workspace: ${newFromScope.join(', ')}`));
130
+ }
131
+ const drift = await this.driftFiles();
132
+ // Direct-push stays bare: asking the host about `mainSyncBranch`'s PR would be the one
133
+ // interaction with it this mode promises not to make.
134
+ if (!drift.length) return directPush ? CONVERGED_SUMMARY : await this.convergedSummary(branch);
135
+ logger.console((0, _cli().formatWarningSummary)(`main -> drift in ${drift.length} file(s): ${drift.slice(0, 20).join(', ')}`));
136
+ if (opts.dryRun) {
137
+ // The working tree WAS written (a diff-based check has no other way); `finally` restores it. Safe
138
+ // only because a dry run over a dirty tree is refused before anything runs — the restore rewinds
139
+ // to HEAD, so an uncommitted change here would be gone (`assertCleanForDryRun`).
140
+ logger.console((0, _cli().formatWarningSummary)(directPush ? `Dry-run: main -> would push the drift directly onto ${branch}` : `Dry-run: main -> would push ${branch} and open a sync PR`));
141
+ return directPush ? `main -> drift detected in ${drift.length} file(s) — would push ${branch} directly` : `main -> drift detected in ${drift.length} file(s) — would open sync PR`;
142
+ }
143
+ await (0, _gitOps().addAllExceptScopeAndModules)();
144
+ await (0, _gitOps().commitWithIdentity)(mainSyncCommitMessage(drift.length));
145
+ // Never force: a rejected push means a concurrent run pushed in between, and in direct-push mode
146
+ // that rejection is the whole safety story for the default branch. Unambiguous refspec — see
147
+ // `lane-sync-executor.commitAllAndPush`.
148
+ await _git().git.push(['origin', `HEAD:refs/heads/${branch}`]);
149
+ logger.console(_chalk().default.green(`Pushed ${branch}`));
150
+ if (directPush) {
151
+ const shortSha = (await _git().git.revparse(['--short', 'HEAD'])).trim();
152
+ return `main -> direct-push (pushed ${branch} @ ${shortSha})`;
153
+ }
154
+ const prUrl = await this.ensureSyncPr({
155
+ branch,
156
+ driftCount: drift.length,
157
+ newFromScope
158
+ });
159
+ return `main -> pushed sync commit to ${branch}${prUrl ? ` (PR ${prUrl})` : ''}`;
160
+ } catch (e) {
161
+ // Same contract as the lane executor: a HALTED line, never a throw mid-run.
162
+ return `${_laneSyncExecutor().HALT_SUMMARY_PREFIX} main -> ${e?.message || e}`;
163
+ } finally {
164
+ await this.restoreWorkspace();
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Put the working tree on the sync branch at a pristine copy of `startPoint`. Pristine is
170
+ * load-bearing for a diff-based reconciler: any stray modification would be indistinguishable from
171
+ * main-scope drift. The reload makes `checkout head` read this branch's `.bitmap`.
172
+ */
173
+ async resetToStartPoint(branch, startPoint) {
174
+ await (0, _gitOps().checkoutPristine)(branch, startPoint, () => this.deps.ci.reloadWorkspaceFromDisk());
175
+ }
176
+
177
+ /**
178
+ * Merge the default branch into the sync branch so drift is computed against the repository's
179
+ * current state. Returns a reason string on failure; a conflict needs a human (or deleting the sync
180
+ * branch, which the next run re-forks).
181
+ */
182
+ async catchUpWithDefaultBranch(branch) {
183
+ const {
184
+ defaultBranch,
185
+ logger
186
+ } = this.deps;
187
+ try {
188
+ // under the identity: a non-fast-forward merge writes a merge COMMIT
189
+ const out = await (0, _gitOps().gitWithIdentity)(['merge', '--no-edit', `origin/${defaultBranch}`]);
190
+ logger.console(_chalk().default.blue(`Brought ${branch} up to date with origin/${defaultBranch}: ${out.trim()}`));
191
+ } catch (e) {
192
+ await _git().git.raw(['merge', '--abort']).catch(() => undefined);
193
+ return `could not bring the sync branch ${branch} up to date with origin/${defaultBranch}: ${e?.message || e}. ` + `Resolve or delete ${branch} (a deleted sync branch is re-forked from ${defaultBranch} on the next run)`;
194
+ }
195
+ // The merge may have brought a new `.bitmap` in from the default branch.
196
+ await this.deps.ci.reloadWorkspaceFromDisk();
197
+ return undefined;
198
+ }
199
+
200
+ /**
201
+ * The "nothing to do" line — plus the open sync PR, if any: converged means the SYNC branch matches
202
+ * the scope, and the default branch may still differ until that PR merges.
203
+ */
204
+ async convergedSummary(branch) {
205
+ const {
206
+ gitHost
207
+ } = this.deps;
208
+ if (!gitHost) return CONVERGED_SUMMARY;
209
+ const pr = await gitHost.findPrByBranch(branch).catch(() => undefined);
210
+ return pr ? `${CONVERGED_SUMMARY} — open sync PR #${pr.number} still awaits review/merge` : CONVERGED_SUMMARY;
211
+ }
212
+
213
+ /**
214
+ * The files `bit checkout head` changed, i.e. the drift. Excludes `.bit`/`node_modules` — the same
215
+ * set the commit paths refuse to touch, keeping "counts as drift" and "gets committed" identical.
216
+ */
217
+ async driftFiles() {
218
+ const status = await _git().git.status();
219
+ const paths = status.files.map(file => file.path).filter(path => !(0, _gitOps().isNonContentPath)(path));
220
+ return [...new Set(paths)];
221
+ }
222
+
223
+ /**
224
+ * Make sure the pushed branch has an open PR. A git-host failure only warns: the branch is pushed,
225
+ * which is the load-bearing half, and the next run retries the PR.
226
+ */
227
+ async ensureSyncPr({
228
+ branch,
229
+ driftCount,
230
+ newFromScope
231
+ }) {
232
+ const {
233
+ gitHost,
234
+ logger,
235
+ defaultBranch
236
+ } = this.deps;
237
+ if (!gitHost) {
238
+ logger.consoleWarning('No configured git host provider (for GitHub: BIT_GITHUB_TOKEN or GITHUB_TOKEN, plus a repository) — ' + 'pushed sync branch, skipping PR operations');
239
+ return undefined;
240
+ }
241
+ try {
242
+ const existing = await gitHost.findPrByBranch(branch);
243
+ if (existing) {
244
+ logger.console(_chalk().default.blue(`Sync PR ${existing.htmlUrl} is already open — pushed the new commit onto it`));
245
+ return existing.htmlUrl;
246
+ }
247
+ const created = await gitHost.createPr({
248
+ head: branch,
249
+ base: defaultBranch,
250
+ title: 'Bit sync: update to latest main scope versions',
251
+ body: mainSyncPrBody({
252
+ driftCount,
253
+ newFromScope
254
+ })
255
+ });
256
+ logger.console(_chalk().default.green(`Opened sync PR ${created.htmlUrl}`));
257
+ return created.htmlUrl;
258
+ } catch (e) {
259
+ logger.consoleWarning(`Could not open or find the sync PR for ${branch}: ${e?.message || e}`);
260
+ return undefined;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Leave git on the default branch and bit on main. Mirrors `LaneSyncExecutor.restoreWorkspace`:
266
+ * best-effort and warn-only so a restore hiccup can't throw out of a `finally` and mask the real
267
+ * error; the clean discards untracked files `checkout head` wrote on the converged/dry-run paths.
268
+ */
269
+ async restoreWorkspace() {
270
+ const {
271
+ logger,
272
+ defaultBranch
273
+ } = this.deps;
274
+ try {
275
+ if ((0, _workspaceLane().currentLaneIdStr)(this.deps.lanes)) {
276
+ const switchErr = await this.deps.ci.switchToLaneForSync('main');
277
+ if (switchErr) logger.consoleWarning(`Could not switch the workspace back to main: ${switchErr.message}`);
278
+ }
279
+ await (0, _gitOps().checkoutPristineRestore)(defaultBranch, () => this.deps.ci.reloadWorkspaceFromDisk());
280
+ } catch (e) {
281
+ logger.consoleWarning(`Could not restore the workspace after the main sync: ${e?.message || e}`);
282
+ }
283
+ }
284
+ }
285
+
286
+ /** The sync commit message: no `Bit-Lane-Head` trailer — main sync keeps no state. */
287
+ exports.MainSyncExecutor = MainSyncExecutor;
288
+ function mainSyncCommitMessage(driftCount) {
289
+ return [`chore(bit-sync): sync git to latest main scope versions (${driftCount} file(s))`, '', _syncState().SYNC_COMMIT_MARKER].join('\n');
290
+ }
291
+ function mainSyncPrBody({
292
+ driftCount,
293
+ newFromScope
294
+ }) {
295
+ const lines = ['Automated sync PR: the Bit scope moved ahead of this repository. This PR checks the workspace out to the ' + 'latest exported versions (`bit checkout head --auto-merge-resolve theirs`).', '', `- files changed: ${driftCount}`, '- conflicts between this repository and the scope were resolved **in favour of the scope** — if a file here ' + 'held changes that were never exported to Bit, this PR reverts them. Review the diff and close the PR ' + 'instead of merging if that is not what you want.'];
296
+ if (newFromScope.length) {
297
+ lines.push(`- components added from the scope (${newFromScope.length}, not previously in this repository's \`.bitmap\`):`,
298
+ // Bounded like the lane PR body: an over-long body is rejected by the host outright.
299
+ ...(0, _laneSyncExecutor().capEntries)(newFromScope.map(id => ` - \`${id}\``), ' - '));
300
+ }
301
+ lines.push('', 'This PR is maintained by `bit ci sync` — re-running the command pushes any further drift onto the same branch.');
302
+ return lines.join('\n');
303
+ }
304
+
305
+ //# sourceMappingURL=main-sync-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_chalk","data","_interopRequireDefault","require","_cli","_git","_syncState","_workspaceLane","_laneSyncExecutor","_gitOps","e","__esModule","default","CONVERGED_SUMMARY","MainSyncExecutor","constructor","deps","syncMain","opts","cfg","defaultBranch","defaultScope","logger","directPush","mainSync","branch","mainSyncBranch","autoMergeMainSyncPr","consoleWarning","fetchRemoteHeads","syncBranchExists","branchExistsOnRemote","startPoint","console","chalk","blue","resetToStartPoint","catchUpErr","catchUpWithDefaultBranch","HALT_SUMMARY_PREFIX","currentLane","currentLaneIdStr","lanes","results","checkout","checkoutByCLIValues","head","skipNpmInstall","includeNewFromScope","mergeStrategy","newFromScope","length","join","drift","driftFiles","convergedSummary","formatWarningSummary","slice","dryRun","addAllExceptScopeAndModules","commitWithIdentity","mainSyncCommitMessage","git","push","green","shortSha","revparse","trim","prUrl","ensureSyncPr","driftCount","message","restoreWorkspace","checkoutPristine","ci","reloadWorkspaceFromDisk","out","gitWithIdentity","raw","catch","undefined","gitHost","pr","findPrByBranch","number","status","paths","files","map","file","path","filter","isNonContentPath","Set","existing","htmlUrl","created","createPr","base","title","body","mainSyncPrBody","switchErr","switchToLaneForSync","checkoutPristineRestore","exports","SYNC_COMMIT_MARKER","lines","capEntries","id"],"sources":["main-sync-executor.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { formatWarningSummary } from '@teambit/cli';\nimport type { Logger } from '@teambit/logger';\nimport type { LanesMain } from '@teambit/lanes';\nimport type { CheckoutMain } from '@teambit/checkout';\nimport { git } from '../git';\nimport type { CiMain } from '../ci.main.runtime';\nimport type { CiSyncConfig } from './sync-config';\nimport { SYNC_COMMIT_MARKER } from './sync-state';\nimport { currentLaneIdStr } from './workspace-lane';\nimport type { GitHostProvider } from './git-host-provider';\nimport { HALT_SUMMARY_PREFIX, capEntries } from './lane-sync-executor';\nimport {\n addAllExceptScopeAndModules,\n branchExistsOnRemote,\n checkoutPristine,\n checkoutPristineRestore,\n commitWithIdentity,\n fetchRemoteHeads,\n gitWithIdentity,\n isNonContentPath,\n} from './git-ops';\n\nexport type MainSyncDeps = {\n checkout: CheckoutMain;\n lanes: LanesMain;\n /** for reloadWorkspaceFromDisk + switchToLaneForSync */\n ci: CiMain;\n logger: Logger;\n /** undefined => PR operations are logged and skipped; the git half of the sync still runs. */\n gitHost?: GitHostProvider;\n cfg: Required<CiSyncConfig>;\n defaultBranch: string;\n defaultScope: string;\n};\n\n/**\n * Reconcile the main scope with the repository's default branch. Stateless: `bit checkout head`\n * writes the latest exported versions, so an empty `git status` IS convergence and any diff IS the\n * drift. Under 'pr' the convergence is proposed via `mainSyncBranch`; under 'direct-push' it lands on\n * the default branch with a plain push. Nothing is ever force-pushed in either mode.\n */\n/** the \"nothing to do\" line, shared by both modes */\nconst CONVERGED_SUMMARY = 'main -> converged (checkout head produced no changes)';\n\nexport class MainSyncExecutor {\n constructor(private deps: MainSyncDeps) {}\n\n /**\n * Returns a summary line (HALT-prefixed on failure) and, like `syncLane`, does not throw — a\n * main-sync failure must not erase the lane results collected before it.\n */\n async syncMain(opts: { dryRun?: boolean } = {}): Promise<string> {\n const { cfg, defaultBranch, defaultScope, logger } = this.deps;\n const directPush = cfg.mainSync === 'direct-push';\n // In direct-push mode the sync branch (and its PR) is not consulted, moved or deleted.\n const branch = directPush ? defaultBranch : cfg.mainSyncBranch;\n\n if (cfg.autoMergeMainSyncPr && !directPush) {\n // Enabling auto-merge is host-specific and not part of the GitHostProvider contract.\n logger.consoleWarning(\n `sync.autoMergeMainSyncPr is enabled in the config, but enabling auto-merge is not implemented yet — ` +\n `the sync PR is opened without it. Use a repository auto-merge rule instead.`\n );\n }\n\n try {\n await fetchRemoteHeads();\n // Direct-push always starts from `origin/<defaultBranch>` — the very tip the plain push must\n // fast-forward. In 'pr' mode an existing sync branch is reused so its PR history survives.\n const syncBranchExists = directPush ? false : await branchExistsOnRemote(branch);\n const startPoint = syncBranchExists ? `origin/${branch}` : `origin/${defaultBranch}`;\n logger.console(\n chalk.blue(\n directPush\n ? `main -> checking the main scope against ${defaultBranch} (direct-push)`\n : `main -> checking the main scope against ${defaultBranch} (sync branch ${branch})`\n )\n );\n\n await this.resetToStartPoint(branch, startPoint);\n\n if (syncBranchExists) {\n // Catch up with the default branch: the PR must stay mergeable, and `checkout head` on a stale\n // tree would re-commit pre-merge content over the default branch's changes. A merge — never a\n // rebase, never a force-push — is the only non-destructive way to move a branch with an open PR.\n const catchUpErr = await this.catchUpWithDefaultBranch(branch);\n if (catchUpErr) return `${HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;\n }\n\n // `checkout head` resolves versions against the CURRENT lane; a lane pointer on this branch\n // would make the \"drift\" the lane's content. Refuse rather than open a wildly wrong PR.\n // `.bitmap`-derived read: the scope-object read answers \"main\" on a fresh runner and the guard\n // would silently stop refusing exactly where it ships (see `workspace-lane.ts`).\n const currentLane = currentLaneIdStr(this.deps.lanes);\n if (currentLane) {\n return (\n `${HALT_SUMMARY_PREFIX} main -> the .bitmap on ${startPoint} points at lane ` +\n `\"${currentLane}\" rather than main, so the main-scope drift cannot be computed`\n );\n }\n\n // `checkoutByCLIValues` rather than `checkout`: it imports current objects first (so `head`\n // resolves to the remote scope's versions) and persists `.bitmap` on destroy.\n // `includeNewFromScope`: a component exported to the scope's main but never in this repo's\n // `.bitmap` would otherwise be invisible to every sync run forever.\n // `mergeStrategy: 'theirs'` is required: unexported source drift makes components \"modified\",\n // and with no strategy the checkout throws. 'theirs' materializes the exported truth — any\n // reversion of unexported drift is visible in the PR diff. 'ours' is forbidden: it would advance\n // `.bitmap` while keeping the old files, a commit asserting a state the tree does not have.\n const results = await this.deps.checkout.checkoutByCLIValues('', {\n head: true,\n skipNpmInstall: true,\n includeNewFromScope: true,\n mergeStrategy: 'theirs',\n });\n const newFromScope = results.newFromScope ?? [];\n if (newFromScope.length) {\n logger.console(\n chalk.blue(\n `Added ${newFromScope.length} component(s) that exist on ${defaultScope}'s main but not in this ` +\n `workspace: ${newFromScope.join(', ')}`\n )\n );\n }\n\n const drift = await this.driftFiles();\n // Direct-push stays bare: asking the host about `mainSyncBranch`'s PR would be the one\n // interaction with it this mode promises not to make.\n if (!drift.length) return directPush ? CONVERGED_SUMMARY : await this.convergedSummary(branch);\n\n logger.console(\n formatWarningSummary(`main -> drift in ${drift.length} file(s): ${drift.slice(0, 20).join(', ')}`)\n );\n\n if (opts.dryRun) {\n // The working tree WAS written (a diff-based check has no other way); `finally` restores it. Safe\n // only because a dry run over a dirty tree is refused before anything runs — the restore rewinds\n // to HEAD, so an uncommitted change here would be gone (`assertCleanForDryRun`).\n logger.console(\n formatWarningSummary(\n directPush\n ? `Dry-run: main -> would push the drift directly onto ${branch}`\n : `Dry-run: main -> would push ${branch} and open a sync PR`\n )\n );\n return directPush\n ? `main -> drift detected in ${drift.length} file(s) — would push ${branch} directly`\n : `main -> drift detected in ${drift.length} file(s) — would open sync PR`;\n }\n\n await addAllExceptScopeAndModules();\n await commitWithIdentity(mainSyncCommitMessage(drift.length));\n // Never force: a rejected push means a concurrent run pushed in between, and in direct-push mode\n // that rejection is the whole safety story for the default branch. Unambiguous refspec — see\n // `lane-sync-executor.commitAllAndPush`.\n await git.push(['origin', `HEAD:refs/heads/${branch}`]);\n logger.console(chalk.green(`Pushed ${branch}`));\n\n if (directPush) {\n const shortSha = (await git.revparse(['--short', 'HEAD'])).trim();\n return `main -> direct-push (pushed ${branch} @ ${shortSha})`;\n }\n const prUrl = await this.ensureSyncPr({ branch, driftCount: drift.length, newFromScope });\n return `main -> pushed sync commit to ${branch}${prUrl ? ` (PR ${prUrl})` : ''}`;\n } catch (e: any) {\n // Same contract as the lane executor: a HALTED line, never a throw mid-run.\n return `${HALT_SUMMARY_PREFIX} main -> ${e?.message || e}`;\n } finally {\n await this.restoreWorkspace();\n }\n }\n\n /**\n * Put the working tree on the sync branch at a pristine copy of `startPoint`. Pristine is\n * load-bearing for a diff-based reconciler: any stray modification would be indistinguishable from\n * main-scope drift. The reload makes `checkout head` read this branch's `.bitmap`.\n */\n private async resetToStartPoint(branch: string, startPoint: string) {\n await checkoutPristine(branch, startPoint, () => this.deps.ci.reloadWorkspaceFromDisk());\n }\n\n /**\n * Merge the default branch into the sync branch so drift is computed against the repository's\n * current state. Returns a reason string on failure; a conflict needs a human (or deleting the sync\n * branch, which the next run re-forks).\n */\n private async catchUpWithDefaultBranch(branch: string): Promise<string | undefined> {\n const { defaultBranch, logger } = this.deps;\n try {\n // under the identity: a non-fast-forward merge writes a merge COMMIT\n const out = await gitWithIdentity(['merge', '--no-edit', `origin/${defaultBranch}`]);\n logger.console(chalk.blue(`Brought ${branch} up to date with origin/${defaultBranch}: ${out.trim()}`));\n } catch (e: any) {\n await git.raw(['merge', '--abort']).catch(() => undefined);\n return (\n `could not bring the sync branch ${branch} up to date with origin/${defaultBranch}: ${e?.message || e}. ` +\n `Resolve or delete ${branch} (a deleted sync branch is re-forked from ${defaultBranch} on the next run)`\n );\n }\n // The merge may have brought a new `.bitmap` in from the default branch.\n await this.deps.ci.reloadWorkspaceFromDisk();\n return undefined;\n }\n\n /**\n * The \"nothing to do\" line — plus the open sync PR, if any: converged means the SYNC branch matches\n * the scope, and the default branch may still differ until that PR merges.\n */\n private async convergedSummary(branch: string): Promise<string> {\n const { gitHost } = this.deps;\n if (!gitHost) return CONVERGED_SUMMARY;\n const pr = await gitHost.findPrByBranch(branch).catch(() => undefined);\n return pr ? `${CONVERGED_SUMMARY} — open sync PR #${pr.number} still awaits review/merge` : CONVERGED_SUMMARY;\n }\n\n /**\n * The files `bit checkout head` changed, i.e. the drift. Excludes `.bit`/`node_modules` — the same\n * set the commit paths refuse to touch, keeping \"counts as drift\" and \"gets committed\" identical.\n */\n private async driftFiles(): Promise<string[]> {\n const status = await git.status();\n const paths = status.files.map((file) => file.path).filter((path) => !isNonContentPath(path));\n return [...new Set(paths)];\n }\n\n /**\n * Make sure the pushed branch has an open PR. A git-host failure only warns: the branch is pushed,\n * which is the load-bearing half, and the next run retries the PR.\n */\n private async ensureSyncPr({\n branch,\n driftCount,\n newFromScope,\n }: {\n branch: string;\n driftCount: number;\n newFromScope: string[];\n }): Promise<string | undefined> {\n const { gitHost, logger, defaultBranch } = this.deps;\n if (!gitHost) {\n logger.consoleWarning(\n 'No configured git host provider (for GitHub: BIT_GITHUB_TOKEN or GITHUB_TOKEN, plus a repository) — ' +\n 'pushed sync branch, skipping PR operations'\n );\n return undefined;\n }\n try {\n const existing = await gitHost.findPrByBranch(branch);\n if (existing) {\n logger.console(chalk.blue(`Sync PR ${existing.htmlUrl} is already open — pushed the new commit onto it`));\n return existing.htmlUrl;\n }\n const created = await gitHost.createPr({\n head: branch,\n base: defaultBranch,\n title: 'Bit sync: update to latest main scope versions',\n body: mainSyncPrBody({ driftCount, newFromScope }),\n });\n logger.console(chalk.green(`Opened sync PR ${created.htmlUrl}`));\n return created.htmlUrl;\n } catch (e: any) {\n logger.consoleWarning(`Could not open or find the sync PR for ${branch}: ${e?.message || e}`);\n return undefined;\n }\n }\n\n /**\n * Leave git on the default branch and bit on main. Mirrors `LaneSyncExecutor.restoreWorkspace`:\n * best-effort and warn-only so a restore hiccup can't throw out of a `finally` and mask the real\n * error; the clean discards untracked files `checkout head` wrote on the converged/dry-run paths.\n */\n private async restoreWorkspace() {\n const { logger, defaultBranch } = this.deps;\n try {\n if (currentLaneIdStr(this.deps.lanes)) {\n const switchErr = await this.deps.ci.switchToLaneForSync('main');\n if (switchErr) logger.consoleWarning(`Could not switch the workspace back to main: ${switchErr.message}`);\n }\n await checkoutPristineRestore(defaultBranch, () => this.deps.ci.reloadWorkspaceFromDisk());\n } catch (e: any) {\n logger.consoleWarning(`Could not restore the workspace after the main sync: ${e?.message || e}`);\n }\n }\n}\n\n/** The sync commit message: no `Bit-Lane-Head` trailer — main sync keeps no state. */\nfunction mainSyncCommitMessage(driftCount: number): string {\n return [\n `chore(bit-sync): sync git to latest main scope versions (${driftCount} file(s))`,\n '',\n SYNC_COMMIT_MARKER,\n ].join('\\n');\n}\n\nfunction mainSyncPrBody({ driftCount, newFromScope }: { driftCount: number; newFromScope: string[] }): string {\n const lines = [\n 'Automated sync PR: the Bit scope moved ahead of this repository. This PR checks the workspace out to the ' +\n 'latest exported versions (`bit checkout head --auto-merge-resolve theirs`).',\n '',\n `- files changed: ${driftCount}`,\n '- conflicts between this repository and the scope were resolved **in favour of the scope** — if a file here ' +\n 'held changes that were never exported to Bit, this PR reverts them. Review the diff and close the PR ' +\n 'instead of merging if that is not what you want.',\n ];\n if (newFromScope.length) {\n lines.push(\n `- components added from the scope (${newFromScope.length}, not previously in this repository's \\`.bitmap\\`):`,\n // Bounded like the lane PR body: an over-long body is rejected by the host outright.\n ...capEntries(\n newFromScope.map((id) => ` - \\`${id}\\``),\n ' - '\n )\n );\n }\n lines.push(\n '',\n 'This PR is maintained by `bit ci sync` — re-running the command pushes any further drift onto the same branch.'\n );\n return lines.join('\\n');\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Is this a branch name git will accept? A conservative pure subset of `git check-ref-format --branch`:
3
+ * stricter than git is fine, laxer is not. A leading `-` must be refused here because it would be read
4
+ * as a command-line option once interpolated into a git invocation.
5
+ */
6
+ export declare function isValidGitBranchName(name: string): boolean;
7
+ /** Why `name` is not a usable branch name, or undefined when it is. */
8
+ export declare function validateBranchName(name: string): string | undefined;
9
+ /** Throw a `BitError` naming the offending config key and value unless `name` is a usable branch name. */
10
+ export declare function assertValidBranchName(name: string, configKey: string): void;
11
+ /**
12
+ * A `branchPrefix` may end in `/` or be empty, so it is validated as the *start* of a branch name.
13
+ * The full check still happens on the derived name (see `laneNameToBranch`).
14
+ */
15
+ export declare function assertValidBranchPrefix(prefix: string, configKey: string): void;
16
+ export interface CiSyncConfig {
17
+ /** The prefix for a lane-mapped branch. 'lane/' maps lane my-lane to branch lane/my-lane. */
18
+ branchPrefix?: string;
19
+ /** Explicit overrides that map a lane name to a branch name. */
20
+ branches?: Record<string, string>;
21
+ /** The glob patterns of the lane names to sync. An empty list stops lane mirroring. */
22
+ lanes?: string[];
23
+ /** The branch that carries the main-scope drift. */
24
+ mainSyncBranch?: string;
25
+ /**
26
+ * How main-scope drift reaches the default branch. 'pr' (the default): the command commits the drift
27
+ * to `mainSyncBranch` and opens a pull request. 'direct-push': the command commits the drift on the
28
+ * default branch and pushes it, and does not use `mainSyncBranch`.
29
+ */
30
+ mainSync?: 'pr' | 'direct-push';
31
+ /**
32
+ * What the command does with one contested line during a `merge-diverged` action. 'halt' (the
33
+ * default): the command stops, labels the pull request, and writes the recovery steps. 'git-wins':
34
+ * the command keeps the branch version. 'lane-wins': the command takes the lane version.
35
+ * Non-conflicting changes always merge, with every value.
36
+ */
37
+ onConflict?: 'halt' | 'git-wins' | 'lane-wins';
38
+ /** Reserved. The command warns and enables no auto-merge on the main sync pull request. */
39
+ autoMergeMainSyncPr?: boolean;
40
+ }
41
+ /**
42
+ * Resolve the configured sync options, validating every branch name before anything runs — a bad name
43
+ * must fail at startup naming the config key, not mid-run with git's own ref error.
44
+ */
45
+ export declare function resolveSyncConfig(raw?: CiSyncConfig): Required<CiSyncConfig>;
46
+ /**
47
+ * The branch a lane name maps to. The derived result is validated too: a prefix that is fine on its
48
+ * own can still combine with a lane name into something git refuses.
49
+ */
50
+ export declare function laneNameToBranch(laneName: string, cfg: Required<CiSyncConfig>): string;
51
+ export declare function branchToLaneName(branch: string, cfg: Required<CiSyncConfig>): string | undefined;
52
+ /**
53
+ * A lane to sync, split into the scope hosting the lane object and the bare name. The branch mapping
54
+ * uses the NAME only; every bit operation uses the full `hostScope/name`.
55
+ */
56
+ export type LaneTarget = {
57
+ /** the scope hosting the lane object; the workspace's defaultScope unless the target said otherwise */
58
+ hostScope: string;
59
+ /** the bare lane name — never contains a '/' */
60
+ name: string;
61
+ };
62
+ /**
63
+ * Parse the `[lane]` argument (`my-lane` or `some-org.some-scope/my-lane`) into a {@link LaneTarget}.
64
+ * A lane name cannot contain `/`, so the single `/` is always the scope/name boundary. The dot in a
65
+ * scope id is not required (self-hosted scopes can be bare names), so it is not validated here.
66
+ */
67
+ export declare function parseLaneTarget(input: string, defaultScope: string): LaneTarget;
68
+ /**
69
+ * The lane name a branch maps to, only if that name could actually be a lane AND maps back to a branch
70
+ * name usable in a git invocation. `branchToLaneName` alone is a string transform (the identity under
71
+ * the default empty prefix), so it would report `feature/foo` as a "lane" — a name bit forbids. The
72
+ * round trip matters because the lane grammar permits `-`: a developer branch called `-x` is a valid
73
+ * lane name, so enumeration would adopt it as a target and the branch mapping would then throw, turning
74
+ * one oddly-named branch into a halt that fails the whole `--all` run. Such a branch is not lane-mapped;
75
+ * skip it like any other.
76
+ */
77
+ export declare function syncableLaneNameForBranch(branch: string, cfg: Required<CiSyncConfig>): string | undefined;
78
+ /**
79
+ * Could this string be a bit lane name at all? Mirrors the module-private `isValidLaneName` in
80
+ * `create-lane.ts`; drift errs toward skipping a branch, and `sync-config.spec.ts` pins the rule.
81
+ */
82
+ export declare function isValidLaneName(name: string): boolean;
83
+ export declare function shouldSyncLane(laneName: string, cfg: Required<CiSyncConfig>): boolean;
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.assertValidBranchName = assertValidBranchName;
7
+ exports.assertValidBranchPrefix = assertValidBranchPrefix;
8
+ exports.branchToLaneName = branchToLaneName;
9
+ exports.isValidGitBranchName = isValidGitBranchName;
10
+ exports.isValidLaneName = isValidLaneName;
11
+ exports.laneNameToBranch = laneNameToBranch;
12
+ exports.parseLaneTarget = parseLaneTarget;
13
+ exports.resolveSyncConfig = resolveSyncConfig;
14
+ exports.shouldSyncLane = shouldSyncLane;
15
+ exports.syncableLaneNameForBranch = syncableLaneNameForBranch;
16
+ exports.validateBranchName = validateBranchName;
17
+ function _bitError() {
18
+ const data = require("@teambit/bit-error");
19
+ _bitError = function () {
20
+ return data;
21
+ };
22
+ return data;
23
+ }
24
+ /**
25
+ * Is this a branch name git will accept? A conservative pure subset of `git check-ref-format --branch`:
26
+ * stricter than git is fine, laxer is not. A leading `-` must be refused here because it would be read
27
+ * as a command-line option once interpolated into a git invocation.
28
+ */
29
+ function isValidGitBranchName(name) {
30
+ return validateBranchName(name) === undefined;
31
+ }
32
+
33
+ /** Why `name` is not a usable branch name, or undefined when it is. */
34
+ function validateBranchName(name) {
35
+ if (!name) return 'it is empty';
36
+ if (name.startsWith('-')) return 'it starts with "-", which git would read as a command-line option';
37
+ if (/\s/.test(name)) return 'it contains whitespace';
38
+ // oxlint-disable-next-line no-control-regex
39
+ if (/[\x00-\x1f\x7f]/.test(name)) return 'it contains a control character';
40
+ if (/[~^:?*[\\]/.test(name)) return 'it contains one of the characters git forbids in a ref: ~ ^ : ? * [ \\';
41
+ if (name.includes('..')) return 'it contains ".."';
42
+ if (name.includes('@{')) return 'it contains "@{"';
43
+ if (name === '@') return 'it is "@", which git reserves';
44
+ // Leading `refs/` only: pushes interpolate the name into `refs/heads/<b>`, so it would double up.
45
+ if (name.startsWith('refs/')) return 'it starts with "refs/" — configure the bare branch name ("main", not "refs/heads/main")';
46
+ if (name.startsWith('/') || name.endsWith('/')) return 'it starts or ends with "/"';
47
+ if (name.includes('//')) return 'it contains an empty path component ("//")';
48
+ if (name.endsWith('.')) return 'it ends with "."';
49
+ if (name.endsWith('.lock')) return 'it ends with ".lock"';
50
+ if (name.split('/').some(component => component.startsWith('.'))) return 'a path component starts with "."';
51
+ if (name.split('/').some(component => component.endsWith('.lock'))) return 'a path component ends with ".lock"';
52
+ return undefined;
53
+ }
54
+
55
+ /** Throw a `BitError` naming the offending config key and value unless `name` is a usable branch name. */
56
+ function assertValidBranchName(name, configKey) {
57
+ const problem = validateBranchName(name);
58
+ if (!problem) return;
59
+ throw new (_bitError().BitError)(`bit ci sync: ${configKey} is "${name}", which is not a valid git branch name — ${problem}. ` + `Fix it in the "teambit.git/ci" sync config in workspace.jsonc.`);
60
+ }
61
+
62
+ /**
63
+ * A `branchPrefix` may end in `/` or be empty, so it is validated as the *start* of a branch name.
64
+ * The full check still happens on the derived name (see `laneNameToBranch`).
65
+ */
66
+ function assertValidBranchPrefix(prefix, configKey) {
67
+ if (!prefix) return;
68
+ const problem = validateBranchName(`${prefix}x`);
69
+ if (!problem) return;
70
+ throw new (_bitError().BitError)(`bit ci sync: ${configKey} is "${prefix}", which cannot start a valid git branch name — ${problem}. ` + `Fix it in the "teambit.git/ci" sync config in workspace.jsonc.`);
71
+ }
72
+ /**
73
+ * Resolve the configured sync options, validating every branch name before anything runs — a bad name
74
+ * must fail at startup naming the config key, not mid-run with git's own ref error.
75
+ */
76
+ function resolveSyncConfig(raw) {
77
+ const resolved = {
78
+ branchPrefix: raw?.branchPrefix ?? '',
79
+ branches: raw?.branches ?? {},
80
+ lanes: raw?.lanes ?? ['*'],
81
+ mainSyncBranch: raw?.mainSyncBranch ?? 'bit-sync/main',
82
+ autoMergeMainSyncPr: raw?.autoMergeMainSyncPr ?? false,
83
+ mainSync: raw?.mainSync ?? 'pr',
84
+ onConflict: raw?.onConflict ?? 'halt'
85
+ };
86
+ // Config comes from workspace.jsonc, so the union types are not enforced at runtime; a typo must
87
+ // fail at startup rather than fall through to whichever mode a comparison happens to miss.
88
+ if (resolved.mainSync !== 'pr' && resolved.mainSync !== 'direct-push') {
89
+ throw new (_bitError().BitError)(`sync.mainSync: "${resolved.mainSync}" is not a valid value. Use "pr" (propose the main-scope drift ` + `as a pull request from sync.mainSyncBranch) or "direct-push" (push the drift straight onto the ` + `default branch)`);
90
+ }
91
+ if (resolved.onConflict !== 'halt' && resolved.onConflict !== 'git-wins' && resolved.onConflict !== 'lane-wins') {
92
+ throw new (_bitError().BitError)(`sync.onConflict: "${resolved.onConflict}" is not a valid value. Use "halt" (stop the conflicted ` + `lane, label the PR and let a human resolve — the default), "git-wins" (conflicting hunks keep ` + `the branch's version) or "lane-wins" (conflicting hunks take the lane's version)`);
93
+ }
94
+ assertValidBranchPrefix(resolved.branchPrefix, 'sync.branchPrefix');
95
+ assertValidBranchName(resolved.mainSyncBranch, 'sync.mainSyncBranch');
96
+ Object.entries(resolved.branches).forEach(([laneName, branch]) => assertValidBranchName(branch, `sync.branches["${laneName}"]`));
97
+ return resolved;
98
+ }
99
+
100
+ /**
101
+ * The branch a lane name maps to. The derived result is validated too: a prefix that is fine on its
102
+ * own can still combine with a lane name into something git refuses.
103
+ */
104
+ function laneNameToBranch(laneName, cfg) {
105
+ const override = cfg.branches[laneName];
106
+ if (override) return override; // already validated by resolveSyncConfig
107
+ const derived = `${cfg.branchPrefix}${laneName}`;
108
+ assertValidBranchName(derived, `the branch for lane "${laneName}" (sync.branchPrefix + lane name)`);
109
+ return derived;
110
+ }
111
+ function branchToLaneName(branch, cfg) {
112
+ const override = Object.entries(cfg.branches).find(([, b]) => b === branch);
113
+ if (override) return override[0];
114
+ if (cfg.branchPrefix) {
115
+ return branch.startsWith(cfg.branchPrefix) ? branch.slice(cfg.branchPrefix.length) : undefined;
116
+ }
117
+ return branch;
118
+ }
119
+
120
+ /**
121
+ * A lane to sync, split into the scope hosting the lane object and the bare name. The branch mapping
122
+ * uses the NAME only; every bit operation uses the full `hostScope/name`.
123
+ */
124
+
125
+ /**
126
+ * Parse the `[lane]` argument (`my-lane` or `some-org.some-scope/my-lane`) into a {@link LaneTarget}.
127
+ * A lane name cannot contain `/`, so the single `/` is always the scope/name boundary. The dot in a
128
+ * scope id is not required (self-hosted scopes can be bare names), so it is not validated here.
129
+ */
130
+ function parseLaneTarget(input, defaultScope) {
131
+ const trimmed = input.trim();
132
+ if (!trimmed) throw new (_bitError().BitError)('bit ci sync: the lane argument is empty');
133
+ if (!trimmed.includes('/')) return {
134
+ hostScope: defaultScope,
135
+ name: trimmed
136
+ };
137
+ const parts = trimmed.split('/');
138
+ const [hostScope, name] = parts;
139
+ if (parts.length !== 2 || !hostScope || !name) {
140
+ throw new (_bitError().BitError)(`bit ci sync: "${input}" is not a valid lane target. Use either a lane name ("my-lane", resolved ` + `against the workspace's defaultScope "${defaultScope}") or a scope-qualified lane id ` + `("some-org.some-scope/my-lane")`);
141
+ }
142
+ return {
143
+ hostScope,
144
+ name
145
+ };
146
+ }
147
+
148
+ /**
149
+ * The lane name a branch maps to, only if that name could actually be a lane AND maps back to a branch
150
+ * name usable in a git invocation. `branchToLaneName` alone is a string transform (the identity under
151
+ * the default empty prefix), so it would report `feature/foo` as a "lane" — a name bit forbids. The
152
+ * round trip matters because the lane grammar permits `-`: a developer branch called `-x` is a valid
153
+ * lane name, so enumeration would adopt it as a target and the branch mapping would then throw, turning
154
+ * one oddly-named branch into a halt that fails the whole `--all` run. Such a branch is not lane-mapped;
155
+ * skip it like any other.
156
+ */
157
+ function syncableLaneNameForBranch(branch, cfg) {
158
+ const laneName = branchToLaneName(branch, cfg);
159
+ if (!laneName || !isValidLaneName(laneName)) return undefined;
160
+ // `laneNameToBranch` asserts rather than returns, so the round trip is checked here: a configured
161
+ // override is already validated, and a derived name must pass the same branch-name rules.
162
+ if (cfg.branches[laneName]) return laneName;
163
+ return validateBranchName(`${cfg.branchPrefix}${laneName}`) === undefined ? laneName : undefined;
164
+ }
165
+
166
+ /** bit's own maximum lane-name length (`create-lane.ts`'s `MAX_LANE_NAME_LENGTH`). */
167
+ const MAX_LANE_NAME_LENGTH = 800;
168
+
169
+ /**
170
+ * Could this string be a bit lane name at all? Mirrors the module-private `isValidLaneName` in
171
+ * `create-lane.ts`; drift errs toward skipping a branch, and `sync-config.spec.ts` pins the rule.
172
+ */
173
+ function isValidLaneName(name) {
174
+ if (!name || name.length > MAX_LANE_NAME_LENGTH) return false;
175
+ return /^[$\-_!a-z0-9]+$/.test(name);
176
+ }
177
+
178
+ /** minimal glob: '*' wildcard only (matches the lanes list use-case; avoids a new dep) */
179
+ function globToRegExp(glob) {
180
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
181
+ return new RegExp(`^${escaped}$`);
182
+ }
183
+ function shouldSyncLane(laneName, cfg) {
184
+ return cfg.lanes.some(pattern => globToRegExp(pattern).test(laneName));
185
+ }
186
+
187
+ //# sourceMappingURL=sync-config.js.map