@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "Manages personal agents files",
5
5
  "keywords": [
6
6
  "agents",
@@ -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")
@@ -158,9 +158,7 @@ export const emitCore = (gitRoot: string, options: EmitOptions) =>
158
158
  )
159
159
  branchExisted = existed
160
160
 
161
- // Unset any remote tracking branch for the emit branch
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
+ })
@@ -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
- return Effect.gen(function* () {
57
- const spinner = ora({
58
- text,
59
- spinner: "dots",
60
- color: "cyan",
61
- }).start()
62
-
63
- try {
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
- if (successText) {
67
- spinner.succeed(successText)
68
- } else {
69
- spinner.stop()
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
- return result
73
- } catch (error) {
74
- if (failText) {
75
- spinner.fail(failText)
76
- } else {
77
- spinner.clear()
78
- spinner.stop()
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
  }