@teambit/ci 1.0.485 → 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-1785858957533.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 +17 -12
  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
@@ -1,16 +1,23 @@
1
- import type { RuntimeDefinition } from '@teambit/harmony';
1
+ import type { RuntimeDefinition, SlotRegistry } from '@teambit/harmony';
2
2
  import { type CLIMain } from '@teambit/cli';
3
3
  import { type LoggerMain, type Logger } from '@teambit/logger';
4
4
  import { type Workspace } from '@teambit/workspace';
5
5
  import { type BuilderMain } from '@teambit/builder';
6
6
  import { type StatusMain } from '@teambit/status';
7
- import type { LanesMain } from '@teambit/lanes';
7
+ import type { SwitchLaneOptions, LanesMain } from '@teambit/lanes';
8
8
  import type { SnappingMain } from '@teambit/snapping';
9
9
  import { type ExportMain } from '@teambit/export';
10
10
  import { type ImporterMain } from '@teambit/importer';
11
11
  import { type CheckoutMain } from '@teambit/checkout';
12
12
  import type { MergeStrategy } from '@teambit/component.modules.merge-helper';
13
13
  import type { ReleaseType } from 'semver';
14
+ import type { CiSyncConfig } from './sync/sync-config';
15
+ import type { GitHostProvider } from './sync/git-host-provider';
16
+ /**
17
+ * Registered git hosts (GitHub ships built-in; others register from their own aspect).
18
+ * @see GitHostProvider
19
+ */
20
+ export type GitHostProviderSlot = SlotRegistry<GitHostProvider>;
14
21
  export interface CiWorkspaceConfig {
15
22
  /**
16
23
  * Path to a custom script that generates commit messages for `bit ci merge` operations.
@@ -68,6 +75,12 @@ export interface CiWorkspaceConfig {
68
75
  * ```
69
76
  */
70
77
  useExplicitBumpKeywords?: boolean;
78
+ /**
79
+ * Configuration for `bit ci sync` — bi-directional lane <-> branch/PR sync.
80
+ * See CiSyncConfig for fields. Example:
81
+ * { "teambit.git/ci": { "sync": { "branchPrefix": "lane/", "lanes": ["*"] } } }
82
+ */
83
+ sync?: CiSyncConfig;
71
84
  }
