automata-cli 0.2.0-develop.1 → 0.2.0-develop.12

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.
Files changed (3) hide show
  1. package/README.md +59 -0
  2. package/dist/index.js +253 -2
  3. 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 Command();
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.1",
3
+ "version": "0.2.0-develop.12",
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",