@markjaquith/agency 1.9.3 → 1.9.5
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 +1 -1
- package/src/commands/pr.test.ts +177 -0
- package/src/commands/pr.ts +129 -10
- package/src/commands/push.test.ts +33 -0
- package/src/commands/push.ts +29 -43
- package/src/utils/pr-branch.ts +67 -0
package/package.json
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { chmod, mkdir } from "node:fs/promises"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { pr } from "./pr"
|
|
5
|
+
import {
|
|
6
|
+
cleanupTempDir,
|
|
7
|
+
createBranch,
|
|
8
|
+
createTempDir,
|
|
9
|
+
initGitRepo,
|
|
10
|
+
runTestEffect,
|
|
11
|
+
} from "../test-utils"
|
|
12
|
+
|
|
13
|
+
const restoreEnv = (key: string, value: string | undefined) => {
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
delete process.env[key]
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
process.env[key] = value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const readGhArgs = async (recordPath: string): Promise<string[]> => {
|
|
23
|
+
const file = Bun.file(recordPath)
|
|
24
|
+
|
|
25
|
+
if (!(await file.exists())) {
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const content = await file.text()
|
|
30
|
+
return content.trim().split("\n").filter(Boolean)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("pr command", () => {
|
|
34
|
+
let tempDir: string
|
|
35
|
+
let recordPath: string
|
|
36
|
+
let originalCwd: string
|
|
37
|
+
let originalPath: string | undefined
|
|
38
|
+
let originalAgencyConfigPath: string | undefined
|
|
39
|
+
let originalGhArgsFile: string | undefined
|
|
40
|
+
|
|
41
|
+
beforeEach(async () => {
|
|
42
|
+
tempDir = await createTempDir()
|
|
43
|
+
recordPath = join(tempDir, "gh-args.txt")
|
|
44
|
+
originalCwd = process.cwd()
|
|
45
|
+
originalPath = process.env.PATH
|
|
46
|
+
originalAgencyConfigPath = process.env.AGENCY_CONFIG_PATH
|
|
47
|
+
originalGhArgsFile = process.env.AGENCY_TEST_GH_ARGS_FILE
|
|
48
|
+
|
|
49
|
+
process.chdir(tempDir)
|
|
50
|
+
process.env.AGENCY_CONFIG_PATH = join(tempDir, "non-existent-config.json")
|
|
51
|
+
process.env.AGENCY_TEST_GH_ARGS_FILE = recordPath
|
|
52
|
+
|
|
53
|
+
const binDir = join(tempDir, "bin")
|
|
54
|
+
const ghPath = join(binDir, "gh")
|
|
55
|
+
await mkdir(binDir)
|
|
56
|
+
await Bun.write(
|
|
57
|
+
ghPath,
|
|
58
|
+
`#!/bin/sh
|
|
59
|
+
: > "$AGENCY_TEST_GH_ARGS_FILE"
|
|
60
|
+
for arg in "$@"; do
|
|
61
|
+
printf '%s\n' "$arg" >> "$AGENCY_TEST_GH_ARGS_FILE"
|
|
62
|
+
done
|
|
63
|
+
`,
|
|
64
|
+
)
|
|
65
|
+
await chmod(ghPath, 0o755)
|
|
66
|
+
process.env.PATH = `${binDir}:${originalPath ?? ""}`
|
|
67
|
+
|
|
68
|
+
await initGitRepo(tempDir)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
afterEach(async () => {
|
|
72
|
+
process.chdir(originalCwd)
|
|
73
|
+
restoreEnv("PATH", originalPath)
|
|
74
|
+
restoreEnv("AGENCY_CONFIG_PATH", originalAgencyConfigPath)
|
|
75
|
+
restoreEnv("AGENCY_TEST_GH_ARGS_FILE", originalGhArgsFile)
|
|
76
|
+
await cleanupTempDir(tempDir)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("passes through to gh pr unchanged outside agency context", async () => {
|
|
80
|
+
await createBranch(tempDir, "feature")
|
|
81
|
+
|
|
82
|
+
await runTestEffect(pr({ args: ["status"], silent: false }))
|
|
83
|
+
|
|
84
|
+
expect(await readGhArgs(recordPath)).toEqual(["pr", "status"])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test("passes through outside agency context with custom emit pattern", async () => {
|
|
88
|
+
const configPath = process.env.AGENCY_CONFIG_PATH
|
|
89
|
+
|
|
90
|
+
if (!configPath) {
|
|
91
|
+
throw new Error("AGENCY_CONFIG_PATH is not set")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await Bun.write(
|
|
95
|
+
configPath,
|
|
96
|
+
JSON.stringify({
|
|
97
|
+
sourceBranchPattern: "agency--%branch%",
|
|
98
|
+
emitBranch: "%branch%--PR",
|
|
99
|
+
}),
|
|
100
|
+
)
|
|
101
|
+
await createBranch(tempDir, "feature")
|
|
102
|
+
|
|
103
|
+
await runTestEffect(pr({ args: ["status"], silent: false }))
|
|
104
|
+
|
|
105
|
+
expect(await readGhArgs(recordPath)).toEqual(["pr", "status"])
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("appends emitted branch in agency context", async () => {
|
|
109
|
+
await createBranch(tempDir, "agency--feature")
|
|
110
|
+
|
|
111
|
+
await runTestEffect(pr({ args: ["view", "--web"], silent: false }))
|
|
112
|
+
|
|
113
|
+
expect(await readGhArgs(recordPath)).toEqual([
|
|
114
|
+
"pr",
|
|
115
|
+
"view",
|
|
116
|
+
"--web",
|
|
117
|
+
"feature",
|
|
118
|
+
])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test("appends emitted branch with gh pr value flags when no selector is provided", async () => {
|
|
122
|
+
await createBranch(tempDir, "agency--feature")
|
|
123
|
+
|
|
124
|
+
await runTestEffect(
|
|
125
|
+
pr({
|
|
126
|
+
args: ["view", "--json", "number", "--jq", ".number"],
|
|
127
|
+
silent: false,
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
expect(await readGhArgs(recordPath)).toEqual([
|
|
132
|
+
"pr",
|
|
133
|
+
"view",
|
|
134
|
+
"--json",
|
|
135
|
+
"number",
|
|
136
|
+
"--jq",
|
|
137
|
+
".number",
|
|
138
|
+
"feature",
|
|
139
|
+
])
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test("does not append emitted branch when an explicit selector is provided", async () => {
|
|
143
|
+
await createBranch(tempDir, "agency--feature")
|
|
144
|
+
|
|
145
|
+
await runTestEffect(
|
|
146
|
+
pr({
|
|
147
|
+
args: [
|
|
148
|
+
"view",
|
|
149
|
+
"123",
|
|
150
|
+
"--json",
|
|
151
|
+
"statusCheckRollup",
|
|
152
|
+
"--jq",
|
|
153
|
+
".statusCheckRollup[]",
|
|
154
|
+
],
|
|
155
|
+
silent: false,
|
|
156
|
+
}),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
expect(await readGhArgs(recordPath)).toEqual([
|
|
160
|
+
"pr",
|
|
161
|
+
"view",
|
|
162
|
+
"123",
|
|
163
|
+
"--json",
|
|
164
|
+
"statusCheckRollup",
|
|
165
|
+
"--jq",
|
|
166
|
+
".statusCheckRollup[]",
|
|
167
|
+
])
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test("does not append emitted branch for subcommands without a selector", async () => {
|
|
171
|
+
await createBranch(tempDir, "agency--feature")
|
|
172
|
+
|
|
173
|
+
await runTestEffect(pr({ args: ["status"], silent: false }))
|
|
174
|
+
|
|
175
|
+
expect(await readGhArgs(recordPath)).toEqual(["pr", "status"])
|
|
176
|
+
})
|
|
177
|
+
})
|
package/src/commands/pr.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Effect } from "effect"
|
|
|
2
2
|
import type { BaseCommandOptions } from "../utils/command"
|
|
3
3
|
import { GitService } from "../services/GitService"
|
|
4
4
|
import { ConfigService } from "../services/ConfigService"
|
|
5
|
-
import {
|
|
5
|
+
import { resolveAgencyBranchPairWithAgencyJson } from "../utils/pr-branch"
|
|
6
6
|
import { ensureGitRepo } from "../utils/effect"
|
|
7
7
|
|
|
8
8
|
interface PrOptions extends BaseCommandOptions {
|
|
@@ -10,6 +10,120 @@ interface PrOptions extends BaseCommandOptions {
|
|
|
10
10
|
args: string[]
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const PR_SUBCOMMANDS_WITH_OPTIONAL_SELECTOR = new Set([
|
|
14
|
+
"checkout",
|
|
15
|
+
"checks",
|
|
16
|
+
"close",
|
|
17
|
+
"comment",
|
|
18
|
+
"diff",
|
|
19
|
+
"edit",
|
|
20
|
+
"lock",
|
|
21
|
+
"merge",
|
|
22
|
+
"ready",
|
|
23
|
+
"reopen",
|
|
24
|
+
"review",
|
|
25
|
+
"unlock",
|
|
26
|
+
"update-branch",
|
|
27
|
+
"view",
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
const GH_PR_FLAGS_WITH_VALUE = new Set([
|
|
31
|
+
"--add-assignee",
|
|
32
|
+
"--add-label",
|
|
33
|
+
"--add-project",
|
|
34
|
+
"--add-reviewer",
|
|
35
|
+
"--app",
|
|
36
|
+
"--assignee",
|
|
37
|
+
"--author",
|
|
38
|
+
"--base",
|
|
39
|
+
"--body",
|
|
40
|
+
"--body-file",
|
|
41
|
+
"--head",
|
|
42
|
+
"--hostname",
|
|
43
|
+
"--json",
|
|
44
|
+
"--jq",
|
|
45
|
+
"--label",
|
|
46
|
+
"--limit",
|
|
47
|
+
"--match-head-commit",
|
|
48
|
+
"--milestone",
|
|
49
|
+
"--project",
|
|
50
|
+
"--remove-assignee",
|
|
51
|
+
"--remove-label",
|
|
52
|
+
"--remove-project",
|
|
53
|
+
"--remove-reviewer",
|
|
54
|
+
"--repo",
|
|
55
|
+
"--reviewer",
|
|
56
|
+
"--search",
|
|
57
|
+
"--state",
|
|
58
|
+
"--template",
|
|
59
|
+
"--title",
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const GH_PR_SHORT_FLAGS_WITH_VALUE = new Set([
|
|
63
|
+
"-A",
|
|
64
|
+
"-B",
|
|
65
|
+
"-H",
|
|
66
|
+
"-L",
|
|
67
|
+
"-R",
|
|
68
|
+
"-a",
|
|
69
|
+
"-b",
|
|
70
|
+
"-l",
|
|
71
|
+
"-m",
|
|
72
|
+
"-p",
|
|
73
|
+
"-q",
|
|
74
|
+
"-r",
|
|
75
|
+
"-s",
|
|
76
|
+
"-t",
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
const hasExplicitPrSelector = (args: readonly string[]): boolean => {
|
|
80
|
+
const subcommandArgs = args.slice(1)
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < subcommandArgs.length; i++) {
|
|
83
|
+
const arg = subcommandArgs[i]
|
|
84
|
+
|
|
85
|
+
if (!arg) {
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (arg === "--") {
|
|
90
|
+
return subcommandArgs.slice(i + 1).some(Boolean)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (arg.startsWith("--")) {
|
|
94
|
+
const flagName = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg
|
|
95
|
+
|
|
96
|
+
if (!arg.includes("=") && GH_PR_FLAGS_WITH_VALUE.has(flagName)) {
|
|
97
|
+
i++
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (arg.startsWith("-") && arg.length > 1) {
|
|
104
|
+
if (GH_PR_SHORT_FLAGS_WITH_VALUE.has(arg)) {
|
|
105
|
+
i++
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const shouldAppendEmitBranch = (args: readonly string[]): boolean => {
|
|
118
|
+
const subcommand = args[0]
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
typeof subcommand === "string" &&
|
|
122
|
+
PR_SUBCOMMANDS_WITH_OPTIONAL_SELECTOR.has(subcommand) &&
|
|
123
|
+
!hasExplicitPrSelector(args)
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
13
127
|
export const pr = (options: PrOptions) =>
|
|
14
128
|
Effect.gen(function* () {
|
|
15
129
|
const git = yield* GitService
|
|
@@ -20,23 +134,27 @@ export const pr = (options: PrOptions) =>
|
|
|
20
134
|
// Load config
|
|
21
135
|
const config = yield* configService.loadConfig()
|
|
22
136
|
|
|
23
|
-
// Get current branch and resolve the branch pair
|
|
137
|
+
// Get current branch and resolve the branch pair when agency context exists
|
|
24
138
|
const currentBranch = yield* git.getCurrentBranch(gitRoot)
|
|
25
|
-
const branches = yield*
|
|
139
|
+
const branches = yield* resolveAgencyBranchPairWithAgencyJson(
|
|
26
140
|
gitRoot,
|
|
27
141
|
currentBranch,
|
|
28
142
|
config.sourceBranchPattern,
|
|
29
143
|
config.emitBranch,
|
|
30
144
|
)
|
|
31
145
|
|
|
32
|
-
// Build the gh pr command with the emit branch
|
|
33
|
-
const ghArgs =
|
|
146
|
+
// Build the gh pr command with the emit branch only when gh accepts a selector.
|
|
147
|
+
const ghArgs =
|
|
148
|
+
branches && shouldAppendEmitBranch(options.args)
|
|
149
|
+
? ["gh", "pr", ...options.args, branches.emitBranch]
|
|
150
|
+
: ["gh", "pr", ...options.args]
|
|
34
151
|
|
|
35
152
|
// Run gh pr with stdio inherited so output goes directly to terminal
|
|
36
153
|
const exitCode = yield* Effect.tryPromise({
|
|
37
154
|
try: async () => {
|
|
38
155
|
const proc = Bun.spawn(ghArgs, {
|
|
39
156
|
cwd: gitRoot,
|
|
157
|
+
env: process.env,
|
|
40
158
|
stdin: "inherit",
|
|
41
159
|
stdout: "inherit",
|
|
42
160
|
stderr: "inherit",
|
|
@@ -59,19 +177,20 @@ export const pr = (options: PrOptions) =>
|
|
|
59
177
|
export const help = `
|
|
60
178
|
Usage: agency pr <subcommand> [flags]
|
|
61
179
|
|
|
62
|
-
Wrapper for 'gh pr' that automatically
|
|
180
|
+
Wrapper for 'gh pr' that automatically uses the emitted branch in agency context.
|
|
63
181
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
182
|
+
For gh pr subcommands that accept a PR selector, this command appends the
|
|
183
|
+
emitted branch name when you do not provide a selector. Outside agency context,
|
|
184
|
+
or when you provide a selector, it passes arguments through to 'gh pr' unchanged.
|
|
67
185
|
|
|
68
186
|
Examples:
|
|
69
187
|
agency pr view --web # gh pr view --web <emit-branch>
|
|
70
188
|
agency pr checks # gh pr checks <emit-branch>
|
|
71
|
-
agency pr
|
|
189
|
+
agency pr diff # gh pr diff <emit-branch>
|
|
72
190
|
|
|
73
191
|
Notes:
|
|
74
192
|
- Requires gh CLI to be installed and authenticated
|
|
75
193
|
- Uses source and emit patterns from ~/.config/agency/agency.json
|
|
76
194
|
- Respects emitBranch field in agency.json when present
|
|
195
|
+
- Falls through to gh pr unchanged outside agency context
|
|
77
196
|
`
|
|
@@ -115,6 +115,39 @@ describe("push command", () => {
|
|
|
115
115
|
expect(remoteBranches).toContain("feature")
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
+
test("forwards verbose output from emit step", async () => {
|
|
119
|
+
const originalLog = console.log
|
|
120
|
+
const logMessages: string[] = []
|
|
121
|
+
console.log = (msg: string) => {
|
|
122
|
+
logMessages.push(msg)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
await runTestEffect(
|
|
127
|
+
push({
|
|
128
|
+
baseBranch: "main",
|
|
129
|
+
verbose: true,
|
|
130
|
+
skipFilter: true,
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
} finally {
|
|
134
|
+
console.log = originalLog
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
expect(logMessages).toContain("Step 1: Emitting...")
|
|
138
|
+
expect(
|
|
139
|
+
logMessages.some(
|
|
140
|
+
(msg) => msg.includes("Using base branch:") && msg.includes("main"),
|
|
141
|
+
),
|
|
142
|
+
).toBe(true)
|
|
143
|
+
expect(
|
|
144
|
+
logMessages.some((msg) =>
|
|
145
|
+
msg.includes("Skipping git-filter-repo (skipFilter=true)"),
|
|
146
|
+
),
|
|
147
|
+
).toBe(true)
|
|
148
|
+
expect(logMessages.some((msg) => msg.includes("Emitted"))).toBe(true)
|
|
149
|
+
})
|
|
150
|
+
|
|
118
151
|
test("works with custom branch name", async () => {
|
|
119
152
|
await runTestEffect(
|
|
120
153
|
push({
|
package/src/commands/push.ts
CHANGED
|
@@ -122,41 +122,36 @@ const pushCore = (gitRoot: string, options: PushOptions) =>
|
|
|
122
122
|
verboseLog(`Starting push workflow from ${highlight.branch(sourceBranch)}`)
|
|
123
123
|
|
|
124
124
|
// Step 1: Create emit branch (agency emit)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return yield* Effect.fail(
|
|
153
|
-
new Error(`Failed to create emit branch: ${message}`),
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
emitHadIgnorableFailure = true
|
|
158
|
-
verboseLog("Emit reported existing branch; continuing")
|
|
125
|
+
verboseLog("Step 1: Emitting...")
|
|
126
|
+
// Use the emit command for the entire emit step so push and emit stay in sync.
|
|
127
|
+
const prEffectWithOptions = emit({
|
|
128
|
+
baseBranch: options.baseBranch,
|
|
129
|
+
emit: options.emit || options.branch,
|
|
130
|
+
silent: options.silent,
|
|
131
|
+
force: options.force,
|
|
132
|
+
verbose: options.verbose,
|
|
133
|
+
skipFilter: options.skipFilter,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const prResult = yield* Effect.either(prEffectWithOptions)
|
|
137
|
+
if (Either.isLeft(prResult)) {
|
|
138
|
+
const error =
|
|
139
|
+
prResult.left instanceof Error
|
|
140
|
+
? prResult.left
|
|
141
|
+
: new Error(String(prResult.left))
|
|
142
|
+
const message = error.message || ""
|
|
143
|
+
const ignorable =
|
|
144
|
+
message === "" ||
|
|
145
|
+
message.includes("already exists") ||
|
|
146
|
+
message.includes("check if branch exists")
|
|
147
|
+
|
|
148
|
+
if (!ignorable) {
|
|
149
|
+
return yield* Effect.fail(
|
|
150
|
+
new Error(`Failed to create emit branch: ${message}`),
|
|
151
|
+
)
|
|
159
152
|
}
|
|
153
|
+
|
|
154
|
+
verboseLog("Emit reported existing branch; continuing")
|
|
160
155
|
}
|
|
161
156
|
|
|
162
157
|
// Compute the emit branch name (emit() command now stays on source branch)
|
|
@@ -164,15 +159,6 @@ const pushCore = (gitRoot: string, options: PushOptions) =>
|
|
|
164
159
|
const emitBranchName =
|
|
165
160
|
options.emit || options.branch || branchInfo.emitBranch
|
|
166
161
|
|
|
167
|
-
// If skipFilter, we skipped emit() so we must create the emit branch manually
|
|
168
|
-
if (options.skipFilter) {
|
|
169
|
-
yield* git.createOrResetBranch(gitRoot, emitBranchName, sourceBranch)
|
|
170
|
-
// Switch back to source branch for consistency
|
|
171
|
-
yield* git.checkoutBranch(gitRoot, sourceBranch)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
log(done(`Emitted ${highlight.branch(emitBranchName)}`))
|
|
175
|
-
|
|
176
162
|
// Step 2: Push to remote (git push)
|
|
177
163
|
const remote = yield* getRemoteName(gitRoot)
|
|
178
164
|
verboseLog(
|
package/src/utils/pr-branch.ts
CHANGED
|
@@ -439,3 +439,70 @@ export const resolveBranchPairWithAgencyJson = (
|
|
|
439
439
|
// Strategy 4: Fall back to pattern-based resolution
|
|
440
440
|
return resolveBranchPair(currentBranch, sourcePattern, emitPattern)
|
|
441
441
|
})
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Resolve branch pair only when the current branch is in agency context.
|
|
445
|
+
* Unlike resolveBranchPairWithAgencyJson, this intentionally does not treat
|
|
446
|
+
* arbitrary legacy branches as source branches.
|
|
447
|
+
*/
|
|
448
|
+
export const resolveAgencyBranchPairWithAgencyJson = (
|
|
449
|
+
gitRoot: string,
|
|
450
|
+
currentBranch: string,
|
|
451
|
+
sourcePattern: string,
|
|
452
|
+
emitPattern: string,
|
|
453
|
+
): Effect.Effect<BranchPair | null, never, GitService | FileSystemService> =>
|
|
454
|
+
Effect.gen(function* () {
|
|
455
|
+
const fromCurrentAgencyJson = yield* tryResolveFromCurrentAgencyJson(
|
|
456
|
+
gitRoot,
|
|
457
|
+
currentBranch,
|
|
458
|
+
)
|
|
459
|
+
if (fromCurrentAgencyJson) {
|
|
460
|
+
return fromCurrentAgencyJson
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const fromOtherBranchAgencyJson =
|
|
464
|
+
yield* tryResolveFromOtherBranchAgencyJson(gitRoot, currentBranch)
|
|
465
|
+
if (fromOtherBranchAgencyJson) {
|
|
466
|
+
return fromOtherBranchAgencyJson
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const fromPatternedSource = yield* tryResolveFromPatternedSourceBranch(
|
|
470
|
+
gitRoot,
|
|
471
|
+
currentBranch,
|
|
472
|
+
sourcePattern,
|
|
473
|
+
emitPattern,
|
|
474
|
+
)
|
|
475
|
+
if (fromPatternedSource) {
|
|
476
|
+
return fromPatternedSource
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (extractCleanBranch(currentBranch, sourcePattern)) {
|
|
480
|
+
return resolveBranchPair(currentBranch, sourcePattern, emitPattern)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (emitPattern !== "%branch%") {
|
|
484
|
+
const cleanFromEmit = extractCleanFromEmit(currentBranch, emitPattern)
|
|
485
|
+
|
|
486
|
+
if (cleanFromEmit) {
|
|
487
|
+
const git = yield* GitService
|
|
488
|
+
const possibleSourceBranch = makeSourceBranchName(
|
|
489
|
+
cleanFromEmit,
|
|
490
|
+
sourcePattern,
|
|
491
|
+
)
|
|
492
|
+
const sourceExists = yield* pipe(
|
|
493
|
+
git.branchExists(gitRoot, possibleSourceBranch),
|
|
494
|
+
Effect.catchAll(() => Effect.succeed(false)),
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
if (sourceExists) {
|
|
498
|
+
return {
|
|
499
|
+
sourceBranch: possibleSourceBranch,
|
|
500
|
+
emitBranch: currentBranch,
|
|
501
|
+
isOnEmitBranch: true,
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return null
|
|
508
|
+
})
|