@markjaquith/agency 2.69.0 → 2.69.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -575,6 +575,8 @@ process.stdout.write(${JSON.stringify(JSON.stringify(record))})
|
|
|
575
575
|
})
|
|
576
576
|
|
|
577
577
|
test("materializes missing workspaces but leaves branch conflicts unresolved", async () => {
|
|
578
|
+
await Bun.write(join(root, "bin", "gh"), "#!/bin/sh\nprintf '[]\\n'\n")
|
|
579
|
+
await chmod(join(root, "bin", "gh"), 0o755)
|
|
578
580
|
for (const [id, branch] of [
|
|
579
581
|
["missing", "feat/missing"],
|
|
580
582
|
["conflict", "feat/conflict"],
|
|
@@ -649,6 +651,158 @@ process.stdout.write(${JSON.stringify(JSON.stringify(record))})
|
|
|
649
651
|
).toBe(false)
|
|
650
652
|
})
|
|
651
653
|
|
|
654
|
+
test("reconciles a recorded merged PR without materializing an absent checkout", async () => {
|
|
655
|
+
await runTestEffect(
|
|
656
|
+
TaskService.pipe(
|
|
657
|
+
Effect.flatMap((service) =>
|
|
658
|
+
service.create(
|
|
659
|
+
{
|
|
660
|
+
id: "recorded-merged",
|
|
661
|
+
ticketUrl: null,
|
|
662
|
+
repo: "agency",
|
|
663
|
+
branch: "feat/example",
|
|
664
|
+
base: "main",
|
|
665
|
+
},
|
|
666
|
+
root,
|
|
667
|
+
),
|
|
668
|
+
),
|
|
669
|
+
),
|
|
670
|
+
)
|
|
671
|
+
await git(
|
|
672
|
+
["remote", "set-url", "origin", "git@github.com:example/agency.git"],
|
|
673
|
+
join(root, "repos/agency"),
|
|
674
|
+
)
|
|
675
|
+
await runTestEffect(
|
|
676
|
+
PullRequestService.pipe(
|
|
677
|
+
Effect.flatMap((service) =>
|
|
678
|
+
service.setUrl(
|
|
679
|
+
"recorded-merged",
|
|
680
|
+
undefined,
|
|
681
|
+
"https://github.com/example/agency/pull/42",
|
|
682
|
+
root,
|
|
683
|
+
),
|
|
684
|
+
),
|
|
685
|
+
),
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
const applied = await runTestEffect(
|
|
689
|
+
SyncService.pipe(
|
|
690
|
+
Effect.flatMap((service) =>
|
|
691
|
+
service.reconcile({
|
|
692
|
+
cwd: root,
|
|
693
|
+
apply: true,
|
|
694
|
+
taskId: "recorded-merged",
|
|
695
|
+
}),
|
|
696
|
+
),
|
|
697
|
+
),
|
|
698
|
+
)
|
|
699
|
+
expect(applied.changes.map((change) => change.kind)).toEqual([
|
|
700
|
+
"record-pr",
|
|
701
|
+
"mark-done",
|
|
702
|
+
])
|
|
703
|
+
expect(applied.executions[0]?.checkouts).toEqual([])
|
|
704
|
+
expect(
|
|
705
|
+
await Bun.file(join(root, "tasks/recorded-merged/code/agency")).exists(),
|
|
706
|
+
).toBe(false)
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
test("reconciles a uniquely discovered merged PR without materializing", async () => {
|
|
710
|
+
await runTestEffect(
|
|
711
|
+
TaskService.pipe(
|
|
712
|
+
Effect.flatMap((service) =>
|
|
713
|
+
service.create(
|
|
714
|
+
{
|
|
715
|
+
id: "discovered-merged",
|
|
716
|
+
ticketUrl: null,
|
|
717
|
+
repo: "agency",
|
|
718
|
+
branch: "feat/example",
|
|
719
|
+
base: "main",
|
|
720
|
+
},
|
|
721
|
+
root,
|
|
722
|
+
),
|
|
723
|
+
),
|
|
724
|
+
),
|
|
725
|
+
)
|
|
726
|
+
await git(
|
|
727
|
+
["remote", "set-url", "origin", "git@github.com:example/agency.git"],
|
|
728
|
+
join(root, "repos/agency"),
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
const applied = await runTestEffect(
|
|
732
|
+
SyncService.pipe(
|
|
733
|
+
Effect.flatMap((service) =>
|
|
734
|
+
service.reconcile({
|
|
735
|
+
cwd: root,
|
|
736
|
+
apply: true,
|
|
737
|
+
taskId: "discovered-merged",
|
|
738
|
+
}),
|
|
739
|
+
),
|
|
740
|
+
),
|
|
741
|
+
)
|
|
742
|
+
expect(applied.changes.map((change) => change.kind)).toEqual([
|
|
743
|
+
"record-pr",
|
|
744
|
+
"mark-done",
|
|
745
|
+
])
|
|
746
|
+
expect(applied.executions[0]?.checkouts).toEqual([])
|
|
747
|
+
expect(
|
|
748
|
+
await Bun.file(
|
|
749
|
+
join(root, "tasks/discovered-merged/code/agency"),
|
|
750
|
+
).exists(),
|
|
751
|
+
).toBe(false)
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
test("materializes when discovered PR evidence is ambiguous", async () => {
|
|
755
|
+
await runTestEffect(
|
|
756
|
+
TaskService.pipe(
|
|
757
|
+
Effect.flatMap((service) =>
|
|
758
|
+
service.create(
|
|
759
|
+
{
|
|
760
|
+
id: "ambiguous",
|
|
761
|
+
ticketUrl: null,
|
|
762
|
+
repo: "agency",
|
|
763
|
+
branch: "feat/example",
|
|
764
|
+
base: "main",
|
|
765
|
+
},
|
|
766
|
+
root,
|
|
767
|
+
),
|
|
768
|
+
),
|
|
769
|
+
),
|
|
770
|
+
)
|
|
771
|
+
const gh = await Bun.file(join(root, "bin", "gh")).text()
|
|
772
|
+
await Bun.write(
|
|
773
|
+
join(root, "bin", "gh"),
|
|
774
|
+
gh
|
|
775
|
+
.replace('[{"number":42', '[{"number":42')
|
|
776
|
+
.replace(
|
|
777
|
+
"]\nJSON\n",
|
|
778
|
+
`,{\"number\":43,\"state\":\"MERGED\",\"title\":\"Ship again\",\"isDraft\":false,\"headRefName\":\"feat/example\",\"baseRefName\":\"main\",\"headRepository\":{\"nameWithOwner\":\"example/agency\"},\"url\":\"https://github.com/example/agency/pull/43\",\"mergedAt\":\"2100-01-01T00:00:00Z\",\"mergeCommit\":{\"oid\":\"def\"},\"mergeable\":\"MERGEABLE\"}]\nJSON\n`,
|
|
779
|
+
),
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
const applied = await runTestEffect(
|
|
783
|
+
SyncService.pipe(
|
|
784
|
+
Effect.flatMap((service) =>
|
|
785
|
+
service.reconcile({
|
|
786
|
+
cwd: root,
|
|
787
|
+
apply: true,
|
|
788
|
+
taskId: "ambiguous",
|
|
789
|
+
}),
|
|
790
|
+
),
|
|
791
|
+
),
|
|
792
|
+
)
|
|
793
|
+
expect(applied.unresolved).toContainEqual(
|
|
794
|
+
expect.objectContaining({ kind: "multiple-prs" }),
|
|
795
|
+
)
|
|
796
|
+
expect(applied.changes).toContainEqual(
|
|
797
|
+
expect.objectContaining({ kind: "materialize-workspace" }),
|
|
798
|
+
)
|
|
799
|
+
expect(
|
|
800
|
+
await Bun.file(
|
|
801
|
+
join(root, "tasks/ambiguous/code/agency/README.md"),
|
|
802
|
+
).text(),
|
|
803
|
+
).toBe("example\n")
|
|
804
|
+
})
|
|
805
|
+
|
|
652
806
|
test("leaves a missing checkout registration unresolved", async () => {
|
|
653
807
|
await runTestEffect(
|
|
654
808
|
TaskService.pipe(
|
|
@@ -162,6 +162,46 @@ const commandErrorSummary = (stderr: string, fallback: string) =>
|
|
|
162
162
|
.map((line) => line.trim())
|
|
163
163
|
.find(Boolean) ?? fallback
|
|
164
164
|
|
|
165
|
+
interface PullRequestQuery {
|
|
166
|
+
readonly remoteUrl: string | null
|
|
167
|
+
readonly remoteRepository: string
|
|
168
|
+
readonly result:
|
|
169
|
+
| {
|
|
170
|
+
readonly exitCode: number
|
|
171
|
+
readonly stdout: string
|
|
172
|
+
readonly stderr: string
|
|
173
|
+
}
|
|
174
|
+
| undefined
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const mergedPullRequestFromGitHub = (
|
|
178
|
+
data: ExecutionData,
|
|
179
|
+
query: PullRequestQuery | undefined,
|
|
180
|
+
) => {
|
|
181
|
+
if (!query?.result || query.result.exitCode !== 0) return null
|
|
182
|
+
const existing = data.pr ? normalizePullRequestRecord(data.pr) : null
|
|
183
|
+
const details = existing
|
|
184
|
+
? [parseJson<Record<string, unknown>>(query.result.stdout, {})]
|
|
185
|
+
: parseJson<Record<string, unknown>[]>(query.result.stdout, []).filter(
|
|
186
|
+
(item) =>
|
|
187
|
+
item.headRefName === data.branch && item.baseRefName === data.base,
|
|
188
|
+
)
|
|
189
|
+
if (details.length !== 1) return null
|
|
190
|
+
const current = recordFromGitHubJson(details[0]!)
|
|
191
|
+
if (
|
|
192
|
+
current.merged !== true ||
|
|
193
|
+
current.headRepository?.toLowerCase() !==
|
|
194
|
+
query.remoteRepository.toLowerCase() ||
|
|
195
|
+
current.headBranch !== data.branch ||
|
|
196
|
+
current.baseRepository?.toLowerCase() !==
|
|
197
|
+
current.repository.toLowerCase() ||
|
|
198
|
+
current.baseBranch !== data.base
|
|
199
|
+
) {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
return current
|
|
203
|
+
}
|
|
204
|
+
|
|
165
205
|
export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
166
206
|
sync: () => ({
|
|
167
207
|
reconcile: (
|
|
@@ -463,6 +503,14 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
463
503
|
let revision = record.revision
|
|
464
504
|
const codePath = join(dirname(record.path), "code")
|
|
465
505
|
const checkoutStates: CheckoutState[] = []
|
|
506
|
+
const query = prQueries.get(record.key)
|
|
507
|
+
const remoteMergedPr = config.delivery
|
|
508
|
+
? null
|
|
509
|
+
: mergedPullRequestFromGitHub(data, query)
|
|
510
|
+
const skipCheckoutReconciliation =
|
|
511
|
+
remoteMergedPr !== null &&
|
|
512
|
+
data.claim?.state !== "active" &&
|
|
513
|
+
!data.completion
|
|
466
514
|
let materialize = false
|
|
467
515
|
let workspaceConflict = false
|
|
468
516
|
const declared: readonly (
|
|
@@ -473,7 +521,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
473
521
|
...(data.repos ?? []),
|
|
474
522
|
]
|
|
475
523
|
|
|
476
|
-
for (const checkout of declared) {
|
|
524
|
+
for (const checkout of skipCheckoutReconciliation ? [] : declared) {
|
|
477
525
|
const repositoryPath = join(root, "repos", checkout.repo)
|
|
478
526
|
const checkoutPath = join(codePath, checkout.repo)
|
|
479
527
|
const kind = "branch" in checkout ? "writable" : "reference"
|
|
@@ -811,7 +859,11 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
811
859
|
state: "none",
|
|
812
860
|
}
|
|
813
861
|
let prConflict = false
|
|
814
|
-
|
|
862
|
+
if (!query) {
|
|
863
|
+
return yield* new SyncError({
|
|
864
|
+
message: `Missing pull request query for '${record.key}'`,
|
|
865
|
+
})
|
|
866
|
+
}
|
|
815
867
|
const { remoteUrl, remoteRepository } = query
|
|
816
868
|
|
|
817
869
|
if (config.delivery && !remoteUrl) {
|