@aldotestino/autoland 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +109 -2
- package/bin/autoland.cjs +42 -0
- package/package.json +35 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aldotestino
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,110 @@
|
|
|
1
|
-
#
|
|
1
|
+
# autoland
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Update, approve if needed, and merge a batch of pull requests in order, one at a time. Built with
|
|
4
|
+
[Effect](https://effect.website) and Bun.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g @aldotestino/autoland
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Runs on macOS and glibc Linux, on arm64 and x64. npm installs only the compiled binary for your platform (about 42-52 MB to download). Bun is not needed, and Node.js is only used to start it.
|
|
13
|
+
|
|
14
|
+
Requires authenticated `gh` and `git`. Repair also requires `opencode` and SSH access to the PR repositories. Run it from the target repository.
|
|
15
|
+
|
|
16
|
+
## Merge a batch of PRs
|
|
17
|
+
|
|
18
|
+
After triage, pass the PR numbers or GitHub URLs:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
autoland --dry-run 125 123 124
|
|
22
|
+
autoland 125 123 124
|
|
23
|
+
printf '125, 123\n124\n' | autoland
|
|
24
|
+
autoland --label agent:ok-to-merge --label agent
|
|
25
|
+
autoland
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Option | Effect |
|
|
29
|
+
| -------------------------------- | ------------------------------------------------- |
|
|
30
|
+
| `--method squash\|merge\|rebase` | Merge method (default: `squash`) |
|
|
31
|
+
| `--timeout SECONDS` | Maximum wait per PR (default: `1800`) |
|
|
32
|
+
| `--label LABEL` | Require this label (repeatable; all must match) |
|
|
33
|
+
| `--dry-run` | Read-only preview; no updates, merges, or repairs |
|
|
34
|
+
| `--yes` | Skip the initial batch confirmation |
|
|
35
|
+
| `--non-interactive` | Skip all prompts and leave conflicts unmerged |
|
|
36
|
+
|
|
37
|
+
Run it in a terminal with no PRs and no `--label` to choose from a checkbox list of every open PR that is not a draft. Use the arrow keys or `j`/`k` to move, space to toggle, `a` to select or clear all, Enter to confirm, and `q` or Esc to cancel. Other options such as `--dry-run` and `--method` still apply. Your selection then goes through the usual batch confirmation unless you pass `--yes`. `--non-interactive` never shows the list, and piped input is always treated as the PR list. Prompts read from the controlling terminal, so a piped list does not consume their answers.
|
|
38
|
+
|
|
39
|
+
PRs are deduplicated and processed in ascending numeric order. The tool updates branches that are behind their base, waits for required checks, and uses squash merges by default. Each PR must finish merging before the next PR starts, including when GitHub uses a merge queue. Branch protections are not bypassed.
|
|
40
|
+
|
|
41
|
+
Before merging, it checks each reviewer's current vote. If no current approval exists, it submits an approval as your authenticated `gh` account, pinned to the checked commit. Existing approvals are not duplicated. Outstanding change requests or approval failures leave the PR unmerged. GitHub does not allow you to approve your own PR. Dry runs do not approve.
|
|
42
|
+
|
|
43
|
+
The order is **update or repair → wait for required checks → approve if needed → merge**. There is no approval check before an update or conflict repair. Branches are compared against the live base branch tip, not the base SHA stored in the PR response. Approval is checked only for the merge-ready commit. If the base moves during a merge attempt, the tool retries the update instead of skipping the PR.
|
|
44
|
+
|
|
45
|
+
In a terminal, each action uses a single-line spinner that becomes a green checkmark or red X when it finishes. Redirected output and dumb terminals use plain text; `NO_COLOR` disables color without disabling animation.
|
|
46
|
+
|
|
47
|
+
Repeat `--label` to require **all** specified labels. Without a PR list, all matching open PRs are found across all pages. With a PR list, only matching PRs from that list are processed. Labels are checked again before each update or merge attempt. If no PRs match, it exits without changes.
|
|
48
|
+
|
|
49
|
+
## Conflicts
|
|
50
|
+
|
|
51
|
+
On a conflict, it sends a Ghostty desktop notification and offers OpenCode repair, retry, skip, or quit. Ghostty must have permission to send desktop notifications.
|
|
52
|
+
|
|
53
|
+
Repair creates a separate worktree, leaving your checkout alone, and runs an OpenCode session there through the [Effect client](https://opencode.ai/v2/docs/build/client/effect/), starting the local background service if needed:
|
|
54
|
+
|
|
55
|
+
- The session runs unattended, with the spinner showing it is working. To watch or take over, run the `opencode <worktree> --session <id>` command it prints from another terminal.
|
|
56
|
+
- OpenCode's permission requests appear as prompts here. They can also be answered in an attached OpenCode UI.
|
|
57
|
+
- Questions that only OpenCode's UI can show are announced with that same command. The session waits until they are answered.
|
|
58
|
+
- Ctrl-C interrupts the session as well as the batch, so the agent does not keep pushing to the PR.
|
|
59
|
+
- When the session finishes, its report is printed and you are asked to review the repair. The batch rechecks GitHub only once you confirm; declining returns to the conflict menu.
|
|
60
|
+
|
|
61
|
+
Repair worktrees are kept for review; remove them with `git worktree remove <path>` when no longer needed.
|
|
62
|
+
|
|
63
|
+
## Failure and exit status
|
|
64
|
+
|
|
65
|
+
Failed checks, closed PRs, drafts, and other blocked PRs are reported, then the batch continues. If a requested merge has not finished by the wait limit, the batch stops to preserve order. Rerunning a batch skips PRs that are already merged.
|
|
66
|
+
|
|
67
|
+
| Status | Meaning |
|
|
68
|
+
| ------ | ------------------------------------- |
|
|
69
|
+
| `0` | Done, or nothing to do |
|
|
70
|
+
| `1` | A PR was left unmerged |
|
|
71
|
+
| `2` | Invalid input or unusable environment |
|
|
72
|
+
| `130` | Interrupted (`143` for `SIGTERM`) |
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
bun install
|
|
78
|
+
bun link # puts this checkout on your PATH as `autoland`
|
|
79
|
+
bun run build # a binary for this machine, at dist/autoland
|
|
80
|
+
bun run autoland --help # from this directory, so it targets this repository
|
|
81
|
+
bun run test
|
|
82
|
+
bun run typecheck
|
|
83
|
+
bun run lint
|
|
84
|
+
bun run fmt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`effect`, `@effect/*`, and `@effect/platform-node-shared` are pinned to the exact version that `@opencode/client` requires as a peer. Upgrade them together.
|
|
88
|
+
|
|
89
|
+
| File | Responsibility |
|
|
90
|
+
| ------------------------- | ------------------------------------------- |
|
|
91
|
+
| `src/cli.ts` | Command definition, layers, exit status |
|
|
92
|
+
| `src/batch.ts` | Input resolution and the batch loop |
|
|
93
|
+
| `src/merge-pr.ts` | The state machine for one PR |
|
|
94
|
+
| `src/github.ts` | `gh` calls and response schemas |
|
|
95
|
+
| `src/repair.ts` | Worktree setup for a conflict repair |
|
|
96
|
+
| `src/opencode.ts` | OpenCode sessions through the Effect client |
|
|
97
|
+
| `src/picker.ts` | The PR checkbox list |
|
|
98
|
+
| `src/tty.ts` | Prompts on the controlling terminal |
|
|
99
|
+
| `src/ui.ts` | Output and the status line |
|
|
100
|
+
| `scripts/npm-packages.ts` | The manifests of what gets published |
|
|
101
|
+
| `scripts/build-npm.ts` | Compiles every platform into `dist/npm` |
|
|
102
|
+
| `scripts/launcher.cjs` | The published `autoland` command |
|
|
103
|
+
|
|
104
|
+
## Releasing
|
|
105
|
+
|
|
106
|
+
Add a changeset with `bun run changeset` and commit it with your change. Merging to `main` opens or updates a `chore: version packages` PR, and merging that PR publishes to npm with no token, through [trusted publishing](https://docs.npmjs.com/trusted-publishers). See [`.changeset/README.md`](.changeset/README.md).
|
|
107
|
+
|
|
108
|
+
The repository holds one package, tagged `vX.Y.Z`. npm receives five, all generated by `bun run build:npm` at release time: `@aldotestino/autoland`, which contains only the launcher, and `@aldotestino/autoland-{darwin,linux}-{arm64,x64}`, which contain the binaries. The source is not published.
|
|
109
|
+
|
|
110
|
+
A trusted publisher can only be added to a package that already exists, so [`scripts/bootstrap-npm.sh`](scripts/bootstrap-npm.sh) does that once, by hand. Run it again if a platform is added.
|
package/bin/autoland.cjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `autoland` command. npm installs only the optional dependency matching this
|
|
3
|
+
// platform, and this starts its compiled binary. Node is only needed for this file.
|
|
4
|
+
"use strict";
|
|
5
|
+
// This runs on plain Node before anything else, so Effect's rules cannot apply.
|
|
6
|
+
// oxlint-disable effecttsgo/global-console, effecttsgo/node-builtin-import
|
|
7
|
+
|
|
8
|
+
const { spawn } = require("node:child_process");
|
|
9
|
+
|
|
10
|
+
const name = `@aldotestino/autoland-${process.platform}-${process.arch}`;
|
|
11
|
+
|
|
12
|
+
let binary;
|
|
13
|
+
try {
|
|
14
|
+
binary = require.resolve(`${name}/bin/autoland`);
|
|
15
|
+
} catch {
|
|
16
|
+
console.error(
|
|
17
|
+
`autoland: ${name} is not installed.\n` +
|
|
18
|
+
"Supported platforms are macOS and glibc Linux, on arm64 and x64.\n" +
|
|
19
|
+
"On a supported platform, reinstall without omitting optional dependencies.",
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
25
|
+
|
|
26
|
+
// Stay alive until the binary has cleaned up, so its exit status is the one reported.
|
|
27
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
28
|
+
process.on(signal, () => {
|
|
29
|
+
child.kill(signal);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
child.on("error", (error) => {
|
|
34
|
+
console.error(`autoland: cannot start ${binary}: ${error.message}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
child.on("exit", (code, signal) => {
|
|
39
|
+
if (signal === null) process.exit(code ?? 1);
|
|
40
|
+
process.removeAllListeners(signal);
|
|
41
|
+
process.kill(process.pid, signal);
|
|
42
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aldotestino/autoland",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "Land a batch of pull requests in order, one at a time: update, wait for checks, approve, merge.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/aldotestino/autoland.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/aldotestino/autoland#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/aldotestino/autoland/issues"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"autoland": "bin/autoland.cjs"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin"
|
|
22
|
+
],
|
|
23
|
+
"os": [
|
|
24
|
+
"darwin",
|
|
25
|
+
"linux"
|
|
26
|
+
],
|
|
27
|
+
"cpu": [
|
|
28
|
+
"arm64",
|
|
29
|
+
"x64"
|
|
30
|
+
],
|
|
31
|
+
"optionalDependencies": {
|
|
32
|
+
"@aldotestino/autoland-darwin-arm64": "0.1.0",
|
|
33
|
+
"@aldotestino/autoland-darwin-x64": "0.1.0",
|
|
34
|
+
"@aldotestino/autoland-linux-arm64": "0.1.0",
|
|
35
|
+
"@aldotestino/autoland-linux-x64": "0.1.0"
|
|
36
|
+
}
|
|
37
|
+
}
|