72
85
  export declare class CiMain {
73
86
  private workspace;
@@ -80,10 +93,11 @@ export declare class CiMain {
80
93
  private checkout;
81
94
  private logger;
82
95
  private config;
96
+ private gitHostProviderSlot;
83
97
  static runtime: RuntimeDefinition;
84
98
  static dependencies: any;
85
- static slots: any;
86
- constructor(workspace: Workspace, builder: BuilderMain, status: StatusMain, lanes: LanesMain, snapping: SnappingMain, exporter: ExportMain, importer: ImporterMain, checkout: CheckoutMain, logger: Logger, config: CiWorkspaceConfig);
99
+ static slots: ((registerFn: () => string) => SlotRegistry<GitHostProvider>)[];
100
+ constructor(workspace: Workspace, builder: BuilderMain, status: StatusMain, lanes: LanesMain, snapping: SnappingMain, exporter: ExportMain, importer: ImporterMain, checkout: CheckoutMain, logger: Logger, config: CiWorkspaceConfig, gitHostProviderSlot: GitHostProviderSlot);
87
101
  static provider([cli, workspace, loggerAspect, builder, status, lanes, snapping, exporter, importer, checkout]: [
88
102
  CLIMain,
89
103
  Workspace,
@@ -95,7 +109,16 @@ export declare class CiMain {
95
109
  ExportMain,
96
110
  ImporterMain,
97
111
  CheckoutMain
98
- ], config: CiWorkspaceConfig): Promise<CiMain>;
112
+ ], config: CiWorkspaceConfig, [gitHostProviderSlot]: [GitHostProviderSlot]): Promise<CiMain>;
113
+ /**
114
+ * Register a git host (GitHub, GitLab, …) that `bit ci sync` can use for its pull-request
115
+ * operations. Exactly one provider is selected per run, by the `origin` remote (see
116
+ * `selectGitHostProvider`); registering a provider never changes how a repository claimed by a
117
+ * different provider is handled.
118
+ */
119
+ registerGitHostProvider(provider: GitHostProvider): this;
120
+ /** Every registered git host provider, in registration order (one provider per registration). */
121
+ listGitHostProviders(): GitHostProvider[];
99
122
  getBranchName(): Promise<string>;
100
123
  private getCirclePullRequestBranchName;
101
124
  /**
@@ -108,7 +131,16 @@ export declare class CiMain {
108
131
  * @example convertBranchToLaneId("feature/New-Component") => "my-scope/feature-new-component"
109
132
  */
110
133
  convertBranchToLaneId(branchName: string): string;
134
+ /**
135
+ * The repository's default branch name, whole — slashes included (see `parseOriginHeadRef`).
136
+ * Local `origin/HEAD` symref first, then the remote's own answer, then the conventional probe.
137
+ */
111
138
  getDefaultBranchName(): Promise<string>;
139
+ /**
140
+ * Last-resort fallback: the two conventional names among the remote branches, then `master`.
141
+ * Deliberately only `main`/`master` — any wider guess risks protecting the wrong branch.
142
+ */
143
+ private probeDefaultBranchName;
112
144
  getGitCommitMessage(): Promise<string | null>;
113
145
  private parseVersionBumpFromCommit;
114
146
  private getCustomCommitMessage;
@@ -120,6 +152,27 @@ export declare class CiMain {
120
152
  * `getCurrentLane()` check.
121
153
  */
122
154
  private switchToLane;
155
+ /**
156
+ * Public entry point onto `switchToLane` for the lane-sync executor; returns the error instead of
157
+ * throwing. `switchToLane` spreads caller options AFTER its defaults, so `forceOurs: true` is
158
+ * overridable — the sync import direction depends on that. Do not reorder that spread.
159
+ */
160
+ switchToLaneForSync(laneName: string, options?: SwitchLaneOptions): Promise<Error | undefined>;
161
+ /**
162
+ * Re-read `.bitmap` from disk into the live workspace. `bit ci sync` moves the git checkout
163
+ * underneath a running process, and until the reload every bit operation resolves against the
164
+ * `.bitmap` loaded at startup — and the next write would persist that stale state.
165
+ */
166
+ reloadWorkspaceFromDisk(): Promise<void>;
167
+ /** `bit ci sync` — reconcile Bit lanes and the main scope with git branches and PRs (see `SyncOrchestrator`). */
168
+ sync(opts?: {
169
+ lane?: string;
170
+ branch?: string;
171
+ all?: boolean;
172
+ main?: boolean;
173
+ dryRun?: boolean;
174
+ init?: boolean;
175
+ }): Promise<string>;
123
176
  /**
124
177
  * Sync *config-only* changes from main onto the lane — without a full `bit lane merge`.
125
178
  *
@@ -171,7 +224,7 @@ export declare class CiMain {
171
224
  code: number;
172
225
  data: string;
173
226
  }>;
174
- snapPrCommit({ laneIdStr, message, build, strict, dryRun, keepLane, skipCleanup, skipTasks, }: {
227
+ snapPrCommit({ laneIdStr, message, build, strict, dryRun, keepLane, skipCleanup, skipTasks, noDestructiveRecovery, }: {
175
228
  laneIdStr: string;
176
229
  message: string;
177
230
  build: boolean | undefined;
@@ -180,6 +233,14 @@ export declare class CiMain {
180
233
  keepLane?: boolean;
181
234
  skipCleanup?: boolean;
182
235
  skipTasks?: string;
236
+ /**
237
+ * Opt out of the `--keep-lane` stale-lane recovery that deletes the remote lane and recreates it
238
+ * from main. That trade is fine for a PR lane (a throwaway mirror of a git branch, recreated on
239
+ * every commit) but unacceptable for `bit ci sync`, where the lane is the *authored* artifact and
240
+ * the branch is its mirror — deleting it would destroy the very thing being synced. When set, the
241
+ * stale-lane case throws instead, which the sync executor surfaces as a halt for a human.
242
+ */
243
+ noDestructiveRecovery?: boolean;
183
244
  }): Promise<string | undefined>;
