automata-cli 0.2.0-develop.1 → 0.2.0-develop.13
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/README.md +59 -0
- package/dist/index.js +253 -2
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -14,6 +14,65 @@ npm install -g automata-cli
|
|
|
14
14
|
automata --help
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
### `automata config`
|
|
20
|
+
|
|
21
|
+
Launch the interactive configuration wizard. Use arrow keys to select the remote environment type and press Enter to save.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
automata config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### `automata config set type <value>`
|
|
28
|
+
|
|
29
|
+
Set a configuration value non-interactively (useful in scripts or CI).
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
automata config set type gh # GitHub
|
|
33
|
+
automata config set type azdo # Azure DevOps
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Configuration is saved to `.automata/config.json` in the current directory.
|
|
37
|
+
|
|
38
|
+
### `automata git get-pr-info`
|
|
39
|
+
|
|
40
|
+
Show the pull request associated with the current branch (requires [`gh` CLI](https://cli.github.com/) installed and authenticated).
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
automata git get-pr-info # human-readable output
|
|
44
|
+
automata git get-pr-info --json # JSON output
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Example output:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
PR: #42
|
|
51
|
+
Title: Fix authentication bug
|
|
52
|
+
State: MERGED
|
|
53
|
+
URL: https://github.com/org/repo/pull/42
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If no PR exists for the current branch, a friendly message is printed and the command exits with code 0.
|
|
57
|
+
|
|
58
|
+
### `automata git finish-feature`
|
|
59
|
+
|
|
60
|
+
Clean up a merged feature branch in one step: checkout `develop`, pull the latest, and delete the local branch.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
automata git finish-feature
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The command validates all preconditions before making any changes:
|
|
67
|
+
|
|
68
|
+
- Must **not** be on the `develop` branch
|
|
69
|
+
- Working tree must be clean (no uncommitted changes)
|
|
70
|
+
- A pull request for the branch must exist
|
|
71
|
+
- The PR must be in `merged` state (not open or closed-without-merge)
|
|
72
|
+
- The remote tracking branch must no longer exist (`origin/<branch>` is gone)
|
|
73
|
+
|
|
74
|
+
If any precondition fails, the command prints a descriptive error to stderr and exits with a non-zero code.
|
|
75
|
+
|
|
17
76
|
## Development
|
|
18
77
|
|
|
19
78
|
### Prerequisites
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command } from "commander";
|
|
4
|
+
import { Command as Command3 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
7
|
import { readFileSync } from "fs";
|
|
@@ -11,9 +11,260 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
11
11
|
var packageJson = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
|
|
12
12
|
var version = packageJson.version;
|
|
13
13
|
|
|
14
|
+
// src/commands/config.ts
|
|
15
|
+
import { Command } from "commander";
|
|
16
|
+
import { render } from "ink";
|
|
17
|
+
import React2 from "react";
|
|
18
|
+
|
|
19
|
+
// src/config/ConfigWizard.tsx
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
import { Box, Text, useInput, useApp } from "ink";
|
|
22
|
+
|
|
23
|
+
// src/config/configStore.ts
|
|
24
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
25
|
+
import { join } from "path";
|
|
26
|
+
var CONFIG_DIR = ".automata";
|
|
27
|
+
var CONFIG_FILE = "config.json";
|
|
28
|
+
function configPath() {
|
|
29
|
+
return join(process.cwd(), CONFIG_DIR, CONFIG_FILE);
|
|
30
|
+
}
|
|
31
|
+
function readConfig() {
|
|
32
|
+
try {
|
|
33
|
+
const raw = readFileSync2(configPath(), "utf8");
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
} catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeConfig(config) {
|
|
40
|
+
const dir = join(process.cwd(), CONFIG_DIR);
|
|
41
|
+
mkdirSync(dir, { recursive: true });
|
|
42
|
+
writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/config/ConfigWizard.tsx
|
|
46
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
47
|
+
var OPTIONS = [
|
|
48
|
+
{ label: "GitHub", value: "gh" },
|
|
49
|
+
{ label: "Azure DevOps", value: "azdo" }
|
|
50
|
+
];
|
|
51
|
+
function ConfigWizard() {
|
|
52
|
+
const existing = readConfig();
|
|
53
|
+
const initialIndex = OPTIONS.findIndex((o) => o.value === existing.remoteType);
|
|
54
|
+
const [selectedIndex, setSelectedIndex] = useState(initialIndex >= 0 ? initialIndex : 0);
|
|
55
|
+
const { exit } = useApp();
|
|
56
|
+
useInput((input, key) => {
|
|
57
|
+
if (key.upArrow) {
|
|
58
|
+
setSelectedIndex((i) => i > 0 ? i - 1 : OPTIONS.length - 1);
|
|
59
|
+
} else if (key.downArrow) {
|
|
60
|
+
setSelectedIndex((i) => i < OPTIONS.length - 1 ? i + 1 : 0);
|
|
61
|
+
} else if (key.return) {
|
|
62
|
+
const chosen = OPTIONS[selectedIndex];
|
|
63
|
+
writeConfig({ ...existing, remoteType: chosen.value });
|
|
64
|
+
exit();
|
|
65
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
66
|
+
exit();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
70
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Configure Automata" }),
|
|
71
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
72
|
+
/* @__PURE__ */ jsx(Text, { children: "Remote environment type:" }),
|
|
73
|
+
OPTIONS.map((option, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : void 0, children: [
|
|
74
|
+
index === selectedIndex ? "\u276F " : " ",
|
|
75
|
+
option.label
|
|
76
|
+
] }) }, option.value)),
|
|
77
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
78
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 to move \xB7 Enter to confirm \xB7 Ctrl+C to cancel" })
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/commands/config.ts
|
|
83
|
+
var VALID_TYPES = ["gh", "azdo"];
|
|
84
|
+
var configSetType = new Command("type").description("Set the remote environment type").argument("<value>", "Remote type: gh (GitHub) or azdo (Azure DevOps)").action((value) => {
|
|
85
|
+
if (!VALID_TYPES.includes(value)) {
|
|
86
|
+
process.stderr.write(`Error: invalid type "${value}". Must be one of: ${VALID_TYPES.join(", ")}
|
|
87
|
+
`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const current = readConfig();
|
|
91
|
+
writeConfig({ ...current, remoteType: value });
|
|
92
|
+
process.stdout.write(`Remote type set to: ${value}
|
|
93
|
+
`);
|
|
94
|
+
});
|
|
95
|
+
var configSet = new Command("set").description("Set a configuration value").addCommand(configSetType);
|
|
96
|
+
var configCommand = new Command("config").description("Configure automata settings").addCommand(configSet).action(async () => {
|
|
97
|
+
const { waitUntilExit } = render(React2.createElement(ConfigWizard));
|
|
98
|
+
await waitUntilExit();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// src/commands/git.ts
|
|
102
|
+
import { Command as Command2 } from "commander";
|
|
103
|
+
|
|
104
|
+
// src/git/gitService.ts
|
|
105
|
+
import { spawnSync } from "child_process";
|
|
106
|
+
function run(cmd, args) {
|
|
107
|
+
const result = spawnSync(cmd, args, { encoding: "utf8" });
|
|
108
|
+
return {
|
|
109
|
+
stdout: result.stdout ?? "",
|
|
110
|
+
stderr: result.stderr ?? "",
|
|
111
|
+
status: result.status ?? 1
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function getCurrentBranch() {
|
|
115
|
+
const { stdout, status } = run("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
116
|
+
if (status !== 0) {
|
|
117
|
+
throw new Error("Failed to determine current branch. Are you inside a git repository?");
|
|
118
|
+
}
|
|
119
|
+
return stdout.trim();
|
|
120
|
+
}
|
|
121
|
+
function getPrInfo(branch) {
|
|
122
|
+
const { stdout, stderr, status } = run("gh", [
|
|
123
|
+
"pr",
|
|
124
|
+
"view",
|
|
125
|
+
branch,
|
|
126
|
+
"--json",
|
|
127
|
+
"number,title,state,url"
|
|
128
|
+
]);
|
|
129
|
+
if (status !== 0) {
|
|
130
|
+
if (stderr.includes("no pull requests found") || stderr.includes("Could not resolve")) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
throw new Error(stderr.trim() || "Failed to query GitHub. Is `gh` installed and authenticated?");
|
|
134
|
+
}
|
|
135
|
+
return JSON.parse(stdout);
|
|
136
|
+
}
|
|
137
|
+
function isUpstreamGone(branch) {
|
|
138
|
+
const { status } = run("git", ["ls-remote", "--exit-code", "--heads", "origin", branch]);
|
|
139
|
+
return status !== 0;
|
|
140
|
+
}
|
|
141
|
+
function hasUncommittedChanges() {
|
|
142
|
+
const { stdout } = run("git", ["status", "--porcelain"]);
|
|
143
|
+
return stdout.trim().length > 0;
|
|
144
|
+
}
|
|
145
|
+
function checkoutAndPull(targetBranch) {
|
|
146
|
+
const checkout = run("git", ["checkout", targetBranch]);
|
|
147
|
+
if (checkout.status !== 0) {
|
|
148
|
+
throw new Error(`Failed to checkout ${targetBranch}: ${checkout.stderr.trim()}`);
|
|
149
|
+
}
|
|
150
|
+
const pull = run("git", ["pull"]);
|
|
151
|
+
if (pull.status !== 0) {
|
|
152
|
+
throw new Error(`Failed to pull ${targetBranch}: ${pull.stderr.trim()}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function deleteLocalBranch(branch) {
|
|
156
|
+
const result = run("git", ["branch", "-d", branch]);
|
|
157
|
+
if (result.status !== 0) {
|
|
158
|
+
throw new Error(`Failed to delete branch ${branch}: ${result.stderr.trim()}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/commands/git.ts
|
|
163
|
+
var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").action((options) => {
|
|
164
|
+
let branch;
|
|
165
|
+
try {
|
|
166
|
+
branch = getCurrentBranch();
|
|
167
|
+
} catch (err) {
|
|
168
|
+
process.stderr.write(`Error: ${err.message}
|
|
169
|
+
`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
let pr;
|
|
173
|
+
try {
|
|
174
|
+
pr = getPrInfo(branch);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
process.stderr.write(`Error: ${err.message}
|
|
177
|
+
`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
if (pr === null) {
|
|
181
|
+
process.stdout.write(`No pull request found for branch: ${branch}
|
|
182
|
+
`);
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
if (options.json) {
|
|
186
|
+
process.stdout.write(JSON.stringify(pr, null, 2) + "\n");
|
|
187
|
+
} else {
|
|
188
|
+
process.stdout.write(`PR: #${pr.number}
|
|
189
|
+
Title: ${pr.title}
|
|
190
|
+
State: ${pr.state}
|
|
191
|
+
URL: ${pr.url}
|
|
192
|
+
`);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
|
|
196
|
+
let branch;
|
|
197
|
+
try {
|
|
198
|
+
branch = getCurrentBranch();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
process.stderr.write(`Error: ${err.message}
|
|
201
|
+
`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
if (branch === "develop") {
|
|
205
|
+
process.stderr.write("Error: finish-feature cannot be run from the develop branch.\n");
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
if (hasUncommittedChanges()) {
|
|
209
|
+
process.stderr.write(
|
|
210
|
+
"Error: You have uncommitted changes. Commit or stash them before running finish-feature.\n"
|
|
211
|
+
);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
let pr;
|
|
215
|
+
try {
|
|
216
|
+
pr = getPrInfo(branch);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
process.stderr.write(`Error: ${err.message}
|
|
219
|
+
`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
if (pr === null) {
|
|
223
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
224
|
+
`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
if (pr.state === "OPEN") {
|
|
228
|
+
process.stderr.write(`Error: Pull request #${pr.number} is still open. Merge it before finishing the feature.
|
|
229
|
+
`);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
if (pr.state === "CLOSED") {
|
|
233
|
+
process.stderr.write(
|
|
234
|
+
`Error: Pull request #${pr.number} was closed without merging. finish-feature only proceeds for merged PRs.
|
|
235
|
+
`
|
|
236
|
+
);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
if (!isUpstreamGone(branch)) {
|
|
240
|
+
process.stderr.write(
|
|
241
|
+
`Error: Remote tracking branch 'origin/${branch}' still exists. Push or delete it remotely before finishing.
|
|
242
|
+
`
|
|
243
|
+
);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
process.stdout.write(`Checking out develop and pulling latest...
|
|
248
|
+
`);
|
|
249
|
+
checkoutAndPull("develop");
|
|
250
|
+
process.stdout.write(`Deleting local branch: ${branch}
|
|
251
|
+
`);
|
|
252
|
+
deleteLocalBranch(branch);
|
|
253
|
+
process.stdout.write(`Done. Branch '${branch}' has been removed.
|
|
254
|
+
`);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
process.stderr.write(`Error: ${err.message}
|
|
257
|
+
`);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
var gitCommand = new Command2("git").description("Git workflow commands (requires gh CLI)").addCommand(getPrInfoCmd).addCommand(finishFeatureCmd);
|
|
262
|
+
|
|
14
263
|
// src/index.ts
|
|
15
|
-
var program = new
|
|
264
|
+
var program = new Command3();
|
|
16
265
|
program.name("automata").description("Automata CLI tool").version(version, "-v, --version");
|
|
266
|
+
program.addCommand(configCommand);
|
|
267
|
+
program.addCommand(gitCommand);
|
|
17
268
|
program.showHelpAfterError();
|
|
18
269
|
program.parse();
|
|
19
270
|
if (process.argv.length <= 2) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automata-cli",
|
|
3
|
-
"version": "0.2.0-develop.
|
|
3
|
+
"version": "0.2.0-develop.13",
|
|
4
4
|
"description": "Automata CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,11 +23,14 @@
|
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"commander": "^14.0.3"
|
|
26
|
+
"commander": "^14.0.3",
|
|
27
|
+
"ink": "^6.8.0",
|
|
28
|
+
"react": "^19.2.4"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"@eslint/js": "^10.0.1",
|
|
30
32
|
"@types/node": "^25.5.0",
|
|
33
|
+
"@types/react": "^19.2.14",
|
|
31
34
|
"eslint": "^10.1.0",
|
|
32
35
|
"prettier": "^3.8.1",
|
|
33
36
|
"tsup": "^8.5.1",
|