@ipul-labs/forge 1.16.7 → 1.17.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.
Files changed (280) hide show
  1. package/README.md +58 -21
  2. package/lib/bin/cli/analyze.d.ts +9 -0
  3. package/lib/bin/cli/analyze.d.ts.map +1 -0
  4. package/lib/bin/cli/analyze.js +491 -0
  5. package/lib/bin/cli/analyze.js.map +1 -0
  6. package/lib/bin/cli/apply.d.ts +2 -0
  7. package/lib/bin/cli/apply.d.ts.map +1 -0
  8. package/lib/bin/cli/apply.js +262 -0
  9. package/lib/bin/cli/apply.js.map +1 -0
  10. package/lib/bin/cli/cifix.d.ts +2 -0
  11. package/lib/bin/cli/cifix.d.ts.map +1 -0
  12. package/lib/bin/cli/cifix.js +310 -0
  13. package/lib/bin/cli/cifix.js.map +1 -0
  14. package/lib/bin/cli/close.d.ts +2 -0
  15. package/lib/bin/cli/close.d.ts.map +1 -0
  16. package/lib/bin/cli/close.js +113 -0
  17. package/lib/bin/cli/close.js.map +1 -0
  18. package/lib/bin/cli/createIssue.d.ts +2 -0
  19. package/lib/bin/cli/createIssue.d.ts.map +1 -0
  20. package/lib/bin/cli/createIssue.js +108 -0
  21. package/lib/bin/cli/createIssue.js.map +1 -0
  22. package/lib/bin/cli/doctor.d.ts +2 -0
  23. package/lib/bin/cli/doctor.d.ts.map +1 -0
  24. package/lib/bin/cli/doctor.js +145 -0
  25. package/lib/bin/cli/doctor.js.map +1 -0
  26. package/lib/bin/cli/init.d.ts +4 -0
  27. package/lib/bin/cli/init.d.ts.map +1 -0
  28. package/lib/bin/cli/init.js +288 -0
  29. package/lib/bin/cli/init.js.map +1 -0
  30. package/lib/bin/cli/ink.d.ts +23 -0
  31. package/lib/bin/cli/ink.d.ts.map +1 -0
  32. package/{bin → lib/bin}/cli/ink.js +18 -19
  33. package/lib/bin/cli/ink.js.map +1 -0
  34. package/lib/bin/cli/issue.d.ts +2 -0
  35. package/lib/bin/cli/issue.d.ts.map +1 -0
  36. package/lib/bin/cli/issue.js +114 -0
  37. package/lib/bin/cli/issue.js.map +1 -0
  38. package/lib/bin/cli/issues.d.ts +2 -0
  39. package/lib/bin/cli/issues.d.ts.map +1 -0
  40. package/lib/bin/cli/issues.js +149 -0
  41. package/lib/bin/cli/issues.js.map +1 -0
  42. package/lib/bin/cli/merge.d.ts +2 -0
  43. package/lib/bin/cli/merge.d.ts.map +1 -0
  44. package/lib/bin/cli/merge.js +162 -0
  45. package/lib/bin/cli/merge.js.map +1 -0
  46. package/lib/bin/cli/refine.d.ts +2 -0
  47. package/lib/bin/cli/refine.d.ts.map +1 -0
  48. package/lib/bin/cli/refine.js +249 -0
  49. package/lib/bin/cli/refine.js.map +1 -0
  50. package/lib/bin/cli/repl.d.ts +2 -0
  51. package/lib/bin/cli/repl.d.ts.map +1 -0
  52. package/lib/bin/cli/repl.js +309 -0
  53. package/lib/bin/cli/repl.js.map +1 -0
  54. package/lib/bin/cli/repos.d.ts +2 -0
  55. package/lib/bin/cli/repos.d.ts.map +1 -0
  56. package/lib/bin/cli/repos.js +86 -0
  57. package/lib/bin/cli/repos.js.map +1 -0
  58. package/lib/bin/cli/resolution.d.ts +2 -0
  59. package/lib/bin/cli/resolution.d.ts.map +1 -0
  60. package/lib/bin/cli/resolution.js +139 -0
  61. package/lib/bin/cli/resolution.js.map +1 -0
  62. package/lib/bin/cli/serverClient.d.ts +26 -0
  63. package/lib/bin/cli/serverClient.d.ts.map +1 -0
  64. package/lib/bin/cli/serverClient.js +111 -0
  65. package/lib/bin/cli/serverClient.js.map +1 -0
  66. package/lib/bin/cli/session.d.ts +8 -0
  67. package/lib/bin/cli/session.d.ts.map +1 -0
  68. package/lib/bin/cli/session.js +212 -0
  69. package/lib/bin/cli/session.js.map +1 -0
  70. package/lib/bin/cli/sessionStore.d.ts +69 -0
  71. package/lib/bin/cli/sessionStore.d.ts.map +1 -0
  72. package/lib/bin/cli/sessionStore.js +196 -0
  73. package/lib/bin/cli/sessionStore.js.map +1 -0
  74. package/lib/bin/cli/ship.d.ts +33 -0
  75. package/lib/bin/cli/ship.d.ts.map +1 -0
  76. package/lib/bin/cli/ship.js +737 -0
  77. package/lib/bin/cli/ship.js.map +1 -0
  78. package/lib/bin/cli/types.d.ts +59 -0
  79. package/lib/bin/cli/types.d.ts.map +1 -0
  80. package/lib/bin/cli/types.js +2 -0
  81. package/lib/bin/cli/types.js.map +1 -0
  82. package/lib/bin/cli/update.d.ts +2 -0
  83. package/lib/bin/cli/update.d.ts.map +1 -0
  84. package/lib/bin/cli/update.js +87 -0
  85. package/lib/bin/cli/update.js.map +1 -0
  86. package/lib/bin/cli/utils.d.ts +43 -0
  87. package/lib/bin/cli/utils.d.ts.map +1 -0
  88. package/lib/bin/cli/utils.js +171 -0
  89. package/lib/bin/cli/utils.js.map +1 -0
  90. package/lib/bin/cli/validate.d.ts +2 -0
  91. package/lib/bin/cli/validate.d.ts.map +1 -0
  92. package/lib/bin/cli/validate.js +283 -0
  93. package/lib/bin/cli/validate.js.map +1 -0
  94. package/lib/bin/cli/watch.d.ts +5 -0
  95. package/lib/bin/cli/watch.d.ts.map +1 -0
  96. package/lib/bin/cli/watch.js +263 -0
  97. package/lib/bin/cli/watch.js.map +1 -0
  98. package/lib/bin/forge-agent.d.ts +3 -0
  99. package/lib/bin/forge-agent.d.ts.map +1 -0
  100. package/{bin → lib/bin}/forge-agent.js +104 -105
  101. package/lib/bin/forge-agent.js.map +1 -0
  102. package/lib/server/forge/core/ForgeAgent.d.ts +260 -0
  103. package/lib/server/forge/core/ForgeAgent.d.ts.map +1 -0
  104. package/lib/server/forge/core/ForgeAgent.js +995 -0
  105. package/lib/server/forge/core/ForgeAgent.js.map +1 -0
  106. package/lib/server/forge/core/agentClient.d.ts +39 -0
  107. package/lib/server/forge/core/agentClient.d.ts.map +1 -0
  108. package/{server → lib/server}/forge/core/agentClient.js +7 -12
  109. package/lib/server/forge/core/agentClient.js.map +1 -0
  110. package/lib/server/forge/core/branchName.d.ts +20 -0
  111. package/lib/server/forge/core/branchName.d.ts.map +1 -0
  112. package/lib/server/forge/core/branchName.js +39 -0
  113. package/lib/server/forge/core/branchName.js.map +1 -0
  114. package/lib/server/forge/core/claudeClient.d.ts +60 -0
  115. package/lib/server/forge/core/claudeClient.d.ts.map +1 -0
  116. package/lib/server/forge/core/claudeClient.js +347 -0
  117. package/lib/server/forge/core/claudeClient.js.map +1 -0
  118. package/lib/server/forge/core/configStore.d.ts +76 -0
  119. package/lib/server/forge/core/configStore.d.ts.map +1 -0
  120. package/lib/server/forge/core/configStore.js +213 -0
  121. package/lib/server/forge/core/configStore.js.map +1 -0
  122. package/lib/server/forge/core/gitlabClient.d.ts +144 -0
  123. package/lib/server/forge/core/gitlabClient.d.ts.map +1 -0
  124. package/{server → lib/server}/forge/core/gitlabClient.js +98 -180
  125. package/lib/server/forge/core/gitlabClient.js.map +1 -0
  126. package/lib/server/forge/core/jobs.d.ts +67 -0
  127. package/lib/server/forge/core/jobs.d.ts.map +1 -0
  128. package/lib/server/forge/core/jobs.js +185 -0
  129. package/lib/server/forge/core/jobs.js.map +1 -0
  130. package/lib/server/forge/core/k8s.d.ts +18 -0
  131. package/lib/server/forge/core/k8s.d.ts.map +1 -0
  132. package/lib/server/forge/core/k8s.js +49 -0
  133. package/lib/server/forge/core/k8s.js.map +1 -0
  134. package/lib/server/forge/core/localRepo.d.ts +36 -0
  135. package/lib/server/forge/core/localRepo.d.ts.map +1 -0
  136. package/lib/server/forge/core/localRepo.js +165 -0
  137. package/lib/server/forge/core/localRepo.js.map +1 -0
  138. package/lib/server/forge/core/openclawClient.d.ts +61 -0
  139. package/lib/server/forge/core/openclawClient.d.ts.map +1 -0
  140. package/lib/server/forge/core/openclawClient.js +111 -0
  141. package/lib/server/forge/core/openclawClient.js.map +1 -0
  142. package/lib/server/forge/core/prompts.d.ts +96 -0
  143. package/lib/server/forge/core/prompts.d.ts.map +1 -0
  144. package/lib/server/forge/core/prompts.js +450 -0
  145. package/lib/server/forge/core/prompts.js.map +1 -0
  146. package/lib/server/forge/core/providers/azuredevops.d.ts +134 -0
  147. package/lib/server/forge/core/providers/azuredevops.d.ts.map +1 -0
  148. package/lib/server/forge/core/providers/azuredevops.js +360 -0
  149. package/lib/server/forge/core/providers/azuredevops.js.map +1 -0
  150. package/lib/server/forge/core/providers/base.d.ts +114 -0
  151. package/lib/server/forge/core/providers/base.d.ts.map +1 -0
  152. package/lib/server/forge/core/providers/base.js +98 -0
  153. package/lib/server/forge/core/providers/base.js.map +1 -0
  154. package/lib/server/forge/core/providers/github.d.ts +133 -0
  155. package/lib/server/forge/core/providers/github.d.ts.map +1 -0
  156. package/lib/server/forge/core/providers/github.js +307 -0
  157. package/lib/server/forge/core/providers/github.js.map +1 -0
  158. package/lib/server/forge/core/providers/gitlab.d.ts +144 -0
  159. package/lib/server/forge/core/providers/gitlab.d.ts.map +1 -0
  160. package/lib/server/forge/core/providers/gitlab.js +407 -0
  161. package/lib/server/forge/core/providers/gitlab.js.map +1 -0
  162. package/lib/server/forge/core/providers/index.d.ts +21 -0
  163. package/lib/server/forge/core/providers/index.d.ts.map +1 -0
  164. package/lib/server/forge/core/providers/index.js +43 -0
  165. package/lib/server/forge/core/providers/index.js.map +1 -0
  166. package/lib/server/forge/core/providers/types.d.ts +80 -0
  167. package/lib/server/forge/core/providers/types.d.ts.map +1 -0
  168. package/lib/server/forge/core/providers/types.js +2 -0
  169. package/lib/server/forge/core/providers/types.js.map +1 -0
  170. package/lib/server/forge/core/quotaClient.d.ts +48 -0
  171. package/lib/server/forge/core/quotaClient.d.ts.map +1 -0
  172. package/lib/server/forge/core/quotaClient.js +88 -0
  173. package/lib/server/forge/core/quotaClient.js.map +1 -0
  174. package/lib/server/forge/core/types.d.ts +160 -0
  175. package/lib/server/forge/core/types.d.ts.map +1 -0
  176. package/lib/server/forge/core/types.js +2 -0
  177. package/lib/server/forge/core/types.js.map +1 -0
  178. package/lib/server/forge/core/validator/attribute.d.ts +31 -0
  179. package/lib/server/forge/core/validator/attribute.d.ts.map +1 -0
  180. package/lib/server/forge/core/validator/attribute.js +82 -0
  181. package/lib/server/forge/core/validator/attribute.js.map +1 -0
  182. package/lib/server/forge/core/validator/clone.d.ts +14 -0
  183. package/lib/server/forge/core/validator/clone.d.ts.map +1 -0
  184. package/lib/server/forge/core/validator/clone.js +99 -0
  185. package/lib/server/forge/core/validator/clone.js.map +1 -0
  186. package/lib/server/forge/core/validator/cmd.d.ts +23 -0
  187. package/lib/server/forge/core/validator/cmd.d.ts.map +1 -0
  188. package/lib/server/forge/core/validator/cmd.js +87 -0
  189. package/lib/server/forge/core/validator/cmd.js.map +1 -0
  190. package/lib/server/forge/core/validator/fs.d.ts +5 -0
  191. package/lib/server/forge/core/validator/fs.d.ts.map +1 -0
  192. package/{server → lib/server}/forge/core/validator/fs.js +8 -3
  193. package/lib/server/forge/core/validator/fs.js.map +1 -0
  194. package/lib/server/forge/core/validator/overlay.d.ts +13 -0
  195. package/lib/server/forge/core/validator/overlay.d.ts.map +1 -0
  196. package/lib/server/forge/core/validator/overlay.js +53 -0
  197. package/lib/server/forge/core/validator/overlay.js.map +1 -0
  198. package/lib/server/forge/core/validator/projectType.d.ts +20 -0
  199. package/lib/server/forge/core/validator/projectType.d.ts.map +1 -0
  200. package/lib/server/forge/core/validator/projectType.js +94 -0
  201. package/lib/server/forge/core/validator/projectType.js.map +1 -0
  202. package/lib/server/forge/core/validator/runners/go.d.ts +21 -0
  203. package/lib/server/forge/core/validator/runners/go.d.ts.map +1 -0
  204. package/lib/server/forge/core/validator/runners/go.js +99 -0
  205. package/lib/server/forge/core/validator/runners/go.js.map +1 -0
  206. package/lib/server/forge/core/validator/runners/node.d.ts +12 -0
  207. package/lib/server/forge/core/validator/runners/node.d.ts.map +1 -0
  208. package/lib/server/forge/core/validator/runners/node.js +75 -0
  209. package/lib/server/forge/core/validator/runners/node.js.map +1 -0
  210. package/lib/server/forge/core/validator/tdd.d.ts +30 -0
  211. package/lib/server/forge/core/validator/tdd.d.ts.map +1 -0
  212. package/lib/server/forge/core/validator/tdd.js +96 -0
  213. package/lib/server/forge/core/validator/tdd.js.map +1 -0
  214. package/lib/server/forge/core/validator.d.ts +58 -0
  215. package/lib/server/forge/core/validator.d.ts.map +1 -0
  216. package/lib/server/forge/core/validator.js +150 -0
  217. package/lib/server/forge/core/validator.js.map +1 -0
  218. package/lib/server/forge/routes.d.ts +22 -0
  219. package/lib/server/forge/routes.d.ts.map +1 -0
  220. package/lib/server/forge/routes.js +984 -0
  221. package/lib/server/forge/routes.js.map +1 -0
  222. package/lib/server.d.ts +2 -0
  223. package/lib/server.d.ts.map +1 -0
  224. package/lib/server.js +52 -0
  225. package/lib/server.js.map +1 -0
  226. package/lib/shared/events.d.ts +12 -0
  227. package/lib/shared/events.d.ts.map +1 -0
  228. package/lib/shared/events.js +12 -0
  229. package/lib/shared/events.js.map +1 -0
  230. package/package.json +17 -13
  231. package/scripts/fix-shebang.cjs +10 -0
  232. package/bin/cli/analyze.js +0 -516
  233. package/bin/cli/apply.js +0 -264
  234. package/bin/cli/cifix.js +0 -300
  235. package/bin/cli/close.js +0 -125
  236. package/bin/cli/createIssue.js +0 -112
  237. package/bin/cli/doctor.js +0 -161
  238. package/bin/cli/init.js +0 -306
  239. package/bin/cli/issue.js +0 -116
  240. package/bin/cli/issues.js +0 -155
  241. package/bin/cli/merge.js +0 -175
  242. package/bin/cli/refine.js +0 -253
  243. package/bin/cli/repl.js +0 -301
  244. package/bin/cli/repos.js +0 -91
  245. package/bin/cli/resolution.js +0 -126
  246. package/bin/cli/session.js +0 -230
  247. package/bin/cli/sessionStore.js +0 -211
  248. package/bin/cli/ship.js +0 -762
  249. package/bin/cli/update.js +0 -95
  250. package/bin/cli/utils.js +0 -145
  251. package/bin/cli/validate.js +0 -280
  252. package/bin/cli/watch.js +0 -274
  253. package/server/forge/core/ForgeAgent.js +0 -1179
  254. package/server/forge/core/ForgeAgent.test.js +0 -179
  255. package/server/forge/core/branchName.js +0 -41
  256. package/server/forge/core/claudeClient.js +0 -378
  257. package/server/forge/core/configStore.js +0 -229
  258. package/server/forge/core/configStore.test.js +0 -89
  259. package/server/forge/core/jobs.js +0 -180
  260. package/server/forge/core/k8s.js +0 -54
  261. package/server/forge/core/localRepo.js +0 -153
  262. package/server/forge/core/openclawClient.js +0 -125
  263. package/server/forge/core/prompts.js +0 -483
  264. package/server/forge/core/prompts.test.js +0 -219
  265. package/server/forge/core/providers/azuredevops.js +0 -409
  266. package/server/forge/core/providers/base.js +0 -208
  267. package/server/forge/core/providers/github.js +0 -348
  268. package/server/forge/core/providers/gitlab.js +0 -529
  269. package/server/forge/core/providers/index.js +0 -47
  270. package/server/forge/core/quotaClient.js +0 -101
  271. package/server/forge/core/validator/attribute.js +0 -84
  272. package/server/forge/core/validator/clone.js +0 -105
  273. package/server/forge/core/validator/cmd.js +0 -98
  274. package/server/forge/core/validator/overlay.js +0 -52
  275. package/server/forge/core/validator/projectType.js +0 -87
  276. package/server/forge/core/validator/runners/go.js +0 -107
  277. package/server/forge/core/validator/runners/node.js +0 -84
  278. package/server/forge/core/validator/tdd.js +0 -105
  279. package/server/forge/core/validator.js +0 -167
  280. package/server/forge/routes.js +0 -1026
