@markjaquith/agency 1.8.6 → 1.9.0
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/cli.ts +4 -0
- package/package.json +1 -1
- package/src/commands/push.test.ts +44 -0
- package/src/commands/push.ts +32 -17
- package/src/services/GitService.ts +4 -0
package/cli.ts
CHANGED
|
@@ -186,6 +186,7 @@ const commands: Record<string, Command> = {
|
|
|
186
186
|
emit: options.emit || options.branch,
|
|
187
187
|
silent: options.silent,
|
|
188
188
|
force: options.force,
|
|
189
|
+
noVerify: options["no-verify"],
|
|
189
190
|
verbose: options.verbose,
|
|
190
191
|
pr: options.pr,
|
|
191
192
|
}),
|
|
@@ -593,6 +594,9 @@ try {
|
|
|
593
594
|
type: "boolean",
|
|
594
595
|
short: "f",
|
|
595
596
|
},
|
|
597
|
+
"no-verify": {
|
|
598
|
+
type: "boolean",
|
|
599
|
+
},
|
|
596
600
|
verbose: {
|
|
597
601
|
type: "boolean",
|
|
598
602
|
short: "v",
|
package/package.json
CHANGED
|
@@ -314,6 +314,50 @@ describe("push command", () => {
|
|
|
314
314
|
expect(await getCurrentBranch(tempDir)).toBe("agency--feature")
|
|
315
315
|
})
|
|
316
316
|
|
|
317
|
+
test("reports pre-push hook output without suggesting --force", async () => {
|
|
318
|
+
await Bun.spawn(
|
|
319
|
+
["git", "config", "core.hooksPath", join(tempDir, ".git", "hooks")],
|
|
320
|
+
{
|
|
321
|
+
cwd: tempDir,
|
|
322
|
+
stdout: "pipe",
|
|
323
|
+
stderr: "pipe",
|
|
324
|
+
},
|
|
325
|
+
).exited
|
|
326
|
+
const hookPath = join(tempDir, ".git", "hooks", "pre-push")
|
|
327
|
+
await Bun.write(
|
|
328
|
+
hookPath,
|
|
329
|
+
[
|
|
330
|
+
"#!/bin/sh",
|
|
331
|
+
"printf 'pre-push hook stdout failure\\n'",
|
|
332
|
+
"printf 'pre-push hook stderr failure\\n' >&2",
|
|
333
|
+
"exit 1",
|
|
334
|
+
"",
|
|
335
|
+
].join("\n"),
|
|
336
|
+
)
|
|
337
|
+
await Bun.spawn(["chmod", "+x", hookPath], {
|
|
338
|
+
cwd: tempDir,
|
|
339
|
+
stdout: "pipe",
|
|
340
|
+
stderr: "pipe",
|
|
341
|
+
}).exited
|
|
342
|
+
|
|
343
|
+
let error: unknown
|
|
344
|
+
try {
|
|
345
|
+
await runTestEffect(
|
|
346
|
+
push({ baseBranch: "main", silent: true, skipFilter: true }),
|
|
347
|
+
)
|
|
348
|
+
} catch (caught) {
|
|
349
|
+
error = caught
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
expect(error).toBeInstanceOf(Error)
|
|
353
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
354
|
+
expect(message).toContain("pre-push hook stdout failure")
|
|
355
|
+
expect(message).toContain("pre-push hook stderr failure")
|
|
356
|
+
expect(message).not.toContain("agency push --force")
|
|
357
|
+
|
|
358
|
+
expect(await getCurrentBranch(tempDir)).toBe("agency--feature")
|
|
359
|
+
})
|
|
360
|
+
|
|
317
361
|
test("does not report force push when --force is provided but not needed", async () => {
|
|
318
362
|
// Capture output to check for force push message
|
|
319
363
|
const originalLog = console.log
|
package/src/commands/push.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface PushOptions extends BaseCommandOptions {
|
|
|
19
19
|
emit?: string
|
|
20
20
|
branch?: string // Deprecated: use emit instead
|
|
21
21
|
force?: boolean
|
|
22
|
+
noVerify?: boolean
|
|
22
23
|
pr?: boolean
|
|
23
24
|
skipFilter?: boolean
|
|
24
25
|
}
|
|
@@ -44,15 +45,21 @@ const getPushFailureStderr = (error: unknown): string => {
|
|
|
44
45
|
return String(error).trim()
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
const formatProcessOutput = (result: {
|
|
49
|
+
readonly stdout?: string
|
|
50
|
+
readonly stderr?: string
|
|
51
|
+
}): string => [result.stdout, result.stderr].filter(Boolean).join("\n").trim()
|
|
52
|
+
|
|
47
53
|
const pushFailureNeedsForce = (stderr: string): boolean => {
|
|
48
54
|
const normalized = stderr.toLowerCase()
|
|
49
55
|
|
|
50
|
-
return
|
|
51
|
-
"non-fast-forward"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
return (
|
|
57
|
+
normalized.includes("non-fast-forward") ||
|
|
58
|
+
normalized.includes(
|
|
59
|
+
"updates were rejected because the tip of your current branch is behind",
|
|
60
|
+
) ||
|
|
61
|
+
(normalized.includes("[rejected]") && normalized.includes("fetch first"))
|
|
62
|
+
)
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
const formatPushFailure = (error: unknown): Error => {
|
|
@@ -180,6 +187,7 @@ const pushCore = (gitRoot: string, options: PushOptions) =>
|
|
|
180
187
|
withSpinner(
|
|
181
188
|
pushBranchToRemoteEffect(gitRoot, emitBranchName, remote, {
|
|
182
189
|
force: options.force,
|
|
190
|
+
noVerify: options.noVerify,
|
|
183
191
|
verbose: options.verbose,
|
|
184
192
|
}),
|
|
185
193
|
{
|
|
@@ -237,16 +245,17 @@ const pushBranchToRemoteEffect = (
|
|
|
237
245
|
remote: string,
|
|
238
246
|
options: {
|
|
239
247
|
readonly force?: boolean
|
|
248
|
+
readonly noVerify?: boolean
|
|
240
249
|
readonly verbose?: boolean
|
|
241
250
|
},
|
|
242
251
|
) =>
|
|
243
252
|
Effect.gen(function* () {
|
|
244
253
|
const git = yield* GitService
|
|
245
|
-
const { force = false } = options
|
|
254
|
+
const { force = false, noVerify = false } = options
|
|
246
255
|
|
|
247
256
|
// Try pushing without force first
|
|
248
257
|
const pushResult = yield* git
|
|
249
|
-
.push(gitRoot, remote, branchName, { setUpstream: true })
|
|
258
|
+
.push(gitRoot, remote, branchName, { setUpstream: true, noVerify })
|
|
250
259
|
.pipe(
|
|
251
260
|
Effect.catchAll((error: any) =>
|
|
252
261
|
Effect.succeed({
|
|
@@ -261,7 +270,7 @@ const pushBranchToRemoteEffect = (
|
|
|
261
270
|
|
|
262
271
|
// If push failed, check if we should retry with --force
|
|
263
272
|
if (pushResult.exitCode !== 0) {
|
|
264
|
-
const stderr = pushResult
|
|
273
|
+
const stderr = formatProcessOutput(pushResult)
|
|
265
274
|
|
|
266
275
|
// Check if this is a force-push-needed error
|
|
267
276
|
const needsForce = pushFailureNeedsForce(stderr)
|
|
@@ -269,7 +278,11 @@ const pushBranchToRemoteEffect = (
|
|
|
269
278
|
if (needsForce && force) {
|
|
270
279
|
// User provided --force flag, retry with force
|
|
271
280
|
const forceResult = yield* git
|
|
272
|
-
.push(gitRoot, remote, branchName, {
|
|
281
|
+
.push(gitRoot, remote, branchName, {
|
|
282
|
+
setUpstream: true,
|
|
283
|
+
force: true,
|
|
284
|
+
noVerify,
|
|
285
|
+
})
|
|
273
286
|
.pipe(
|
|
274
287
|
Effect.catchAll((error: any) =>
|
|
275
288
|
Effect.succeed({
|
|
@@ -283,7 +296,7 @@ const pushBranchToRemoteEffect = (
|
|
|
283
296
|
if (forceResult.exitCode !== 0) {
|
|
284
297
|
return yield* Effect.fail(
|
|
285
298
|
new Error(
|
|
286
|
-
`Failed to force push branch to remote: ${forceResult
|
|
299
|
+
`Failed to force push branch to remote: ${formatProcessOutput(forceResult)}`,
|
|
287
300
|
),
|
|
288
301
|
)
|
|
289
302
|
}
|
|
@@ -399,15 +412,17 @@ Arguments:
|
|
|
399
412
|
|
|
400
413
|
Options:
|
|
401
414
|
--emit Custom name for emit branch (defaults to pattern from config)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
415
|
+
--branch (Deprecated: use --emit) Custom name for emit branch
|
|
416
|
+
-f, --force Force push to remote if branch has diverged
|
|
417
|
+
--no-verify Bypass git pre-push hooks
|
|
418
|
+
--pr Open GitHub PR in browser after pushing (requires gh CLI)
|
|
405
419
|
|
|
406
420
|
Examples:
|
|
407
421
|
agency push # Create PR, push, return to source
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
422
|
+
agency push origin/main # Explicitly use origin/main as base
|
|
423
|
+
agency push --force # Force push if branch has diverged
|
|
424
|
+
agency push --no-verify # Push without running pre-push hooks
|
|
425
|
+
agency push --pr # Push and open GitHub PR in browser
|
|
411
426
|
|
|
412
427
|
Notes:
|
|
413
428
|
- Must be run from a source branch (not a emit branch)
|
|
@@ -1046,6 +1046,7 @@ export class GitService extends Effect.Service<GitService>()("GitService", {
|
|
|
1046
1046
|
options?: {
|
|
1047
1047
|
readonly setUpstream?: boolean
|
|
1048
1048
|
readonly force?: boolean
|
|
1049
|
+
readonly noVerify?: boolean
|
|
1049
1050
|
},
|
|
1050
1051
|
) => {
|
|
1051
1052
|
const args = ["git", "push"]
|
|
@@ -1055,6 +1056,9 @@ export class GitService extends Effect.Service<GitService>()("GitService", {
|
|
|
1055
1056
|
if (options?.force) {
|
|
1056
1057
|
args.push("--force")
|
|
1057
1058
|
}
|
|
1059
|
+
if (options?.noVerify) {
|
|
1060
|
+
args.push("--no-verify")
|
|
1061
|
+
}
|
|
1058
1062
|
args.push(remote, branch)
|
|
1059
1063
|
|
|
1060
1064
|
return pipe(
|