@markjaquith/agency 2.61.1 → 2.61.2
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
2
|
import { Effect } from "effect"
|
|
3
|
-
import { mkdir } from "node:fs/promises"
|
|
3
|
+
import { chmod, mkdir } from "node:fs/promises"
|
|
4
4
|
import { join } from "node:path"
|
|
5
5
|
import { TaskService } from "../services/TaskService"
|
|
6
6
|
import {
|
|
@@ -99,6 +99,44 @@ describe("sync command", () => {
|
|
|
99
99
|
})
|
|
100
100
|
})
|
|
101
101
|
|
|
102
|
+
test("groups repeated human-readable warnings by affected target", async () => {
|
|
103
|
+
await runTestEffect(
|
|
104
|
+
TaskService.pipe(
|
|
105
|
+
Effect.flatMap((service) =>
|
|
106
|
+
service.create(
|
|
107
|
+
{
|
|
108
|
+
id: "second",
|
|
109
|
+
ticketUrl: null,
|
|
110
|
+
repo: "agency",
|
|
111
|
+
branch: "task/second",
|
|
112
|
+
base: "main",
|
|
113
|
+
},
|
|
114
|
+
root,
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
const originalPath = process.env.PATH
|
|
120
|
+
const bin = join(root, "bin")
|
|
121
|
+
await mkdir(bin)
|
|
122
|
+
await Bun.write(
|
|
123
|
+
join(bin, "gh"),
|
|
124
|
+
'#!/bin/sh\necho "provider unavailable" >&2\nexit 1\n',
|
|
125
|
+
)
|
|
126
|
+
await chmod(join(bin, "gh"), 0o755)
|
|
127
|
+
process.env.PATH = `${bin}:${originalPath}`
|
|
128
|
+
try {
|
|
129
|
+
const logs = await captureLogs(() =>
|
|
130
|
+
runTestEffect(sync({ cwd: root, dryRun: true })),
|
|
131
|
+
)
|
|
132
|
+
expect(
|
|
133
|
+
logs.filter((line) => line.includes("provider unavailable")),
|
|
134
|
+
).toEqual(["Warning 'task:example', 'task:second': provider unavailable"])
|
|
135
|
+
} finally {
|
|
136
|
+
process.env.PATH = originalPath
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
102
140
|
test("reports human-readable progress without polluting JSON output", async () => {
|
|
103
141
|
const updates: string[] = []
|
|
104
142
|
const progress: Progress = {
|
package/src/commands/sync.ts
CHANGED
|
@@ -8,6 +8,27 @@ interface SyncCommandOptions extends BaseCommandOptions {
|
|
|
8
8
|
readonly dryRun?: boolean
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
interface Notice {
|
|
12
|
+
readonly kind: string
|
|
13
|
+
readonly target: string
|
|
14
|
+
readonly message: string
|
|
15
|
+
readonly action?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const groupedNotices = <T extends Notice>(notices: readonly T[]) => {
|
|
19
|
+
const groups = new Map<string, { notice: T; targets: string[] }>()
|
|
20
|
+
for (const notice of notices) {
|
|
21
|
+
const key = JSON.stringify([notice.kind, notice.message, notice.action])
|
|
22
|
+
const group = groups.get(key)
|
|
23
|
+
if (group) group.targets.push(notice.target)
|
|
24
|
+
else groups.set(key, { notice, targets: [notice.target] })
|
|
25
|
+
}
|
|
26
|
+
return groups.values()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const formatTargets = (targets: readonly string[]) =>
|
|
30
|
+
targets.map((target) => `'${target}'`).join(", ")
|
|
31
|
+
|
|
11
32
|
export const sync = (
|
|
12
33
|
options: SyncCommandOptions = {},
|
|
13
34
|
progress: Progress = createProgress({
|
|
@@ -62,9 +83,11 @@ export const sync = (
|
|
|
62
83
|
`${change.status === "applied" ? "Applied" : "Planned"} ${change.kind} '${change.target}': ${change.message}`,
|
|
63
84
|
)
|
|
64
85
|
}
|
|
65
|
-
for (const warning of
|
|
86
|
+
for (const { notice: warning, targets } of groupedNotices(
|
|
87
|
+
result.warnings,
|
|
88
|
+
)) {
|
|
66
89
|
log(
|
|
67
|
-
`Warning
|
|
90
|
+
`Warning ${formatTargets(targets)}: ${warning.message}${warning.action ? `. ${warning.action}` : ""}`,
|
|
68
91
|
)
|
|
69
92
|
}
|
|
70
93
|
for (const issue of result.repositories.unresolved) {
|
|
@@ -72,9 +95,11 @@ export const sync = (
|
|
|
72
95
|
`Unresolved repository '${issue.alias}': ${issue.message}. ${issue.action}`,
|
|
73
96
|
)
|
|
74
97
|
}
|
|
75
|
-
for (const issue of
|
|
98
|
+
for (const { notice: issue, targets } of groupedNotices(
|
|
99
|
+
result.unresolved,
|
|
100
|
+
)) {
|
|
76
101
|
log(
|
|
77
|
-
`Unresolved
|
|
102
|
+
`Unresolved ${formatTargets(targets)}: ${issue.message}${issue.action ? `. ${issue.action}` : ""}`,
|
|
78
103
|
)
|
|
79
104
|
}
|
|
80
105
|
if (
|
|
@@ -64,14 +64,17 @@ case "$*" in
|
|
|
64
64
|
*mergeable*) ;;
|
|
65
65
|
*) echo "mergeable field was not requested" >&2; exit 2 ;;
|
|
66
66
|
esac
|
|
67
|
+
case "$*" in
|
|
68
|
+
*baseRepository*) echo "unsupported baseRepository field was requested" >&2; exit 3 ;;
|
|
69
|
+
esac
|
|
67
70
|
if [ "$2" = "view" ]; then
|
|
68
71
|
cat <<'JSON'
|
|
69
|
-
{"number":42,"state":"MERGED","title":"Ship","isDraft":false,"headRefName":"feat/example","baseRefName":"main","headRepository":{"nameWithOwner":"example/agency"},"
|
|
72
|
+
{"number":42,"state":"MERGED","title":"Ship","isDraft":false,"headRefName":"feat/example","baseRefName":"main","headRepository":{"nameWithOwner":"example/agency"},"url":"https://github.com/example/agency/pull/42","mergedAt":"2100-01-01T00:00:00Z","mergeCommit":{"oid":"abc"},"mergeable":"MERGEABLE"}
|
|
70
73
|
JSON
|
|
71
74
|
exit 0
|
|
72
75
|
fi
|
|
73
76
|
cat <<'JSON'
|
|
74
|
-
[{"number":42,"state":"MERGED","title":"Ship","isDraft":false,"headRefName":"feat/example","baseRefName":"main","headRepository":{"nameWithOwner":"example/agency"},"
|
|
77
|
+
[{"number":42,"state":"MERGED","title":"Ship","isDraft":false,"headRefName":"feat/example","baseRefName":"main","headRepository":{"nameWithOwner":"example/agency"},"url":"https://github.com/example/agency/pull/42","mergedAt":"2100-01-01T00:00:00Z","mergeCommit":{"oid":"abc"},"mergeable":"MERGEABLE"}]
|
|
75
78
|
JSON
|
|
76
79
|
`,
|
|
77
80
|
)
|
|
@@ -172,6 +175,7 @@ pr: null
|
|
|
172
175
|
)
|
|
173
176
|
const invocation = await Bun.file(capture).text()
|
|
174
177
|
expect(invocation).toContain("--repo\n")
|
|
178
|
+
expect(invocation).not.toContain("baseRepository")
|
|
175
179
|
expect(invocation).toContain("GIT_DIR=")
|
|
176
180
|
expect(invocation).toContain(".jj")
|
|
177
181
|
|
|
@@ -1013,4 +1017,44 @@ exit 9
|
|
|
1013
1017
|
),
|
|
1014
1018
|
).toEqual([])
|
|
1015
1019
|
})
|
|
1020
|
+
|
|
1021
|
+
test("keeps pull request query failures concise", async () => {
|
|
1022
|
+
await runTestEffect(
|
|
1023
|
+
TaskService.pipe(
|
|
1024
|
+
Effect.flatMap((service) =>
|
|
1025
|
+
service.create(
|
|
1026
|
+
{
|
|
1027
|
+
id: "unavailable",
|
|
1028
|
+
ticketUrl: null,
|
|
1029
|
+
repo: "agency",
|
|
1030
|
+
branch: "feat/unavailable",
|
|
1031
|
+
base: "main",
|
|
1032
|
+
},
|
|
1033
|
+
root,
|
|
1034
|
+
),
|
|
1035
|
+
),
|
|
1036
|
+
),
|
|
1037
|
+
)
|
|
1038
|
+
await Bun.write(
|
|
1039
|
+
join(root, "bin", "gh"),
|
|
1040
|
+
`#!/bin/sh
|
|
1041
|
+
echo "Unknown JSON field: unsupported" >&2
|
|
1042
|
+
echo "Available fields:" >&2
|
|
1043
|
+
echo " additions" >&2
|
|
1044
|
+
exit 1
|
|
1045
|
+
`,
|
|
1046
|
+
)
|
|
1047
|
+
await chmod(join(root, "bin", "gh"), 0o755)
|
|
1048
|
+
|
|
1049
|
+
const result = await runTestEffect(
|
|
1050
|
+
SyncService.pipe(
|
|
1051
|
+
Effect.flatMap((service) => service.reconcile({ cwd: root })),
|
|
1052
|
+
),
|
|
1053
|
+
)
|
|
1054
|
+
expect(result.warnings).toContainEqual({
|
|
1055
|
+
kind: "pr-discovery-unavailable",
|
|
1056
|
+
target: "task:unavailable",
|
|
1057
|
+
message: "Unknown JSON field: unsupported",
|
|
1058
|
+
})
|
|
1059
|
+
})
|
|
1016
1060
|
})
|
|
@@ -153,6 +153,15 @@ const isCommitId = (ref: string) => /^[0-9a-f]{40,64}$/i.test(ref)
|
|
|
153
153
|
const originRef = (ref: string) =>
|
|
154
154
|
ref.replace(/^refs\/remotes\/origin\//, "").replace(/^origin\//, "")
|
|
155
155
|
|
|
156
|
+
const GITHUB_PR_FIELDS =
|
|
157
|
+
"number,state,title,isDraft,headRefName,baseRefName,headRepository,url,mergedAt,mergeCommit,mergeable"
|
|
158
|
+
|
|
159
|
+
const commandErrorSummary = (stderr: string, fallback: string) =>
|
|
160
|
+
stderr
|
|
161
|
+
.split("\n")
|
|
162
|
+
.map((line) => line.trim())
|
|
163
|
+
.find(Boolean) ?? fallback
|
|
164
|
+
|
|
156
165
|
export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
157
166
|
sync: () => ({
|
|
158
167
|
reconcile: (
|
|
@@ -349,7 +358,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
349
358
|
"view",
|
|
350
359
|
existing.url,
|
|
351
360
|
"--json",
|
|
352
|
-
|
|
361
|
+
GITHUB_PR_FIELDS,
|
|
353
362
|
],
|
|
354
363
|
{
|
|
355
364
|
cwd: repositoryPath,
|
|
@@ -372,7 +381,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
372
381
|
"--state",
|
|
373
382
|
"all",
|
|
374
383
|
"--json",
|
|
375
|
-
|
|
384
|
+
GITHUB_PR_FIELDS,
|
|
376
385
|
],
|
|
377
386
|
{
|
|
378
387
|
cwd: repositoryPath,
|
|
@@ -824,8 +833,10 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
824
833
|
warnings.push({
|
|
825
834
|
kind: existing ? "pr-unavailable" : "pr-discovery-unavailable",
|
|
826
835
|
target: record.key,
|
|
827
|
-
message:
|
|
828
|
-
queried.stderr
|
|
836
|
+
message: commandErrorSummary(
|
|
837
|
+
queried.stderr,
|
|
838
|
+
"Could not query delivery provider",
|
|
839
|
+
),
|
|
829
840
|
})
|
|
830
841
|
}
|
|
831
842
|
} else if (existing) {
|
|
@@ -858,7 +869,7 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
858
869
|
warnings.push({
|
|
859
870
|
kind: "pr-unavailable",
|
|
860
871
|
target: record.key,
|
|
861
|
-
message: `Could not inspect ${existing.url}: ${viewed.stderr
|
|
872
|
+
message: `Could not inspect ${existing.url}: ${commandErrorSummary(viewed.stderr, "GitHub query failed")}`,
|
|
862
873
|
})
|
|
863
874
|
}
|
|
864
875
|
} else {
|
|
@@ -901,8 +912,10 @@ export class SyncService extends Effect.Service<SyncService>()("SyncService", {
|
|
|
901
912
|
warnings.push({
|
|
902
913
|
kind: "pr-discovery-unavailable",
|
|
903
914
|
target: record.key,
|
|
904
|
-
message:
|
|
905
|
-
listed.stderr
|
|
915
|
+
message: commandErrorSummary(
|
|
916
|
+
listed.stderr,
|
|
917
|
+
"Could not discover pull requests",
|
|
918
|
+
),
|
|
906
919
|
})
|
|
907
920
|
}
|
|
908
921
|
}
|