184
245
  /**
185
246
  * Post-export cleanup: restore the workspace to the original lane / main so an interactive
@@ -4,6 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.CiMain = void 0;
7
+ function _harmony() {
8
+ const data = require("@teambit/harmony");
9
+ _harmony = function () {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
7
14
  function _cli() {
8
15
  const data = require("@teambit/cli");
9
16
  _cli = function () {
@@ -144,6 +151,13 @@ function _merge() {
144
151
  };
145
152
  return data;
146
153
  }
154
+ function _sync() {
155
+ const data = require("./commands/sync.cmd");
156
+ _sync = function () {
157
+ return data;
158
+ };
159
+ return data;
160
+ }
147
161
  function _git() {
148
162
  const data = require("./git");
149
163
  _git = function () {
@@ -214,12 +228,45 @@ function _pullRequestRef() {
214
228
  };
215
229
  return data;
216
230
  }
231
+ function _syncOrchestrator() {
232
+ const data = require("./sync/sync-orchestrator");
233
+ _syncOrchestrator = function () {
234
+ return data;
235
+ };
236
+ return data;
237
+ }
238
+ function _githubClient() {
239
+ const data = require("./sync/github-client");
240
+ _githubClient = function () {
241
+ return data;
242
+ };
243
+ return data;
244
+ }
245
+ function _gitOps() {
246
+ const data = require("./sync/git-ops");
247
+ _gitOps = function () {
248
+ return data;
249
+ };
250
+ return data;
251
+ }
252
+ function _syncConfig() {
253
+ const data = require("./sync/sync-config");
254
+ _syncConfig = function () {
255
+ return data;
256
+ };
257
+ return data;
258
+ }
217
259
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
218
260
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
219
261
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
220
262
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
221
263
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
222
264
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
265
+ /**
266
+ * Registered git hosts (GitHub ships built-in; others register from their own aspect).
267
+ * @see GitHostProvider
268
+ */
269
+
223
270
  // Two distinct conflicts can surface from the remote on a concurrent `bit ci pr` race.
224
271
  // LANE_HASH_MISMATCH fires when both runners called `Lane.create` (the lane didn't exist on
225
272
  // the remote yet), so they each minted a random `sha1(v4())` hash — `sources.mergeLane` then
@@ -232,7 +279,7 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
232
279
  const LANE_HASH_MISMATCH_MARKER = 'a lane with the same id already exists with a different hash';
233
280
  const COMPONENT_DIVERGENCE_MARKER = 'merge error occurred when exporting';
