@teambit/ci 1.0.507 → 1.0.512

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 (36) hide show
  1. package/dist/ci.main.runtime.d.ts +3 -2
  2. package/dist/ci.main.runtime.js +5 -6
  3. package/dist/ci.main.runtime.js.map +1 -1
  4. package/dist/{preview-1787147173446.js → preview-1787168213927.js} +1 -1
  5. package/dist/sync/git-host-provider.d.ts +9 -0
  6. package/dist/sync/git-host-provider.js.map +1 -1
  7. package/dist/sync/github-client.d.ts +28 -0
  8. package/dist/sync/github-client.js +81 -3
  9. package/dist/sync/github-client.js.map +1 -1
  10. package/dist/sync/github-client.spec.js +249 -0
  11. package/dist/sync/github-client.spec.js.map +1 -1
  12. package/dist/sync/lane-sync-executor.d.ts +47 -3
  13. package/dist/sync/lane-sync-executor.js +191 -39
  14. package/dist/sync/lane-sync-executor.js.map +1 -1
  15. package/dist/sync/lane-sync-executor.spec.js +336 -0
  16. package/dist/sync/lane-sync-executor.spec.js.map +1 -1
  17. package/dist/sync/sync-planner.d.ts +12 -2
  18. package/dist/sync/sync-planner.js +17 -8
  19. package/dist/sync/sync-planner.js.map +1 -1
  20. package/dist/sync/sync-planner.spec.js +64 -0
  21. package/dist/sync/sync-planner.spec.js.map +1 -1
  22. package/dist/sync/sync-state.d.ts +34 -3
  23. package/dist/sync/sync-state.js +61 -3
  24. package/dist/sync/sync-state.js.map +1 -1
  25. package/dist/sync/sync-state.spec.js +54 -0
  26. package/dist/sync/sync-state.spec.js.map +1 -1
  27. package/package.json +15 -15
  28. package/sync/git-host-provider.ts +8 -0
  29. package/sync/github-client.spec.ts +160 -0
  30. package/sync/github-client.ts +90 -3
  31. package/sync/lane-sync-executor.spec.ts +291 -0
  32. package/sync/lane-sync-executor.ts +233 -50
  33. package/sync/sync-planner.spec.ts +41 -0
  34. package/sync/sync-planner.ts +31 -10
  35. package/sync/sync-state.spec.ts +48 -0
  36. package/sync/sync-state.ts +104 -4
