greenbump 0.4.0 → 0.6.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.
- package/README.md +673 -66
- package/README.zh-CN.md +525 -100
- package/dist/agent/fixer.js +161 -12
- package/dist/agent/fixer.js.map +1 -1
- package/dist/agent/fixer.test.js +410 -0
- package/dist/agent/fixer.test.js.map +1 -0
- package/dist/cli/interactive.js +81 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli.js +53 -0
- package/dist/cli.js.map +1 -1
- package/dist/engine/analyzers/changelog-parser.js +121 -0
- package/dist/engine/analyzers/changelog-parser.js.map +1 -0
- package/dist/engine/batch.test.js +59 -0
- package/dist/engine/batch.test.js.map +1 -0
- package/dist/engine/cache/manager.js +165 -0
- package/dist/engine/cache/manager.js.map +1 -0
- package/dist/engine/cache/manager.test.js +75 -0
- package/dist/engine/cache/manager.test.js.map +1 -0
- package/dist/engine/checks.test.js +92 -0
- package/dist/engine/checks.test.js.map +1 -0
- package/dist/engine/context/optimizer.js +189 -0
- package/dist/engine/context/optimizer.js.map +1 -0
- package/dist/engine/context/optimizer.test.js +81 -0
- package/dist/engine/context/optimizer.test.js.map +1 -0
- package/dist/engine/ecosystems/parsers.test.js +255 -0
- package/dist/engine/ecosystems/parsers.test.js.map +1 -0
- package/dist/engine/fixer/patterns.js +356 -0
- package/dist/engine/fixer/patterns.js.map +1 -0
- package/dist/engine/fixer/patterns.test.js +124 -0
- package/dist/engine/fixer/patterns.test.js.map +1 -0
- package/dist/engine/git.js +1 -0
- package/dist/engine/git.js.map +1 -1
- package/dist/engine/git.test.js +111 -0
- package/dist/engine/git.test.js.map +1 -0
- package/dist/engine/perf/metrics.js +171 -0
- package/dist/engine/perf/metrics.js.map +1 -0
- package/dist/engine/perf/regression.js +115 -0
- package/dist/engine/perf/regression.js.map +1 -0
- package/dist/engine/run.js +93 -2
- package/dist/engine/run.js.map +1 -1
- package/dist/engine/run.test.js +47 -0
- package/dist/engine/run.test.js.map +1 -0
- package/dist/engine/sandbox/docker-manager.js +215 -0
- package/dist/engine/sandbox/docker-manager.js.map +1 -0
- package/dist/engine/sandbox/orchestrator.js +107 -0
- package/dist/engine/sandbox/orchestrator.js.map +1 -0
- package/dist/engine/sandbox/service-detector.js +147 -0
- package/dist/engine/sandbox/service-detector.js.map +1 -0
- package/dist/engine/sandbox/template-generator.js +241 -0
- package/dist/engine/sandbox/template-generator.js.map +1 -0
- package/dist/engine/verifiers/ast-analyzer.js +212 -0
- package/dist/engine/verifiers/ast-analyzer.js.map +1 -0
- package/dist/engine/verifiers/ast-analyzer.test.js +96 -0
- package/dist/engine/verifiers/ast-analyzer.test.js.map +1 -0
- package/dist/engine/workspace.test.js +95 -0
- package/dist/engine/workspace.test.js.map +1 -0
- package/dist/exitcode.test.js +62 -0
- package/dist/exitcode.test.js.map +1 -0
- package/dist/format.js +15 -0
- package/dist/format.js.map +1 -1
- package/dist/report.test.js +56 -0
- package/dist/report.test.js.map +1 -0
- package/package.json +7 -4
- package/CHANGELOG.md +0 -93
- package/CONTRIBUTING.md +0 -52
- package/SECURITY.md +0 -38
- package/action-dist/index.cjs +0 -19935
- package/action.yml +0 -76
- package/competitive-analysis.md +0 -304
- package/examples/greenbump.yml +0 -37
- package/examples/legacy-repo/package.json +0 -12
- package/examples/legacy-repo/test.js +0 -17
- package/examples/multi-file-repo/models/todo.js +0 -8
- package/examples/multi-file-repo/models/user.js +0 -11
- package/examples/multi-file-repo/package.json +0 -12
- package/examples/multi-file-repo/services/session.js +0 -9
- package/examples/multi-file-repo/test.js +0 -19
- package/optimization-roadmap.md +0 -772
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
4
|
+
/**
|
|
5
|
+
* Detect project type based on files present
|
|
6
|
+
*/
|
|
7
|
+
export async function detectProjectType(cwd) {
|
|
8
|
+
if (existsSync(join(cwd, "package.json"))) {
|
|
9
|
+
return "nodejs";
|
|
10
|
+
}
|
|
11
|
+
if (existsSync(join(cwd, "requirements.txt")) ||
|
|
12
|
+
existsSync(join(cwd, "pyproject.toml")) ||
|
|
13
|
+
existsSync(join(cwd, "setup.py"))) {
|
|
14
|
+
return "python";
|
|
15
|
+
}
|
|
16
|
+
return "unknown";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate Dockerfile for project
|
|
20
|
+
*/
|
|
21
|
+
export async function generateDockerfile(projectType, cwd) {
|
|
22
|
+
if (projectType === "nodejs") {
|
|
23
|
+
return await generateNodeJsDockerfile(cwd);
|
|
24
|
+
}
|
|
25
|
+
if (projectType === "python") {
|
|
26
|
+
return await generatePythonDockerfile(cwd);
|
|
27
|
+
}
|
|
28
|
+
// Fallback to Node.js
|
|
29
|
+
return await generateNodeJsDockerfile(cwd);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate Node.js Dockerfile
|
|
33
|
+
*/
|
|
34
|
+
async function generateNodeJsDockerfile(cwd) {
|
|
35
|
+
const hasPnpm = existsSync(join(cwd, "pnpm-lock.yaml"));
|
|
36
|
+
const hasYarn = existsSync(join(cwd, "yarn.lock"));
|
|
37
|
+
let installCmd = "npm ci";
|
|
38
|
+
let copyLockFile = "COPY package*.json ./";
|
|
39
|
+
if (hasPnpm) {
|
|
40
|
+
installCmd = "corepack enable && pnpm install --frozen-lockfile";
|
|
41
|
+
copyLockFile = "COPY package*.json pnpm-lock.yaml ./";
|
|
42
|
+
}
|
|
43
|
+
else if (hasYarn) {
|
|
44
|
+
installCmd = "yarn install --frozen-lockfile";
|
|
45
|
+
copyLockFile = "COPY package*.json yarn.lock ./";
|
|
46
|
+
}
|
|
47
|
+
return `FROM node:20-slim
|
|
48
|
+
|
|
49
|
+
WORKDIR /workspace
|
|
50
|
+
|
|
51
|
+
# Copy package files
|
|
52
|
+
${copyLockFile}
|
|
53
|
+
|
|
54
|
+
# Install dependencies
|
|
55
|
+
RUN ${installCmd}
|
|
56
|
+
|
|
57
|
+
# Copy source code
|
|
58
|
+
COPY . .
|
|
59
|
+
|
|
60
|
+
# Build if build script exists
|
|
61
|
+
RUN if grep -q '"build":' package.json; then npm run build || true; fi
|
|
62
|
+
|
|
63
|
+
# Default command: run tests
|
|
64
|
+
CMD ["npm", "test"]
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generate Python Dockerfile
|
|
69
|
+
*/
|
|
70
|
+
async function generatePythonDockerfile(cwd) {
|
|
71
|
+
const hasRequirements = existsSync(join(cwd, "requirements.txt"));
|
|
72
|
+
const hasPyproject = existsSync(join(cwd, "pyproject.toml"));
|
|
73
|
+
let installCmd = "";
|
|
74
|
+
let copyFiles = "";
|
|
75
|
+
if (hasRequirements) {
|
|
76
|
+
copyFiles = "COPY requirements*.txt ./";
|
|
77
|
+
installCmd = "pip install --no-cache-dir -r requirements.txt";
|
|
78
|
+
}
|
|
79
|
+
else if (hasPyproject) {
|
|
80
|
+
copyFiles = "COPY pyproject.toml setup.py* ./";
|
|
81
|
+
installCmd = "pip install --no-cache-dir -e .";
|
|
82
|
+
}
|
|
83
|
+
return `FROM python:3.11-slim
|
|
84
|
+
|
|
85
|
+
WORKDIR /workspace
|
|
86
|
+
|
|
87
|
+
# Copy dependency files
|
|
88
|
+
${copyFiles}
|
|
89
|
+
|
|
90
|
+
# Install dependencies
|
|
91
|
+
RUN ${installCmd}
|
|
92
|
+
|
|
93
|
+
# Copy source code
|
|
94
|
+
COPY . .
|
|
95
|
+
|
|
96
|
+
# Default command: run tests
|
|
97
|
+
CMD ["pytest"]
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Generate docker-compose.yml for services
|
|
102
|
+
*/
|
|
103
|
+
export async function generateComposeFile(services) {
|
|
104
|
+
const serviceConfigs = services.map(getServiceConfig).filter((config) => config !== null);
|
|
105
|
+
if (serviceConfigs.length === 0) {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
const servicesYaml = serviceConfigs.map(formatServiceConfig).join("\n");
|
|
109
|
+
return `version: '3.8'
|
|
110
|
+
|
|
111
|
+
services:
|
|
112
|
+
${servicesYaml}
|
|
113
|
+
|
|
114
|
+
networks:
|
|
115
|
+
default:
|
|
116
|
+
name: greenbump_network
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get service configuration
|
|
121
|
+
*/
|
|
122
|
+
function getServiceConfig(serviceName) {
|
|
123
|
+
const configs = {
|
|
124
|
+
postgres: {
|
|
125
|
+
name: "postgres",
|
|
126
|
+
image: "postgres:16-alpine",
|
|
127
|
+
ports: ["5432:5432"],
|
|
128
|
+
environment: {
|
|
129
|
+
POSTGRES_USER: "test",
|
|
130
|
+
POSTGRES_PASSWORD: "test",
|
|
131
|
+
POSTGRES_DB: "test",
|
|
132
|
+
},
|
|
133
|
+
healthcheck: {
|
|
134
|
+
test: ["CMD-SHELL", "pg_isready -U test"],
|
|
135
|
+
interval: "5s",
|
|
136
|
+
timeout: "5s",
|
|
137
|
+
retries: 5,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
mysql: {
|
|
141
|
+
name: "mysql",
|
|
142
|
+
image: "mysql:8",
|
|
143
|
+
ports: ["3306:3306"],
|
|
144
|
+
environment: {
|
|
145
|
+
MYSQL_ROOT_PASSWORD: "test",
|
|
146
|
+
MYSQL_DATABASE: "test",
|
|
147
|
+
},
|
|
148
|
+
healthcheck: {
|
|
149
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"],
|
|
150
|
+
interval: "5s",
|
|
151
|
+
timeout: "5s",
|
|
152
|
+
retries: 5,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
redis: {
|
|
156
|
+
name: "redis",
|
|
157
|
+
image: "redis:7-alpine",
|
|
158
|
+
ports: ["6379:6379"],
|
|
159
|
+
healthcheck: {
|
|
160
|
+
test: ["CMD", "redis-cli", "ping"],
|
|
161
|
+
interval: "5s",
|
|
162
|
+
timeout: "5s",
|
|
163
|
+
retries: 5,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
mongodb: {
|
|
167
|
+
name: "mongodb",
|
|
168
|
+
image: "mongo:7",
|
|
169
|
+
ports: ["27017:27017"],
|
|
170
|
+
environment: {
|
|
171
|
+
MONGO_INITDB_ROOT_USERNAME: "test",
|
|
172
|
+
MONGO_INITDB_ROOT_PASSWORD: "test",
|
|
173
|
+
},
|
|
174
|
+
healthcheck: {
|
|
175
|
+
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"],
|
|
176
|
+
interval: "5s",
|
|
177
|
+
timeout: "5s",
|
|
178
|
+
retries: 5,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
return configs[serviceName] || null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Format service config as YAML
|
|
186
|
+
*/
|
|
187
|
+
function formatServiceConfig(config) {
|
|
188
|
+
const lines = [` ${config.name}:`];
|
|
189
|
+
lines.push(` image: ${config.image}`);
|
|
190
|
+
if (config.environment) {
|
|
191
|
+
lines.push(" environment:");
|
|
192
|
+
for (const [key, value] of Object.entries(config.environment)) {
|
|
193
|
+
lines.push(` ${key}: ${value}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (config.ports.length > 0) {
|
|
197
|
+
lines.push(" ports:");
|
|
198
|
+
for (const port of config.ports) {
|
|
199
|
+
lines.push(` - "${port}"`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
lines.push(" healthcheck:");
|
|
203
|
+
lines.push(` test: ${JSON.stringify(config.healthcheck.test)}`);
|
|
204
|
+
lines.push(` interval: ${config.healthcheck.interval}`);
|
|
205
|
+
lines.push(` timeout: ${config.healthcheck.timeout}`);
|
|
206
|
+
lines.push(` retries: ${config.healthcheck.retries}`);
|
|
207
|
+
return lines.join("\n");
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Check if project has build script
|
|
211
|
+
*/
|
|
212
|
+
export async function hasBuildScript(cwd) {
|
|
213
|
+
const pkgPath = join(cwd, "package.json");
|
|
214
|
+
if (!existsSync(pkgPath))
|
|
215
|
+
return false;
|
|
216
|
+
try {
|
|
217
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
218
|
+
const pkg = JSON.parse(content);
|
|
219
|
+
return pkg.scripts?.build !== undefined;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Check if project has test script
|
|
227
|
+
*/
|
|
228
|
+
export async function hasTestScript(cwd) {
|
|
229
|
+
const pkgPath = join(cwd, "package.json");
|
|
230
|
+
if (!existsSync(pkgPath))
|
|
231
|
+
return false;
|
|
232
|
+
try {
|
|
233
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
234
|
+
const pkg = JSON.parse(content);
|
|
235
|
+
return pkg.scripts?.test !== undefined;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=template-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-generator.js","sourceRoot":"","sources":["../../../src/engine/sandbox/template-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAiBvC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW;IACjD,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;QAC1C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IACE,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACzC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;QACvC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,EACjC,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAwB,EACxB,GAAW;IAEX,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED,sBAAsB;IACtB,OAAO,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,wBAAwB,CAAC,GAAW;IACjD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IAEnD,IAAI,UAAU,GAAG,QAAQ,CAAC;IAC1B,IAAI,YAAY,GAAG,uBAAuB,CAAC;IAE3C,IAAI,OAAO,EAAE,CAAC;QACZ,UAAU,GAAG,mDAAmD,CAAC;QACjE,YAAY,GAAG,sCAAsC,CAAC;IACxD,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,UAAU,GAAG,gCAAgC,CAAC;QAC9C,YAAY,GAAG,iCAAiC,CAAC;IACnD,CAAC;IAED,OAAO;;;;;EAKP,YAAY;;;MAGR,UAAU;;;;;;;;;;CAUf,CAAC;AACF,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,wBAAwB,CAAC,GAAW;IACjD,MAAM,eAAe,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAClE,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAE7D,IAAI,UAAU,GAAG,EAAE,CAAC;IACpB,IAAI,SAAS,GAAG,EAAE,CAAC;IAEnB,IAAI,eAAe,EAAE,CAAC;QACpB,SAAS,GAAG,2BAA2B,CAAC;QACxC,UAAU,GAAG,gDAAgD,CAAC;IAChE,CAAC;SAAM,IAAI,YAAY,EAAE,CAAC;QACxB,SAAS,GAAG,kCAAkC,CAAC;QAC/C,UAAU,GAAG,iCAAiC,CAAC;IACjD,CAAC;IAED,OAAO;;;;;EAKP,SAAS;;;MAGL,UAAU;;;;;;;CAOf,CAAC;AACF,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAkB;IAElB,MAAM,cAAc,GAAG,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAA2B,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IAEnH,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAExE,OAAO;;;EAGP,YAAY;;;;;CAKb,CAAC;AACF,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,MAAM,OAAO,GAAkC;QAC7C,QAAQ,EAAE;YACR,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,oBAAoB;YAC3B,KAAK,EAAE,CAAC,WAAW,CAAC;YACpB,WAAW,EAAE;gBACX,aAAa,EAAE,MAAM;gBACrB,iBAAiB,EAAE,MAAM;gBACzB,WAAW,EAAE,MAAM;aACpB;YACD,WAAW,EAAE;gBACX,IAAI,EAAE,CAAC,WAAW,EAAE,oBAAoB,CAAC;gBACzC,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;aACX;SACF;QACD,KAAK,EAAE;YACL,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,CAAC,WAAW,CAAC;YACpB,WAAW,EAAE;gBACX,mBAAmB,EAAE,MAAM;gBAC3B,cAAc,EAAE,MAAM;aACvB;YACD,WAAW,EAAE;gBACX,IAAI,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC;gBACtD,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;aACX;SACF;QACD,KAAK,EAAE;YACL,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,CAAC,WAAW,CAAC;YACpB,WAAW,EAAE;gBACX,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC;gBAClC,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;aACX;SACF;QACD,OAAO,EAAE;YACP,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,CAAC,aAAa,CAAC;YACtB,WAAW,EAAE;gBACX,0BAA0B,EAAE,MAAM;gBAClC,0BAA0B,EAAE,MAAM;aACnC;YACD,WAAW,EAAE;gBACX,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,yBAAyB,CAAC;gBAC7D,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;aACX;SACF;KACF,CAAC;IAEF,OAAO,OAAO,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,MAAqB;IAChD,MAAM,KAAK,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,cAAc,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAEzC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9D,KAAK,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrE,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7D,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAE3D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,GAAG,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,GAAG,CAAC,OAAO,EAAE,IAAI,KAAK,SAAS,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
const ANALYZABLE_EXT = new Set([".ts", ".tsx", ".mts", ".cts"]);
|
|
5
|
+
const MAX_FILE_SIZE = 500_000;
|
|
6
|
+
/** Extract the exported symbol table from a TS/TSX file. */
|
|
7
|
+
function extractExports(code, filename) {
|
|
8
|
+
const symbols = new Map();
|
|
9
|
+
let sourceFile;
|
|
10
|
+
try {
|
|
11
|
+
sourceFile = ts.createSourceFile(filename, code, ts.ScriptTarget.Latest, true);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return symbols;
|
|
15
|
+
}
|
|
16
|
+
const hasExportModifier = (node) => {
|
|
17
|
+
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
18
|
+
return mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
19
|
+
};
|
|
20
|
+
const isDefault = (node) => {
|
|
21
|
+
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
22
|
+
return mods?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
|
|
23
|
+
};
|
|
24
|
+
const visit = (node) => {
|
|
25
|
+
// export function foo(...) / export class Foo / export const foo
|
|
26
|
+
if ((ts.isFunctionDeclaration(node) ||
|
|
27
|
+
ts.isClassDeclaration(node) ||
|
|
28
|
+
ts.isInterfaceDeclaration(node) ||
|
|
29
|
+
ts.isTypeAliasDeclaration(node) ||
|
|
30
|
+
ts.isEnumDeclaration(node)) &&
|
|
31
|
+
hasExportModifier(node)) {
|
|
32
|
+
const name = isDefault(node) ? "default" : node.name?.getText(sourceFile);
|
|
33
|
+
if (name) {
|
|
34
|
+
symbols.set(name, {
|
|
35
|
+
name,
|
|
36
|
+
kind: ts.isFunctionDeclaration(node)
|
|
37
|
+
? "function"
|
|
38
|
+
: ts.isClassDeclaration(node)
|
|
39
|
+
? "class"
|
|
40
|
+
: ts.isInterfaceDeclaration(node)
|
|
41
|
+
? "interface"
|
|
42
|
+
: ts.isTypeAliasDeclaration(node)
|
|
43
|
+
? "type"
|
|
44
|
+
: "enum",
|
|
45
|
+
signature: ts.isFunctionDeclaration(node) ? signatureOf(node, sourceFile) : undefined,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// export const foo = ...
|
|
50
|
+
if (ts.isVariableStatement(node) && hasExportModifier(node)) {
|
|
51
|
+
for (const decl of node.declarationList.declarations) {
|
|
52
|
+
const name = decl.name.getText(sourceFile);
|
|
53
|
+
symbols.set(name, {
|
|
54
|
+
name,
|
|
55
|
+
kind: "const",
|
|
56
|
+
signature: decl.type?.getText(sourceFile),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// export { a, b } / export { a as b }
|
|
61
|
+
if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
62
|
+
for (const el of node.exportClause.elements) {
|
|
63
|
+
const name = el.name.getText(sourceFile);
|
|
64
|
+
symbols.set(name, { name, kind: "unknown" });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// export default <expr>
|
|
68
|
+
if (ts.isExportAssignment(node) && !node.isExportEquals) {
|
|
69
|
+
symbols.set("default", { name: "default", kind: "unknown" });
|
|
70
|
+
}
|
|
71
|
+
ts.forEachChild(node, visit);
|
|
72
|
+
};
|
|
73
|
+
visit(sourceFile);
|
|
74
|
+
return symbols;
|
|
75
|
+
}
|
|
76
|
+
function signatureOf(fn, sf) {
|
|
77
|
+
const params = fn.parameters.map((p) => {
|
|
78
|
+
const type = p.type ? p.type.getText(sf) : "any";
|
|
79
|
+
return `${p.name.getText(sf)}: ${type}`;
|
|
80
|
+
});
|
|
81
|
+
const ret = fn.type ? fn.type.getText(sf) : "void";
|
|
82
|
+
return `(${params.join(", ")}) => ${ret}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Compare the exported API surface before vs after the fix.
|
|
86
|
+
* `changedFiles` are the files the fix agent touched (project-relative).
|
|
87
|
+
* For each, we compare git HEAD content (pre-upgrade... actually pre-fix)
|
|
88
|
+
* against current working-tree content.
|
|
89
|
+
*/
|
|
90
|
+
export async function analyzeApiChanges(cwd, changedFiles, readBefore) {
|
|
91
|
+
const changes = [];
|
|
92
|
+
for (const file of changedFiles) {
|
|
93
|
+
const ext = file.slice(file.lastIndexOf("."));
|
|
94
|
+
if (!ANALYZABLE_EXT.has(ext))
|
|
95
|
+
continue;
|
|
96
|
+
const abs = join(cwd, file);
|
|
97
|
+
let after;
|
|
98
|
+
try {
|
|
99
|
+
const s = await stat(abs);
|
|
100
|
+
if (s.size > MAX_FILE_SIZE)
|
|
101
|
+
continue;
|
|
102
|
+
after = await readFile(abs, "utf8");
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
continue; // file deleted or unreadable — covered by change-detector
|
|
106
|
+
}
|
|
107
|
+
const before = await readBefore(file);
|
|
108
|
+
if (before === null)
|
|
109
|
+
continue; // new file — nothing to compare
|
|
110
|
+
const beforeExports = extractExports(before, file);
|
|
111
|
+
const afterExports = extractExports(after, file);
|
|
112
|
+
// Removed exports
|
|
113
|
+
for (const [name, sym] of beforeExports) {
|
|
114
|
+
if (!afterExports.has(name)) {
|
|
115
|
+
changes.push({
|
|
116
|
+
kind: sym.kind === "type" || sym.kind === "interface" ? "export-type-removed" : "export-removed",
|
|
117
|
+
file,
|
|
118
|
+
symbol: name,
|
|
119
|
+
detail: `${sym.kind} \`${name}\` was exported before the fix but is no longer exported`,
|
|
120
|
+
severity: name === "default" ? "critical" : "warning",
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
// Signature changes on functions
|
|
125
|
+
const afterSym = afterExports.get(name);
|
|
126
|
+
if (sym.kind === "function" &&
|
|
127
|
+
afterSym.kind === "function" &&
|
|
128
|
+
sym.signature &&
|
|
129
|
+
afterSym.signature &&
|
|
130
|
+
sym.signature !== afterSym.signature) {
|
|
131
|
+
changes.push({
|
|
132
|
+
kind: "signature-changed",
|
|
133
|
+
file,
|
|
134
|
+
symbol: name,
|
|
135
|
+
detail: `\`${name}\` signature changed: ${sym.signature} → ${afterSym.signature}`,
|
|
136
|
+
severity: "warning",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Newly-loosened types: any introduced where a concrete type existed
|
|
141
|
+
const loosened = detectTypeLoosening(before, after, file);
|
|
142
|
+
changes.push(...loosened);
|
|
143
|
+
}
|
|
144
|
+
const hasCritical = changes.some((c) => c.severity === "critical");
|
|
145
|
+
const criticalCount = changes.filter((c) => c.severity === "critical").length;
|
|
146
|
+
const warningCount = changes.filter((c) => c.severity === "warning").length;
|
|
147
|
+
const summary = changes.length === 0
|
|
148
|
+
? "no API surface changes detected"
|
|
149
|
+
: `${criticalCount} critical, ${warningCount} warning(s) — exported API changed`;
|
|
150
|
+
return { changes, hasCritical, summary };
|
|
151
|
+
}
|
|
152
|
+
/** Detect `any` annotations introduced by the fix (silencing the compiler). */
|
|
153
|
+
function detectTypeLoosening(before, after, file) {
|
|
154
|
+
const out = [];
|
|
155
|
+
const countAny = (code) => {
|
|
156
|
+
const m = code.match(/:\s*any\b|<any>|as any\b/g);
|
|
157
|
+
return m ? m.length : 0;
|
|
158
|
+
};
|
|
159
|
+
const beforeCount = countAny(before);
|
|
160
|
+
const afterCount = countAny(after);
|
|
161
|
+
if (afterCount > beforeCount) {
|
|
162
|
+
out.push({
|
|
163
|
+
kind: "type-loosened-to-any",
|
|
164
|
+
file,
|
|
165
|
+
symbol: "(multiple)",
|
|
166
|
+
detail: `fix introduced ${afterCount - beforeCount} new \`any\` annotation(s) — may be masking a real type error`,
|
|
167
|
+
severity: "warning",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
/** Read a file's content from git HEAD (used as the "before" state). */
|
|
173
|
+
export async function readFromGitHead(cwd, path) {
|
|
174
|
+
const { execFile } = await import("node:child_process");
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
execFile("git", ["show", `HEAD:${path}`], { cwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
|
|
177
|
+
if (err)
|
|
178
|
+
resolve(null);
|
|
179
|
+
else
|
|
180
|
+
resolve(stdout);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/** List TS/TSX files in the project (for full-project scans, if needed). */
|
|
185
|
+
export async function listAnalyzableFiles(cwd, root = ".") {
|
|
186
|
+
const IGNORE = new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
|
|
187
|
+
const out = [];
|
|
188
|
+
async function walk(dir) {
|
|
189
|
+
let entries;
|
|
190
|
+
try {
|
|
191
|
+
entries = await readdir(join(cwd, dir), { withFileTypes: true });
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (const e of entries) {
|
|
197
|
+
if (IGNORE.has(e.name))
|
|
198
|
+
continue;
|
|
199
|
+
const rel = dir === "." ? e.name : `${dir}/${e.name}`;
|
|
200
|
+
if (e.isDirectory())
|
|
201
|
+
await walk(rel);
|
|
202
|
+
else {
|
|
203
|
+
const ext = e.name.slice(e.name.lastIndexOf("."));
|
|
204
|
+
if (ANALYZABLE_EXT.has(ext))
|
|
205
|
+
out.push(rel);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
await walk(root);
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=ast-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-analyzer.js","sourceRoot":"","sources":["../../../src/engine/verifiers/ast-analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { analyzeApiChanges } from "./ast-analyzer.js";
|
|
7
|
+
async function withFile(content, before, fn) {
|
|
8
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-ast-"));
|
|
9
|
+
await writeFile(join(dir, "api.ts"), content, "utf8");
|
|
10
|
+
try {
|
|
11
|
+
const result = await analyzeApiChanges(dir, ["api.ts"], async () => before);
|
|
12
|
+
fn(result);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
await rm(dir, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
test("ast-analyzer: detects removed exported function", async () => {
|
|
19
|
+
const before = `export function renderApp(el: Element): void {}
|
|
20
|
+
export function cleanup(): void {}
|
|
21
|
+
`;
|
|
22
|
+
const after = `export function cleanup(): void {}
|
|
23
|
+
`;
|
|
24
|
+
await withFile(after, before, (result) => {
|
|
25
|
+
assert.equal(result.changes.length, 1);
|
|
26
|
+
assert.equal(result.changes[0].kind, "export-removed");
|
|
27
|
+
assert.equal(result.changes[0].symbol, "renderApp");
|
|
28
|
+
assert.equal(result.hasCritical, false); // warning, not critical
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
test("ast-analyzer: removed default export is critical", async () => {
|
|
32
|
+
const before = `export default function App() { return null; }
|
|
33
|
+
`;
|
|
34
|
+
const after = `function App() { return null; }
|
|
35
|
+
`;
|
|
36
|
+
await withFile(after, before, (result) => {
|
|
37
|
+
const removed = result.changes.find((c) => c.kind === "export-removed");
|
|
38
|
+
assert.ok(removed);
|
|
39
|
+
assert.equal(removed.severity, "critical");
|
|
40
|
+
assert.equal(result.hasCritical, true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
test("ast-analyzer: detects function signature changes", async () => {
|
|
44
|
+
const before = `export function fetchData(url: string): Promise<Response> { throw 0; }
|
|
45
|
+
`;
|
|
46
|
+
const after = `export function fetchData(url: string, opts: object): Promise<Response> { throw 0; }
|
|
47
|
+
`;
|
|
48
|
+
await withFile(after, before, (result) => {
|
|
49
|
+
const sig = result.changes.find((c) => c.kind === "signature-changed");
|
|
50
|
+
assert.ok(sig);
|
|
51
|
+
assert.equal(sig.symbol, "fetchData");
|
|
52
|
+
assert.equal(sig.severity, "warning");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
test("ast-analyzer: unchanged exports produce no changes", async () => {
|
|
56
|
+
const code = `export function stable(x: number): number { return x * 2; }
|
|
57
|
+
`;
|
|
58
|
+
await withFile(code, code, (result) => {
|
|
59
|
+
assert.equal(result.changes.length, 0);
|
|
60
|
+
assert.equal(result.hasCritical, false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
test("ast-analyzer: flags newly introduced any annotations", async () => {
|
|
64
|
+
const before = `export function parse(input: string): object { return JSON.parse(input); }
|
|
65
|
+
`;
|
|
66
|
+
const after = `export function parse(input: any): any { return JSON.parse(input); }
|
|
67
|
+
`;
|
|
68
|
+
await withFile(after, before, (result) => {
|
|
69
|
+
const loosened = result.changes.find((c) => c.kind === "type-loosened-to-any");
|
|
70
|
+
assert.ok(loosened);
|
|
71
|
+
assert.equal(loosened.severity, "warning");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
test("ast-analyzer: skips non-TS files", async () => {
|
|
75
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-ast-"));
|
|
76
|
+
await writeFile(join(dir, "style.css"), "body { color: red }", "utf8");
|
|
77
|
+
try {
|
|
78
|
+
const result = await analyzeApiChanges(dir, ["style.css"], async () => "body { color: blue }");
|
|
79
|
+
assert.equal(result.changes.length, 0);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await rm(dir, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
test("ast-analyzer: new file (no before) is skipped silently", async () => {
|
|
86
|
+
const dir = await mkdtemp(join(tmpdir(), "greenbump-ast-"));
|
|
87
|
+
await writeFile(join(dir, "api.ts"), "export const x = 1;", "utf8");
|
|
88
|
+
try {
|
|
89
|
+
const result = await analyzeApiChanges(dir, ["api.ts"], async () => null);
|
|
90
|
+
assert.equal(result.changes.length, 0);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await rm(dir, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=ast-analyzer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-analyzer.test.js","sourceRoot":"","sources":["../../../src/engine/verifiers/ast-analyzer.test.ts"],"names":[],"mappings":"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"}
|