234
281
  class CiMain {
235
- constructor(workspace, builder, status, lanes, snapping, exporter, importer, checkout, logger, config) {
282
+ constructor(workspace, builder, status, lanes, snapping, exporter, importer, checkout, logger, config, gitHostProviderSlot) {
236
283
  this.workspace = workspace;
237
284
  this.builder = builder;
238
285
  this.status = status;
@@ -243,15 +290,35 @@ class CiMain {
243
290
  this.checkout = checkout;
244
291
  this.logger = logger;
245
292
  this.config = config;
293
+ this.gitHostProviderSlot = gitHostProviderSlot;
246
294
  }
247
- static async provider([cli, workspace, loggerAspect, builder, status, lanes, snapping, exporter, importer, checkout], config) {
295
+ static async provider([cli, workspace, loggerAspect, builder, status, lanes, snapping, exporter, importer, checkout], config, [gitHostProviderSlot]) {
248
296
  const logger = loggerAspect.createLogger(_ci().CiAspect.id);
249
- const ci = new CiMain(workspace, builder, status, lanes, snapping, exporter, importer, checkout, logger, config);
297
+ const ci = new CiMain(workspace, builder, status, lanes, snapping, exporter, importer, checkout, logger, config, gitHostProviderSlot);
298
+ // GitHub ships built-in but is not privileged: registered through the same public slot any other
299
+ // host aspect uses. Credentials resolve lazily, so registering at aspect load is always safe.
300
+ ci.registerGitHostProvider(new (_githubClient().GitHubHostProvider)(message => logger.consoleWarning(message)));
250
301
  const ciCmd = new (_ci2().CiCmd)(workspace, logger);
251
- ciCmd.commands = [new (_verify().CiVerifyCmd)(workspace, logger, ci), new (_pr().CiPrCmd)(workspace, logger, ci), new (_merge().CiMergeCmd)(workspace, logger, ci)];
302
+ ciCmd.commands = [new (_verify().CiVerifyCmd)(workspace, logger, ci), new (_pr().CiPrCmd)(workspace, logger, ci), new (_merge().CiMergeCmd)(workspace, logger, ci), new (_sync().CiSyncCmd)(workspace, logger, ci)];
252
303
  cli.register(ciCmd);
253
304
  return ci;
254
305
  }
306
+
307
+ /**
308
+ * Register a git host (GitHub, GitLab, …) that `bit ci sync` can use for its pull-request
309
+ * operations. Exactly one provider is selected per run, by the `origin` remote (see
310
+ * `selectGitHostProvider`); registering a provider never changes how a repository claimed by a
311
+ * different provider is handled.
312
+ */
313
+ registerGitHostProvider(provider) {
314
+ this.gitHostProviderSlot.register(provider);
315
+ return this;
316
+ }
317
+
318
+ /** Every registered git host provider, in registration order (one provider per registration). */
319
+ listGitHostProviders() {
320
+ return this.gitHostProviderSlot.values();
321
+ }
255
322
  async getBranchName() {
256
323
  try {
257
324
  // if we are running on github, use the GITHUB_HEAD_REF env var
@@ -315,28 +382,43 @@ class CiMain {
315
382
  * @example convertBranchToLaneId("feature/New-Component") => "my-scope/feature-new-component"
316
383
  */
317
384
  convertBranchToLaneId(branchName) {
318
- // Sanitize branch name to make it valid for Bit lane IDs by replacing slashes and dots with dashes
319
- // and converting to lowercase
320
385
  const sanitizedBranch = branchName.replace(/[/.]/g, '-').toLowerCase();
321
386
  return `${this.workspace.defaultScope}/${sanitizedBranch}`;
322
387
  }
388
+
389
+ /**
390
+ * The repository's default branch name, whole — slashes included (see `parseOriginHeadRef`).
391
+ * Local `origin/HEAD` symref first, then the remote's own answer, then the conventional probe.
392
+ */
323
393
  async getDefaultBranchName() {
324
394
  try {
325
- // Try to get the default branch from git symbolic-ref
326
395
  const result = await _git().git.raw(['symbolic-ref', 'refs/remotes/origin/HEAD']);
327
- const defaultBranch = result.trim().split('/').pop();
328
- return defaultBranch || 'master';
329
- } catch (e) {
330
- // Fallback to common default branch names
331
- try {
332
- const branches = await _git().git.branch(['-r']);
333
- if (branches.all.includes('origin/main')) return 'main';
334
- if (branches.all.includes('origin/master')) return 'master';
335
- return 'master'; // Final fallback
336
- } catch {
337
- this.logger.console(_chalk().default.yellow(`Unable to detect default branch, using 'master': ${e.toString()}`));
338
- return 'master';
339
- }
396
+ const local = (0, _gitOps().parseOriginHeadRef)(result);
397
+ // Every candidate is validated before it is returned: this value becomes a checkout target and a
398
+ // push refspec, and an option-like name ("-x") would be read by git as a flag. An unusable answer
399
+ // falls through to the next source rather than reaching the argv.
400
+ if (local && (0, _syncConfig().isValidGitBranchName)(local)) return local;
401
+ } catch {
402
+ // no local origin/HEAD — normal in a fresh CI clone; ask the remote itself below.
403
+ }
404
+ const remote = await (0, _gitOps().remoteHeadBranch)();
405
+ if (remote && (0, _syncConfig().isValidGitBranchName)(remote)) return remote;
406
+ return this.probeDefaultBranchName();
407
+ }
408
+
409
+ /**
410
+ * Last-resort fallback: the two conventional names among the remote branches, then `master`.
411
+ * Deliberately only `main`/`master` — any wider guess risks protecting the wrong branch.
412
+ */
413
+ async probeDefaultBranchName(originalError) {
414
+ try {
415
+ const branches = await _git().git.branch(['-r']);
416
+ if (branches.all.includes('origin/main')) return 'main';
417
+ if (branches.all.includes('origin/master')) return 'master';
418
+ return 'master';
419
+ } catch {
420
+ this.logger.console(_chalk().default.yellow(`Unable to detect default branch, using 'master'${originalError ? `: ${originalError}` : ''}`));
421
+ return 'master';
340
422
  }
341
423
  }
342
424
  async getGitCommitMessage() {
@@ -474,6 +556,36 @@ class CiMain {
474
556
  return undefined;
475
557
  }
476
558
 
559
+ /**
560
+ * Public entry point onto `switchToLane` for the lane-sync executor; returns the error instead of
561
+ * throwing. `switchToLane` spreads caller options AFTER its defaults, so `forceOurs: true` is
562
+ * overridable — the sync import direction depends on that. Do not reorder that spread.
563
+ */
564
+ async switchToLaneForSync(laneName, options = {}) {
565
+ return this.switchToLane(laneName, options);
566
+ }
567
+
568
+ /**
569
+ * Re-read `.bitmap` from disk into the live workspace. `bit ci sync` moves the git checkout
570
+ * underneath a running process, and until the reload every bit operation resolves against the
571
+ * `.bitmap` loaded at startup — and the next write would persist that stale state.
572
+ */
573
+ async reloadWorkspaceFromDisk() {
574
+ await this.workspace._reloadConsumer();
575
+ }
576
+
577
+ /** `bit ci sync` — reconcile Bit lanes and the main scope with git branches and PRs (see `SyncOrchestrator`). */
578
+ async sync(opts = {}) {
579
+ return new (_syncOrchestrator().SyncOrchestrator)({
580
+ ci: this,
581
+ workspace: this.workspace,
582
+ logger: this.logger,
583
+ lanes: this.lanes,
584
+ checkout: this.checkout,
585
+ config: this.config
586
+ }).sync(opts);
587
+ }
588
+
477
589
  /**
478
590
  * Sync *config-only* changes from main onto the lane — without a full `bit lane merge`.
479
591
  *
@@ -729,7 +841,8 @@ class CiMain {
729
841
  dryRun,
730
842
  keepLane,
731
843
  skipCleanup,
732
- skipTasks
844
+ skipTasks,
845
+ noDestructiveRecovery
733
846
  }) {
734
847
  // The post-export cleanup switches the workspace back to main, which re-checks-out main's HEAD
735
848
  // and re-imports every workspace component — pointless when the workspace is about to be
@@ -778,7 +891,8 @@ class CiMain {
778
891
  build,
779
892
  dryRun,
780
893
  skipCleanup: resolvedSkipCleanup,
781
- skipTasks: resolvedSkipTasks
894
+ skipTasks: resolvedSkipTasks,
895
+ noDestructiveRecovery
782
896
  });
783
897
  }
784
898
  return this.snapAndExportWithTempLane({
@@ -850,7 +964,8 @@ class CiMain {
850
964
  build,
851
965
  dryRun,
852
966
  skipCleanup,
853
- skipTasks
967
+ skipTasks,
968
+ noDestructiveRecovery
854
969
  }) {
855
970
  // Query the remote (by name, to avoid fetching all lanes) so we know whether to reuse or create
856
971
  const existingLanes = await this.lanes.getLanes({
@@ -903,6 +1018,14 @@ class CiMain {
903
1018
  if (!isStaleLane) {
904
1019
  throw new Error(`Failed to switch to remote lane ${laneId.toString()}: ${errMsg || '(no error captured)'}. ` + `Refusing destructive recovery for this failure class — the error doesn't match the ` + `stale-lane marker, so deleting the lane could destroy real history. Investigate or retry.`);
905
1020
  }
1021
+ if (noDestructiveRecovery) {
1022
+ // `bit ci sync` mirrors an authored lane onto a branch. Recovering by deleting the remote
1023
+ // lane and re-forking it from main would discard the lane's components and history — the
1024
+ // exact artifact the sync exists to preserve — and the next sync would then happily
1025
+ // mirror the now-empty lane onto the branch. Fail loudly instead; the executor turns this
1026
+ // into a halt so a human resolves the lane on bit.cloud.
1027
+ throw new Error(`Failed to switch to remote lane ${laneId.toString()}: ${errMsg || '(no error captured)'}. ` + `The stale-lane recovery (delete the remote lane and recreate it from main) is disabled ` + `for this caller, because the lane is an authored artifact rather than a throwaway PR ` + `lane. Resolve the lane on bit.cloud and re-run.`);
1028
+ }
906
1029
  this.logger.console(_chalk().default.yellow(`Stale remote lane ${laneId.toString()} — switching failed. ` + `Deleting it and creating a fresh lane to recover.`));
907
1030
  // Re-check the remote lane's hash immediately before deleting. The central-hub delete
908
1031
  // API is name-based — there's no compare-and-swap — so two CI jobs racing the same
@@ -1512,7 +1635,11 @@ class CiMain {
1512
1635
 
1513
1636
  // Stage everything: `bit checkout head` earlier in the flow may modify files
1514
1637
  // beyond .bitmap and pnpm-lock.yaml, so a narrow `git add` would miss them.
1515
- await _git().git.add(['.']);
1638
+ // The two exclusions in the shared helper keep the local bit scope (`.bit`) and `node_modules` out
1639
+ // of the commit in a workspace whose `.gitignore` lacks Bit's block — otherwise this stages the
1640
+ // object store into the default branch. Same helper the sync executors use, so all three commit
1641
+ // paths agree on what counts as workspace content.
1642
+ await (0, _gitOps().addAllExceptScopeAndModules)();
1516
1643
  const commitMessage = await this.getCustomCommitMessage();
1517
1644
  await _git().git.commit(commitMessage);
1518
1645
  const statusAfterCommit = await _git().git.status();
@@ -1705,7 +1832,7 @@ class CiMain {
1705
1832
  exports.CiMain = CiMain;
1706
1833
  _defineProperty(CiMain, "runtime", _cli().MainRuntime);
1707
1834
  _defineProperty(CiMain, "dependencies", [_cli().CLIAspect, _workspace().WorkspaceAspect, _logger().LoggerAspect, _builder().BuilderAspect, _status().StatusAspect, _lanes().LanesAspect, _snapping().SnappingAspect, _export().ExportAspect, _importer().ImporterAspect, _checkout().CheckoutAspect]);
1708
- _defineProperty(CiMain, "slots", []);
1835
+ _defineProperty(CiMain, "slots", [_harmony().Slot.withType()]);
1709
1836
  function reportToString(result) {
1710
1837
  return typeof result === 'string' ? result : result.data;
1711
1838
  }