@markjaquith/agency 1.9.1 → 1.9.3
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/emit.test.ts +44 -0
- package/src/commands/emit.ts +43 -3
- package/src/utils/spinner.test.ts +45 -0
- package/src/utils/spinner.ts +39 -26
package/package.json
CHANGED
|
@@ -125,6 +125,50 @@ describe("emit command", () => {
|
|
|
125
125
|
expect(currentBranch).toBe("agency--feature")
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
+
test("sets emit branch upstream when remote branch exists", async () => {
|
|
129
|
+
await checkoutBranch(tempDir, "main")
|
|
130
|
+
await createBranch(tempDir, "agency--feature")
|
|
131
|
+
await Bun.write(
|
|
132
|
+
join(tempDir, "agency.json"),
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
version: 1,
|
|
135
|
+
injectedFiles: ["AGENTS.md"],
|
|
136
|
+
template: "test",
|
|
137
|
+
createdAt: new Date().toISOString(),
|
|
138
|
+
}),
|
|
139
|
+
)
|
|
140
|
+
await addAndCommit(tempDir, "agency.json", "Feature commit")
|
|
141
|
+
|
|
142
|
+
await getGitOutput(tempDir, [
|
|
143
|
+
"update-ref",
|
|
144
|
+
"refs/remotes/origin/feature",
|
|
145
|
+
"HEAD",
|
|
146
|
+
])
|
|
147
|
+
|
|
148
|
+
await runTestEffect(emit({ silent: true, skipFilter: true }))
|
|
149
|
+
|
|
150
|
+
const remote = await getGitOutput(tempDir, [
|
|
151
|
+
"config",
|
|
152
|
+
"--get",
|
|
153
|
+
"branch.feature.remote",
|
|
154
|
+
])
|
|
155
|
+
const merge = await getGitOutput(tempDir, [
|
|
156
|
+
"config",
|
|
157
|
+
"--get",
|
|
158
|
+
"branch.feature.merge",
|
|
159
|
+
])
|
|
160
|
+
expect(remote.trim()).toBe("origin")
|
|
161
|
+
expect(merge.trim()).toBe("refs/heads/feature")
|
|
162
|
+
|
|
163
|
+
await checkoutBranch(tempDir, "feature")
|
|
164
|
+
const pushRef = await getGitOutput(tempDir, [
|
|
165
|
+
"rev-parse",
|
|
166
|
+
"--abbrev-ref",
|
|
167
|
+
"@{push}",
|
|
168
|
+
])
|
|
169
|
+
expect(pushRef.trim()).toBe("origin/feature")
|
|
170
|
+
})
|
|
171
|
+
|
|
128
172
|
test("creates emit branch with custom name", async () => {
|
|
129
173
|
await createBranch(tempDir, "agency--feature")
|
|
130
174
|
await createCommit(tempDir, "Feature commit")
|
package/src/commands/emit.ts
CHANGED
|
@@ -158,9 +158,7 @@ export const emitCore = (gitRoot: string, options: EmitOptions) =>
|
|
|
158
158
|
)
|
|
159
159
|
branchExisted = existed
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
yield* git.unsetGitConfig(`branch.${emitBranchName}.remote`, gitRoot)
|
|
163
|
-
yield* git.unsetGitConfig(`branch.${emitBranchName}.merge`, gitRoot)
|
|
161
|
+
yield* configureEmitBranchTracking(gitRoot, emitBranchName, verboseLog)
|
|
164
162
|
})
|
|
165
163
|
|
|
166
164
|
yield* withSpinner(createBranch, {
|
|
@@ -435,6 +433,48 @@ const getFilterRepoStateDir = (gitRoot: string) =>
|
|
|
435
433
|
return resolveGitInternalPath(gitRoot, gitPath)
|
|
436
434
|
})
|
|
437
435
|
|
|
436
|
+
const configureEmitBranchTracking = (
|
|
437
|
+
gitRoot: string,
|
|
438
|
+
emitBranchName: string,
|
|
439
|
+
verboseLog: (message: string) => void,
|
|
440
|
+
) =>
|
|
441
|
+
Effect.gen(function* () {
|
|
442
|
+
const git = yield* GitService
|
|
443
|
+
const remote = yield* git.resolveRemote(gitRoot).pipe(Effect.option)
|
|
444
|
+
|
|
445
|
+
if (remote._tag === "None") {
|
|
446
|
+
yield* git.unsetGitConfig(`branch.${emitBranchName}.remote`, gitRoot)
|
|
447
|
+
yield* git.unsetGitConfig(`branch.${emitBranchName}.merge`, gitRoot)
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const remoteBranch = `${remote.value}/${emitBranchName}`
|
|
452
|
+
const remoteBranchExists = yield* git.branchExists(gitRoot, remoteBranch)
|
|
453
|
+
|
|
454
|
+
if (!remoteBranchExists) {
|
|
455
|
+
yield* git.unsetGitConfig(`branch.${emitBranchName}.remote`, gitRoot)
|
|
456
|
+
yield* git.unsetGitConfig(`branch.${emitBranchName}.merge`, gitRoot)
|
|
457
|
+
verboseLog(
|
|
458
|
+
`No existing remote branch ${highlight.branch(remoteBranch)}; leaving ${highlight.branch(emitBranchName)} without upstream`,
|
|
459
|
+
)
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
yield* git.setGitConfig(
|
|
464
|
+
`branch.${emitBranchName}.remote`,
|
|
465
|
+
remote.value,
|
|
466
|
+
gitRoot,
|
|
467
|
+
)
|
|
468
|
+
yield* git.setGitConfig(
|
|
469
|
+
`branch.${emitBranchName}.merge`,
|
|
470
|
+
`refs/heads/${emitBranchName}`,
|
|
471
|
+
gitRoot,
|
|
472
|
+
)
|
|
473
|
+
verboseLog(
|
|
474
|
+
`Set ${highlight.branch(emitBranchName)} upstream to ${highlight.branch(remoteBranch)}`,
|
|
475
|
+
)
|
|
476
|
+
})
|
|
477
|
+
|
|
438
478
|
/**
|
|
439
479
|
* Find the best fork point by checking both local and remote tracking branches.
|
|
440
480
|
*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { Effect } from "effect"
|
|
3
|
+
import { withSpinner } from "./spinner"
|
|
4
|
+
|
|
5
|
+
describe("withSpinner", () => {
|
|
6
|
+
test("clears and stops the spinner when an Effect failure has no fail text", async () => {
|
|
7
|
+
const originalNodeEnv = process.env.NODE_ENV
|
|
8
|
+
const originalBunEnv = process.env.BUN_ENV
|
|
9
|
+
delete process.env.NODE_ENV
|
|
10
|
+
delete process.env.BUN_ENV
|
|
11
|
+
|
|
12
|
+
const calls: string[] = []
|
|
13
|
+
const error = new Error("boom")
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
await expect(
|
|
17
|
+
Effect.runPromise(
|
|
18
|
+
withSpinner(Effect.fail(error), {
|
|
19
|
+
text: "Working",
|
|
20
|
+
createSpinner: () => ({
|
|
21
|
+
succeed: () => calls.push("succeed"),
|
|
22
|
+
fail: () => calls.push("fail"),
|
|
23
|
+
clear: () => calls.push("clear"),
|
|
24
|
+
stop: () => calls.push("stop"),
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
).rejects.toThrow("boom")
|
|
29
|
+
|
|
30
|
+
expect(calls).toEqual(["clear", "stop"])
|
|
31
|
+
} finally {
|
|
32
|
+
if (originalNodeEnv === undefined) {
|
|
33
|
+
delete process.env.NODE_ENV
|
|
34
|
+
} else {
|
|
35
|
+
process.env.NODE_ENV = originalNodeEnv
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (originalBunEnv === undefined) {
|
|
39
|
+
delete process.env.BUN_ENV
|
|
40
|
+
} else {
|
|
41
|
+
process.env.BUN_ENV = originalBunEnv
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
})
|
package/src/utils/spinner.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import ora from "ora"
|
|
2
|
-
import { Effect } from "effect"
|
|
2
|
+
import { Effect, Exit } from "effect"
|
|
3
|
+
|
|
4
|
+
interface Spinner {
|
|
5
|
+
succeed: (text?: string) => unknown
|
|
6
|
+
fail: (text?: string) => unknown
|
|
7
|
+
clear: () => unknown
|
|
8
|
+
stop: () => unknown
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
/**
|
|
5
12
|
* Check if we're running in a test environment
|
|
@@ -20,6 +27,8 @@ interface SpinnerConfig {
|
|
|
20
27
|
failText?: string
|
|
21
28
|
/** Whether the spinner is enabled (defaults to true) */
|
|
22
29
|
enabled?: boolean
|
|
30
|
+
/** Creates the spinner instance. Intended for tests. */
|
|
31
|
+
createSpinner?: (text: string) => Spinner
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
/**
|
|
@@ -53,31 +62,35 @@ export const withSpinner = <A, E, R>(
|
|
|
53
62
|
return effect
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const result = yield* effect
|
|
65
|
+
const createSpinner =
|
|
66
|
+
config.createSpinner ??
|
|
67
|
+
((text: string) =>
|
|
68
|
+
ora({
|
|
69
|
+
text,
|
|
70
|
+
spinner: "dots",
|
|
71
|
+
color: "cyan",
|
|
72
|
+
}).start())
|
|
65
73
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
return Effect.acquireUseRelease(
|
|
75
|
+
Effect.sync(() => createSpinner(text)),
|
|
76
|
+
() => effect,
|
|
77
|
+
(spinner, exit) =>
|
|
78
|
+
Effect.sync(() => {
|
|
79
|
+
if (Exit.isSuccess(exit)) {
|
|
80
|
+
if (successText) {
|
|
81
|
+
spinner.succeed(successText)
|
|
82
|
+
} else {
|
|
83
|
+
spinner.stop()
|
|
84
|
+
}
|
|
85
|
+
return
|
|
86
|
+
}
|
|
71
87
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
throw error
|
|
81
|
-
}
|
|
82
|
-
})
|
|
88
|
+
if (failText) {
|
|
89
|
+
spinner.fail(failText)
|
|
90
|
+
} else {
|
|
91
|
+
spinner.clear()
|
|
92
|
+
spinner.stop()
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
83
96
|
}
|