@@ -1 +1 @@
1
- {"version":3,"names":["_chai","data","require","_githubClient","REMOTES","undefined","describe","it","forEach","url","repo","isGitHub","expect","parseGitHubRepo","to","equal","isGitHubRemote","envKeys","saved","beforeEach","key","process","env","GITHUB_TOKEN","afterEach","OURS","RESOLUTION","name","repository","remote","warns","GITHUB_REPOSITORY","warnings","client","GitHubClient","fromEnv","message","push","fragment","join","contain","deep","realFetch","calls","globalThis","fetch","init","String","Response","JSON","stringify","status","headers","sentToken","findPrByBranch","authorization","PRECEDENCE","BIT_GITHUB_TOKEN","expected","Object","entries","value","have","lengthOf","fakeFetch","number","state","labels","head","ref","html_url","token","fetchImpl","pr","createPr","base","title","body","method","parse"],"sources":["github-client.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport { GitHubClient, isGitHubRemote, parseGitHubRepo } from './github-client';\n\n/**\n * A claimed remote the parse then fails on silently degrades the whole run to PR-less mode, so the two\n * predicates are pinned over the same remote shapes: whatever `isGitHubRemote` claims must parse.\n */\nconst REMOTES: Array<[string, string | undefined, boolean]> = [\n // url, parseGitHubRepo, isGitHubRemote\n ['git@github.com:acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop', 'acme/shop', true],\n ['ssh://git@github.com/acme/shop.git', 'acme/shop', true],\n // mixed-case host, an explicit port, and credentials in the url are all still github.com\n ['https://GitHub.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com:443/acme/shop.git', 'acme/shop', true],\n ['ssh://git@github.com:22/acme/shop.git', 'acme/shop', true],\n ['https://x-access-token:tok@github.com/acme/shop.git', 'acme/shop', true],\n // an all-digits scp owner is an owner, never a port\n ['git@github.com:12345/shop.git', '12345/shop', true],\n // another host that merely has \"github.com\" in its path, in both url forms\n ['https://gitlab.example.com/mirrors/github.com/acme/repo.git', undefined, false],\n ['git@gitlab.example.com:mirrors/github.com/acme/repo.git', undefined, false],\n // look-alike and unrelated hosts\n ['https://mygithub.com/acme/shop', undefined, false],\n ['https://github.company.com/acme/shop', undefined, false],\n ['git@gitlab.com:acme/shop.git', undefined, false],\n ['https://gitlab.com/acme/shop.git', undefined, false],\n ['/srv/git/github.com/acme/shop.git', undefined, false],\n];\n\ndescribe('parseGitHubRepo / isGitHubRemote', () => {\n it('agree on every remote form git accepts, and on every host that is not github.com', () => {\n REMOTES.forEach(([url, repo, isGitHub]) => {\n expect(parseGitHubRepo(url), url).to.equal(repo);\n expect(isGitHubRemote(url), url).to.equal(isGitHub);\n });\n });\n});\n\ndescribe('GitHubClient.fromEnv', () => {\n const envKeys = ['GITHUB_TOKEN', 'BIT_GITHUB_TOKEN', 'GITHUB_REPOSITORY'] as const;\n const saved: Record<string, string | undefined> = {};\n\n beforeEach(() => {\n envKeys.forEach((key) => {\n saved[key] = process.env[key];\n delete process.env[key];\n });\n process.env.GITHUB_TOKEN = 'tok';\n });\n\n afterEach(() => {\n envKeys.forEach((key) => {\n if (saved[key] === undefined) delete process.env[key];\n else process.env[key] = saved[key];\n });\n });\n\n const OURS = 'git@github.com:acme/shop.git';\n /** [case, GITHUB_REPOSITORY, remote, resolved repo, warning fragments]. */\n const RESOLUTION: Array<[string, string, string | undefined, string | undefined, string[]?]> = [\n ['the remote and the environment agree', 'acme/shop', OURS, 'acme/shop'],\n // the ORIGIN-parsed repository outranks GITHUB_REPOSITORY, and the disagreement must be reported\n ['they disagree', 'other-org/other-repo', OURS, 'acme/shop', ['other-org/other-repo', 'acme/shop']],\n // GitHub repository names are case-insensitive, so a case-only difference is not a disagreement\n ['they differ only in case', 'Acme/Shop', OURS, 'acme/shop'],\n ['there is no remote to parse', 'acme/shop', undefined, 'acme/shop'],\n ['the remote is a github url nothing parses out of', 'acme/shop', 'https://github.com/', 'acme/shop'],\n // a remote that is demonstrably not github stays unconfigured however complete the environment is\n ['the remote is another host', 'acme/shop', 'https://gitlab.com/acme/shop.git', undefined],\n ['the remote is a look-alike host', 'acme/shop', 'https://mygithub.com/acme/shop', undefined],\n ];\n\n RESOLUTION.forEach(([name, repository, remote, repo, warns]) => {\n it(`resolves the repository when ${name}`, () => {\n process.env.GITHUB_REPOSITORY = repository;\n const warnings: string[] = [];\n const client = GitHubClient.fromEnv(remote, (message) => warnings.push(message));\n expect(client?.repo).to.equal(repo);\n (warns ?? []).forEach((fragment) => expect(warnings.join('\\n')).to.contain(fragment));\n if (!warns) expect(warnings).to.deep.equal([]);\n });\n });\n\n it('needs a token: a repository on its own configures nothing', () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n });\n\n // Asserted through the `authorization` header, so what is pinned is the token the client actually\n // sends. The fetch stub must be installed before `fromEnv` runs.\n describe('token precedence', () => {\n let realFetch: typeof fetch;\n let calls: Array<{ url: string; init: any }>;\n\n beforeEach(() => {\n realFetch = globalThis.fetch;\n calls = [];\n globalThis.fetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(JSON.stringify([]), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n });\n\n afterEach(() => {\n globalThis.fetch = realFetch;\n });\n\n /** The token a client built from this environment puts on the wire. */\n async function sentToken(): Promise<string | undefined> {\n const client = GitHubClient.fromEnv('git@github.com:acme/shop.git');\n await client?.findPrByBranch('lane-x');\n return calls[0]?.init?.headers?.authorization;\n }\n\n const PRECEDENCE: Array<[string, Record<string, string | undefined>, string]> = [\n // the override beats the ambient default\n ['both set', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n // the ordinary Actions job\n ['only GITHUB_TOKEN', { GITHUB_TOKEN: 'workflow-token' }, 'Bearer workflow-token'],\n // any CI that is not Actions\n ['only BIT_GITHUB_TOKEN', { BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n ['an EMPTY override', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: '' }, 'Bearer workflow-token'],\n ];\n\n PRECEDENCE.forEach(([name, env, expected]) => {\n it(`sends the right token with ${name}`, async () => {\n delete process.env.GITHUB_TOKEN;\n Object.entries(env).forEach(([key, value]) => {\n process.env[key] = value;\n });\n expect(await sentToken()).to.equal(expected);\n });\n });\n\n it('is not configured when neither token is set, and puts nothing on the wire', async () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n expect(calls).to.have.lengthOf(0);\n });\n });\n});\n\ndescribe('GitHubClient', () => {\n it('createPr posts the pull-request payload to the pulls endpoint', async () => {\n const calls: Array<{ url: string; init: any }> = [];\n const fakeFetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(\n JSON.stringify({ number: 7, state: 'open', labels: [], head: { ref: 'lane-x' }, html_url: 'http://pr/7' }),\n { status: 201, headers: { 'content-type': 'application/json' } }\n );\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n const pr = await client.createPr({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n expect(pr.number).to.equal(7);\n expect(calls[0].url).to.equal('https://api.github.com/repos/acme/shop/pulls');\n expect(calls[0].init.method).to.equal('POST');\n expect(JSON.parse(calls[0].init.body)).to.deep.equal({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n });\n\n it('findPrByBranch returns undefined on empty list', async () => {\n const fakeFetch = (async () =>\n new Response(JSON.stringify([]), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n expect(await client.findPrByBranch('lane-x')).to.equal(undefined);\n });\n});\n"],"mappings":";;AAAA,SAAAA,MAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,KAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAE,cAAA;EAAA,MAAAF,IAAA,GAAAC,OAAA;EAAAC,aAAA,YAAAA,CAAA;IAAA,OAAAF,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEA;AACA;AACA;AACA;AACA,MAAMG,OAAqD,GAAG;AAC5D;AACA,CAAC,8BAA8B,EAAE,WAAW,EAAE,IAAI,CAAC,EACnD,CAAC,kCAAkC,EAAE,WAAW,EAAE,IAAI,CAAC,EACvD,CAAC,8BAA8B,EAAE,WAAW,EAAE,IAAI,CAAC,EACnD,CAAC,oCAAoC,EAAE,WAAW,EAAE,IAAI,CAAC;AACzD;AACA,CAAC,kCAAkC,EAAE,WAAW,EAAE,IAAI,CAAC,EACvD,CAAC,sCAAsC,EAAE,WAAW,EAAE,IAAI,CAAC,EAC3D,CAAC,uCAAuC,EAAE,WAAW,EAAE,IAAI,CAAC,EAC5D,CAAC,qDAAqD,EAAE,WAAW,EAAE,IAAI,CAAC;AAC1E;AACA,CAAC,+BAA+B,EAAE,YAAY,EAAE,IAAI,CAAC;AACrD;AACA,CAAC,6DAA6D,EAAEC,SAAS,EAAE,KAAK,CAAC,EACjF,CAAC,yDAAyD,EAAEA,SAAS,EAAE,KAAK,CAAC;AAC7E;AACA,CAAC,gCAAgC,EAAEA,SAAS,EAAE,KAAK,CAAC,EACpD,CAAC,sCAAsC,EAAEA,SAAS,EAAE,KAAK,CAAC,EAC1D,CAAC,8BAA8B,EAAEA,SAAS,EAAE,KAAK,CAAC,EAClD,CAAC,kCAAkC,EAAEA,SAAS,EAAE,KAAK,CAAC,EACtD,CAAC,mCAAmC,EAAEA,SAAS,EAAE,KAAK,CAAC,CACxD;AAEDC,QAAQ,CAAC,kCAAkC,EAAE,MAAM;EACjDC,EAAE,CAAC,kFAAkF,EAAE,MAAM;IAC3FH,OAAO,CAACI,OAAO,CAAC,CAAC,CAACC,GAAG,EAAEC,IAAI,EAAEC,QAAQ,CAAC,KAAK;MACzC,IAAAC,cAAM,EAAC,IAAAC,+BAAe,EAACJ,GAAG,CAAC,EAAEA,GAAG,CAAC,CAACK,EAAE,CAACC,KAAK,CAACL,IAAI,CAAC;MAChD,IAAAE,cAAM,EAAC,IAAAI,8BAAc,EAACP,GAAG,CAAC,EAAEA,GAAG,CAAC,CAACK,EAAE,CAACC,KAAK,CAACJ,QAAQ,CAAC;IACrD,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;AAEFL,QAAQ,CAAC,sBAAsB,EAAE,MAAM;EACrC,MAAMW,OAAO,GAAG,CAAC,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,CAAU;EAClF,MAAMC,KAAyC,GAAG,CAAC,CAAC;EAEpDC,UAAU,CAAC,MAAM;IACfF,OAAO,CAACT,OAAO,CAAEY,GAAG,IAAK;MACvBF,KAAK,CAACE,GAAG,CAAC,GAAGC,OAAO,CAACC,GAAG,CAACF,GAAG,CAAC;MAC7B,OAAOC,OAAO,CAACC,GAAG,CAACF,GAAG,CAAC;IACzB,CAAC,CAAC;IACFC,OAAO,CAACC,GAAG,CAACC,YAAY,GAAG,KAAK;EAClC,CAAC,CAAC;EAEFC,SAAS,CAAC,MAAM;IACdP,OAAO,CAACT,OAAO,CAAEY,GAAG,IAAK;MACvB,IAAIF,KAAK,CAACE,GAAG,CAAC,KAAKf,SAAS,EAAE,OAAOgB,OAAO,CAACC,GAAG,CAACF,GAAG,CAAC,CAAC,KACjDC,OAAO,CAACC,GAAG,CAACF,GAAG,CAAC,GAAGF,KAAK,CAACE,GAAG,CAAC;IACpC,CAAC,CAAC;EACJ,CAAC,CAAC;EAEF,MAAMK,IAAI,GAAG,8BAA8B;EAC3C;EACA,MAAMC,UAAsF,GAAG,CAC7F,CAAC,sCAAsC,EAAE,WAAW,EAAED,IAAI,EAAE,WAAW,CAAC;EACxE;EACA,CAAC,eAAe,EAAE,sBAAsB,EAAEA,IAAI,EAAE,WAAW,EAAE,CAAC,sBAAsB,EAAE,WAAW,CAAC,CAAC;EACnG;EACA,CAAC,0BAA0B,EAAE,WAAW,EAAEA,IAAI,EAAE,WAAW,CAAC,EAC5D,CAAC,6BAA6B,EAAE,WAAW,EAAEpB,SAAS,EAAE,WAAW,CAAC,EACpE,CAAC,kDAAkD,EAAE,WAAW,EAAE,qBAAqB,EAAE,WAAW,CAAC;EACrG;EACA,CAAC,4BAA4B,EAAE,WAAW,EAAE,kCAAkC,EAAEA,SAAS,CAAC,EAC1F,CAAC,iCAAiC,EAAE,WAAW,EAAE,gCAAgC,EAAEA,SAAS,CAAC,CAC9F;EAEDqB,UAAU,CAAClB,OAAO,CAAC,CAAC,CAACmB,IAAI,EAAEC,UAAU,EAAEC,MAAM,EAAEnB,IAAI,EAAEoB,KAAK,CAAC,KAAK;IAC9DvB,EAAE,CAAC,gCAAgCoB,IAAI,EAAE,EAAE,MAAM;MAC/CN,OAAO,CAACC,GAAG,CAACS,iBAAiB,GAAGH,UAAU;MAC1C,MAAMI,QAAkB,GAAG,EAAE;MAC7B,MAAMC,MAAM,GAAGC,4BAAY,CAACC,OAAO,CAACN,MAAM,EAAGO,OAAO,IAAKJ,QAAQ,CAACK,IAAI,CAACD,OAAO,CAAC,CAAC;MAChF,IAAAxB,cAAM,EAACqB,MAAM,EAAEvB,IAAI,CAAC,CAACI,EAAE,CAACC,KAAK,CAACL,IAAI,CAAC;MACnC,CAACoB,KAAK,IAAI,EAAE,EAAEtB,OAAO,CAAE8B,QAAQ,IAAK,IAAA1B,cAAM,EAACoB,QAAQ,CAACO,IAAI,CAAC,IAAI,CAAC,CAAC,CAACzB,EAAE,CAAC0B,OAAO,CAACF,QAAQ,CAAC,CAAC;MACrF,IAAI,CAACR,KAAK,EAAE,IAAAlB,cAAM,EAACoB,QAAQ,CAAC,CAAClB,EAAE,CAAC2B,IAAI,CAAC1B,KAAK,CAAC,EAAE,CAAC;IAChD,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFR,EAAE,CAAC,2DAA2D,EAAE,MAAM;IACpE,OAAOc,OAAO,CAACC,GAAG,CAACC,YAAY;IAC/BF,OAAO,CAACC,GAAG,CAACS,iBAAiB,GAAG,WAAW;IAC3C,IAAAnB,cAAM,EAACsB,4BAAY,CAACC,OAAO,CAAC,8BAA8B,CAAC,CAAC,CAACrB,EAAE,CAACC,KAAK,CAACV,SAAS,CAAC;EAClF,CAAC,CAAC;;EAEF;EACA;EACAC,QAAQ,CAAC,kBAAkB,EAAE,MAAM;IACjC,IAAIoC,SAAuB;IAC3B,IAAIC,KAAwC;IAE5CxB,UAAU,CAAC,MAAM;MACfuB,SAAS,GAAGE,UAAU,CAACC,KAAK;MAC5BF,KAAK,GAAG,EAAE;MACVC,UAAU,CAACC,KAAK,GAAI,OAAOpC,GAAQ,EAAEqC,IAAS,KAAK;QACjDH,KAAK,CAACN,IAAI,CAAC;UAAE5B,GAAG,EAAEsC,MAAM,CAACtC,GAAG,CAAC;UAAEqC;QAAK,CAAC,CAAC;QACtC,OAAO,IAAIE,QAAQ,CAACC,IAAI,CAACC,SAAS,CAAC,EAAE,CAAC,EAAE;UAAEC,MAAM,EAAE,GAAG;UAAEC,OAAO,EAAE;YAAE,cAAc,EAAE;UAAmB;QAAE,CAAC,CAAC;MAC3G,CAAkB;IACpB,CAAC,CAAC;IAEF5B,SAAS,CAAC,MAAM;MACdoB,UAAU,CAACC,KAAK,GAAGH,SAAS;IAC9B,CAAC,CAAC;;IAEF;IACA,eAAeW,SAASA,CAAA,EAAgC;MACtD,MAAMpB,MAAM,GAAGC,4BAAY,CAACC,OAAO,CAAC,8BAA8B,CAAC;MACnE,MAAMF,MAAM,EAAEqB,cAAc,CAAC,QAAQ,CAAC;MACtC,OAAOX,KAAK,CAAC,CAAC,CAAC,EAAEG,IAAI,EAAEM,OAAO,EAAEG,aAAa;IAC/C;IAEA,MAAMC,UAAuE,GAAG;IAC9E;IACA,CAAC,UAAU,EAAE;MAAEjC,YAAY,EAAE,gBAAgB;MAAEkC,gBAAgB,EAAE;IAAW,CAAC,EAAE,iBAAiB,CAAC;IACjG;IACA,CAAC,mBAAmB,EAAE;MAAElC,YAAY,EAAE;IAAiB,CAAC,EAAE,uBAAuB,CAAC;IAClF;IACA,CAAC,uBAAuB,EAAE;MAAEkC,gBAAgB,EAAE;IAAW,CAAC,EAAE,iBAAiB,CAAC,EAC9E,CAAC,mBAAmB,EAAE;MAAElC,YAAY,EAAE,gBAAgB;MAAEkC,gBAAgB,EAAE;IAAG,CAAC,EAAE,uBAAuB,CAAC,CACzG;IAEDD,UAAU,CAAChD,OAAO,CAAC,CAAC,CAACmB,IAAI,EAAEL,GAAG,EAAEoC,QAAQ,CAAC,KAAK;MAC5CnD,EAAE,CAAC,8BAA8BoB,IAAI,EAAE,EAAE,YAAY;QACnD,OAAON,OAAO,CAACC,GAAG,CAACC,YAAY;QAC/BoC,MAAM,CAACC,OAAO,CAACtC,GAAG,CAAC,CAACd,OAAO,CAAC,CAAC,CAACY,GAAG,EAAEyC,KAAK,CAAC,KAAK;UAC5CxC,OAAO,CAACC,GAAG,CAACF,GAAG,CAAC,GAAGyC,KAAK;QAC1B,CAAC,CAAC;QACF,IAAAjD,cAAM,EAAC,MAAMyC,SAAS,CAAC,CAAC,CAAC,CAACvC,EAAE,CAACC,KAAK,CAAC2C,QAAQ,CAAC;MAC9C,CAAC,CAAC;IACJ,CAAC,CAAC;IAEFnD,EAAE,CAAC,2EAA2E,EAAE,YAAY;MAC1F,OAAOc,OAAO,CAACC,GAAG,CAACC,YAAY;MAC/BF,OAAO,CAACC,GAAG,CAACS,iBAAiB,GAAG,WAAW;MAC3C,IAAAnB,cAAM,EAACsB,4BAAY,CAACC,OAAO,CAAC,8BAA8B,CAAC,CAAC,CAACrB,EAAE,CAACC,KAAK,CAACV,SAAS,CAAC;MAChF,IAAAO,cAAM,EAAC+B,KAAK,CAAC,CAAC7B,EAAE,CAACgD,IAAI,CAACC,QAAQ,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;AAEFzD,QAAQ,CAAC,cAAc,EAAE,MAAM;EAC7BC,EAAE,CAAC,+DAA+D,EAAE,YAAY;IAC9E,MAAMoC,KAAwC,GAAG,EAAE;IACnD,MAAMqB,SAAS,GAAI,MAAAA,CAAOvD,GAAQ,EAAEqC,IAAS,KAAK;MAChDH,KAAK,CAACN,IAAI,CAAC;QAAE5B,GAAG,EAAEsC,MAAM,CAACtC,GAAG,CAAC;QAAEqC;MAAK,CAAC,CAAC;MACtC,OAAO,IAAIE,QAAQ,CACjBC,IAAI,CAACC,SAAS,CAAC;QAAEe,MAAM,EAAE,CAAC;QAAEC,KAAK,EAAE,MAAM;QAAEC,MAAM,EAAE,EAAE;QAAEC,IAAI,EAAE;UAAEC,GAAG,EAAE;QAAS,CAAC;QAAEC,QAAQ,EAAE;MAAc,CAAC,CAAC,EAC1G;QAAEnB,MAAM,EAAE,GAAG;QAAEC,OAAO,EAAE;UAAE,cAAc,EAAE;QAAmB;MAAE,CACjE,CAAC;IACH,CAAkB;IAClB,MAAMnB,MAAM,GAAG,KAAIC,4BAAY,EAAC;MAAEqC,KAAK,EAAE,KAAK;MAAE7D,IAAI,EAAE,WAAW;MAAE8D,SAAS,EAAER;IAAU,CAAC,CAAC;IAC1F,MAAMS,EAAE,GAAG,MAAMxC,MAAM,CAACyC,QAAQ,CAAC;MAAEN,IAAI,EAAE,QAAQ;MAAEO,IAAI,EAAE,MAAM;MAAEC,KAAK,EAAE,GAAG;MAAEC,IAAI,EAAE;IAAI,CAAC,CAAC;IACzF,IAAAjE,cAAM,EAAC6D,EAAE,CAACR,MAAM,CAAC,CAACnD,EAAE,CAACC,KAAK,CAAC,CAAC,CAAC;IAC7B,IAAAH,cAAM,EAAC+B,KAAK,CAAC,CAAC,CAAC,CAAClC,GAAG,CAAC,CAACK,EAAE,CAACC,KAAK,CAAC,8CAA8C,CAAC;IAC7E,IAAAH,cAAM,EAAC+B,KAAK,CAAC,CAAC,CAAC,CAACG,IAAI,CAACgC,MAAM,CAAC,CAAChE,EAAE,CAACC,KAAK,CAAC,MAAM,CAAC;IAC7C,IAAAH,cAAM,EAACqC,IAAI,CAAC8B,KAAK,CAACpC,KAAK,CAAC,CAAC,CAAC,CAACG,IAAI,CAAC+B,IAAI,CAAC,CAAC,CAAC/D,EAAE,CAAC2B,IAAI,CAAC1B,KAAK,CAAC;MAAEqD,IAAI,EAAE,QAAQ;MAAEO,IAAI,EAAE,MAAM;MAAEC,KAAK,EAAE,GAAG;MAAEC,IAAI,EAAE;IAAI,CAAC,CAAC;EAC/G,CAAC,CAAC;EAEFtE,EAAE,CAAC,gDAAgD,EAAE,YAAY;IAC/D,MAAMyD,SAAS,GAAI,MAAAA,CAAA,KACjB,IAAIhB,QAAQ,CAACC,IAAI,CAACC,SAAS,CAAC,EAAE,CAAC,EAAE;MAC/BC,MAAM,EAAE,GAAG;MACXC,OAAO,EAAE;QAAE,cAAc,EAAE;MAAmB;IAChD,CAAC,CAAkB;IACrB,MAAMnB,MAAM,GAAG,KAAIC,4BAAY,EAAC;MAAEqC,KAAK,EAAE,KAAK;MAAE7D,IAAI,EAAE,WAAW;MAAE8D,SAAS,EAAER;IAAU,CAAC,CAAC;IAC1F,IAAApD,cAAM,EAAC,MAAMqB,MAAM,CAACqB,cAAc,CAAC,QAAQ,CAAC,CAAC,CAACxC,EAAE,CAACC,KAAK,CAACV,SAAS,CAAC;EACnE,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["_chai","data","require","_githubClient","REMOTES","undefined","describe","it","forEach","url","repo","isGitHub","expect","parseGitHubRepo","to","equal","isGitHubRemote","envKeys","saved","beforeEach","key","process","env","GITHUB_TOKEN","afterEach","OURS","RESOLUTION","name","repository","remote","warns","GITHUB_REPOSITORY","warnings","client","GitHubClient","fromEnv","message","push","fragment","join","contain","deep","realFetch","calls","globalThis","fetch","init","String","Response","JSON","stringify","status","headers","sentToken","findPrByBranch","authorization","PRECEDENCE","BIT_GITHUB_TOKEN","expected","Object","entries","value","have","lengthOf","fakeFetch","number","state","labels","head","ref","html_url","token","fetchImpl","pr","createPr","base","title","body","method","parse","fakeFetchOver","existingComments","upsertComment","posts","filter","c","id","patches","some","createIfAbsent","includes","link","gets","include","thrown","catch","e","be","an"],"sources":["github-client.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport { GitHubClient, isGitHubRemote, parseGitHubRepo } from './github-client';\n\n/**\n * A claimed remote the parse then fails on silently degrades the whole run to PR-less mode, so the two\n * predicates are pinned over the same remote shapes: whatever `isGitHubRemote` claims must parse.\n */\nconst REMOTES: Array<[string, string | undefined, boolean]> = [\n // url, parseGitHubRepo, isGitHubRemote\n ['git@github.com:acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop', 'acme/shop', true],\n ['ssh://git@github.com/acme/shop.git', 'acme/shop', true],\n // mixed-case host, an explicit port, and credentials in the url are all still github.com\n ['https://GitHub.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com:443/acme/shop.git', 'acme/shop', true],\n ['ssh://git@github.com:22/acme/shop.git', 'acme/shop', true],\n ['https://x-access-token:tok@github.com/acme/shop.git', 'acme/shop', true],\n // an all-digits scp owner is an owner, never a port\n ['git@github.com:12345/shop.git', '12345/shop', true],\n // another host that merely has \"github.com\" in its path, in both url forms\n ['https://gitlab.example.com/mirrors/github.com/acme/repo.git', undefined, false],\n ['git@gitlab.example.com:mirrors/github.com/acme/repo.git', undefined, false],\n // look-alike and unrelated hosts\n ['https://mygithub.com/acme/shop', undefined, false],\n ['https://github.company.com/acme/shop', undefined, false],\n ['git@gitlab.com:acme/shop.git', undefined, false],\n ['https://gitlab.com/acme/shop.git', undefined, false],\n ['/srv/git/github.com/acme/shop.git', undefined, false],\n];\n\ndescribe('parseGitHubRepo / isGitHubRemote', () => {\n it('agree on every remote form git accepts, and on every host that is not github.com', () => {\n REMOTES.forEach(([url, repo, isGitHub]) => {\n expect(parseGitHubRepo(url), url).to.equal(repo);\n expect(isGitHubRemote(url), url).to.equal(isGitHub);\n });\n });\n});\n\ndescribe('GitHubClient.fromEnv', () => {\n const envKeys = ['GITHUB_TOKEN', 'BIT_GITHUB_TOKEN', 'GITHUB_REPOSITORY'] as const;\n const saved: Record<string, string | undefined> = {};\n\n beforeEach(() => {\n envKeys.forEach((key) => {\n saved[key] = process.env[key];\n delete process.env[key];\n });\n process.env.GITHUB_TOKEN = 'tok';\n });\n\n afterEach(() => {\n envKeys.forEach((key) => {\n if (saved[key] === undefined) delete process.env[key];\n else process.env[key] = saved[key];\n });\n });\n\n const OURS = 'git@github.com:acme/shop.git';\n /** [case, GITHUB_REPOSITORY, remote, resolved repo, warning fragments]. */\n const RESOLUTION: Array<[string, string, string | undefined, string | undefined, string[]?]> = [\n ['the remote and the environment agree', 'acme/shop', OURS, 'acme/shop'],\n // the ORIGIN-parsed repository outranks GITHUB_REPOSITORY, and the disagreement must be reported\n ['they disagree', 'other-org/other-repo', OURS, 'acme/shop', ['other-org/other-repo', 'acme/shop']],\n // GitHub repository names are case-insensitive, so a case-only difference is not a disagreement\n ['they differ only in case', 'Acme/Shop', OURS, 'acme/shop'],\n ['there is no remote to parse', 'acme/shop', undefined, 'acme/shop'],\n ['the remote is a github url nothing parses out of', 'acme/shop', 'https://github.com/', 'acme/shop'],\n // a remote that is demonstrably not github stays unconfigured however complete the environment is\n ['the remote is another host', 'acme/shop', 'https://gitlab.com/acme/shop.git', undefined],\n ['the remote is a look-alike host', 'acme/shop', 'https://mygithub.com/acme/shop', undefined],\n ];\n\n RESOLUTION.forEach(([name, repository, remote, repo, warns]) => {\n it(`resolves the repository when ${name}`, () => {\n process.env.GITHUB_REPOSITORY = repository;\n const warnings: string[] = [];\n const client = GitHubClient.fromEnv(remote, (message) => warnings.push(message));\n expect(client?.repo).to.equal(repo);\n (warns ?? []).forEach((fragment) => expect(warnings.join('\\n')).to.contain(fragment));\n if (!warns) expect(warnings).to.deep.equal([]);\n });\n });\n\n it('needs a token: a repository on its own configures nothing', () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n });\n\n // Asserted through the `authorization` header, so what is pinned is the token the client actually\n // sends. The fetch stub must be installed before `fromEnv` runs.\n describe('token precedence', () => {\n let realFetch: typeof fetch;\n let calls: Array<{ url: string; init: any }>;\n\n beforeEach(() => {\n realFetch = globalThis.fetch;\n calls = [];\n globalThis.fetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(JSON.stringify([]), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n });\n\n afterEach(() => {\n globalThis.fetch = realFetch;\n });\n\n /** The token a client built from this environment puts on the wire. */\n async function sentToken(): Promise<string | undefined> {\n const client = GitHubClient.fromEnv('git@github.com:acme/shop.git');\n await client?.findPrByBranch('lane-x');\n return calls[0]?.init?.headers?.authorization;\n }\n\n const PRECEDENCE: Array<[string, Record<string, string | undefined>, string]> = [\n // the override beats the ambient default\n ['both set', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n // the ordinary Actions job\n ['only GITHUB_TOKEN', { GITHUB_TOKEN: 'workflow-token' }, 'Bearer workflow-token'],\n // any CI that is not Actions\n ['only BIT_GITHUB_TOKEN', { BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n ['an EMPTY override', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: '' }, 'Bearer workflow-token'],\n ];\n\n PRECEDENCE.forEach(([name, env, expected]) => {\n it(`sends the right token with ${name}`, async () => {\n delete process.env.GITHUB_TOKEN;\n Object.entries(env).forEach(([key, value]) => {\n process.env[key] = value;\n });\n expect(await sentToken()).to.equal(expected);\n });\n });\n\n it('is not configured when neither token is set, and puts nothing on the wire', async () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n expect(calls).to.have.lengthOf(0);\n });\n });\n});\n\ndescribe('GitHubClient', () => {\n it('createPr posts the pull-request payload to the pulls endpoint', async () => {\n const calls: Array<{ url: string; init: any }> = [];\n const fakeFetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(\n JSON.stringify({ number: 7, state: 'open', labels: [], head: { ref: 'lane-x' }, html_url: 'http://pr/7' }),\n { status: 201, headers: { 'content-type': 'application/json' } }\n );\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n const pr = await client.createPr({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n expect(pr.number).to.equal(7);\n expect(calls[0].url).to.equal('https://api.github.com/repos/acme/shop/pulls');\n expect(calls[0].init.method).to.equal('POST');\n expect(JSON.parse(calls[0].init.body)).to.deep.equal({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n });\n\n it('findPrByBranch returns undefined on empty list', async () => {\n const fakeFetch = (async () =>\n new Response(JSON.stringify([]), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n expect(await client.findPrByBranch('lane-x')).to.equal(undefined);\n });\n\n describe('upsertComment', () => {\n // named `upsertComment`, matching `GitHostProvider.upsertComment` exactly — see the method's\n // own doc comment for why a differently-named method would be a silent-no-op trap.\n function fakeFetchOver(existingComments: Array<{ id: number; body: string }>) {\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET') {\n return new Response(JSON.stringify(existingComments), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n return { calls, fetchImpl };\n }\n\n it('posts a new comment when no marked comment exists', async () => {\n const { calls, fetchImpl } = fakeFetchOver([]);\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nbody');\n const posts = calls.filter((c) => c.init?.method === 'POST');\n expect(posts).to.have.lengthOf(1);\n expect(posts[0].url).to.equal('https://api.github.com/repos/acme/shop/issues/7/comments');\n });\n\n it('patches the marked comment in place, and never posts a second one', async () => {\n const { calls, fetchImpl } = fakeFetchOver([\n { id: 42, body: 'unrelated comment' },\n { id: 99, body: '<!-- marker -->\\nold report' },\n ]);\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nnew report');\n const patches = calls.filter((c) => c.init?.method === 'PATCH');\n expect(patches).to.have.lengthOf(1);\n expect(patches[0].url).to.equal('https://api.github.com/repos/acme/shop/issues/comments/99');\n expect(JSON.parse(patches[0].init.body)).to.deep.equal({ body: '<!-- marker -->\\nnew report' });\n expect(calls.some((c) => c.init?.method === 'POST')).to.equal(false);\n });\n\n it('skips silently when createIfAbsent is false and no marked comment exists', async () => {\n const { calls, fetchImpl } = fakeFetchOver([]);\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', 'cleared', { createIfAbsent: false });\n expect(calls.some((c) => c.init?.method === 'POST' || c.init?.method === 'PATCH')).to.equal(false);\n });\n\n it('lists at per_page=100 and follows Link-header pagination to find a comment past page 1', async () => {\n // GitHub defaults to per_page=30; a naive single-page list would miss this comment and post a\n // duplicate report on every push instead of updating it.\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET' && !String(url).includes('page=2')) {\n return new Response(JSON.stringify([{ id: 1, body: 'unrelated' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2>; rel=\"next\"',\n },\n });\n }\n if (method === 'GET') {\n return new Response(JSON.stringify([{ id: 99, body: '<!-- marker -->\\nold report' }]), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nnew report');\n\n const gets = calls.filter((c) => (c.init?.method ?? 'GET') === 'GET');\n expect(gets).to.have.lengthOf(2);\n expect(gets[0].url).to.include('per_page=100');\n expect(gets[1].url).to.equal('https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2');\n const patches = calls.filter((c) => c.init?.method === 'PATCH');\n expect(patches).to.have.lengthOf(1);\n expect(patches[0].url).to.equal('https://api.github.com/repos/acme/shop/issues/comments/99');\n // the found-on-page-2 comment was updated, not duplicated\n expect(calls.some((c) => c.init?.method === 'POST')).to.equal(false);\n });\n\n it('refuses to follow a Link header that leaves api.github.com — the token stays home', async () => {\n // The next-page URL is response data, and requestRaw attaches the bearer token to whatever URL\n // it is handed — an off-host \"next\" link must read as \"last page\", never as a request target.\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET') {\n return new Response(JSON.stringify([{ id: 1, body: 'unrelated' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://evil.test/repos/acme/shop/issues/7/comments?page=2>; rel=\"next\"',\n },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nbody');\n\n expect(calls.some((c) => c.url.includes('evil.test'))).to.equal(false);\n const gets = calls.filter((c) => (c.init?.method ?? 'GET') === 'GET');\n expect(gets, 'the off-host link is the last page, not a loop').to.have.lengthOf(1);\n });\n\n it('stops paginating as soon as the marked comment is seen', async () => {\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET') {\n return new Response(JSON.stringify([{ id: 99, body: '<!-- marker -->\\nold report' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2>; rel=\"next\"',\n },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nnew report');\n\n const gets = calls.filter((c) => (c.init?.method ?? 'GET') === 'GET');\n expect(gets, 'the match on page 1 makes page 2 pointless').to.have.lengthOf(1);\n expect(calls.filter((c) => c.init?.method === 'PATCH')).to.have.lengthOf(1);\n });\n\n it('exhausting the page budget throws instead of posting a duplicate', async () => {\n // \"Ran out of budget\" must not read as \"absent\": a POST here would add a duplicate report on\n // every later push. The throw surfaces through the caller's existing warn-and-skip path.\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(JSON.stringify([{ id: 1, body: 'unrelated' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2>; rel=\"next\"',\n },\n });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n let thrown: Error | undefined;\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nbody').catch((e) => (thrown = e));\n\n expect(thrown, 'the budget exhaustion is an error, not an absence').to.be.an('error');\n expect(calls.some((c) => c.init?.method === 'POST')).to.equal(false);\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
@@ -32,6 +32,11 @@ export declare function racedLedgerPushSummary(laneName: string, policyClause?:
32
32
  * run re-plans first contact against the winner's tip.
33
33
  */
34
34
  export declare function racedAdoptionPushSummary(laneName: string): string;
35
+ /**
36
+ * An HTML comment embedded in the run-summary comment's body, so `upsertComment` finds and replaces
37
+ * THIS comment on a later run rather than posting a duplicate summary on every push.
38
+ */
39
+ export declare const RUN_SUMMARY_MARKER = "<!-- bit-ci-sync:run-summary -->";
35
40
  export type LaneSyncDeps = {
36
41
  lanes: LanesMain;
37
42
  /** for snapPrCommit + getDefaultBranchName + switchToLaneForSync */
@@ -133,8 +138,12 @@ export declare class LaneSyncExecutor {
133
138
  */
134
139
  private executeImportLane;
135
140
  /**
136
- * Push the branch's dev commits back onto the lane: snap+export the branch's tree, then commit the
137
- * resulting `.bitmap` back onto the branch so the next run sees the two sides as converged.
141
+ * Push the branch's dev commits back onto the lane. Probe first, mutate second: like adoption, land
142
+ * the lane in the workspace keeping the BRANCH's files, then ask `bit status` whether the branch's
143
+ * tree holds anything the lane is missing. Nothing → converged, and nothing is written or pushed —
144
+ * which is why a suspected-work plan can repeat run after run without a memo, and why no commit
145
+ * message is ever consulted to suppress it. Something → snap + export, then commit the resulting
146
+ * `.bitmap` back onto the branch so the next run reads the two sides as converged.
138
147
  */
139
148
  private executeExportBranch;
140
149
  /**
@@ -170,7 +179,8 @@ export declare class LaneSyncExecutor {
170
179
  * so any earlier fingerprint is stale), commit — crucially the `.bitmap` the snap rewrote — and push.
171
180
  * `unreadable` means the lane could no longer be read back, in which case the caller halts;
172
181
  * `raced` means a concurrent run's push already got there first, in which case the caller reports
173
- * a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`.
182
+ * a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`. The re-queried lane is
183
+ * also the caller's only cheap source of POST-export component versions.
174
184
  */
175
185
  private recordLaneHeadOnBranch;
176
186
  /**
@@ -264,6 +274,15 @@ export declare class LaneSyncExecutor {
264
274
  * failing the lane — the git side of the sync is still correct and the next run re-reads the PR.
265
275
  */
266
276
  private findPr;
277
+ /**
278
+ * Post (or update) ONE maintained comment on the branch's PR: what this export did to the scope —
279
+ * the components it snapped and the synced branch/lane anchors — information the branch's git diff
280
+ * cannot show, since the snap step alone can move dependency ranges the PR's files never touched.
281
+ * Best-effort throughout: no configured git host, no open PR yet, or a provider that doesn't
282
+ * implement `upsertComment` are all normal states, not failures — the git/lane pair already
283
+ * converged without this comment. A comment API error warns and never fails the run.
284
+ */
285
+ private postRunSummaryComment;
267
286
  /**
268
287
  * The subject of the newest commit on the branch that isn't one of our own sync commits — it's
269
288
  * the developer's own description of the change, and becomes the snap message on the lane.
@@ -310,6 +329,31 @@ export declare function laneSyncPrBody({ laneIdStr, laneUrl, branch, laneHead, c
310
329
  laneHead: string;
311
330
  components: LaneData['components'];
312
331
  }): string;
332
+ /**
333
+ * The lane components this export actually changed: new on the lane, or moved to a new head. Diffed
334
+ * by id against the lane's content BEFORE the export — `LaneData.head` is the snap hash bit assigns a
335
+ * lane component, so comparing it (not looking at git) is the only way to name what the snap did.
336
+ */
337
+ /**
338
+ * The lane entries the run SUMMARY diffs: the visible components plus the hidden `updateDependents`
339
+ * (a cascade export moves those heads too, and a summary reading "nothing changed" over one would be
340
+ * false). Summary-only — the fingerprint and the foreign-scope check keep reading `components` alone.
341
+ */
342
+ export declare function laneSummaryComponents(lane: LaneData | undefined): LaneData['components'];
343
+ export declare function changedLaneComponents(before: LaneData['components'] | undefined, after: LaneData['components']): LaneData['components'];
344
+ /**
345
+ * What a sync run did to the scope — information the branch's own git diff cannot show, since the
346
+ * snap step can move a component's recorded version with no file in the PR ever changing. Kept to one
347
+ * maintained comment (`RUN_SUMMARY_MARKER` + `upsertComment`) rather than one post per push.
348
+ */
349
+ export declare function runSummaryCommentBody({ laneIdStr, laneUrl, branch, branchTipSha, laneHead, changed, }: {
350
+ laneIdStr: string;
351
+ laneUrl: string;
352
+ branch: string;
353
+ branchTipSha: string | undefined;
354
+ laneHead: string;
355
+ changed: LaneData['components'];
356
+ }): string;
313
357
  /**
314
358
  * Resolution instructions posted on a halted PR. `note` replaces the default steps for halts where
315
359
  * they do not apply (the branch-aliasing halt, whose PR belongs to a different lane).
@@ -3,10 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.REFUSED_SUMMARY_PREFIX = exports.LaneSyncExecutor = exports.KEPT_BECAUSE = exports.HALT_SUMMARY_PREFIX = void 0;
6
+ exports.RUN_SUMMARY_MARKER = exports.REFUSED_SUMMARY_PREFIX = exports.LaneSyncExecutor = exports.KEPT_BECAUSE = exports.HALT_SUMMARY_PREFIX = void 0;
7
7
  exports.branchMirrorsOtherLaneNote = branchMirrorsOtherLaneNote;
8
8
  exports.branchMirrorsOtherLaneReason = branchMirrorsOtherLaneReason;
9
9
  exports.capEntries = capEntries;
10
+ exports.changedLaneComponents = changedLaneComponents;
10
11
  exports.crossScopeDescription = crossScopeDescription;
11
12
  exports.crossScopeMidFlightHaltReason = crossScopeMidFlightHaltReason;
12
13
  exports.crossScopeRefusal = crossScopeRefusal;
@@ -16,9 +17,11 @@ exports.foreignLaneComponents = foreignLaneComponents;
16
17
  exports.haltCommentBody = haltCommentBody;
17
18
  exports.isProtectedBranch = isProtectedBranch;
18
19
  exports.laneHeadFingerprint = laneHeadFingerprint;
20
+ exports.laneSummaryComponents = laneSummaryComponents;
19
21
  exports.laneSyncPrBody = laneSyncPrBody;
20
22
  exports.racedAdoptionPushSummary = racedAdoptionPushSummary;
21
23
  exports.racedLedgerPushSummary = racedLedgerPushSummary;
24
+ exports.runSummaryCommentBody = runSummaryCommentBody;
22
25
  function _chalk() {
23
26
  const data = _interopRequireDefault(require("chalk"));
24
27
  _chalk = function () {
@@ -147,6 +150,12 @@ function racedLedgerPushSummary(laneName, policyClause = '') {
147
150
  function racedAdoptionPushSummary(laneName) {
148
151
  return racedSummary(laneName, 'lane untouched; adoption ledger commit lost the push race — next run re-plans');
149
152
  }
153
+
154
+ /**
155
+ * An HTML comment embedded in the run-summary comment's body, so `upsertComment` finds and replaces
156
+ * THIS comment on a later run rather than posting a duplicate summary on every push.
157
+ */
158
+ const RUN_SUMMARY_MARKER = exports.RUN_SUMMARY_MARKER = '<!-- bit-ci-sync:run-summary -->';
150
159
  /**
151
160
  * Fingerprint of a lane's content — same primitive as `branchStateFingerprint`, so "equal" means
152
161
  * exactly "the branch records every lane component at the lane's head".
@@ -376,12 +385,9 @@ class LaneSyncExecutor {
376
385
  stateCommit: undefined,
377
386
  bitmap: undefined,
378
387
  hasDevCommits: false,
388
+ stateCommitBundlesSources: false,
379
389
  tipMessage: ''
380
390
  };
381
- if ((0, _syncState().hasSyncMarker)(branchState.tipMessage)) {
382
- logger.console('branch tip is a bit-sync commit; reconciler will no-op unless the lane moved');
383
- }
384
-
385
391
  // Read before any refusal below: every halt must be suppressible by the conflict label, or a
386
392
  // standing problem re-comments on the same PR on every scheduled run.
387
393
  const pr = await this.findPr(branch);
@@ -450,32 +456,25 @@ class LaneSyncExecutor {
450
456
  // merely quotes the marker must not read as one we wrote.
451
457
  const tipIsSyncCommit = (0, _syncState().isSyncAuthoredMessage)(branchState.tipMessage);
452
458
  const hasIndependentHistory = await this.computeHasIndependentHistory(ownership, branchState.stateCommit, defaultBranch);
453
- const branchNamesDifferentLane = await this.computeBranchNamesDifferentLane(lastSyncedHead, branchState.hasDevCommits, mirroredLane, laneIdStr, branch, defaultBranch);
459
+ const branchNamesDifferentLane = await this.computeBranchNamesDifferentLane(lastSyncedHead, branchState.hasDevCommits || branchState.stateCommitBundlesSources, mirroredLane, laneIdStr, branch, defaultBranch);
454
460
  const action = (0, _syncPlanner().planLaneSync)({
455
461
  laneHead,
456
462
  branchExists,
457
463
  lastSyncedHead,
458
464
  hasDevCommits: branchState.hasDevCommits,
465
+ stateCommitBundlesSources: branchState.stateCommitBundlesSources,
459
466
  tipIsSyncCommit,
460
467
  hasIndependentHistory,
461
468
  conflictLabelPresent,
462
469
  branchNamesDifferentLane,
463
470
  ownership
464
471
  });
465
- logger.console(_chalk().default.blue(`${laneName} -> ${action.type} (branch: ${branch}, lane head: ${laneHead?.slice(0, 9) ?? 'none'}, ` + `branch state: ${lastSyncedHead?.slice(0, 9) ?? 'none'}, ` + `dev commits: ${branchState.hasDevCommits}${laneIsGone ? `, branch claim: ${ownership}` : ''})`));
472
+ logger.console(_chalk().default.blue(`${laneName} -> ${action.type} (branch: ${branch}, lane head: ${laneHead?.slice(0, 9) ?? 'none'}, ` + `branch state: ${lastSyncedHead?.slice(0, 9) ?? 'none'}, ` + `dev commits: ${branchState.hasDevCommits}` + `${branchState.stateCommitBundlesSources ? ' (+sources bundled into the state commit)' : ''}` + `${laneIsGone ? `, branch claim: ${ownership}` : ''})`));
466
473
  if (dryRun) {
467
474
  const line = dryRunSummaryLine(laneName, action);
468
475
  logger.console((0, _cli().formatWarningSummary)(`Dry-run: ${line}`));
469
476
  return line;
470
477
  }
471
-
472
- // A pair can be converged at the bit level while the tip still holds unsnapped source edits (a
473
- // single commit that rewrites `.bitmap` AND carries an edit is its own state commit) — say so.
474
- // `laneHead` must be checked explicitly: with no lane and no attribution both fingerprints are
475
- // undefined, and `undefined === undefined` would fire on every ordinary developer branch.
476
- if (action.type === 'noop' && action.reason === 'converged' && !tipIsSyncCommit && laneHead && lastSyncedHead === laneHead) {
477
- logger.console((0, _cli().formatWarningSummary)(`converged on bit state, but ${branch}'s tip is not a bit ci sync commit — any source edits it ` + `carries that were never snapped stay invisible until the next commit on the branch`));
478
- }
479
478
  switch (action.type) {
480
479
  case 'noop':
481
480
  return `${laneName} -> noop (${action.reason})`;
@@ -492,29 +491,20 @@ class LaneSyncExecutor {
492
491
  pr
493
492
  });
494
493
  case 'export-branch':
495
- // The tip is already this reconciler's own commit — it already confirmed everything up to
496
- // it (that is what writing it means), and `hasDevCommits`/`stateCommit` cannot tell a real
497
- // dev commit from one that touches no bit-tracked file (docs, CI config): `stateCommit` is
498
- // derived from `.bitmap`'s content, never from commit messages (sync-state.ts), so a commit
499
- // that leaves `.bitmap` byte-identical never advances it, however many runs re-confirm
500
- // "nothing to snap" on top. Recognizing our own tip here — not by loosening `hasDevCommits`
501
- // itself, which the ownership/retirement path also reads and must stay strict — is what
502
- // makes that settle instead of re-planning `export-branch` forever.
503
- if (tipIsSyncCommit) {
504
- return `${laneName} -> noop (converged; branch tip is already this reconciler's own sync commit)`;
505
- }
506
494
  return this.executeExportBranch({
507
495
  target,
508
496
  laneIdStr,
509
497
  branch,
510
- defaultBranch
498
+ defaultBranch,
499
+ preExportLane: remoteLane
511
500
  });
512
501
  case 'merge-diverged':
513
502
  return this.executeMergeDiverged({
514
503
  target,
515
504
  laneIdStr,
516
505
  branch,
517
- defaultBranch
506
+ defaultBranch,
507
+ preExportLane: remoteLane
518
508
  });
519
509
  case 'adopt-branch':
520
510
  return this.executeAdoptBranch({
@@ -671,23 +661,56 @@ class LaneSyncExecutor {
671
661
  }
672
662
 
673
663
  /**
674
- * Push the branch's dev commits back onto the lane: snap+export the branch's tree, then commit the
675
- * resulting `.bitmap` back onto the branch so the next run sees the two sides as converged.
664
+ * Push the branch's dev commits back onto the lane. Probe first, mutate second: like adoption, land
665
+ * the lane in the workspace keeping the BRANCH's files, then ask `bit status` whether the branch's
666
+ * tree holds anything the lane is missing. Nothing → converged, and nothing is written or pushed —
667
+ * which is why a suspected-work plan can repeat run after run without a memo, and why no commit
668
+ * message is ever consulted to suppress it. Something → snap + export, then commit the resulting
669
+ * `.bitmap` back onto the branch so the next run reads the two sides as converged.
676
670
  */
677
671
  async executeExportBranch({
678
672
  target,
679
673
  laneIdStr,
680
674
  branch,
681
- defaultBranch
675
+ defaultBranch,
676
+ preExportLane
682
677
  }) {
683
678
  const {
684
679
  logger
685
680
  } = this.deps;
686
681
  const laneName = target.name;
687
682
  logger.console(_chalk().default.blue(`Exporting branch ${branch} onto lane ${laneIdStr}`));
688
- const message = await this.lastNonSyncCommitMessage(branch, defaultBranch);
689
683
  await this.checkoutFromRemote(branch, `origin/${branch}`);
690
684
  try {
685
+ const switchErr = await this.materializeLane(laneIdStr, false);
686
+ if (switchErr) {
687
+ return await this.executeHalt({
688
+ laneName,
689
+ laneIdStr,
690
+ branch,
691
+ reason: `failed to check branch ${branch} against lane ${laneIdStr} before exporting: ${switchErr.message}`,
692
+ pr: await this.findPr(branch)
693
+ });
694
+ }
695
+ // The probe's "clean" answer means converged only against the lane the PLAN read. A lane that
696
+ // moved in between must re-plan (merge-diverged owns that shape): comparing the branch's files
697
+ // with the newer lane would misread the lane's own advance as branch work, and the export would
698
+ // put the older content on top of a concurrent developer's push. The materialize above just
699
+ // fetched, so this read and the status below see the same lane.
700
+ if (preExportLane) {
701
+ const laneNow = await this.getRemoteLane(target);
702
+ const movedTo = laneNow ? laneHeadFingerprint(laneNow.components) : undefined;
703
+ if (movedTo !== laneHeadFingerprint(preExportLane.components)) {
704
+ return `${laneName} -> noop (lane ${laneIdStr} moved while this run was planning; the next run re-plans)`;
705
+ }
706
+ }
707
+ if (!(await this.deps.ci.hasUnsyncedWorkChanges())) {
708
+ logger.console(_chalk().default.green(`Branch ${branch}'s tree holds nothing lane ${laneIdStr} is missing`));
709
+ return `${laneName} -> noop (converged)`;
710
+ }
711
+
712
+ // Read only once the probe found work: it walks up to 200 commits, wasted on a converged probe.
713
+ const message = await this.lastNonSyncCommitMessage(branch, defaultBranch);
691
714
  const exported = await this.snapAndExportOntoLane(laneIdStr, message);
692
715
  if (exported.status === 'error') {
693
716
  // Halt rather than propagate: one lane's failed snap/export must not abort the lanes after it.
@@ -699,6 +722,12 @@ class LaneSyncExecutor {
699
722
  pr: await this.findPr(branch)
700
723
  });
701
724
  }
725
+ if (exported.status === 'noop') {
726
+ // `bit status` saw a difference the snap did not record. Nothing reached the lane and nothing
727
+ // is pushed, so the next run re-reads the identical state — visible-but-repeating beats a
728
+ // ledger commit asserting a convergence the status read just denied.
729
+ return `${laneName} -> noop (bit status reported unsynced changes but the snap found nothing to record)`;
730
+ }
702
731
  const recorded = await this.recordLaneHeadOnBranch(target, laneIdStr, branch);
703
732
  if (recorded.status === 'raced') return racedLedgerPushSummary(laneName);
704
733
  if (recorded.status === 'unreadable') {
@@ -710,6 +739,17 @@ class LaneSyncExecutor {
710
739
  pr: await this.findPr(branch)
711
740
  });
712
741
  }
742
+
743
+ // Best-effort: a comment failure must never undo a git/lane pair that already converged.
744
+ await this.postRunSummaryComment({
745
+ target,
746
+ laneIdStr,
747
+ branch,
748
+ branchTipSha: recorded.branchTipSha,
749
+ laneHead: recorded.laneHead,
750
+ preComponents: laneSummaryComponents(preExportLane),
751
+ postComponents: laneSummaryComponents(recorded.remoteLane)
752
+ });
713
753
  return `${laneName} -> export-branch (lane ${laneIdStr} @ ${recorded.laneHead.slice(0, 9)}, ` + `branch ${branch} updated)`;
714
754
  } finally {
715
755
  await this.restoreWorkspace(defaultBranch);
@@ -793,7 +833,8 @@ class LaneSyncExecutor {
793
833
  target,
794
834
  laneIdStr,
795
835
  branch,
796
- defaultBranch
836
+ defaultBranch,
837
+ preExportLane
797
838
  }) {
798
839
  const {
799
840
  logger
@@ -812,6 +853,17 @@ class LaneSyncExecutor {
812
853
  // lane pointer and the merge base, and a stale local branch must never leak into the result.
813
854
  await this.checkoutFromRemote(branch, `origin/${branch}`);
814
855
 
856
+ // Same freshness rule as the export probe: the plan's inputs must still describe the world.
857
+ // A lane that moved since planning re-plans — the next run merges the newer head — rather
858
+ // than proceeding on a divergence assessment the planner never made.
859
+ if (preExportLane) {
860
+ const laneNow = await this.getRemoteLane(target);
861
+ const movedTo = laneNow ? laneHeadFingerprint(laneNow.components) : undefined;
862
+ if (movedTo !== laneHeadFingerprint(preExportLane.components)) {
863
+ return `${laneName} -> noop (lane ${laneIdStr} moved while this run was planning; the next run re-plans)`;
864
+ }
865
+ }
866
+
815
867
  // ---- step 1: merge the lane's snaps into the branch's working tree -----------------------
816
868
  const merge = await this.mergeLaneIntoBranchWorkingTree(laneIdStr);
817
869
  if (merge.error) {
@@ -860,7 +912,21 @@ class LaneSyncExecutor {
860
912
  if (recorded.status === 'unreadable') {
861
913
  return await halt(`lane ${laneIdStr} could not be read back from the remote after the merge export`);
862
914
  }
863
- return `${laneName} -> merge-diverged (${policyClause}merged lane into branch, then exported; lane ${laneIdStr} @ ` + `${recorded.laneHead.slice(0, 9)}, branch ${branch} updated)`;
915
+ // The summary comment belongs only to runs that exported — same as the export path, and a
916
+ // nothing-new merge (the branch had nothing of its own) must not claim it exported.
917
+ if (exported.status === 'exported') {
918
+ await this.postRunSummaryComment({
919
+ target,
920
+ laneIdStr,
921
+ branch,
922
+ branchTipSha: recorded.branchTipSha,
923
+ laneHead: recorded.laneHead,
924
+ preComponents: laneSummaryComponents(preExportLane),
925
+ postComponents: laneSummaryComponents(recorded.remoteLane)
926
+ });
927
+ }
928
+ const exportClause = exported.status === 'exported' ? 'then exported' : 'the merge left nothing new to export';
929
+ return `${laneName} -> merge-diverged (${policyClause}merged lane into branch, ${exportClause}; lane ${laneIdStr} @ ` + `${recorded.laneHead.slice(0, 9)}, branch ${branch} updated)`;
864
930
  } catch (e) {
865
931
  // Something unforeseen — halt anyway: no single lane may abort the rest of the run.
866
932
  return await halt(`unexpected failure while reconciling diverged lane ${laneIdStr} with branch ${branch}: ${e?.message || e}`);
@@ -908,7 +974,8 @@ class LaneSyncExecutor {
908
974
  * so any earlier fingerprint is stale), commit — crucially the `.bitmap` the snap rewrote — and push.
909
975
  * `unreadable` means the lane could no longer be read back, in which case the caller halts;
910
976
  * `raced` means a concurrent run's push already got there first, in which case the caller reports
911
- * a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`.
977
+ * a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`. The re-queried lane is
978
+ * also the caller's only cheap source of POST-export component versions.
912
979
  */
913
980
  async recordLaneHeadOnBranch(target, laneIdStr, branch, opts = {}) {
914
981
  const remoteLane = await this.getRemoteLane(target);
@@ -922,7 +989,9 @@ class LaneSyncExecutor {
922
989
  };
923
990
  return {
924
991
  status: 'ok',
925
- laneHead
992
+ laneHead,
993
+ remoteLane,
994
+ branchTipSha: pushResult.pushedSha
926
995
  };
927
996
  }
928
997
 
@@ -1052,8 +1121,8 @@ class LaneSyncExecutor {
1052
1121
  }
1053
1122
 
1054
1123
  /** Gates `differentLaneStillClaims` to the planner's first-contact path, the only one that reads it. */
1055
- async computeBranchNamesDifferentLane(lastSyncedHead, hasDevCommits, mirroredLane, laneIdStr, branch, defaultBranch) {
1056
- if (lastSyncedHead || !hasDevCommits) return false;
1124
+ async computeBranchNamesDifferentLane(lastSyncedHead, mayCarryWork, mirroredLane, laneIdStr, branch, defaultBranch) {
1125
+ if (lastSyncedHead || !mayCarryWork) return false;
1057
1126
  return this.differentLaneStillClaims(mirroredLane, laneIdStr, branch, defaultBranch);
1058
1127
  }
1059
1128
 
@@ -1324,6 +1393,50 @@ class LaneSyncExecutor {
1324
1393
  }
1325
1394
  }
1326
1395
 
1396
+ /**
1397
+ * Post (or update) ONE maintained comment on the branch's PR: what this export did to the scope —
1398
+ * the components it snapped and the synced branch/lane anchors — information the branch's git diff
1399
+ * cannot show, since the snap step alone can move dependency ranges the PR's files never touched.
1400
+ * Best-effort throughout: no configured git host, no open PR yet, or a provider that doesn't
1401
+ * implement `upsertComment` are all normal states, not failures — the git/lane pair already
1402
+ * converged without this comment. A comment API error warns and never fails the run.
1403
+ */
1404
+ async postRunSummaryComment({
1405
+ target,
1406
+ laneIdStr,
1407
+ branch,
1408
+ branchTipSha,
1409
+ laneHead,
1410
+ preComponents,
1411
+ postComponents
1412
+ }) {
1413
+ const {
1414
+ gitHost,
1415
+ logger
1416
+ } = this.deps;
1417
+ if (!gitHost?.upsertComment) return;
1418
+ const pr = await this.findPr(branch);
1419
+ if (!pr) return;
1420
+ // Same URL shape as the lane-sync PR body (line ~1249): the scope that HOSTS the lane, not
1421
+ // necessarily `defaultScope`.
1422
+ const laneUrl = `https://${(0, _legacy().getCloudDomain)()}/${target.hostScope.replace('.', '/')}/~lane/${target.name}`;
1423
+ // Body construction sits INSIDE the guard: `currentBranchTip` is a git refetch and can reject,
1424
+ // and an export that already succeeded must never be failed by its own progress report.
1425
+ try {
1426
+ const body = runSummaryCommentBody({
1427
+ laneIdStr,
1428
+ laneUrl,
1429
+ branch,
1430
+ branchTipSha,
1431
+ laneHead,
1432
+ changed: changedLaneComponents(preComponents, postComponents)
1433
+ });
1434
+ await gitHost.upsertComment(pr.number, RUN_SUMMARY_MARKER, body);
1435
+ } catch (e) {
1436
+ logger.consoleWarning(`Could not post the run-summary comment on ${branch}'s PR: ${e?.message || e}`);
1437
+ }
1438
+ }
1439
+
1327
1440
  /**
1328
1441
  * The subject of the newest commit on the branch that isn't one of our own sync commits — it's
1329
1442
  * the developer's own description of the change, and becomes the snap message on the lane.
@@ -1353,6 +1466,9 @@ class LaneSyncExecutor {
1353
1466
  await (0, _gitOps().commitWithIdentity)(message, {
1354
1467
  extraArgs: ['--allow-empty']
1355
1468
  });
1469
+ // The sha of the commit this run is about to push — the summary anchors on it, never on a
1470
+ // refetch of the mutable remote tip (a concurrent dev push must not read as the synced state).
1471
+ const pushedSha = (await _git().git.revparse(['HEAD'])).trim() || undefined;
1356
1472
  try {
1357
1473
  // `HEAD:refs/heads/<branch>`: a full-ref destination cannot be resolved as a tag or
1358
1474
  // reinterpreted — the configured branch name is user input (see `sync-config.ts`).
@@ -1376,7 +1492,8 @@ class LaneSyncExecutor {
1376
1492
  }
1377
1493
  this.deps.logger.console(_chalk().default.green(`Pushed ${branch}`));
1378
1494
  return {
1379
- raced: false
1495
+ raced: false,
1496
+ pushedSha
1380
1497
  };
1381
1498
  }
1382
1499
 
@@ -1492,6 +1609,41 @@ function laneSyncPrBody({
1492
1609
  return [`Mirrors the Bit lane [\`${laneIdStr}\`](${laneUrl}) onto \`${branch}\`.`, '', `- lane: ${laneUrl}`, `- lane head: \`${laneHead}\``, '', `Components on the lane (${components.length}):`, listed || '_none_', '', 'This PR is maintained by `bit ci sync` — push to the branch to send changes back to the lane.'].join('\n');
1493
1610
  }
1494
1611
 
1612
+ /**
1613
+ * The lane components this export actually changed: new on the lane, or moved to a new head. Diffed
1614
+ * by id against the lane's content BEFORE the export — `LaneData.head` is the snap hash bit assigns a
1615
+ * lane component, so comparing it (not looking at git) is the only way to name what the snap did.
1616
+ */
1617
+ /**
1618
+ * The lane entries the run SUMMARY diffs: the visible components plus the hidden `updateDependents`
1619
+ * (a cascade export moves those heads too, and a summary reading "nothing changed" over one would be
1620
+ * false). Summary-only — the fingerprint and the foreign-scope check keep reading `components` alone.
1621
+ */
1622
+ function laneSummaryComponents(lane) {
1623
+ return lane ? [...lane.components, ...(lane.updateDependents ?? [])] : [];
1624
+ }
1625
+ function changedLaneComponents(before, after) {
1626
+ const beforeHeads = new Map((before ?? []).map(comp => [comp.id.toStringWithoutVersion(), comp.head]));
1627
+ return after.filter(comp => beforeHeads.get(comp.id.toStringWithoutVersion()) !== comp.head);
1628
+ }
1629
+
1630
+ /**
1631
+ * What a sync run did to the scope — information the branch's own git diff cannot show, since the
1632
+ * snap step can move a component's recorded version with no file in the PR ever changing. Kept to one
1633
+ * maintained comment (`RUN_SUMMARY_MARKER` + `upsertComment`) rather than one post per push.
1634
+ */
1635
+ function runSummaryCommentBody({
1636
+ laneIdStr,
1637
+ laneUrl,
1638
+ branch,
1639
+ branchTipSha,
1640
+ laneHead,
1641
+ changed
1642
+ }) {
1643
+ const listed = capEntries(changed.map(comp => `- \`${comp.id.toStringWithoutVersion()}\` @ \`${comp.head.slice(0, 9)}\``), '- ').join('\n');
1644
+ return [RUN_SUMMARY_MARKER, `**bit ci sync** synced this branch with lane [\`${laneIdStr}\`](${laneUrl}).`, '', `Components snapped (${changed.length}):`, listed || '_none — nothing on the lane changed this run_', '', `- branch: \`${branch}\`${branchTipSha ? ` @ \`${branchTipSha.slice(0, 9)}\`` : ''}`, `- lane: [\`${laneIdStr}\`](${laneUrl}) @ \`${laneHead.slice(0, 9)}\``].join('\n');
1645
+ }
1646
+
1495
1647
  /**
1496
1648
  * Single-quote a value interpolated into a copy-pasteable shell command. Lane names may contain `$` and
1497
1649
  * `!`, and a configured branch name may contain anything git accepts as a ref — including a quote.