@visulima/vis 1.0.0-alpha.11 → 1.0.0-alpha.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/CHANGELOG.md +101 -0
- package/LICENSE.md +559 -186
- package/README.md +18 -0
- package/dist/bin.js +1 -9
- package/dist/config/index.d.ts +477 -556
- package/dist/config/index.js +1 -2
- package/dist/generate/index.js +1 -3
- package/dist/packem_chunks/applyDefaults.js +2 -336
- package/dist/packem_chunks/bin.js +234 -9552
- package/dist/packem_chunks/doctor-probe.js +2 -112
- package/dist/packem_chunks/fix.js +11 -234
- package/dist/packem_chunks/handler.js +1 -99
- package/dist/packem_chunks/handler10.js +2 -53
- package/dist/packem_chunks/handler11.js +1 -32
- package/dist/packem_chunks/handler12.js +5 -100
- package/dist/packem_chunks/handler13.js +1 -25
- package/dist/packem_chunks/handler14.js +18 -916
- package/dist/packem_chunks/handler15.js +15 -201
- package/dist/packem_chunks/handler16.js +1 -124
- package/dist/packem_chunks/handler17.js +1 -13
- package/dist/packem_chunks/handler18.js +1 -106
- package/dist/packem_chunks/handler19.js +1 -19
- package/dist/packem_chunks/handler2.js +2 -75
- package/dist/packem_chunks/handler20.js +5 -29
- package/dist/packem_chunks/handler21.js +1 -222
- package/dist/packem_chunks/handler22.js +1 -237
- package/dist/packem_chunks/handler23.js +5 -101
- package/dist/packem_chunks/handler24.js +1 -110
- package/dist/packem_chunks/handler25.js +3 -402
- package/dist/packem_chunks/handler26.js +1 -13
- package/dist/packem_chunks/handler27.js +1 -63
- package/dist/packem_chunks/handler28.js +7 -34
- package/dist/packem_chunks/handler29.js +21 -456
- package/dist/packem_chunks/handler3.js +4 -95
- package/dist/packem_chunks/handler30.js +3 -170
- package/dist/packem_chunks/handler31.js +1 -530
- package/dist/packem_chunks/handler32.js +2 -214
- package/dist/packem_chunks/handler33.js +25 -119
- package/dist/packem_chunks/handler34.js +2 -630
- package/dist/packem_chunks/handler35.js +3 -283
- package/dist/packem_chunks/handler36.js +22 -542
- package/dist/packem_chunks/handler37.js +410 -744
- package/dist/packem_chunks/handler38.js +22 -989
- package/dist/packem_chunks/handler39.js +22 -574
- package/dist/packem_chunks/handler4.js +2 -90
- package/dist/packem_chunks/handler40.js +22 -1685
- package/dist/packem_chunks/handler41.js +6 -1088
- package/dist/packem_chunks/handler42.js +5 -797
- package/dist/packem_chunks/handler43.js +10 -2658
- package/dist/packem_chunks/handler44.js +51 -3784
- package/dist/packem_chunks/handler45.js +25 -2574
- package/dist/packem_chunks/handler46.js +3 -3769
- package/dist/packem_chunks/handler47.js +21 -1485
- package/dist/packem_chunks/handler48.js +42 -0
- package/dist/packem_chunks/handler5.js +8 -174
- package/dist/packem_chunks/handler6.js +1 -95
- package/dist/packem_chunks/handler7.js +1 -115
- package/dist/packem_chunks/handler8.js +1 -12
- package/dist/packem_chunks/handler9.js +1 -29
- package/dist/packem_chunks/heal-accept.js +10 -522
- package/dist/packem_chunks/heal.js +14 -673
- package/dist/packem_chunks/index.js +7 -873
- package/dist/packem_chunks/loader.js +1 -23
- package/dist/packem_chunks/tar.js +3 -0
- package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
- package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
- package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
- package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
- package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
- package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
- package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
- package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
- package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
- package/dist/packem_shared/index-DH-5hsrC.js +1 -0
- package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
- package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
- package/dist/packem_shared/registry-CkubDdiY.js +2 -0
- package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
- package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
- package/dist/packem_shared/selectors-BylODRiM.js +3 -0
- package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
- package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
- package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
- package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
- package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
- package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
- package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
- package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
- package/index.js +556 -727
- package/package.json +19 -29
- package/schemas/project.schema.json +739 -297
- package/schemas/vis-config.schema.json +3365 -384
- package/templates/buildkite-ci/template.yml +20 -20
- package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
- package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
- package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
- package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
- package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
- package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
- package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
- package/dist/packem_shared/docker-2iZzc280.js +0 -181
- package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
- package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
- package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
- package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
- package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
- package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
- package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
- package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
- package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
- package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
- package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
- package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
- package/dist/packem_shared/utils-CthVdBPS.js +0 -40
- package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const execute = async ({ logger, options, visConfig, workspaceRoot: wsRoot }) => {
|
|
4
|
-
const cwd = wsRoot ?? process.cwd();
|
|
5
|
-
const pm = resolveInstaller(cwd, { configBackend: visConfig?.install?.backend });
|
|
6
|
-
const code = runDedupe(pm, options.check || false, cwd, logger);
|
|
7
|
-
if (code !== 0) {
|
|
8
|
-
process.exitCode = code;
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export { execute as default };
|
|
1
|
+
var i=Object.defineProperty;var n=(e,o)=>i(e,"name",{value:o,configurable:!0});import{m as l,d as p}from"./bin.js";var f=Object.defineProperty,d=n((e,o)=>f(e,"name",{value:o,configurable:!0}),"n");const m=d(async({logger:e,options:o,visConfig:a,workspaceRoot:r})=>{const c=r??process.cwd(),t=l(c,{configBackend:a?.install?.backend,configCorepack:a?.install?.corepack}),s=p(t,o.check||!1,c,e);s!==0&&(process.exitCode=s)},"execute");export{m as default};
|
|
@@ -1,29 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const execute = async ({ argument, logger, options, visConfig, workspaceRoot: wsRoot }) => {
|
|
4
|
-
const args = argument;
|
|
5
|
-
if (!args || args.length === 0) {
|
|
6
|
-
throw new Error("No package specified. Usage: vis dlx <package[@version]> [args...]");
|
|
7
|
-
}
|
|
8
|
-
const [pkg, ...rest] = args;
|
|
9
|
-
const cwd = wsRoot ?? process.cwd();
|
|
10
|
-
const pm = resolveInstaller(cwd, { configBackend: visConfig?.install?.backend });
|
|
11
|
-
const additionalPackages = options.package ? Array.isArray(options.package) ? options.package : [options.package] : [];
|
|
12
|
-
const code = runDlx(
|
|
13
|
-
pm,
|
|
14
|
-
{
|
|
15
|
-
additionalPackages,
|
|
16
|
-
args: rest,
|
|
17
|
-
package: pkg,
|
|
18
|
-
shellMode: options.shellMode || false,
|
|
19
|
-
silent: options.silent || false
|
|
20
|
-
},
|
|
21
|
-
cwd,
|
|
22
|
-
logger
|
|
23
|
-
);
|
|
24
|
-
if (code !== 0) {
|
|
25
|
-
process.exitCode = code;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export { execute as default };
|
|
1
|
+
var d=Object.defineProperty;var i=(a,s)=>d(a,"name",{value:s,configurable:!0});import{m as f,e as m}from"./bin.js";var u=Object.defineProperty,v=i((a,s)=>u(a,"name",{value:s,configurable:!0}),"n");const x=v(async({argument:a,logger:s,options:e,visConfig:r,workspaceRoot:t})=>{const o=a;if(!o||o.length===0)throw new Error("No package specified. Usage: vis dlx <package[@version]> [args...]");const[g,...l]=o,c=t??process.cwd(),p=f(c,{configBackend:r?.install?.backend,configCorepack:r?.install?.corepack}),k=e.package?Array.isArray(e.package)?e.package:[e.package]:[],n=m(p,{additionalPackages:k,args:l,package:g,shellMode:e.shellMode||!1,silent:e.silent||!1},c,s);n!==0&&(process.exitCode=n)},"execute");export{x as default};
|
|
@@ -1,522 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
-
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
-
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
// Fallback to createRequire
|
|
17
|
-
return __cjs_require(module);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
readFile
|
|
22
|
-
} = __cjs_getBuiltinModule("node:fs/promises");
|
|
23
|
-
import { resolve, relative } from '@visulima/path';
|
|
24
|
-
import { d as detectCiContext, f as findHealCandidate, p as proposeAndApply, v as validateAppliedFix, a as postPrComment } from './heal.js';
|
|
25
|
-
const {
|
|
26
|
-
createInterface
|
|
27
|
-
} = __cjs_getBuiltinModule("node:readline");
|
|
28
|
-
import { c as detectPm, F as runAdd, p as pail } from './bin.js';
|
|
29
|
-
|
|
30
|
-
const isInteractive = () => Boolean(process.stdout.isTTY) && process.env.CI !== "true";
|
|
31
|
-
const defaultPrompt = (question) => new Promise((resolve) => {
|
|
32
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
33
|
-
rl.question(`${question} (Y/n) `, (answer) => {
|
|
34
|
-
rl.close();
|
|
35
|
-
const trimmed = answer.trim().toLowerCase();
|
|
36
|
-
resolve(trimmed === "" || trimmed === "y" || trimmed === "yes");
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
const defaultRunInstall = (sdk, workspaceRoot) => {
|
|
40
|
-
const pm = detectPm(workspaceRoot);
|
|
41
|
-
const exitCode = runAdd(
|
|
42
|
-
pm,
|
|
43
|
-
{
|
|
44
|
-
exact: false,
|
|
45
|
-
filter: [],
|
|
46
|
-
global: false,
|
|
47
|
-
optional: false,
|
|
48
|
-
packages: [sdk],
|
|
49
|
-
peer: false,
|
|
50
|
-
// Save into "dependencies" (saveDev=false) — the user wants
|
|
51
|
-
// this available in CI without `--include=dev` toggling.
|
|
52
|
-
// The SDK is a runtime dep of `vis ai heal accept`,
|
|
53
|
-
// conceptually.
|
|
54
|
-
saveDev: false,
|
|
55
|
-
workspace: false,
|
|
56
|
-
workspaceRoot: false
|
|
57
|
-
},
|
|
58
|
-
workspaceRoot,
|
|
59
|
-
// eslint-disable-next-line no-console
|
|
60
|
-
console
|
|
61
|
-
);
|
|
62
|
-
return Promise.resolve({ exitCode });
|
|
63
|
-
};
|
|
64
|
-
const installCommandFor = (sdk) => `pnpm add ${sdk}`;
|
|
65
|
-
const defaultImport = (sdk) => {
|
|
66
|
-
if (sdk === "@gitbeaker/rest") {
|
|
67
|
-
return import('@gitbeaker/rest');
|
|
68
|
-
}
|
|
69
|
-
return import('@octokit/rest');
|
|
70
|
-
};
|
|
71
|
-
const loadOptionalSdk = async (sdk, options = {}) => {
|
|
72
|
-
const interactive = options.interactive ?? isInteractive();
|
|
73
|
-
const prompt = options.prompt ?? defaultPrompt;
|
|
74
|
-
const runInstall = options.runInstall ?? defaultRunInstall;
|
|
75
|
-
const importImpl = options.importImpl ?? defaultImport;
|
|
76
|
-
const workspaceRoot = options.workspaceRoot ?? process.cwd();
|
|
77
|
-
try {
|
|
78
|
-
return await importImpl(sdk);
|
|
79
|
-
} catch (error) {
|
|
80
|
-
const code = error.code;
|
|
81
|
-
if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") {
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (!interactive) {
|
|
86
|
-
throw new Error(
|
|
87
|
-
`${sdk} is not installed. \`vis ai heal accept\` needs it to talk to the host. Install it in your repo first:
|
|
88
|
-
${installCommandFor(sdk)}`
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
const accepted = await prompt(`${sdk} isn't installed. Install it now?`);
|
|
92
|
-
if (!accepted) {
|
|
93
|
-
throw new Error(`${sdk} install declined. Re-run \`vis ai heal accept\` after installing manually:
|
|
94
|
-
${installCommandFor(sdk)}`);
|
|
95
|
-
}
|
|
96
|
-
const result = await runInstall(sdk, workspaceRoot);
|
|
97
|
-
if (result.exitCode !== 0) {
|
|
98
|
-
throw new Error(`Install of ${sdk} failed (exit ${String(result.exitCode)}). Install manually and retry:
|
|
99
|
-
${installCommandFor(sdk)}`);
|
|
100
|
-
}
|
|
101
|
-
return await importImpl(sdk);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const splitGithubRepo = (repo) => {
|
|
105
|
-
const slash = repo.indexOf("/");
|
|
106
|
-
if (slash <= 0 || slash === repo.length - 1) {
|
|
107
|
-
throw new Error(`Expected GITHUB_REPOSITORY in "owner/repo" form, got: ${repo}`);
|
|
108
|
-
}
|
|
109
|
-
return { owner: repo.slice(0, slash), repo: repo.slice(slash + 1) };
|
|
110
|
-
};
|
|
111
|
-
const loadGithubClient = async (token, options) => {
|
|
112
|
-
if (options.githubClient) {
|
|
113
|
-
return options.githubClient;
|
|
114
|
-
}
|
|
115
|
-
const loader = options.loadSdk ?? loadOptionalSdk;
|
|
116
|
-
const module_ = await loader("@octokit/rest");
|
|
117
|
-
const OctokitCtor = module_.Octokit;
|
|
118
|
-
if (!OctokitCtor) {
|
|
119
|
-
throw new TypeError("Loaded `@octokit/rest` but no `Octokit` export was found. Reinstall the package or pin to a supported major.");
|
|
120
|
-
}
|
|
121
|
-
return new OctokitCtor({ auth: token });
|
|
122
|
-
};
|
|
123
|
-
const loadGitlabClient = async (token, host, options) => {
|
|
124
|
-
if (options.gitlabClient) {
|
|
125
|
-
return options.gitlabClient;
|
|
126
|
-
}
|
|
127
|
-
const loader = options.loadSdk ?? loadOptionalSdk;
|
|
128
|
-
const module_ = await loader("@gitbeaker/rest");
|
|
129
|
-
const GitlabCtor = module_.Gitlab;
|
|
130
|
-
if (!GitlabCtor) {
|
|
131
|
-
throw new TypeError("Loaded `@gitbeaker/rest` but no `Gitlab` export was found. Reinstall the package or pin to a supported major.");
|
|
132
|
-
}
|
|
133
|
-
return new GitlabCtor({ host, token });
|
|
134
|
-
};
|
|
135
|
-
const apiBaseToHost = (apiBaseUrl) => {
|
|
136
|
-
return apiBaseUrl.replace(/\/api\/v\d+\/?$/, "");
|
|
137
|
-
};
|
|
138
|
-
const commitToGithub = async (options) => {
|
|
139
|
-
const { branch, ciContext, files, message, workspaceRoot } = options;
|
|
140
|
-
const readFile$1 = options.readFile ?? ((absolute) => readFile(absolute, "utf8"));
|
|
141
|
-
if (!ciContext.repo) {
|
|
142
|
-
throw new Error("Cannot commit on GitHub: GITHUB_REPOSITORY (owner/repo) is not set.");
|
|
143
|
-
}
|
|
144
|
-
if (!ciContext.token) {
|
|
145
|
-
throw new Error("Cannot commit on GitHub: GITHUB_TOKEN is not set. Grant the workflow `contents: write` and pass the token through.");
|
|
146
|
-
}
|
|
147
|
-
const { owner, repo } = splitGithubRepo(ciContext.repo);
|
|
148
|
-
const client = await loadGithubClient(ciContext.token, options);
|
|
149
|
-
const refResponse = await client.rest.git.getRef({ owner, ref: `heads/${branch}`, repo });
|
|
150
|
-
const parentSha = refResponse.data.object.sha;
|
|
151
|
-
const commitResponse = await client.rest.git.getCommit({ commit_sha: parentSha, owner, repo });
|
|
152
|
-
const baseTreeSha = commitResponse.data.tree.sha;
|
|
153
|
-
const blobs = await Promise.all(
|
|
154
|
-
files.map(async (file) => {
|
|
155
|
-
const absolute = resolve(workspaceRoot, file);
|
|
156
|
-
const contents = await readFile$1(absolute);
|
|
157
|
-
const blob = await client.rest.git.createBlob({
|
|
158
|
-
content: Buffer.from(contents, "utf8").toString("base64"),
|
|
159
|
-
encoding: "base64",
|
|
160
|
-
owner,
|
|
161
|
-
repo
|
|
162
|
-
});
|
|
163
|
-
return { mode: "100644", path: file, sha: blob.data.sha, type: "blob" };
|
|
164
|
-
})
|
|
165
|
-
);
|
|
166
|
-
const tree = await client.rest.git.createTree({ base_tree: baseTreeSha, owner, repo, tree: blobs });
|
|
167
|
-
const newCommit = await client.rest.git.createCommit({ message, owner, parents: [parentSha], repo, tree: tree.data.sha });
|
|
168
|
-
await client.rest.git.updateRef({ owner, ref: `heads/${branch}`, repo, sha: newCommit.data.sha });
|
|
169
|
-
return { sha: newCommit.data.sha, url: newCommit.data.html_url };
|
|
170
|
-
};
|
|
171
|
-
const commitToGitlab = async (options) => {
|
|
172
|
-
const { branch, ciContext, files, message, workspaceRoot } = options;
|
|
173
|
-
const readFile$1 = options.readFile ?? ((absolute) => readFile(absolute, "utf8"));
|
|
174
|
-
if (!ciContext.repo) {
|
|
175
|
-
throw new Error("Cannot commit on GitLab: CI_PROJECT_ID / CI_PROJECT_PATH is not set.");
|
|
176
|
-
}
|
|
177
|
-
if (!ciContext.token) {
|
|
178
|
-
throw new Error("Cannot commit on GitLab: no token. CI_JOB_TOKEN cannot push commits — set GITLAB_TOKEN to a PAT/project-token with `api` scope.");
|
|
179
|
-
}
|
|
180
|
-
if (!ciContext.apiBaseUrl) {
|
|
181
|
-
throw new Error("Cannot commit on GitLab: CI_API_V4_URL is not set.");
|
|
182
|
-
}
|
|
183
|
-
const host = apiBaseToHost(ciContext.apiBaseUrl);
|
|
184
|
-
const client = await loadGitlabClient(ciContext.token, host, options);
|
|
185
|
-
const actions = await Promise.all(
|
|
186
|
-
files.map(async (file) => {
|
|
187
|
-
const absolute = resolve(workspaceRoot, file);
|
|
188
|
-
const contents = await readFile$1(absolute);
|
|
189
|
-
return { action: "update", content: contents, filePath: file };
|
|
190
|
-
})
|
|
191
|
-
);
|
|
192
|
-
const result = await client.Commits.create(ciContext.repo, branch, message, actions);
|
|
193
|
-
const sha = result.id ?? result.sha;
|
|
194
|
-
if (!sha) {
|
|
195
|
-
throw new Error("GitLab Commits.create returned no commit ID — cannot reference the new commit.");
|
|
196
|
-
}
|
|
197
|
-
return { sha, url: result.web_url ?? result.webUrl };
|
|
198
|
-
};
|
|
199
|
-
const commitFiles = async (options) => {
|
|
200
|
-
if (options.files.length === 0) {
|
|
201
|
-
throw new Error("Cannot commit: no files to include.");
|
|
202
|
-
}
|
|
203
|
-
if (options.ciContext.provider === "github-actions") {
|
|
204
|
-
return await commitToGithub(options);
|
|
205
|
-
}
|
|
206
|
-
if (options.ciContext.provider === "gitlab-ci") {
|
|
207
|
-
return await commitToGitlab(options);
|
|
208
|
-
}
|
|
209
|
-
throw new Error(`Cannot commit: unsupported CI provider \`${options.ciContext.provider}\`. Run \`vis ai heal accept\` from a recognised CI environment.`);
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const TRIGGER_PHRASE = "/vis heal accept";
|
|
213
|
-
const loadGithubTrigger = async (eventPath) => {
|
|
214
|
-
if (!eventPath) {
|
|
215
|
-
return void 0;
|
|
216
|
-
}
|
|
217
|
-
let raw;
|
|
218
|
-
try {
|
|
219
|
-
raw = await readFile(eventPath, "utf8");
|
|
220
|
-
} catch {
|
|
221
|
-
return void 0;
|
|
222
|
-
}
|
|
223
|
-
const payload = JSON.parse(raw);
|
|
224
|
-
const body = payload.comment?.body ?? "";
|
|
225
|
-
const actor = payload.comment?.user?.login ?? "";
|
|
226
|
-
const headRepo = payload.pull_request?.head?.repo?.full_name;
|
|
227
|
-
const baseRepo = payload.pull_request?.base?.repo?.full_name ?? payload.repository?.full_name;
|
|
228
|
-
const isFork = headRepo !== void 0 && baseRepo !== void 0 && headRepo !== baseRepo;
|
|
229
|
-
return {
|
|
230
|
-
actor,
|
|
231
|
-
body,
|
|
232
|
-
headRef: payload.pull_request?.head?.ref,
|
|
233
|
-
isFork
|
|
234
|
-
};
|
|
235
|
-
};
|
|
236
|
-
const loadGitlabTrigger = (env) => {
|
|
237
|
-
const body = env.VIS_HEAL_TRIGGER_BODY;
|
|
238
|
-
const actor = env.VIS_HEAL_TRIGGER_ACTOR;
|
|
239
|
-
if (!body || !actor) {
|
|
240
|
-
return void 0;
|
|
241
|
-
}
|
|
242
|
-
return {
|
|
243
|
-
actor,
|
|
244
|
-
body,
|
|
245
|
-
// GitLab forks (called "subgroups" in the public docs) are
|
|
246
|
-
// detected at the host level — the bridge should refuse to
|
|
247
|
-
// forward note events from forks. We don't have visibility from
|
|
248
|
-
// here, so default to false and rely on the API auth scope.
|
|
249
|
-
headRef: env.VIS_HEAL_HEAD_REF,
|
|
250
|
-
isFork: false
|
|
251
|
-
};
|
|
252
|
-
};
|
|
253
|
-
const loadBuildkiteTrigger = (env) => {
|
|
254
|
-
const actor = env.BUILDKITE_UNBLOCKER_EMAIL ?? env.BUILDKITE_UNBLOCKER;
|
|
255
|
-
if (!actor) {
|
|
256
|
-
return void 0;
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
actor,
|
|
260
|
-
body: TRIGGER_PHRASE,
|
|
261
|
-
// BUILDKITE_BRANCH is the source branch in the upstream remote
|
|
262
|
-
// namespace. Unlike GitHub Actions (which exposes a synthetic
|
|
263
|
-
// `refs/pull/<n>/merge` ref via GITHUB_REF on PR builds),
|
|
264
|
-
// Buildkite always reports the underlying branch — exactly what
|
|
265
|
-
// the heal commit needs to land on. For non-PR builds it's the
|
|
266
|
-
// pushed branch, same target either way.
|
|
267
|
-
headRef: env.BUILDKITE_BRANCH,
|
|
268
|
-
// Buildkite doesn't model forks at this layer; whether the
|
|
269
|
-
// upstream PR is from a fork is a property of the originating
|
|
270
|
-
// VCS (GitHub/GitLab), checked when we derive the commit context.
|
|
271
|
-
isFork: false
|
|
272
|
-
};
|
|
273
|
-
};
|
|
274
|
-
const loadTrigger = async (ciContext, env) => {
|
|
275
|
-
if (ciContext.provider === "github-actions") {
|
|
276
|
-
return await loadGithubTrigger(env.GITHUB_EVENT_PATH);
|
|
277
|
-
}
|
|
278
|
-
if (ciContext.provider === "gitlab-ci") {
|
|
279
|
-
return loadGitlabTrigger(env);
|
|
280
|
-
}
|
|
281
|
-
if (ciContext.provider === "buildkite") {
|
|
282
|
-
return loadBuildkiteTrigger(env);
|
|
283
|
-
}
|
|
284
|
-
return void 0;
|
|
285
|
-
};
|
|
286
|
-
const deriveBuildkiteCommitContext = (buildkite, env) => {
|
|
287
|
-
const repoUrl = env.BUILDKITE_REPO;
|
|
288
|
-
if (!repoUrl) {
|
|
289
|
-
return void 0;
|
|
290
|
-
}
|
|
291
|
-
const githubMatch = /(?:^|@|\/\/)github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?\/?$/i.exec(repoUrl);
|
|
292
|
-
if (githubMatch && env.GITHUB_TOKEN) {
|
|
293
|
-
return {
|
|
294
|
-
apiBaseUrl: void 0,
|
|
295
|
-
buildId: void 0,
|
|
296
|
-
buildNumber: void 0,
|
|
297
|
-
prNumber: buildkite.prNumber,
|
|
298
|
-
provider: "github-actions",
|
|
299
|
-
repo: `${githubMatch[1]}/${githubMatch[2]}`,
|
|
300
|
-
sha: buildkite.sha,
|
|
301
|
-
token: env.GITHUB_TOKEN
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
const gitlabHttpsMatch = /\/\/([^/]*gitlab[^/]*)\/([^/]+)\/(.+?)(?:\.git)?\/?$/i.exec(repoUrl);
|
|
305
|
-
const gitlabSshMatch = gitlabHttpsMatch ? null : /@([^/:]*gitlab[^/:]*):([^/]+)\/(.+?)(?:\.git)?\/?$/i.exec(repoUrl);
|
|
306
|
-
const gitlabMatch = gitlabHttpsMatch ?? gitlabSshMatch;
|
|
307
|
-
if (gitlabMatch && env.GITLAB_TOKEN) {
|
|
308
|
-
const host = gitlabMatch[1];
|
|
309
|
-
const apiBaseUrl = env.CI_API_V4_URL ?? `https://${host}/api/v4`;
|
|
310
|
-
return {
|
|
311
|
-
apiBaseUrl,
|
|
312
|
-
buildId: void 0,
|
|
313
|
-
buildNumber: void 0,
|
|
314
|
-
prNumber: buildkite.prNumber,
|
|
315
|
-
provider: "gitlab-ci",
|
|
316
|
-
repo: `${gitlabMatch[2]}/${gitlabMatch[3]}`,
|
|
317
|
-
sha: buildkite.sha,
|
|
318
|
-
token: env.GITLAB_TOKEN
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
return void 0;
|
|
322
|
-
};
|
|
323
|
-
const fetchGithubHeadRef = async (options) => {
|
|
324
|
-
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
325
|
-
const base = options.apiBaseUrl ?? "https://api.github.com";
|
|
326
|
-
const url = `${base.replace(/\/+$/, "")}/repos/${options.repo}/pulls/${String(options.prNumber)}`;
|
|
327
|
-
try {
|
|
328
|
-
const response = await fetchImpl(url, {
|
|
329
|
-
headers: {
|
|
330
|
-
Accept: "application/vnd.github+json",
|
|
331
|
-
Authorization: `Bearer ${options.token}`,
|
|
332
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
if (!response.ok) {
|
|
336
|
-
return void 0;
|
|
337
|
-
}
|
|
338
|
-
const json = await response.json();
|
|
339
|
-
return json.head?.ref;
|
|
340
|
-
} catch {
|
|
341
|
-
return void 0;
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
const summariseDetail = (commit, files, failingTaskId, actor) => {
|
|
345
|
-
const fileLines = files.map((file) => `- \`${file}\``).join("\n");
|
|
346
|
-
return [
|
|
347
|
-
"## vis ai heal — committed",
|
|
348
|
-
"",
|
|
349
|
-
`Accepted by @${actor}.`,
|
|
350
|
-
"",
|
|
351
|
-
`Failing task: \`${failingTaskId}\``,
|
|
352
|
-
commit.url ? `Commit: [\`${commit.sha.slice(0, 7)}\`](${commit.url})` : `Commit: \`${commit.sha.slice(0, 7)}\``,
|
|
353
|
-
"",
|
|
354
|
-
"### Files changed",
|
|
355
|
-
fileLines,
|
|
356
|
-
"",
|
|
357
|
-
"_Re-run the failing job to confirm the fix landed._"
|
|
358
|
-
].join("\n");
|
|
359
|
-
};
|
|
360
|
-
const acceptHeal = async (toolbox, deps = {}) => {
|
|
361
|
-
const { logger, visConfig, workspaceRoot: wsRoot } = toolbox;
|
|
362
|
-
const workspaceRoot = wsRoot ?? process.cwd();
|
|
363
|
-
const env = deps.env ?? process.env;
|
|
364
|
-
const ciContext = await (deps.detectCi ?? (() => detectCiContext(env)))();
|
|
365
|
-
if (ciContext.provider === "unknown") {
|
|
366
|
-
pail.error("`vis ai heal accept` must run inside a recognised CI provider (GitHub Actions, GitLab CI, or Buildkite).");
|
|
367
|
-
process.exitCode = 1;
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const trigger = await loadTrigger(ciContext, env);
|
|
371
|
-
if (!trigger) {
|
|
372
|
-
const message2 = ciContext.provider === "github-actions" ? "No issue_comment payload found. Trigger this command from a workflow listening for `issue_comment.created`." : ciContext.provider === "gitlab-ci" ? "No GitLab trigger payload found. Set VIS_HEAL_TRIGGER_BODY, VIS_HEAL_TRIGGER_ACTOR, and VIS_HEAL_HEAD_REF in the bridge that re-emits note hooks as pipeline runs." : "No Buildkite unblock signal found. Wire this command to run after a manually-unblocked block step so BUILDKITE_UNBLOCKER_EMAIL is set.";
|
|
373
|
-
pail.error(message2);
|
|
374
|
-
process.exitCode = 1;
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (ciContext.provider !== "buildkite" && !trigger.body.includes(TRIGGER_PHRASE)) {
|
|
378
|
-
pail.notice(`Trigger comment does not contain \`${TRIGGER_PHRASE}\`; nothing to do.`);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
const aiConfig = visConfig?.ai;
|
|
382
|
-
const allowedActors = aiConfig?.heal?.allowedActors ?? [];
|
|
383
|
-
if (allowedActors.length === 0) {
|
|
384
|
-
pail.error("`ai.heal.allowedActors` is empty. Configure the allow-list in `vis.config.*` before enabling auto-commit.");
|
|
385
|
-
process.exitCode = 1;
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
if (!trigger.actor || !allowedActors.includes(trigger.actor)) {
|
|
389
|
-
const hint = ciContext.provider === "buildkite" ? "Buildkite entries are emails (BUILDKITE_UNBLOCKER_EMAIL) or Buildkite usernames (BUILDKITE_UNBLOCKER), not the upstream GitHub/GitLab username." : ciContext.provider === "gitlab-ci" ? "GitLab entries are platform usernames (without the leading `@`)." : "GitHub entries are platform usernames (without the leading `@`).";
|
|
390
|
-
pail.error(`Actor \`${trigger.actor || "(unknown)"}\` is not in \`ai.heal.allowedActors\`. Refusing to commit. ${hint}`);
|
|
391
|
-
process.exitCode = 1;
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (trigger.isFork) {
|
|
395
|
-
pail.error("Refusing to accept: the change is from a forked repository. The CI token does not have write access to the fork.");
|
|
396
|
-
process.exitCode = 1;
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
let branch = trigger.headRef;
|
|
400
|
-
if (!branch && ciContext.provider === "github-actions" && ciContext.prNumber !== void 0 && ciContext.repo && ciContext.token) {
|
|
401
|
-
branch = await fetchGithubHeadRef({
|
|
402
|
-
fetchImpl: deps.fetchImpl,
|
|
403
|
-
prNumber: ciContext.prNumber,
|
|
404
|
-
repo: ciContext.repo,
|
|
405
|
-
token: ciContext.token
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
if (!branch) {
|
|
409
|
-
pail.error("Could not resolve the PR / MR head branch. Ensure the trigger payload includes head.ref or set VIS_HEAL_HEAD_REF.");
|
|
410
|
-
process.exitCode = 1;
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
pail.info(`Accepting fix on \`${branch}\` for actor \`${trigger.actor}\`.`);
|
|
414
|
-
const candidateResult = await findHealCandidate(workspaceRoot, toolbox.options.run);
|
|
415
|
-
if (candidateResult.outcome === "no-failed-task") {
|
|
416
|
-
pail.error("No failed tasks found in the run summary. The accept command should run on the same workspace as the original failure.");
|
|
417
|
-
process.exitCode = 1;
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
if (candidateResult.outcome === "missing-metadata") {
|
|
421
|
-
pail.error(`Failed task ${candidateResult.failedTask.taskId} is missing project/target metadata; cannot validate the fix.`);
|
|
422
|
-
process.exitCode = 1;
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (candidateResult.outcome === "no-failure-context") {
|
|
426
|
-
pail.error(`No failure log or run summary found for ${candidateResult.failedTask.taskId}.`);
|
|
427
|
-
process.exitCode = 1;
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const candidate = candidateResult;
|
|
431
|
-
const proposeResult = await proposeAndApply(toolbox, candidate);
|
|
432
|
-
if (proposeResult.outcome === "no-proposal") {
|
|
433
|
-
pail.error("AI fix proposal failed; cannot commit.");
|
|
434
|
-
process.exitCode = 1;
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
if (proposeResult.outcome === "cannot-fix") {
|
|
438
|
-
pail.warn(`AI declined to fix: ${proposeResult.detail ?? "(no reason)"}. Nothing to commit.`);
|
|
439
|
-
process.exitCode = 1;
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
if (proposeResult.outcome === "empty-patches") {
|
|
443
|
-
pail.warn("AI returned an empty patch set. Nothing to commit.");
|
|
444
|
-
process.exitCode = 1;
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (proposeResult.outcome === "no-patches-applied") {
|
|
448
|
-
pail.error("Patches could not be applied to the workspace. Refusing to commit.");
|
|
449
|
-
process.exitCode = 1;
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
pail.info(`Re-running ${candidate.failedTask.taskId} to validate the fix before committing...`);
|
|
453
|
-
const validation = await validateAppliedFix(toolbox, candidate, { validate: deps.validate });
|
|
454
|
-
if (validation.exitCode !== 0) {
|
|
455
|
-
pail.error(`Validation failed (exit ${String(validation.exitCode)}). Refusing to commit.`);
|
|
456
|
-
if (validation.stderr.trim().length > 0) {
|
|
457
|
-
logger.info("--- validation stderr (tail) ---");
|
|
458
|
-
logger.info(validation.stderr.split("\n").slice(-20).join("\n"));
|
|
459
|
-
}
|
|
460
|
-
process.exitCode = 1;
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
pail.success("Validation passed. Committing.");
|
|
464
|
-
const appliedFiles = (proposeResult.applyResults ?? []).filter((result) => result.status === "applied").map((result) => {
|
|
465
|
-
const absolute = result.absolutePath ?? result.patch.file;
|
|
466
|
-
const rel = relative(workspaceRoot, absolute);
|
|
467
|
-
return rel === "" || rel.startsWith("..") ? result.patch.file : rel;
|
|
468
|
-
});
|
|
469
|
-
if (appliedFiles.length === 0) {
|
|
470
|
-
pail.error("No applied files to commit. Aborting.");
|
|
471
|
-
process.exitCode = 1;
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
const message = [
|
|
475
|
-
`fix: vis ai heal accepted by @${trigger.actor}`,
|
|
476
|
-
"",
|
|
477
|
-
`Failing task: ${candidate.failedTask.taskId}`,
|
|
478
|
-
proposeResult.proposal?.explanation ? `
|
|
479
|
-
${proposeResult.proposal.explanation}` : "",
|
|
480
|
-
"",
|
|
481
|
-
"Auto-committed by `vis ai heal accept`."
|
|
482
|
-
].join("\n");
|
|
483
|
-
let commitContext = ciContext;
|
|
484
|
-
if (ciContext.provider === "buildkite") {
|
|
485
|
-
const derived = deriveBuildkiteCommitContext(ciContext, env);
|
|
486
|
-
if (!derived) {
|
|
487
|
-
pail.error(
|
|
488
|
-
"Cannot determine the upstream VCS to commit to. Buildkite jobs need GITHUB_TOKEN or GITLAB_TOKEN set (matching the host parsed from BUILDKITE_REPO) for `vis ai heal accept` to land the commit."
|
|
489
|
-
);
|
|
490
|
-
process.exitCode = 1;
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
commitContext = derived;
|
|
494
|
-
}
|
|
495
|
-
let commit;
|
|
496
|
-
try {
|
|
497
|
-
commit = await commitFiles({
|
|
498
|
-
branch,
|
|
499
|
-
ciContext: commitContext,
|
|
500
|
-
files: appliedFiles,
|
|
501
|
-
message,
|
|
502
|
-
workspaceRoot,
|
|
503
|
-
...deps.commitOverrides
|
|
504
|
-
});
|
|
505
|
-
} catch (error) {
|
|
506
|
-
pail.error(`Commit failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
507
|
-
process.exitCode = 1;
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
pail.success(`Committed as ${commit.sha.slice(0, 7)}${commit.url ? ` — ${commit.url}` : ""}.`);
|
|
511
|
-
const confirmationBody = summariseDetail(commit, appliedFiles, candidate.failedTask.taskId, trigger.actor);
|
|
512
|
-
const postCommentImpl = deps.postComment ?? (async (body, context) => postPrComment({ body, context }));
|
|
513
|
-
const postResult = await postCommentImpl(confirmationBody, ciContext);
|
|
514
|
-
if (!postResult.posted && postResult.method !== "skipped") {
|
|
515
|
-
pail.warn(`Commit landed but the confirmation comment failed: ${postResult.error ?? "unknown"}`);
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
const aiHealAccept = async (toolbox) => {
|
|
519
|
-
await acceptHeal(toolbox);
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
export { TRIGGER_PHRASE, aiHealAccept, deriveBuildkiteCommitContext as deriveBuildkiteCommitContextForTesting, fetchGithubHeadRef as fetchGithubHeadRefForTesting, loadBuildkiteTrigger as loadBuildkiteTriggerForTesting, loadGithubTrigger as loadGithubTriggerForTesting, loadGitlabTrigger as loadGitlabTriggerForTesting, acceptHeal as runHealAcceptForTesting, summariseDetail as summariseDetailForTesting };
|
|
1
|
+
var H=Object.defineProperty;var k=(e,t)=>H(e,"name",{value:t,configurable:!0});import{createRequire as U}from"node:module";import{resolve as O,relative as F}from"@visulima/path";import{d as D,P as K,R as S,I as j,p as V}from"./heal.js";import{A as q,G as Y,p as a}from"./bin.js";const P=U(import.meta.url),T=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,A=k(e=>{if(typeof T<"u"&&T.versions&&T.versions.node){const[t,o]=T.versions.node.split(".").map(Number);if(t>22||t===22&&o>=3||t===20&&o>=16)return T.getBuiltinModule(e)}return P(e)},"__cjs_getBuiltinModule"),{readFile:B}=A("node:fs/promises"),{createInterface:M}=A("node:readline");var J=Object.defineProperty,I=k((e,t)=>J(e,"name",{value:t,configurable:!0}),"o");const W=I(()=>!!process.stdout.isTTY&&process.env.CI!=="true","isInteractive"),z=I(e=>new Promise(t=>{const o=M({input:process.stdin,output:process.stderr});o.question(`${e} (Y/n) `,i=>{o.close();const s=i.trim().toLowerCase();t(s===""||s==="y"||s==="yes")})}),"defaultPrompt"),X=I((e,t)=>{const o=q(t),i=Y(o,{exact:!1,filter:[],global:!1,optional:!1,packages:[e],peer:!1,saveDev:!1,workspace:!1,workspaceRoot:!1},t,console);return Promise.resolve({exitCode:i})},"defaultRunInstall"),y=I(e=>`pnpm add ${e}`,"installCommandFor"),Q=I(e=>e==="@gitbeaker/rest"?import("@gitbeaker/rest"):import("@octokit/rest"),"defaultImport"),L=I(async(e,t={})=>{const o=t.interactive??W(),i=t.prompt??z,s=t.runInstall??X,l=t.importImpl??Q,d=t.workspaceRoot??process.cwd();try{return await l(e)}catch(n){const{code:u}=n;if(u!=="ERR_MODULE_NOT_FOUND"&&u!=="MODULE_NOT_FOUND")throw n}if(!o)throw new Error(`${e} is not installed. \`vis ai heal accept\` needs it to talk to the host. Install it in your repo first:
|
|
2
|
+
${y(e)}`);if(!await i(`${e} isn't installed. Install it now?`))throw new Error(`${e} install declined. Re-run \`vis ai heal accept\` after installing manually:
|
|
3
|
+
${y(e)}`);const r=await s(e,d);if(r.exitCode!==0)throw new Error(`Install of ${e} failed (exit ${String(r.exitCode)}). Install manually and retry:
|
|
4
|
+
${y(e)}`);return await l(e)},"loadOptionalSdk");var Z=Object.defineProperty,C=k((e,t)=>Z(e,"name",{value:t,configurable:!0}),"s");const ee=C(e=>{const t=e.indexOf("/");if(t<=0||t===e.length-1)throw new Error(`Expected GITHUB_REPOSITORY in "owner/repo" form, got: ${e}`);return{owner:e.slice(0,t),repo:e.slice(t+1)}},"splitGithubRepo"),te=C(async(e,t)=>{if(t.githubClient)return t.githubClient;const o=(await(t.loadSdk??L)("@octokit/rest")).Octokit;if(!o)throw new TypeError("Loaded `@octokit/rest` but no `Octokit` export was found. Reinstall the package or pin to a supported major.");return new o({auth:e})},"loadGithubClient"),oe=C(async(e,t,o)=>{if(o.gitlabClient)return o.gitlabClient;const i=(await(o.loadSdk??L)("@gitbeaker/rest")).Gitlab;if(!i)throw new TypeError("Loaded `@gitbeaker/rest` but no `Gitlab` export was found. Reinstall the package or pin to a supported major.");return new i({host:t,token:e})},"loadGitlabClient"),re=C(e=>e.replace(/\/api\/v\d+\/?$/,""),"apiBaseToHost"),ie=C(async e=>{const{branch:t,ciContext:o,files:i,message:s,workspaceRoot:l}=e,d=e.readFile??(w=>B(w,"utf8"));if(!o.repo)throw new Error("Cannot commit on GitHub: GITHUB_REPOSITORY (owner/repo) is not set.");if(!o.token)throw new Error("Cannot commit on GitHub: GITHUB_TOKEN is not set. Grant the workflow `contents: write` and pass the token through.");const{owner:r,repo:n}=ee(o.repo),u=await te(o.token,e),p=(await u.rest.git.getRef({owner:r,ref:`heads/${t}`,repo:n})).data.object.sha,h=(await u.rest.git.getCommit({commit_sha:p,owner:r,repo:n})).data.tree.sha,m=await Promise.all(i.map(async w=>{const E=O(l,w),v=await d(E),_=await u.rest.git.createBlob({content:Buffer.from(v,"utf8").toString("base64"),encoding:"base64",owner:r,repo:n});return{mode:"100644",path:w,sha:_.data.sha,type:"blob"}})),f=await u.rest.git.createTree({base_tree:h,owner:r,repo:n,tree:m}),b=await u.rest.git.createCommit({message:s,owner:r,parents:[p],repo:n,tree:f.data.sha});return await u.rest.git.updateRef({owner:r,ref:`heads/${t}`,repo:n,sha:b.data.sha}),{sha:b.data.sha,url:b.data.html_url}},"commitToGithub"),ae=C(async e=>{const{branch:t,ciContext:o,files:i,message:s,workspaceRoot:l}=e,d=e.readFile??(m=>B(m,"utf8"));if(!o.repo)throw new Error("Cannot commit on GitLab: CI_PROJECT_ID / CI_PROJECT_PATH is not set.");if(!o.token)throw new Error("Cannot commit on GitLab: no token. CI_JOB_TOKEN cannot push commits — set GITLAB_TOKEN to a PAT/project-token with `api` scope.");if(!o.apiBaseUrl)throw new Error("Cannot commit on GitLab: CI_API_V4_URL is not set.");const r=re(o.apiBaseUrl),n=await oe(o.token,r,e),u=await Promise.all(i.map(async m=>{const f=O(l,m);return{action:"update",content:await d(f),filePath:m}})),p=await n.Commits.create(o.repo,t,s,u),h=p.id??p.sha;if(!h)throw new Error("GitLab Commits.create returned no commit ID — cannot reference the new commit.");return{sha:h,url:p.web_url??p.webUrl}},"commitToGitlab"),ne=C(async e=>{if(e.files.length===0)throw new Error("Cannot commit: no files to include.");if(e.ciContext.provider==="github-actions")return await ie(e);if(e.ciContext.provider==="gitlab-ci")return await ae(e);throw new Error(`Cannot commit: unsupported CI provider \`${e.ciContext.provider}\`. Run \`vis ai heal accept\` from a recognised CI environment.`)},"commitFiles");var se=Object.defineProperty,g=k((e,t)=>se(e,"name",{value:t,configurable:!0}),"u");const G="/vis heal accept",ce=g(async e=>{if(!e)return;let t;try{t=await B(e,"utf8")}catch{return}const o=JSON.parse(t),i=o.comment?.body??"",s=o.comment?.user?.login??"",l=o.pull_request?.head?.repo?.full_name,d=o.pull_request?.base?.repo?.full_name??o.repository?.full_name,r=l!==void 0&&d!==void 0&&l!==d;return{actor:s,body:i,headRef:o.pull_request?.head?.ref,isFork:r}},"loadGithubTrigger"),le=g(e=>{const t=e.VIS_HEAL_TRIGGER_BODY,o=e.VIS_HEAL_TRIGGER_ACTOR;if(!(!t||!o))return{actor:o,body:t,headRef:e.VIS_HEAL_HEAD_REF,isFork:!1}},"loadGitlabTrigger"),de=g(e=>{const t=e.BUILDKITE_UNBLOCKER_EMAIL??e.BUILDKITE_UNBLOCKER;if(t)return{actor:t,body:G,headRef:e.BUILDKITE_BRANCH,isFork:!1}},"loadBuildkiteTrigger"),ue=g(async(e,t)=>{if(e.provider==="github-actions")return await ce(t.GITHUB_EVENT_PATH);if(e.provider==="gitlab-ci")return le(t);if(e.provider==="buildkite")return de(t)},"loadTrigger"),pe=g((e,t)=>{const o=t.BUILDKITE_REPO;if(!o)return;const i=/(?:^|@|\/\/)github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?\/?$/i.exec(o);if(i&&t.GITHUB_TOKEN)return{apiBaseUrl:void 0,buildId:void 0,buildNumber:void 0,prNumber:e.prNumber,provider:"github-actions",repo:`${i[1]}/${i[2]}`,sha:e.sha,token:t.GITHUB_TOKEN};const s=/\/\/([^/]*gitlab[^/]*)\/([^/]+)\/(.+?)(?:\.git)?\/?$/i.exec(o),l=s?null:/@([^/:]*gitlab[^/:]*):([^/]+)\/(.+?)(?:\.git)?\/?$/i.exec(o),d=s??l;if(d&&t.GITLAB_TOKEN){const r=d[1];return{apiBaseUrl:t.CI_API_V4_URL??`https://${r}/api/v4`,buildId:void 0,buildNumber:void 0,prNumber:e.prNumber,provider:"gitlab-ci",repo:`${d[2]}/${d[3]}`,sha:e.sha,token:t.GITLAB_TOKEN}}},"deriveBuildkiteCommitContext"),me=g(async e=>{const t=e.fetchImpl??globalThis.fetch,o=`${(e.apiBaseUrl??"https://api.github.com").replace(/\/+$/,"")}/repos/${e.repo}/pulls/${String(e.prNumber)}`;try{const i=await t(o,{headers:{Accept:"application/vnd.github+json",Authorization:`Bearer ${e.token}`,"X-GitHub-Api-Version":"2022-11-28"}});return i.ok?(await i.json()).head?.ref:void 0}catch{return}},"fetchGithubHeadRef"),fe=g((e,t,o,i)=>{const s=t.map(l=>`- \`${l}\``).join(`
|
|
5
|
+
`);return["## vis ai heal — committed","",`Accepted by @${i}.`,"",`Failing task: \`${o}\``,e.url?`Commit: [\`${e.sha.slice(0,7)}\`](${e.url})`:`Commit: \`${e.sha.slice(0,7)}\``,"","### Files changed",s,"","_Re-run the failing job to confirm the fix landed._"].join(`
|
|
6
|
+
`)},"summariseDetail"),he=g(async(e,t={})=>{const{logger:o,visConfig:i,workspaceRoot:s}=e,l=s??process.cwd(),d=t.env??process.env,r=await(t.detectCi??(()=>D(d)))();if(r.provider==="unknown"){a.error("`vis ai heal accept` must run inside a recognised CI provider (GitHub Actions, GitLab CI, or Buildkite)."),process.exitCode=1;return}const n=await ue(r,d);if(!n){const c=r.provider==="github-actions"?"No issue_comment payload found. Trigger this command from a workflow listening for `issue_comment.created`.":r.provider==="gitlab-ci"?"No GitLab trigger payload found. Set VIS_HEAL_TRIGGER_BODY, VIS_HEAL_TRIGGER_ACTOR, and VIS_HEAL_HEAD_REF in the bridge that re-emits note hooks as pipeline runs.":"No Buildkite unblock signal found. Wire this command to run after a manually-unblocked block step so BUILDKITE_UNBLOCKER_EMAIL is set.";a.error(c),process.exitCode=1;return}if(r.provider!=="buildkite"&&!n.body.includes(G)){a.notice(`Trigger comment does not contain \`${G}\`; nothing to do.`);return}const u=i?.ai?.heal?.allowedActors??[];if(u.length===0){a.error("`ai.heal.allowedActors` is empty. Configure the allow-list in `vis.config.*` before enabling auto-commit."),process.exitCode=1;return}if(!n.actor||!u.includes(n.actor)){const c=r.provider==="buildkite"?"Buildkite entries are emails (BUILDKITE_UNBLOCKER_EMAIL) or Buildkite usernames (BUILDKITE_UNBLOCKER), not the upstream GitHub/GitLab username.":r.provider==="gitlab-ci"?"GitLab entries are platform usernames (without the leading `@`).":"GitHub entries are platform usernames (without the leading `@`).";a.error(`Actor \`${n.actor||"(unknown)"}\` is not in \`ai.heal.allowedActors\`. Refusing to commit. ${c}`),process.exitCode=1;return}if(n.isFork){a.error("Refusing to accept: the change is from a forked repository. The CI token does not have write access to the fork."),process.exitCode=1;return}let p=n.headRef;if(!p&&r.provider==="github-actions"&&r.prNumber!==void 0&&r.repo&&r.token&&(p=await me({fetchImpl:t.fetchImpl,prNumber:r.prNumber,repo:r.repo,token:r.token})),!p){a.error("Could not resolve the PR / MR head branch. Ensure the trigger payload includes head.ref or set VIS_HEAL_HEAD_REF."),process.exitCode=1;return}a.info(`Accepting fix on \`${p}\` for actor \`${n.actor}\`.`);const h=await K(l,e.options.run);if(h.outcome==="no-failed-task"){a.error("No failed tasks found in the run summary. The accept command should run on the same workspace as the original failure."),process.exitCode=1;return}if(h.outcome==="missing-metadata"){a.error(`Failed task ${h.failedTask.taskId} is missing project/target metadata; cannot validate the fix.`),process.exitCode=1;return}if(h.outcome==="no-failure-context"){a.error(`No failure log or run summary found for ${h.failedTask.taskId}.`),process.exitCode=1;return}const m=h,f=await S(e,m);if(f.outcome==="no-proposal"){a.error("AI fix proposal failed; cannot commit."),process.exitCode=1;return}if(f.outcome==="cannot-fix"){a.warn(`AI declined to fix: ${f.detail??"(no reason)"}. Nothing to commit.`),process.exitCode=1;return}if(f.outcome==="empty-patches"){a.warn("AI returned an empty patch set. Nothing to commit."),process.exitCode=1;return}if(f.outcome==="no-patches-applied"){a.error("Patches could not be applied to the workspace. Refusing to commit."),process.exitCode=1;return}a.info(`Re-running ${m.failedTask.taskId} to validate the fix before committing...`);const b=await j(e,m,{validate:t.validate});if(b.exitCode!==0){a.error(`Validation failed (exit ${String(b.exitCode)}). Refusing to commit.`),b.stderr.trim().length>0&&(o.info("--- validation stderr (tail) ---"),o.info(b.stderr.split(`
|
|
7
|
+
`).slice(-20).join(`
|
|
8
|
+
`))),process.exitCode=1;return}a.success("Validation passed. Committing.");const w=(f.applyResults??[]).filter(c=>c.status==="applied").map(c=>{const $=c.absolutePath??c.patch.file,x=F(l,$);return x===""||x.startsWith("..")?c.patch.file:x});if(w.length===0){a.error("No applied files to commit. Aborting."),process.exitCode=1;return}const E=[`fix: vis ai heal accepted by @${n.actor}`,"",`Failing task: ${m.failedTask.taskId}`,f.proposal?.explanation?`
|
|
9
|
+
${f.proposal.explanation}`:"","","Auto-committed by `vis ai heal accept`."].join(`
|
|
10
|
+
`);let v=r;if(r.provider==="buildkite"){const c=pe(r,d);if(!c){a.error("Cannot determine the upstream VCS to commit to. Buildkite jobs need GITHUB_TOKEN or GITLAB_TOKEN set (matching the host parsed from BUILDKITE_REPO) for `vis ai heal accept` to land the commit."),process.exitCode=1;return}v=c}let _;try{_=await ne({branch:p,ciContext:v,files:w,message:E,workspaceRoot:l,...t.commitOverrides})}catch(c){a.error(`Commit failed: ${c instanceof Error?c.message:String(c)}`),process.exitCode=1;return}a.success(`Committed as ${_.sha.slice(0,7)}${_.url?` — ${_.url}`:""}.`);const N=fe(_,w,m.failedTask.taskId,n.actor),R=await(t.postComment??(async(c,$)=>V({body:c,context:$})))(N,r);!R.posted&&R.method!=="skipped"&&a.warn(`Commit landed but the confirmation comment failed: ${R.error??"unknown"}`)},"acceptHeal"),Ie=g(async e=>{await he(e)},"aiHealAccept");export{G as TRIGGER_PHRASE,Ie as aiHealAccept,pe as deriveBuildkiteCommitContextForTesting,me as fetchGithubHeadRefForTesting,de as loadBuildkiteTriggerForTesting,ce as loadGithubTriggerForTesting,le as loadGitlabTriggerForTesting,he as runHealAcceptForTesting,fe as summariseDetailForTesting};
|