@@ -1,112 +0,0 @@
1
- import path from "node:path";
2
- import React, { useEffect, useState } from "react";
3
- import Spinner from "ink-spinner";
4
- import { h, Box, Text, useApp, runApp } from "./ink.js";
5
- import { getFlag, projectRoot, resolvePrimaryRepo } from "./utils.js";
6
-
7
- function CreateIssueView({ repo, title, description, onDone }) {
8
- const { exit } = useApp();
9
- const [phase, setPhase] = useState("creating");
10
- const [issue, setIssue] = useState(null);
11
- const [error, setError] = useState(null);
12
-
13
- useEffect(() => {
14
- if (phase === "done" || phase === "error") {
15
- onDone({ phase, issue, error });
16
- exit();
17
- }
18
- }, [phase]);
19
-
20
- useEffect(() => {
21
- let cancelled = false;
22
- (async () => {
23
- try {
24
- const { createProvider } = await import(
25
- path.join(projectRoot, "server/forge/core/providers/index.js")
26
- );
27
- const provider = createProvider(repo);
28
- const created = await provider.createIssue(title, description);
29
- if (cancelled) return;
30
- setIssue(created);
31
- setPhase("done");
32
- } catch (err) {
33
- if (cancelled) return;
34
- setError(err);
35
- setPhase("error");
36
- }
37
- })();
38
- return () => {
39
- cancelled = true;
40
- };
41
- }, []);
42
-
43
- if (phase === "creating") {
44
- return h`<${Box}>
45
- <${Text} color="cyan"><${Spinner} type="dots"/></>
46
- <${Text}> Creating issue in ${repo}…</>
47
- </>`;
48
- }
49
-
50
- if (phase === "done") {
51
- return h`<${Box} flexDirection="column">
52
- <${Box}>
53
- <${Text} color="green">✓ </>
54
- <${Text}>Created </>
55
- <${Text} color="yellow">#${issue.iid}</>
56
- <${Text}> — ${issue.title}</>
57
- </>
58
- ${issue.web_url
59
- ? h`<${Box}><${Text} dimColor>${issue.web_url}</></>`
60
- : null}
61
- <${Box} marginTop=${1}>
62
- <${Text} dimColor>${`Next: forge ship ${issue.iid}`}</>
63
- </>
64
- </>`;
65
- }
66
-
67
- return h`<${Box}>
68
- <${Text} color="red">${`✘ ${error?.message ?? String(error)}`}</>
69
- </>`;
70
- }
71
-
72
- export async function cmdCreateIssue(argv) {
73
- const title = getFlag(argv, "--title") ?? argv.slice(1).find((a) => !a.startsWith("-"));
74
- if (!title) {
75
- console.error("Usage: forge create-issue --title \"Bug: widget broken\" [--description \"...\"] [--repo <id>] [--json]");
76
- process.exit(1);
77
- }
78
-
79
- const description = getFlag(argv, "--description") ?? "";
80
- const repo = await resolvePrimaryRepo(argv);
81
- if (!repo) {
82
- console.error(
83
- "No repo. Pass --repo <group/repo>, set FORGE_GITLAB_PROJECT, or run `forge init`.",
84
- );
85
- process.exit(1);
86
- }
87
-
88
- const json = argv.includes("--json");
89
- if (json) {
90
- const { createProvider } = await import(
91
- path.join(projectRoot, "server/forge/core/providers/index.js")
92
- );
93
- try {
94
- const issue = await createProvider(repo).createIssue(title, description);
95
- process.stdout.write(`${JSON.stringify(issue, null, 2)}\n`);
96
- } catch (err) {
97
- process.stdout.write(`${JSON.stringify({ error: err.message })}\n`);
98
- process.exit(2);
99
- }
100
- return;
101
- }
102
-
103
- let outcome = { phase: null };
104
- await runApp(
105
- h`<${CreateIssueView}
106
- repo=${repo}
107
- title=${title}
108
- description=${description}
109
- onDone=${(o) => (outcome = o)} />`,
110
- );
111
- if (outcome.phase === "error") process.exit(2);
112
- }
package/bin/cli/doctor.js DELETED
@@ -1,161 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { execFile } from "node:child_process";
6
- import { promisify } from "node:util";
7
- import React, { useEffect, useState } from "react";
8
- import Spinner from "ink-spinner";
9
- import { h, Box, Text, useApp, runApp } from "./ink.js";
10
- import { projectRoot } from "./utils.js";
11
-
12
- const execFileP = promisify(execFile);
13
-
14
- function CheckRow({ name, status, message }) {
15
- const label = name.padEnd(18);
16
- if (status === "pending") {
17
- return h`<${Box}>
18
- <${Text} color="gray">○ ${label} </>
19
- <${Text} dimColor>queued</>
20
- </>`;
21
- }
22
- if (status === "running") {
23
- return h`<${Box}>
24
- <${Text} color="cyan"><${Spinner} type="dots"/></>
25
- <${Text}> ${label} </>
26
- <${Text} dimColor>checking…</>
27
- </>`;
28
- }
29
- if (status === "pass") {
30
- return h`<${Box}>
31
- <${Text} color="green">✓</>
32
- <${Text}> ${label} </>
33
- <${Text} dimColor>${message ?? ""}</>
34
- </>`;
35
- }
36
- return h`<${Box}>
37
- <${Text} color="red">✘</>
38
- <${Text}> ${label} </>
39
- <${Text} color="red">${message ?? ""}</>
40
- </>`;
41
- }
42
-
43
- function Doctor({ checks, onDone }) {
44
- const { exit } = useApp();
45
- const [rows, setRows] = useState(() =>
46
- checks.map((c) => ({ name: c.name, status: "pending", message: "" })),
47
- );
48
- const [done, setDone] = useState(null);
49
-
50
- useEffect(() => {
51
- let cancelled = false;
52
- (async () => {
53
- let failed = 0;
54
- for (let i = 0; i < checks.length; i++) {
55
- if (cancelled) return;
56
- setRows((s) =>
57
- s.map((row, idx) => (idx === i ? { ...row, status: "running" } : row)),
58
- );
59
- try {
60
- const msg = await checks[i].fn();
61
- if (cancelled) return;
62
- setRows((s) =>
63
- s.map((row, idx) =>
64
- idx === i ? { ...row, status: "pass", message: msg } : row,
65
- ),
66
- );
67
- } catch (err) {
68
- if (cancelled) return;
69
- setRows((s) =>
70
- s.map((row, idx) =>
71
- idx === i
72
- ? { ...row, status: "fail", message: err.message || String(err) }
73
- : row,
74
- ),
75
- );
76
- failed += 1;
77
- }
78
- }
79
- setDone({ failed });
80
- })();
81
- return () => {
82
- cancelled = true;
83
- };
84
- }, []);
85
-
86
- useEffect(() => {
87
- if (!done) return;
88
- onDone(done.failed);
89
- exit();
90
- }, [done]);
91
-
92
- return h`<${Box} flexDirection="column">
93
- ${rows.map((r, i) =>
94
- h`<${CheckRow} key=${i} name=${r.name} status=${r.status} message=${r.message} />`,
95
- )}
96
- </>`;
97
- }
98
-
99
- export async function cmdDoctor() {
100
- const checks = [
101
- {
102
- name: "node >= 20",
103
- fn: () => {
104
- const major = parseInt(process.versions.node.split(".")[0], 10);
105
- if (major < 20) throw new Error(`found ${process.versions.node}`);
106
- return process.versions.node;
107
- },
108
- },
109
- {
110
- name: "git",
111
- fn: async () => {
112
- const { stdout } = await execFileP("git", ["--version"]);
113
- return stdout.trim();
114
- },
115
- },
116
- {
117
- name: "claude CLI",
118
- fn: async () => {
119
- const { stdout } = await execFileP("claude", ["--version"]);
120
- return stdout.trim();
121
- },
122
- },
123
- {
124
- name: "FORGE_TOKEN",
125
- fn: () => {
126
- if (!process.env.FORGE_TOKEN) throw new Error("not set (run: forge init)");
127
- return "set";
128
- },
129
- },
130
- {
131
- name: "config file",
132
- fn: async () => {
133
- const p =
134
- process.env.FORGE_CONFIG_PATH ??
135
- path.join(os.homedir(), ".forge", "config.json");
136
- if (!existsSync(p)) throw new Error(`missing at ${p}`);
137
- const cfg = JSON.parse(await fs.readFile(p, "utf8"));
138
- const n = cfg.repos?.length ?? 0;
139
- return `${p} (${n} repo${n === 1 ? "" : "s"})`;
140
- },
141
- },
142
- {
143
- name: "frontend build",
144
- fn: () => {
145
- const spa = path.join(projectRoot, "dist", "index.html");
146
- if (!existsSync(spa)) throw new Error("not built (run: npm run build:app)");
147
- return "dist/";
148
- },
149
- },
150
- ];
151
-
152
- let failed = 0;
153
- await runApp(
154
- h`<${Doctor} checks=${checks} onDone=${(n) => (failed = n)} />`,
155
- );
156
-
157
- if (failed) {
158
- process.stderr.write(`\n${failed} check${failed === 1 ? "" : "s"} failed.\n`);
159
- process.exit(1);
160
- }
161
- }
package/bin/cli/init.js DELETED
@@ -1,306 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { randomBytes } from "node:crypto";
6
- import React, { useEffect, useState } from "react";
7
- import TextInput from "ink-text-input";
8
- import SelectInput from "ink-select-input";
9
- import { h, Box, Text, useApp, runApp } from "./ink.js";
10
- import { projectRoot } from "./utils.js";
11
-
12
- const SCM_OPTIONS = [
13
- { label: "gitlab", value: "gitlab" },
14
- { label: "github", value: "github" },
15
- { label: "azuredevops", value: "azuredevops" },
16
- ];
17
-
18
- const YESNO = [
19
- { label: "no, abort", value: "no" },
20
- { label: "yes, overwrite", value: "yes" },
21
- ];
22
-
23
- const ADD_REPO_YESNO = [
24
- { label: "no, that's all", value: "no" },
25
- { label: "yes, add another", value: "yes" },
26
- ];
27
-
28
- function Field({ label, hint, children }) {
29
- return h`<${Box} flexDirection="column" marginBottom=${1}>
30
- <${Box}>
31
- <${Text} color="cyan">? </>
32
- <${Text}>${label}</>
33
- ${hint ? h`<${Text} dimColor> (${hint})</>` : null}
34
- </>
35
- <${Box} marginLeft=${2}>${children}</>
36
- </>`;
37
- }
38
-
39
- function Summary({ field, value }) {
40
- return h`<${Box}>
41
- <${Text} color="green">✓ </>
42
- <${Text}>${field}: </>
43
- <${Text} dimColor>${value}</>
44
- </>`;
45
- }
46
-
47
- function InitWizard({ envPath, hasExisting, force, onDone }) {
48
- const { exit } = useApp();
49
- const generatedToken = React.useMemo(() => randomBytes(24).toString("hex"), []);
50
-
51
- const [step, setStep] = useState(hasExisting && !force ? "overwrite" : "token");
52
- const [data, setData] = useState({
53
- token: "",
54
- port: "",
55
- scm: "gitlab",
56
- repos: [],
57
- scmToken: "",
58
- });
59
- const [input, setInput] = useState("");
60
- const [savedPaths, setSavedPaths] = useState(null);
61
- const [error, setError] = useState(null);
62
-
63
- useEffect(() => {
64
- if (step === "abort") {
65
- onDone({ aborted: true });
66
- exit();
67
- } else if (step === "done") {
68
- onDone({ ok: true, savedPaths });
69
- exit();
70
- } else if (step === "error") {
71
- onDone({ error });
72
- exit(error);
73
- }
74
- }, [step]);
75
-
76
- useEffect(() => {
77
- if (step !== "save") return;
78
- (async () => {
79
- try {
80
- await fs.writeFile(
81
- envPath,
82
- `FORGE_TOKEN=${data.token}\nPORT=${data.port}\nNODE_ENV=production\n`,
83
- "utf8",
84
- );
85
- const { patch } = await import(
86
- path.join(projectRoot, "server/forge/core/configStore.js")
87
- );
88
- await patch({
89
- repos: data.repos.map((id) => ({ id, label: id })),
90
- connections: { [data.scm]: { token: data.scmToken } },
91
- });
92
- const cfgPath =
93
- process.env.FORGE_CONFIG_PATH ??
94
- path.join(os.homedir(), ".forge", "config.json");
95
- setSavedPaths({ envPath, cfgPath });
96
- setStep("done");
97
- } catch (err) {
98
- setError(err);
99
- setStep("error");
100
- }
101
- })();
102
- }, [step]);
103
-
104
- if (step === "overwrite") {
105
- return h`<${Box} flexDirection="column">
106
- <${Text}>forge init</>
107
- <${Box} marginTop=${1}>
108
- <${Text} color="yellow">! </>
109
- <${Text}>${envPath} exists — overwrite?</>
110
- </>
111
- <${Box} marginLeft=${2}>
112
- <${SelectInput}
113
- items=${YESNO}
114
- onSelect=${(item) => {
115
- if (item.value === "yes") setStep("token");
116
- else setStep("abort");
117
- }} />
118
- </>
119
- </>`;
120
- }
121
-
122
- if (step === "token") {
123
- return h`<${Box} flexDirection="column">
124
- <${Text}>forge init</>
125
- <${Box} marginTop=${1}/>
126
- <${Field} label="FORGE_TOKEN" hint="press Enter to use generated">
127
- <${TextInput}
128
- value=${input}
129
- placeholder=${generatedToken}
130
- onChange=${setInput}
131
- onSubmit=${(v) => {
132
- const value = v || generatedToken;
133
- setData((d) => ({ ...d, token: value }));
134
- setInput("");
135
- setStep("port");
136
- }} />
137
- </>
138
- </>`;
139
- }
140
-
141
- if (step === "port") {
142
- return h`<${Box} flexDirection="column">
143
- <${Summary} field="FORGE_TOKEN" value=${maskToken(data.token)} />
144
- <${Field} label="PORT" hint="default 5001">
145
- <${TextInput}
146
- value=${input}
147
- placeholder="5001"
148
- onChange=${setInput}
149
- onSubmit=${(v) => {
150
- const value = v || "5001";
151
- setData((d) => ({ ...d, port: value }));
152
- setInput("");
153
- setStep("scm");
154
- }} />
155
- </>
156
- </>`;
157
- }
158
-
159
- if (step === "scm") {
160
- return h`<${Box} flexDirection="column">
161
- <${Summary} field="FORGE_TOKEN" value=${maskToken(data.token)} />
162
- <${Summary} field="PORT" value=${data.port} />
163
- <${Field} label="SCM provider" hint="↑/↓ to choose">
164
- <${SelectInput}
165
- items=${SCM_OPTIONS}
166
- onSelect=${(item) => {
167
- setData((d) => ({ ...d, scm: item.value }));
168
- setStep("repo");
169
- }} />
170
- </>
171
- </>`;
172
- }
173
-
174
- if (step === "repo") {
175
- const isFirst = data.repos.length === 0;
176
- const label = isFirst ? "Primary repo" : `Additional repo #${data.repos.length + 1}`;
177
- const hint = isFirst
178
- ? "group/repo (this becomes the default)"
179
- : "group/repo for cross-repo fixes";
180
- return h`<${Box} flexDirection="column">
181
- <${Summary} field="FORGE_TOKEN" value=${maskToken(data.token)} />
182
- <${Summary} field="PORT" value=${data.port} />
183
- <${Summary} field="SCM provider" value=${data.scm} />
184
- ${data.repos.map((r, i) =>
185
- h`<${Summary} key=${i} field=${i === 0 ? "Primary repo" : `Repo #${i + 1}`} value=${r} />`,
186
- )}
187
- <${Field} label=${label} hint=${hint}>
188
- <${TextInput}
189
- value=${input}
190
- onChange=${setInput}
191
- onSubmit=${(v) => {
192
- const trimmed = v.trim();
193
- if (!trimmed) {
194
- if (isFirst) return; // primary required, stay on step
195
- setStep("scmToken");
196
- return;
197
- }
198
- setData((d) => ({ ...d, repos: [...d.repos, trimmed] }));
199
- setInput("");
200
- setStep("repoMore");
201
- }} />
202
- </>
203
- </>`;
204
- }
205
-
206
- if (step === "repoMore") {
207
- return h`<${Box} flexDirection="column">
208
- <${Summary} field="FORGE_TOKEN" value=${maskToken(data.token)} />
209
- <${Summary} field="PORT" value=${data.port} />
210
- <${Summary} field="SCM provider" value=${data.scm} />
211
- ${data.repos.map((r, i) =>
212
- h`<${Summary} key=${i} field=${i === 0 ? "Primary repo" : `Repo #${i + 1}`} value=${r} />`,
213
- )}
214
- <${Field} label="Add another repo?" hint="enables cross-repo fixes (e.g. web-app + api)">
215
- <${SelectInput}
216
- items=${ADD_REPO_YESNO}
217
- onSelect=${(item) => {
218
- if (item.value === "yes") setStep("repo");
219
- else setStep("scmToken");
220
- }} />
221
- </>
222
- </>`;
223
- }
224
-
225
- if (step === "scmToken") {
226
- return h`<${Box} flexDirection="column">
227
- <${Summary} field="FORGE_TOKEN" value=${maskToken(data.token)} />
228
- <${Summary} field="PORT" value=${data.port} />
229
- <${Summary} field="SCM provider" value=${data.scm} />
230
- ${data.repos.map((r, i) =>
231
- h`<${Summary} key=${i} field=${i === 0 ? "Primary repo" : `Repo #${i + 1}`} value=${r} />`,
232
- )}
233
- <${Field} label=${`${data.scm} token`}>
234
- <${TextInput}
235
- value=${input}
236
- onChange=${setInput}
237
- mask="*"
238
- onSubmit=${(v) => {
239
- setData((d) => ({ ...d, scmToken: v }));
240
- setInput("");
241
- setStep("save");
242
- }} />
243
- </>
244
- </>`;
245
- }
246
-
247
- if (step === "save") {
248
- return h`<${Box}>
249
- <${Text} color="cyan">… </>
250
- <${Text}>Writing config…</>
251
- </>`;
252
- }
253
-
254
- if (step === "done") {
255
- return h`<${Box} flexDirection="column">
256
- <${Box}>
257
- <${Text} color="green">✓ </>
258
- <${Text}>Wrote ${savedPaths?.envPath}</>
259
- </>
260
- <${Box}>
261
- <${Text} color="green">✓ </>
262
- <${Text}>Wrote ${savedPaths?.cfgPath}</>
263
- </>
264
- <${Box} marginTop=${1}>
265
- <${Text} dimColor>${`Configured ${data.repos.length} repo${data.repos.length === 1 ? "" : "s"} (primary: ${data.repos[0] ?? "none"}).`}</>
266
- </>
267
- <${Box}>
268
- <${Text} dimColor>${"Next: forge doctor | forge issues | forge start"}</>
269
- </>
270
- </>`;
271
- }
272
-
273
- if (step === "abort") {
274
- return h`<${Text} dimColor>Aborted.</>`;
275
- }
276
-
277
- if (step === "error") {
278
- return h`<${Box}>
279
- <${Text} color="red">✘ </>
280
- <${Text}>${error?.message || String(error)}</>
281
- </>`;
282
- }
283
-
284
- return null;
285
- }
286
-
287
- function maskToken(t) {
288
- if (!t) return "";
289
- if (t.length <= 8) return "*".repeat(t.length);
290
- return `${t.slice(0, 4)}…${t.slice(-4)}`;
291
- }
292
-
293
- export async function cmdInit({ force }) {
294
- const envPath = path.join(process.cwd(), ".env");
295
- const hasExisting = existsSync(envPath);
296
-
297
- let outcome = { ok: false };
298
- await runApp(
299
- h`<${InitWizard}
300
- envPath=${envPath}
301
- hasExisting=${hasExisting}
302
- force=${force}
303
- onDone=${(o) => (outcome = o)} />`,
304
- );
305
- if (outcome.error) process.exit(1);
306
- }
package/bin/cli/issue.js DELETED
@@ -1,116 +0,0 @@
1
- import path from "node:path";
2
- import React, { useEffect, useState } from "react";
3
- import Spinner from "ink-spinner";
4
- import { h, Box, Text, useApp, runApp } from "./ink.js";
5
- import { getFlag, projectRoot, resolvePrimaryRepo } from "./utils.js";
6
-
7
- function IssueDetail({ issue, repo }) {
8
- const labels = (issue.labels ?? []).join(", ");
9
- return h`<${Box} flexDirection="column">
10
- <${Box}>
11
- <${Text} color="yellow">#${issue.iid} </>
12
- <${Text} bold>${issue.title}</>
13
- </>
14
- <${Box}>
15
- <${Text} dimColor>${`${repo} · ${issue.author?.name ?? "unknown"} · ${issue.created_at ?? ""}`}</>
16
- </>
17
- ${labels
18
- ? h`<${Box}>
19
- <${Text} dimColor>labels: </>
20
- <${Text}>${labels}</>
21
- </>`
22
- : null}
23
- ${issue.web_url
24
- ? h`<${Box}>
25
- <${Text} dimColor>${issue.web_url}</>
26
- </>`
27
- : null}
28
- <${Box} marginTop=${1}>
29
- <${Text} dimColor>${"─── description ───"}</>
30
- </>
31
- <${Box} marginTop=${1}>
32
- <${Text}>${issue.description ?? "(no description)"}</>
33
- </>
34
- <${Box} marginTop=${1}>
35
- <${Text} dimColor>${`Next: forge ship ${issue.iid}`}</>
36
- </>
37
- </>`;
38
- }
39
-
40
- function IssueView({ iid, repo, onDone }) {
41
- const { exit } = useApp();
42
- const [issue, setIssue] = useState(null);
43
- const [error, setError] = useState(null);
44
- const [done, setDone] = useState(null);
45
-
46
- useEffect(() => {
47
- if (!done) return;
48
- onDone(done);
49
- exit();
50
- }, [done]);
51
-
52
- useEffect(() => {
53
- (async () => {
54
- try {
55
- const { createProvider } = await import(
56
- path.join(projectRoot, "server/forge/core/providers/index.js")
57
- );
58
- const provider = createProvider(repo);
59
- const i = await provider.getIssue(iid);
60
- setIssue(i);
61
- setDone({ ok: true });
62
- } catch (err) {
63
- setError(err);
64
- setDone({ ok: false, error: err });
65
- }
66
- })();
67
- }, []);
68
-
69
- if (error) {
70
- return h`<${Box}>
71
- <${Text} color="red">${`✘ ${error.message ?? String(error)}`}</>
72
- </>`;
73
- }
74
- if (!issue) {
75
- return h`<${Box}>
76
- <${Text} color="cyan"><${Spinner} type="dots"/></>
77
- <${Text}>${` Fetching issue #${iid} from ${repo}…`}</>
78
- </>`;
79
- }
80
- return h`<${IssueDetail} issue=${issue} repo=${repo} />`;
81
- }
82
-
83
- export async function cmdIssue(argv) {
84
- const iid = Number(argv[1]);
85
- if (!Number.isInteger(iid) || iid <= 0) {
86
- console.error("Usage: forge issue <iid> [--repo <id>]");
87
- process.exit(1);
88
- }
89
- const repo = (await resolvePrimaryRepo(argv)) ?? null;
90
- if (!repo) {
91
- console.error(
92
- "No repo. Pass --repo <group/repo>, set FORGE_GITLAB_PROJECT, or run `forge init`.",
93
- );
94
- process.exit(1);
95
- }
96
- const json = argv.includes("--json");
97
- if (json) {
98
- const { createProvider } = await import(
99
- path.join(projectRoot, "server/forge/core/providers/index.js")
100
- );
101
- try {
102
- const i = await createProvider(repo).getIssue(iid);
103
- process.stdout.write(`${JSON.stringify(i, null, 2)}\n`);
104
- } catch (err) {
105
- process.stdout.write(`${JSON.stringify({ error: err.message })}\n`);
106
- process.exit(2);
107
- }
108
- return;
109
- }
110
-
111
- let outcome = { ok: false };
112
- await runApp(
113
- h`<${IssueView} iid=${iid} repo=${repo} onDone=${(o) => (outcome = o)} />`,
114
- );
115
- if (!outcome.ok) process.exit(2);
116
- }