@ipul-labs/forge 1.16.8 → 1.17.1
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 +39 -21
- package/lib/bin/cli/analyze.d.ts +9 -0
- package/lib/bin/cli/analyze.d.ts.map +1 -0
- package/lib/bin/cli/analyze.js +491 -0
- package/lib/bin/cli/analyze.js.map +1 -0
- package/lib/bin/cli/apply.d.ts +2 -0
- package/lib/bin/cli/apply.d.ts.map +1 -0
- package/lib/bin/cli/apply.js +262 -0
- package/lib/bin/cli/apply.js.map +1 -0
- package/lib/bin/cli/cifix.d.ts +2 -0
- package/lib/bin/cli/cifix.d.ts.map +1 -0
- package/lib/bin/cli/cifix.js +310 -0
- package/lib/bin/cli/cifix.js.map +1 -0
- package/lib/bin/cli/close.d.ts +2 -0
- package/lib/bin/cli/close.d.ts.map +1 -0
- package/lib/bin/cli/close.js +113 -0
- package/lib/bin/cli/close.js.map +1 -0
- package/lib/bin/cli/createIssue.d.ts +2 -0
- package/lib/bin/cli/createIssue.d.ts.map +1 -0
- package/lib/bin/cli/createIssue.js +108 -0
- package/lib/bin/cli/createIssue.js.map +1 -0
- package/lib/bin/cli/doctor.d.ts +2 -0
- package/lib/bin/cli/doctor.d.ts.map +1 -0
- package/lib/bin/cli/doctor.js +145 -0
- package/lib/bin/cli/doctor.js.map +1 -0
- package/lib/bin/cli/init.d.ts +4 -0
- package/lib/bin/cli/init.d.ts.map +1 -0
- package/lib/bin/cli/init.js +288 -0
- package/lib/bin/cli/init.js.map +1 -0
- package/lib/bin/cli/ink.d.ts +23 -0
- package/lib/bin/cli/ink.d.ts.map +1 -0
- package/{bin → lib/bin}/cli/ink.js +18 -19
- package/lib/bin/cli/ink.js.map +1 -0
- package/lib/bin/cli/issue.d.ts +2 -0
- package/lib/bin/cli/issue.d.ts.map +1 -0
- package/lib/bin/cli/issue.js +114 -0
- package/lib/bin/cli/issue.js.map +1 -0
- package/lib/bin/cli/issues.d.ts +2 -0
- package/lib/bin/cli/issues.d.ts.map +1 -0
- package/lib/bin/cli/issues.js +149 -0
- package/lib/bin/cli/issues.js.map +1 -0
- package/lib/bin/cli/merge.d.ts +2 -0
- package/lib/bin/cli/merge.d.ts.map +1 -0
- package/lib/bin/cli/merge.js +162 -0
- package/lib/bin/cli/merge.js.map +1 -0
- package/lib/bin/cli/refine.d.ts +2 -0
- package/lib/bin/cli/refine.d.ts.map +1 -0
- package/lib/bin/cli/refine.js +249 -0
- package/lib/bin/cli/refine.js.map +1 -0
- package/lib/bin/cli/repl.d.ts +2 -0
- package/lib/bin/cli/repl.d.ts.map +1 -0
- package/lib/bin/cli/repl.js +309 -0
- package/lib/bin/cli/repl.js.map +1 -0
- package/lib/bin/cli/repos.d.ts +2 -0
- package/lib/bin/cli/repos.d.ts.map +1 -0
- package/lib/bin/cli/repos.js +86 -0
- package/lib/bin/cli/repos.js.map +1 -0
- package/lib/bin/cli/resolution.d.ts +2 -0
- package/lib/bin/cli/resolution.d.ts.map +1 -0
- package/lib/bin/cli/resolution.js +139 -0
- package/lib/bin/cli/resolution.js.map +1 -0
- package/lib/bin/cli/serverClient.d.ts +26 -0
- package/lib/bin/cli/serverClient.d.ts.map +1 -0
- package/lib/bin/cli/serverClient.js +111 -0
- package/lib/bin/cli/serverClient.js.map +1 -0
- package/lib/bin/cli/session.d.ts +8 -0
- package/lib/bin/cli/session.d.ts.map +1 -0
- package/lib/bin/cli/session.js +212 -0
- package/lib/bin/cli/session.js.map +1 -0
- package/lib/bin/cli/sessionStore.d.ts +69 -0
- package/lib/bin/cli/sessionStore.d.ts.map +1 -0
- package/lib/bin/cli/sessionStore.js +196 -0
- package/lib/bin/cli/sessionStore.js.map +1 -0
- package/lib/bin/cli/ship.d.ts +33 -0
- package/lib/bin/cli/ship.d.ts.map +1 -0
- package/lib/bin/cli/ship.js +737 -0
- package/lib/bin/cli/ship.js.map +1 -0
- package/lib/bin/cli/types.d.ts +59 -0
- package/lib/bin/cli/types.d.ts.map +1 -0
- package/lib/bin/cli/types.js +2 -0
- package/lib/bin/cli/types.js.map +1 -0
- package/lib/bin/cli/update.d.ts +2 -0
- package/lib/bin/cli/update.d.ts.map +1 -0
- package/lib/bin/cli/update.js +87 -0
- package/lib/bin/cli/update.js.map +1 -0
- package/lib/bin/cli/utils.d.ts +43 -0
- package/lib/bin/cli/utils.d.ts.map +1 -0
- package/lib/bin/cli/utils.js +181 -0
- package/lib/bin/cli/utils.js.map +1 -0
- package/lib/bin/cli/validate.d.ts +2 -0
- package/lib/bin/cli/validate.d.ts.map +1 -0
- package/lib/bin/cli/validate.js +283 -0
- package/lib/bin/cli/validate.js.map +1 -0
- package/lib/bin/cli/watch.d.ts +5 -0
- package/lib/bin/cli/watch.d.ts.map +1 -0
- package/lib/bin/cli/watch.js +263 -0
- package/lib/bin/cli/watch.js.map +1 -0
- package/lib/bin/forge-agent.d.ts +3 -0
- package/lib/bin/forge-agent.d.ts.map +1 -0
- package/{bin → lib/bin}/forge-agent.js +104 -105
- package/lib/bin/forge-agent.js.map +1 -0
- package/lib/server/forge/core/ForgeAgent.d.ts +260 -0
- package/lib/server/forge/core/ForgeAgent.d.ts.map +1 -0
- package/lib/server/forge/core/ForgeAgent.js +995 -0
- package/lib/server/forge/core/ForgeAgent.js.map +1 -0
- package/lib/server/forge/core/agentClient.d.ts +39 -0
- package/lib/server/forge/core/agentClient.d.ts.map +1 -0
- package/{server → lib/server}/forge/core/agentClient.js +7 -12
- package/lib/server/forge/core/agentClient.js.map +1 -0
- package/lib/server/forge/core/branchName.d.ts +20 -0
- package/lib/server/forge/core/branchName.d.ts.map +1 -0
- package/lib/server/forge/core/branchName.js +39 -0
- package/lib/server/forge/core/branchName.js.map +1 -0
- package/lib/server/forge/core/claudeClient.d.ts +60 -0
- package/lib/server/forge/core/claudeClient.d.ts.map +1 -0
- package/lib/server/forge/core/claudeClient.js +347 -0
- package/lib/server/forge/core/claudeClient.js.map +1 -0
- package/lib/server/forge/core/configStore.d.ts +76 -0
- package/lib/server/forge/core/configStore.d.ts.map +1 -0
- package/lib/server/forge/core/configStore.js +213 -0
- package/lib/server/forge/core/configStore.js.map +1 -0
- package/lib/server/forge/core/gitlabClient.d.ts +144 -0
- package/lib/server/forge/core/gitlabClient.d.ts.map +1 -0
- package/{server → lib/server}/forge/core/gitlabClient.js +98 -180
- package/lib/server/forge/core/gitlabClient.js.map +1 -0
- package/lib/server/forge/core/jobs.d.ts +67 -0
- package/lib/server/forge/core/jobs.d.ts.map +1 -0
- package/lib/server/forge/core/jobs.js +185 -0
- package/lib/server/forge/core/jobs.js.map +1 -0
- package/lib/server/forge/core/k8s.d.ts +18 -0
- package/lib/server/forge/core/k8s.d.ts.map +1 -0
- package/lib/server/forge/core/k8s.js +49 -0
- package/lib/server/forge/core/k8s.js.map +1 -0
- package/lib/server/forge/core/localRepo.d.ts +36 -0
- package/lib/server/forge/core/localRepo.d.ts.map +1 -0
- package/lib/server/forge/core/localRepo.js +165 -0
- package/lib/server/forge/core/localRepo.js.map +1 -0
- package/lib/server/forge/core/openclawClient.d.ts +61 -0
- package/lib/server/forge/core/openclawClient.d.ts.map +1 -0
- package/lib/server/forge/core/openclawClient.js +111 -0
- package/lib/server/forge/core/openclawClient.js.map +1 -0
- package/lib/server/forge/core/prompts.d.ts +96 -0
- package/lib/server/forge/core/prompts.d.ts.map +1 -0
- package/lib/server/forge/core/prompts.js +450 -0
- package/lib/server/forge/core/prompts.js.map +1 -0
- package/lib/server/forge/core/providers/azuredevops.d.ts +134 -0
- package/lib/server/forge/core/providers/azuredevops.d.ts.map +1 -0
- package/lib/server/forge/core/providers/azuredevops.js +360 -0
- package/lib/server/forge/core/providers/azuredevops.js.map +1 -0
- package/lib/server/forge/core/providers/base.d.ts +114 -0
- package/lib/server/forge/core/providers/base.d.ts.map +1 -0
- package/lib/server/forge/core/providers/base.js +98 -0
- package/lib/server/forge/core/providers/base.js.map +1 -0
- package/lib/server/forge/core/providers/github.d.ts +133 -0
- package/lib/server/forge/core/providers/github.d.ts.map +1 -0
- package/lib/server/forge/core/providers/github.js +307 -0
- package/lib/server/forge/core/providers/github.js.map +1 -0
- package/lib/server/forge/core/providers/gitlab.d.ts +144 -0
- package/lib/server/forge/core/providers/gitlab.d.ts.map +1 -0
- package/lib/server/forge/core/providers/gitlab.js +407 -0
- package/lib/server/forge/core/providers/gitlab.js.map +1 -0
- package/lib/server/forge/core/providers/index.d.ts +21 -0
- package/lib/server/forge/core/providers/index.d.ts.map +1 -0
- package/lib/server/forge/core/providers/index.js +43 -0
- package/lib/server/forge/core/providers/index.js.map +1 -0
- package/lib/server/forge/core/providers/types.d.ts +80 -0
- package/lib/server/forge/core/providers/types.d.ts.map +1 -0
- package/lib/server/forge/core/providers/types.js +2 -0
- package/lib/server/forge/core/providers/types.js.map +1 -0
- package/lib/server/forge/core/quotaClient.d.ts +48 -0
- package/lib/server/forge/core/quotaClient.d.ts.map +1 -0
- package/lib/server/forge/core/quotaClient.js +88 -0
- package/lib/server/forge/core/quotaClient.js.map +1 -0
- package/lib/server/forge/core/types.d.ts +160 -0
- package/lib/server/forge/core/types.d.ts.map +1 -0
- package/lib/server/forge/core/types.js +2 -0
- package/lib/server/forge/core/types.js.map +1 -0
- package/lib/server/forge/core/validator/attribute.d.ts +31 -0
- package/lib/server/forge/core/validator/attribute.d.ts.map +1 -0
- package/lib/server/forge/core/validator/attribute.js +82 -0
- package/lib/server/forge/core/validator/attribute.js.map +1 -0
- package/lib/server/forge/core/validator/clone.d.ts +14 -0
- package/lib/server/forge/core/validator/clone.d.ts.map +1 -0
- package/lib/server/forge/core/validator/clone.js +99 -0
- package/lib/server/forge/core/validator/clone.js.map +1 -0
- package/lib/server/forge/core/validator/cmd.d.ts +23 -0
- package/lib/server/forge/core/validator/cmd.d.ts.map +1 -0
- package/lib/server/forge/core/validator/cmd.js +87 -0
- package/lib/server/forge/core/validator/cmd.js.map +1 -0
- package/lib/server/forge/core/validator/fs.d.ts +5 -0
- package/lib/server/forge/core/validator/fs.d.ts.map +1 -0
- package/{server → lib/server}/forge/core/validator/fs.js +8 -3
- package/lib/server/forge/core/validator/fs.js.map +1 -0
- package/lib/server/forge/core/validator/overlay.d.ts +13 -0
- package/lib/server/forge/core/validator/overlay.d.ts.map +1 -0
- package/lib/server/forge/core/validator/overlay.js +53 -0
- package/lib/server/forge/core/validator/overlay.js.map +1 -0
- package/lib/server/forge/core/validator/projectType.d.ts +20 -0
- package/lib/server/forge/core/validator/projectType.d.ts.map +1 -0
- package/lib/server/forge/core/validator/projectType.js +94 -0
- package/lib/server/forge/core/validator/projectType.js.map +1 -0
- package/lib/server/forge/core/validator/runners/go.d.ts +21 -0
- package/lib/server/forge/core/validator/runners/go.d.ts.map +1 -0
- package/lib/server/forge/core/validator/runners/go.js +99 -0
- package/lib/server/forge/core/validator/runners/go.js.map +1 -0
- package/lib/server/forge/core/validator/runners/node.d.ts +12 -0
- package/lib/server/forge/core/validator/runners/node.d.ts.map +1 -0
- package/lib/server/forge/core/validator/runners/node.js +75 -0
- package/lib/server/forge/core/validator/runners/node.js.map +1 -0
- package/lib/server/forge/core/validator/tdd.d.ts +30 -0
- package/lib/server/forge/core/validator/tdd.d.ts.map +1 -0
- package/lib/server/forge/core/validator/tdd.js +96 -0
- package/lib/server/forge/core/validator/tdd.js.map +1 -0
- package/lib/server/forge/core/validator.d.ts +58 -0
- package/lib/server/forge/core/validator.d.ts.map +1 -0
- package/lib/server/forge/core/validator.js +150 -0
- package/lib/server/forge/core/validator.js.map +1 -0
- package/lib/server/forge/routes.d.ts +22 -0
- package/lib/server/forge/routes.d.ts.map +1 -0
- package/lib/server/forge/routes.js +984 -0
- package/lib/server/forge/routes.js.map +1 -0
- package/lib/server.d.ts +2 -0
- package/lib/server.d.ts.map +1 -0
- package/lib/server.js +52 -0
- package/lib/server.js.map +1 -0
- package/lib/shared/events.d.ts +12 -0
- package/lib/shared/events.d.ts.map +1 -0
- package/lib/shared/events.js +12 -0
- package/lib/shared/events.js.map +1 -0
- package/package.json +15 -13
- package/scripts/fix-shebang.cjs +11 -0
- package/bin/cli/analyze.js +0 -516
- package/bin/cli/apply.js +0 -264
- package/bin/cli/cifix.js +0 -300
- package/bin/cli/close.js +0 -125
- package/bin/cli/createIssue.js +0 -112
- package/bin/cli/doctor.js +0 -161
- package/bin/cli/init.js +0 -306
- package/bin/cli/issue.js +0 -116
- package/bin/cli/issues.js +0 -155
- package/bin/cli/merge.js +0 -175
- package/bin/cli/refine.js +0 -253
- package/bin/cli/repl.js +0 -301
- package/bin/cli/repos.js +0 -91
- package/bin/cli/resolution.js +0 -126
- package/bin/cli/session.js +0 -230
- package/bin/cli/sessionStore.js +0 -211
- package/bin/cli/ship.js +0 -762
- package/bin/cli/update.js +0 -95
- package/bin/cli/utils.js +0 -145
- package/bin/cli/validate.js +0 -280
- package/bin/cli/watch.js +0 -274
- package/server/forge/core/ForgeAgent.js +0 -1179
- package/server/forge/core/ForgeAgent.test.js +0 -179
- package/server/forge/core/branchName.js +0 -41
- package/server/forge/core/claudeClient.js +0 -378
- package/server/forge/core/configStore.js +0 -229
- package/server/forge/core/configStore.test.js +0 -89
- package/server/forge/core/jobs.js +0 -180
- package/server/forge/core/k8s.js +0 -54
- package/server/forge/core/localRepo.js +0 -163
- package/server/forge/core/openclawClient.js +0 -125
- package/server/forge/core/prompts.js +0 -483
- package/server/forge/core/prompts.test.js +0 -219
- package/server/forge/core/providers/azuredevops.js +0 -409
- package/server/forge/core/providers/base.js +0 -208
- package/server/forge/core/providers/github.js +0 -348
- package/server/forge/core/providers/gitlab.js +0 -529
- package/server/forge/core/providers/index.js +0 -47
- package/server/forge/core/quotaClient.js +0 -101
- package/server/forge/core/validator/attribute.js +0 -84
- package/server/forge/core/validator/clone.js +0 -116
- package/server/forge/core/validator/cmd.js +0 -98
- package/server/forge/core/validator/overlay.js +0 -52
- package/server/forge/core/validator/projectType.js +0 -87
- package/server/forge/core/validator/runners/go.js +0 -107
- package/server/forge/core/validator/runners/node.js +0 -84
- package/server/forge/core/validator/tdd.js +0 -105
- package/server/forge/core/validator.js +0 -167
- package/server/forge/routes.js +0 -1027
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Forge persistent config.
|
|
3
|
-
*
|
|
4
|
-
* Storage contract
|
|
5
|
-
* ────────────────
|
|
6
|
-
* A single JSON blob on disk (default: ~/.forge/config.json). Loaded once at
|
|
7
|
-
* process start into an in-memory cache so route handlers read synchronously.
|
|
8
|
-
* Writes are atomic (tmp-file + rename) and serialized to avoid interleaving.
|
|
9
|
-
*
|
|
10
|
-
* Swap to Redis or another backend by re-exporting a different instance from
|
|
11
|
-
* this module — callers import `get()` / `patch()` and don't know the source.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fsSync from "node:fs";
|
|
15
|
-
import fs from "node:fs/promises";
|
|
16
|
-
import os from "node:os";
|
|
17
|
-
import path from "node:path";
|
|
18
|
-
|
|
19
|
-
/** @typedef {{ id: string, label: string, projectType?: "auto" | "node" | "go" }} ForgeRepo */
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {{ baseUrl: string, token: string }} GitLabConnection
|
|
23
|
-
* @typedef {{ token: string }} GitHubConnection
|
|
24
|
-
* @typedef {{ token: string }} AzureDevOpsConnection
|
|
25
|
-
*
|
|
26
|
-
* @typedef {{
|
|
27
|
-
* repos: ForgeRepo[],
|
|
28
|
-
* defaults: {
|
|
29
|
-
* model: string | null,
|
|
30
|
-
* agent: string | null,
|
|
31
|
-
* autoPushOnValidatePass: boolean,
|
|
32
|
-
* refineRepoAccess: boolean,
|
|
33
|
-
* promptRules: string,
|
|
34
|
-
* },
|
|
35
|
-
* connections: {
|
|
36
|
-
* gitlab: GitLabConnection,
|
|
37
|
-
* github: GitHubConnection,
|
|
38
|
-
* azuredevops: AzureDevOpsConnection,
|
|
39
|
-
* }
|
|
40
|
-
* }} ForgeConfig
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
const BUILTIN_BASE_URLS = {
|
|
44
|
-
gitlab: "https://gitlab.com/api/v4",
|
|
45
|
-
github: "https://api.github.com",
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/** @type {ForgeConfig} */
|
|
49
|
-
const DEFAULT_CONFIG = {
|
|
50
|
-
repos: [],
|
|
51
|
-
defaults: {
|
|
52
|
-
model: null,
|
|
53
|
-
agent: null,
|
|
54
|
-
autoPushOnValidatePass: false,
|
|
55
|
-
// When true (default), Refine gets Read/Glob/Grep access to a local
|
|
56
|
-
// clone of the primary repo. Off → lighter "file tree in prompt" flow.
|
|
57
|
-
// The FORGE_REFINE_REPO_ACCESS env var, when set, overrides this.
|
|
58
|
-
refineRepoAccess: true,
|
|
59
|
-
// Free-form rules injected into every analysis and refine prompt.
|
|
60
|
-
// Lets the user steer the model without touching code.
|
|
61
|
-
promptRules: "",
|
|
62
|
-
// Test-deploy URL surfaced next to "Approve & Create MR" when the
|
|
63
|
-
// branch pipeline is green. Plain URL (e.g. "https://test.example.com")
|
|
64
|
-
// for a shared env; supports "{branch}" / "{slug}" placeholders when
|
|
65
|
-
// moving to ephemeral per-branch deploys.
|
|
66
|
-
testDeployUrlTemplate: "",
|
|
67
|
-
},
|
|
68
|
-
connections: {
|
|
69
|
-
gitlab: { baseUrl: BUILTIN_BASE_URLS.gitlab, token: "" },
|
|
70
|
-
github: { token: "" },
|
|
71
|
-
azuredevops: { token: "" },
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
function configPath() {
|
|
76
|
-
return process.env.FORGE_CONFIG_PATH ?? path.join(os.homedir(), ".forge", "config.json");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Synchronous load — called once at startup so the rest of the routes can
|
|
81
|
-
* read config without awaiting. If the file is missing we return defaults;
|
|
82
|
-
* if it's malformed we warn and return defaults (don't crash the server over
|
|
83
|
-
* a bad config).
|
|
84
|
-
*/
|
|
85
|
-
function loadSync() {
|
|
86
|
-
const p = configPath();
|
|
87
|
-
try {
|
|
88
|
-
const raw = fsSync.readFileSync(p, "utf8");
|
|
89
|
-
const parsed = JSON.parse(raw);
|
|
90
|
-
return mergeDeep(DEFAULT_CONFIG, parsed);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
if (err.code === "ENOENT") return structuredClone(DEFAULT_CONFIG);
|
|
93
|
-
console.warn(`[forge] config file at ${p} is unreadable (${err.message}); using defaults`);
|
|
94
|
-
return structuredClone(DEFAULT_CONFIG);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** @type {ForgeConfig} */
|
|
99
|
-
let cache = loadSync();
|
|
100
|
-
|
|
101
|
-
/** Serializes writes so two concurrent PUTs can't interleave. */
|
|
102
|
-
let inFlight = Promise.resolve();
|
|
103
|
-
|
|
104
|
-
async function writeToDisk(cfg) {
|
|
105
|
-
const p = configPath();
|
|
106
|
-
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
107
|
-
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
108
|
-
await fs.writeFile(tmp, JSON.stringify(cfg, null, 2), "utf8");
|
|
109
|
-
// 0600 — owner read/write only. Belt-and-suspenders for when tokens land here.
|
|
110
|
-
await fs.chmod(tmp, 0o600);
|
|
111
|
-
await fs.rename(tmp, p);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function mergeDeep(base, patch) {
|
|
115
|
-
const out = Array.isArray(base) ? [...base] : { ...base };
|
|
116
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
117
|
-
const bv = base?.[k];
|
|
118
|
-
if (
|
|
119
|
-
v !== null &&
|
|
120
|
-
typeof v === "object" &&
|
|
121
|
-
!Array.isArray(v) &&
|
|
122
|
-
bv !== null &&
|
|
123
|
-
typeof bv === "object" &&
|
|
124
|
-
!Array.isArray(bv)
|
|
125
|
-
) {
|
|
126
|
-
out[k] = mergeDeep(bv, v);
|
|
127
|
-
} else {
|
|
128
|
-
out[k] = v;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return out;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Returns the current cached config. Synchronous. */
|
|
135
|
-
export function get() {
|
|
136
|
-
return cache;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Deep-merges `patch` into the current config, persists, and updates the
|
|
141
|
-
* cache. Arrays are replaced (not merged) so callers send the whole repos
|
|
142
|
-
* list when editing it.
|
|
143
|
-
*/
|
|
144
|
-
export function patch(delta) {
|
|
145
|
-
inFlight = inFlight.then(async () => {
|
|
146
|
-
const next = mergeDeep(cache, delta);
|
|
147
|
-
await writeToDisk(next);
|
|
148
|
-
cache = next;
|
|
149
|
-
return next;
|
|
150
|
-
});
|
|
151
|
-
return inFlight;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Escape hatch for tests / explicit reload from disk. */
|
|
155
|
-
export function reload() {
|
|
156
|
-
cache = loadSync();
|
|
157
|
-
return cache;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Resolves the primary project identifier.
|
|
162
|
-
* Priority: persisted config repos[0] → FORGE_REPOS first entry → FORGE_GITLAB_PROJECT.
|
|
163
|
-
*/
|
|
164
|
-
export function getPrimaryProject() {
|
|
165
|
-
const repos = cache.repos;
|
|
166
|
-
if (repos.length > 0) return repos[0].id;
|
|
167
|
-
const raw = process.env.FORGE_REPOS ?? process.env.FORGE_GITLAB_PROJECT ?? "";
|
|
168
|
-
return raw.split(",")[0]?.trim() || null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const ENV_TOKEN_KEYS = {
|
|
172
|
-
gitlab: "GITLAB_TOKEN",
|
|
173
|
-
github: "GITHUB_TOKEN",
|
|
174
|
-
azuredevops: "AZURE_DEVOPS_TOKEN",
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Resolved provider connection: config value wins, env var fills gaps, builtin
|
|
179
|
-
* URL is the last fallback. Providers call this instead of reading process.env.
|
|
180
|
-
*
|
|
181
|
-
* @param {"gitlab"|"github"|"azuredevops"} provider
|
|
182
|
-
* @returns {{ baseUrl?: string, token: string }}
|
|
183
|
-
*/
|
|
184
|
-
export function getConnection(provider) {
|
|
185
|
-
const fromCfg = cache.connections?.[provider] ?? {};
|
|
186
|
-
const envToken = process.env[ENV_TOKEN_KEYS[provider]] ?? "";
|
|
187
|
-
const baseUrl = fromCfg.baseUrl || BUILTIN_BASE_URLS[provider];
|
|
188
|
-
return {
|
|
189
|
-
...(baseUrl ? { baseUrl } : {}),
|
|
190
|
-
token: fromCfg.token || envToken,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function maskToken(t) {
|
|
195
|
-
if (!t) return "";
|
|
196
|
-
if (t.length <= 4) return "••••";
|
|
197
|
-
return `••••${t.slice(-4)}`;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Safe view for the API — raw tokens are replaced with a short hint and a
|
|
202
|
-
* boolean so the UI can render state without ever seeing a secret.
|
|
203
|
-
*/
|
|
204
|
-
export function getPublic() {
|
|
205
|
-
const cfg = cache;
|
|
206
|
-
const conn = cfg.connections ?? {};
|
|
207
|
-
return {
|
|
208
|
-
repos: cfg.repos,
|
|
209
|
-
defaults: cfg.defaults,
|
|
210
|
-
connections: {
|
|
211
|
-
gitlab: {
|
|
212
|
-
baseUrl: conn.gitlab?.baseUrl ?? BUILTIN_BASE_URLS.gitlab,
|
|
213
|
-
hasToken: Boolean(conn.gitlab?.token || process.env.GITLAB_TOKEN),
|
|
214
|
-
tokenHint: maskToken(conn.gitlab?.token || process.env.GITLAB_TOKEN || ""),
|
|
215
|
-
tokenSource: conn.gitlab?.token ? "config" : process.env.GITLAB_TOKEN ? "env" : "none",
|
|
216
|
-
},
|
|
217
|
-
github: {
|
|
218
|
-
hasToken: Boolean(conn.github?.token || process.env.GITHUB_TOKEN),
|
|
219
|
-
tokenHint: maskToken(conn.github?.token || process.env.GITHUB_TOKEN || ""),
|
|
220
|
-
tokenSource: conn.github?.token ? "config" : process.env.GITHUB_TOKEN ? "env" : "none",
|
|
221
|
-
},
|
|
222
|
-
azuredevops: {
|
|
223
|
-
hasToken: Boolean(conn.azuredevops?.token || process.env.AZURE_DEVOPS_TOKEN),
|
|
224
|
-
tokenHint: maskToken(conn.azuredevops?.token || process.env.AZURE_DEVOPS_TOKEN || ""),
|
|
225
|
-
tokenSource: conn.azuredevops?.token ? "config" : process.env.AZURE_DEVOPS_TOKEN ? "env" : "none",
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
|
|
3
|
-
// We test getPrimaryProject and getConnection which resolve from
|
|
4
|
-
// cache (configStore state) → env vars → defaults.
|
|
5
|
-
|
|
6
|
-
import { getPrimaryProject, getConnection, get, reload } from "./configStore.js";
|
|
7
|
-
|
|
8
|
-
describe("getPrimaryProject", () => {
|
|
9
|
-
const originalEnv = { ...process.env };
|
|
10
|
-
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
process.env = { ...originalEnv };
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("returns first config repo when config has repos", () => {
|
|
16
|
-
const cfg = get();
|
|
17
|
-
// If the test env has repos in config, this tests the real path.
|
|
18
|
-
// Otherwise we test the env fallback paths below.
|
|
19
|
-
if (cfg.repos.length > 0) {
|
|
20
|
-
expect(getPrimaryProject()).toBe(cfg.repos[0].id);
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("falls back to FORGE_REPOS first entry", () => {
|
|
25
|
-
// Temporarily clear config repos by checking current state
|
|
26
|
-
const cfg = get();
|
|
27
|
-
if (cfg.repos.length === 0) {
|
|
28
|
-
process.env.FORGE_REPOS = "group/web-app,group/api";
|
|
29
|
-
expect(getPrimaryProject()).toBe("group/web-app");
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("falls back to FORGE_GITLAB_PROJECT when FORGE_REPOS is unset", () => {
|
|
34
|
-
const cfg = get();
|
|
35
|
-
if (cfg.repos.length === 0) {
|
|
36
|
-
delete process.env.FORGE_REPOS;
|
|
37
|
-
process.env.FORGE_GITLAB_PROJECT = "legacy/project";
|
|
38
|
-
expect(getPrimaryProject()).toBe("legacy/project");
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("returns null when nothing is configured", () => {
|
|
43
|
-
const cfg = get();
|
|
44
|
-
if (cfg.repos.length === 0) {
|
|
45
|
-
delete process.env.FORGE_REPOS;
|
|
46
|
-
delete process.env.FORGE_GITLAB_PROJECT;
|
|
47
|
-
expect(getPrimaryProject()).toBeNull();
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("trims whitespace from FORGE_REPOS entries", () => {
|
|
52
|
-
const cfg = get();
|
|
53
|
-
if (cfg.repos.length === 0) {
|
|
54
|
-
process.env.FORGE_REPOS = " group/web-app , group/api ";
|
|
55
|
-
expect(getPrimaryProject()).toBe("group/web-app");
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("getConnection", () => {
|
|
61
|
-
const originalEnv = { ...process.env };
|
|
62
|
-
|
|
63
|
-
afterEach(() => {
|
|
64
|
-
process.env = { ...originalEnv };
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("returns token from env when config has none", () => {
|
|
68
|
-
process.env.GITLAB_TOKEN = "test-token-123";
|
|
69
|
-
const conn = getConnection("gitlab");
|
|
70
|
-
// Config token takes precedence, so if config has one we get that.
|
|
71
|
-
// Either way, we should get a non-empty token.
|
|
72
|
-
expect(conn.token).toBeTruthy();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("returns a baseUrl for gitlab", () => {
|
|
76
|
-
const conn = getConnection("gitlab");
|
|
77
|
-
expect(conn.baseUrl).toContain("gitlab.com");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("returns empty token when nothing is configured", () => {
|
|
81
|
-
delete process.env.GITLAB_TOKEN;
|
|
82
|
-
const cfg = get();
|
|
83
|
-
// Only test if config also has no token
|
|
84
|
-
if (!cfg.connections?.gitlab?.token) {
|
|
85
|
-
const conn = getConnection("gitlab");
|
|
86
|
-
expect(conn.token).toBe("");
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* server/forge/core/jobs.js
|
|
3
|
-
*
|
|
4
|
-
* Unified in-memory job store with event buffering + multi-subscriber replay.
|
|
5
|
-
*
|
|
6
|
-
* Why this exists: SSE endpoints like refine / ci-fix / validate / analyze
|
|
7
|
-
* run a long task and stream events to one EventSource. If the browser
|
|
8
|
-
* reloads or React re-mounts the component, that stream dies. Before this
|
|
9
|
-
* module, the job also died — its state was deleted the moment the stream
|
|
10
|
-
* was consumed, so there was nothing to reconnect to.
|
|
11
|
-
*
|
|
12
|
-
* With this store:
|
|
13
|
-
* 1. `createJob(type, runner)` starts the runner immediately in the
|
|
14
|
-
* background and buffers every event it emits.
|
|
15
|
-
* 2. `subscribe(id, handler)` replays the buffer synchronously, then wires
|
|
16
|
-
* the handler to future events until the job is terminal.
|
|
17
|
-
* 3. A reconnecting client gets the full history on reconnect and then
|
|
18
|
-
* live events — identical to if it had been watching all along.
|
|
19
|
-
*
|
|
20
|
-
* Process-restart survival is out of scope for v1 (Map is in-memory only).
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const TTL_MS = 10 * 60 * 1000;
|
|
24
|
-
const SWEEP_INTERVAL_MS = 60 * 1000;
|
|
25
|
-
|
|
26
|
-
const jobs = new Map();
|
|
27
|
-
let sweeper = null;
|
|
28
|
-
|
|
29
|
-
function ensureSweeper() {
|
|
30
|
-
if (sweeper) return;
|
|
31
|
-
sweeper = setInterval(() => {
|
|
32
|
-
const now = Date.now();
|
|
33
|
-
for (const [id, job] of jobs) {
|
|
34
|
-
if (job.expiresAt && job.expiresAt <= now && job.subscribers.size === 0) {
|
|
35
|
-
jobs.delete(id);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}, SWEEP_INTERVAL_MS);
|
|
39
|
-
sweeper.unref?.();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function newId(prefix) {
|
|
43
|
-
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Create and start a job. `runner` is an async function that receives
|
|
48
|
-
* `(send, signal)` — any value it returns is captured as `doneResult`.
|
|
49
|
-
* A terminal `done`, `error`, or `cancelled` event is emitted automatically
|
|
50
|
-
* when runner settles; runners must not emit those themselves.
|
|
51
|
-
*
|
|
52
|
-
* The `signal` is an `AbortSignal` that fires when a client calls `cancelJob`.
|
|
53
|
-
* Runners should forward it to any subprocess / fetch / SDK call that
|
|
54
|
-
* supports AbortSignal so the underlying work actually stops (and API/CLI
|
|
55
|
-
* costs aren't incurred for work the user no longer wants).
|
|
56
|
-
*/
|
|
57
|
-
export function createJob(type, runner, { idPrefix = type } = {}) {
|
|
58
|
-
ensureSweeper();
|
|
59
|
-
const id = newId(idPrefix);
|
|
60
|
-
const controller = new AbortController();
|
|
61
|
-
const job = {
|
|
62
|
-
id,
|
|
63
|
-
type,
|
|
64
|
-
status: "running",
|
|
65
|
-
events: [],
|
|
66
|
-
doneResult: undefined,
|
|
67
|
-
error: null,
|
|
68
|
-
createdAt: Date.now(),
|
|
69
|
-
finishedAt: null,
|
|
70
|
-
expiresAt: null,
|
|
71
|
-
subscribers: new Set(),
|
|
72
|
-
controller,
|
|
73
|
-
};
|
|
74
|
-
jobs.set(id, job);
|
|
75
|
-
|
|
76
|
-
const send = (event) => {
|
|
77
|
-
job.events.push(event);
|
|
78
|
-
for (const sub of job.subscribers) {
|
|
79
|
-
try { sub(event); } catch { /* subscriber errors don't break fanout */ }
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
(async () => {
|
|
84
|
-
try {
|
|
85
|
-
const result = await runner(send, controller.signal);
|
|
86
|
-
if (job.status === "cancelled") return;
|
|
87
|
-
job.doneResult = result ?? null;
|
|
88
|
-
job.status = "done";
|
|
89
|
-
send({ type: "done" });
|
|
90
|
-
} catch (e) {
|
|
91
|
-
if (job.status === "cancelled") return;
|
|
92
|
-
// If the runner threw because of our own abort, prefer the cancelled
|
|
93
|
-
// terminal over a misleading error event.
|
|
94
|
-
if (controller.signal.aborted) {
|
|
95
|
-
job.status = "cancelled";
|
|
96
|
-
send({ type: "cancelled" });
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
job.status = "error";
|
|
100
|
-
job.error = e?.message ?? String(e);
|
|
101
|
-
send({ type: "error", message: job.error });
|
|
102
|
-
} finally {
|
|
103
|
-
job.finishedAt = Date.now();
|
|
104
|
-
job.expiresAt = job.finishedAt + TTL_MS;
|
|
105
|
-
}
|
|
106
|
-
})();
|
|
107
|
-
|
|
108
|
-
return id;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Cancel a running job. Aborts the signal, emits a terminal `cancelled`
|
|
113
|
-
* event, and marks the job non-running. No-op if the job is already
|
|
114
|
-
* terminal or doesn't exist — returns whether a cancel was actually issued.
|
|
115
|
-
*/
|
|
116
|
-
export function cancelJob(id) {
|
|
117
|
-
const job = jobs.get(id);
|
|
118
|
-
if (!job) return false;
|
|
119
|
-
if (job.status !== "running") return false;
|
|
120
|
-
job.status = "cancelled";
|
|
121
|
-
try {
|
|
122
|
-
job.controller.abort();
|
|
123
|
-
} catch { /* ignore */ }
|
|
124
|
-
// Emit the terminal *before* the runner's catch block runs, so reconnecting
|
|
125
|
-
// clients always see a cancellation event in the buffer.
|
|
126
|
-
job.events.push({ type: "cancelled" });
|
|
127
|
-
for (const sub of job.subscribers) {
|
|
128
|
-
try { sub({ type: "cancelled" }); } catch { /* ignore */ }
|
|
129
|
-
}
|
|
130
|
-
job.finishedAt = Date.now();
|
|
131
|
-
job.expiresAt = job.finishedAt + TTL_MS;
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function getJob(id) {
|
|
136
|
-
return jobs.get(id) ?? null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Replay buffered events to `handler`, then subscribe for live events until
|
|
141
|
-
* the job reaches terminal state. Returns an `unsubscribe` function, or
|
|
142
|
-
* `null` if the job doesn't exist.
|
|
143
|
-
*
|
|
144
|
-
* The terminal `done`/`error` event is part of the buffered replay — callers
|
|
145
|
-
* that want to end the response on terminal should check for it inside
|
|
146
|
-
* their handler.
|
|
147
|
-
*/
|
|
148
|
-
export function subscribe(id, handler) {
|
|
149
|
-
const job = jobs.get(id);
|
|
150
|
-
if (!job) return null;
|
|
151
|
-
|
|
152
|
-
for (const e of job.events) {
|
|
153
|
-
try { handler(e); } catch { /* ignore */ }
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (job.status !== "running") {
|
|
157
|
-
return () => {};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
job.subscribers.add(handler);
|
|
161
|
-
return () => { job.subscribers.delete(handler); };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Public status snapshot — safe to send to clients. Excludes the event
|
|
166
|
-
* buffer (potentially large) and subscribers (not serialisable).
|
|
167
|
-
*/
|
|
168
|
-
export function snapshot(id) {
|
|
169
|
-
const job = jobs.get(id);
|
|
170
|
-
if (!job) return null;
|
|
171
|
-
return {
|
|
172
|
-
id: job.id,
|
|
173
|
-
type: job.type,
|
|
174
|
-
status: job.status,
|
|
175
|
-
createdAt: job.createdAt,
|
|
176
|
-
finishedAt: job.finishedAt,
|
|
177
|
-
error: job.error,
|
|
178
|
-
eventCount: job.events.length,
|
|
179
|
-
};
|
|
180
|
-
}
|
package/server/forge/core/k8s.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import * as k8s from "@kubernetes/client-node";
|
|
2
|
-
|
|
3
|
-
let kc = new k8s.KubeConfig();
|
|
4
|
-
|
|
5
|
-
function loadConfig() {
|
|
6
|
-
kc = new k8s.KubeConfig();
|
|
7
|
-
const token = process.env.K8S_CLUSTER_TOKEN;
|
|
8
|
-
const server = process.env.K8S_CLUSTER_SERVER;
|
|
9
|
-
|
|
10
|
-
if (token && server) {
|
|
11
|
-
kc.loadFromOptions({
|
|
12
|
-
clusters: [{ name: "cluster", server, skipTLSVerify: process.env.K8S_SKIP_TLS_VERIFY === "true" }],
|
|
13
|
-
users: [{ name: "admin", token }],
|
|
14
|
-
contexts: [{ name: "default", cluster: "cluster", user: "admin" }],
|
|
15
|
-
currentContext: "default",
|
|
16
|
-
});
|
|
17
|
-
} else {
|
|
18
|
-
kc.loadFromDefault();
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
loadConfig();
|
|
23
|
-
|
|
24
|
-
function getAppsApi() {
|
|
25
|
-
return kc.makeApiClient(k8s.AppsV1Api);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function formatDeployment(dep) {
|
|
29
|
-
const containers = dep.spec?.template?.spec?.containers || [];
|
|
30
|
-
const status = dep.status || {};
|
|
31
|
-
return {
|
|
32
|
-
name: dep.metadata?.name,
|
|
33
|
-
namespace: dep.metadata?.namespace,
|
|
34
|
-
image: containers[0]?.image || "unknown",
|
|
35
|
-
replicas: {
|
|
36
|
-
desired: dep.spec?.replicas || 0,
|
|
37
|
-
ready: status.readyReplicas || 0,
|
|
38
|
-
available: status.availableReplicas || 0,
|
|
39
|
-
unavailable: status.unavailableReplicas || 0,
|
|
40
|
-
updated: status.updatedReplicas || 0,
|
|
41
|
-
},
|
|
42
|
-
conditions: (status.conditions || []).map((c) => ({
|
|
43
|
-
type: c.type,
|
|
44
|
-
status: c.status,
|
|
45
|
-
message: c.message,
|
|
46
|
-
})),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function listDeployments(namespace) {
|
|
51
|
-
const api = getAppsApi();
|
|
52
|
-
const res = await api.listNamespacedDeployment({ namespace });
|
|
53
|
-
return (res.items || []).map(formatDeployment);
|
|
54
|
-
}
|