@synchronized-studio/cmsassets-agent 0.1.2 → 0.1.3
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/dist/aiReview-6WOTHK5N.js +9 -0
- package/dist/chunk-E74TGIFQ.js +88 -0
- package/dist/chunk-OAWCNTAC.js +397 -0
- package/dist/chunk-Q6VYUIS7.js +1035 -0
- package/dist/chunk-WDZHZ32V.js +1433 -0
- package/dist/cli.js +79 -42
- package/dist/index.js +4 -3
- package/dist/openaiClient-YGAFYB3X.js +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1433 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WRAP_TEMPLATES,
|
|
3
|
+
buildCmsOptions,
|
|
4
|
+
findInjectionPoints,
|
|
5
|
+
getImportStatement,
|
|
6
|
+
getTransformFunctionName,
|
|
7
|
+
validateDiff
|
|
8
|
+
} from "./chunk-Q6VYUIS7.js";
|
|
9
|
+
import {
|
|
10
|
+
chatCompletion,
|
|
11
|
+
isOpenAiError
|
|
12
|
+
} from "./chunk-E74TGIFQ.js";
|
|
13
|
+
|
|
14
|
+
// src/scanner/index.ts
|
|
15
|
+
import { resolve } from "path";
|
|
16
|
+
|
|
17
|
+
// src/scanner/detectFramework.ts
|
|
18
|
+
import { existsSync, readFileSync } from "fs";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
var FRAMEWORK_SIGNATURES = {
|
|
21
|
+
nuxt: {
|
|
22
|
+
packages: ["nuxt"],
|
|
23
|
+
configFiles: ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mjs"]
|
|
24
|
+
},
|
|
25
|
+
next: {
|
|
26
|
+
packages: ["next"],
|
|
27
|
+
configFiles: ["next.config.js", "next.config.mjs", "next.config.ts"]
|
|
28
|
+
},
|
|
29
|
+
remix: {
|
|
30
|
+
packages: ["@remix-run/react", "@remix-run/node", "@remix-run/dev"],
|
|
31
|
+
configFiles: ["remix.config.js", "remix.config.ts"]
|
|
32
|
+
},
|
|
33
|
+
astro: {
|
|
34
|
+
packages: ["astro"],
|
|
35
|
+
configFiles: ["astro.config.mjs", "astro.config.ts", "astro.config.js"]
|
|
36
|
+
},
|
|
37
|
+
sveltekit: {
|
|
38
|
+
packages: ["@sveltejs/kit"],
|
|
39
|
+
configFiles: ["svelte.config.js", "svelte.config.ts"]
|
|
40
|
+
},
|
|
41
|
+
hono: {
|
|
42
|
+
packages: ["hono"],
|
|
43
|
+
configFiles: []
|
|
44
|
+
},
|
|
45
|
+
fastify: {
|
|
46
|
+
packages: ["fastify"],
|
|
47
|
+
configFiles: []
|
|
48
|
+
},
|
|
49
|
+
express: {
|
|
50
|
+
packages: ["express"],
|
|
51
|
+
configFiles: []
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var DETECTION_ORDER = [
|
|
55
|
+
"nuxt",
|
|
56
|
+
"next",
|
|
57
|
+
"remix",
|
|
58
|
+
"astro",
|
|
59
|
+
"sveltekit",
|
|
60
|
+
"hono",
|
|
61
|
+
"fastify",
|
|
62
|
+
"express"
|
|
63
|
+
];
|
|
64
|
+
function readPackageJson(root) {
|
|
65
|
+
const pkgPath = join(root, "package.json");
|
|
66
|
+
if (!existsSync(pkgPath)) return null;
|
|
67
|
+
try {
|
|
68
|
+
const raw = readFileSync(pkgPath, "utf-8");
|
|
69
|
+
const pkg = JSON.parse(raw);
|
|
70
|
+
return {
|
|
71
|
+
dependencies: pkg.dependencies ?? {},
|
|
72
|
+
devDependencies: pkg.devDependencies ?? {}
|
|
73
|
+
};
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function getVersionFromPkg(allDeps, packages) {
|
|
79
|
+
for (const pkg of packages) {
|
|
80
|
+
if (allDeps[pkg]) return allDeps[pkg];
|
|
81
|
+
}
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
function detectFramework(root) {
|
|
85
|
+
const pkg = readPackageJson(root);
|
|
86
|
+
const allDeps = {
|
|
87
|
+
...pkg?.dependencies ?? {},
|
|
88
|
+
...pkg?.devDependencies ?? {}
|
|
89
|
+
};
|
|
90
|
+
for (const name of DETECTION_ORDER) {
|
|
91
|
+
const sig = FRAMEWORK_SIGNATURES[name];
|
|
92
|
+
const hasPackage = sig.packages.some((p) => p in allDeps);
|
|
93
|
+
const configFile = sig.configFiles.find((f) => existsSync(join(root, f))) ?? null;
|
|
94
|
+
if (hasPackage || configFile) {
|
|
95
|
+
let resolvedName = name;
|
|
96
|
+
const version = getVersionFromPkg(allDeps, sig.packages);
|
|
97
|
+
if (name === "nuxt") {
|
|
98
|
+
const majorVersion = parseMajorVersion(version);
|
|
99
|
+
if (majorVersion !== null && majorVersion < 3) {
|
|
100
|
+
resolvedName = "nuxt2";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
name: resolvedName,
|
|
105
|
+
version,
|
|
106
|
+
configFile
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { name: "unknown", version: "", configFile: null };
|
|
111
|
+
}
|
|
112
|
+
function parseMajorVersion(versionRange) {
|
|
113
|
+
const cleaned = versionRange.replace(/^[\^~>=<\s]+/, "");
|
|
114
|
+
const major = parseInt(cleaned, 10);
|
|
115
|
+
return Number.isFinite(major) ? major : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/scanner/detectCms.ts
|
|
119
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
120
|
+
import { join as join2, relative } from "path";
|
|
121
|
+
import fg from "fast-glob";
|
|
122
|
+
var CMS_SIGNATURES = {
|
|
123
|
+
prismic: {
|
|
124
|
+
packages: [
|
|
125
|
+
"@prismicio/client",
|
|
126
|
+
"@prismicio/vue",
|
|
127
|
+
"@prismicio/react",
|
|
128
|
+
"@prismicio/next",
|
|
129
|
+
"@prismicio/svelte",
|
|
130
|
+
"@nuxtjs/prismic"
|
|
131
|
+
],
|
|
132
|
+
urlPatterns: [
|
|
133
|
+
/https?:\/\/([a-z0-9-]+)\.cdn\.prismic\.io/gi,
|
|
134
|
+
/https?:\/\/images\.prismic\.io\/([a-z0-9-]+)/gi
|
|
135
|
+
],
|
|
136
|
+
paramExtractors: {
|
|
137
|
+
repository: (match) => {
|
|
138
|
+
const m1 = match.match(/https?:\/\/([a-z0-9-]+)\.cdn\.prismic\.io/);
|
|
139
|
+
if (m1) return m1[1];
|
|
140
|
+
const m2 = match.match(/https?:\/\/images\.prismic\.io\/([a-z0-9-]+)/);
|
|
141
|
+
if (m2) return m2[1];
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
contentful: {
|
|
147
|
+
packages: ["contentful"],
|
|
148
|
+
urlPatterns: [
|
|
149
|
+
/https?:\/\/images\.ctfassets\.net\/([a-z0-9]+)/gi,
|
|
150
|
+
/https?:\/\/videos\.ctfassets\.net\/([a-z0-9]+)/gi,
|
|
151
|
+
/https?:\/\/cdn\.contentful\.com\/spaces\/([a-z0-9]+)/gi
|
|
152
|
+
],
|
|
153
|
+
paramExtractors: {
|
|
154
|
+
spaceId: (match) => {
|
|
155
|
+
const m = match.match(/ctfassets\.net\/([a-z0-9]+)/) || match.match(/spaces\/([a-z0-9]+)/);
|
|
156
|
+
return m?.[1] ?? null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
sanity: {
|
|
161
|
+
packages: ["@sanity/client", "next-sanity", "@nuxtjs/sanity", "sanity"],
|
|
162
|
+
urlPatterns: [
|
|
163
|
+
/https?:\/\/cdn\.sanity\.io\/(images|files)\/([a-z0-9]+)\/([a-z0-9-]+)/gi
|
|
164
|
+
],
|
|
165
|
+
paramExtractors: {
|
|
166
|
+
projectId: (match) => {
|
|
167
|
+
const m = match.match(/cdn\.sanity\.io\/(?:images|files)\/([a-z0-9]+)/);
|
|
168
|
+
return m?.[1] ?? null;
|
|
169
|
+
},
|
|
170
|
+
dataset: (match) => {
|
|
171
|
+
const m = match.match(
|
|
172
|
+
/cdn\.sanity\.io\/(?:images|files)\/[a-z0-9]+\/([a-z0-9-]+)/
|
|
173
|
+
);
|
|
174
|
+
return m?.[1] ?? null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
shopify: {
|
|
179
|
+
packages: [
|
|
180
|
+
"@shopify/storefront-api-client",
|
|
181
|
+
"@shopify/hydrogen",
|
|
182
|
+
"@shopify/shopify-api"
|
|
183
|
+
],
|
|
184
|
+
urlPatterns: [
|
|
185
|
+
/https?:\/\/([a-z0-9-]+)\.myshopify\.com\/cdn\//gi,
|
|
186
|
+
/https?:\/\/([a-z0-9-]+)\.myshopify\.com\/api\//gi
|
|
187
|
+
],
|
|
188
|
+
paramExtractors: {
|
|
189
|
+
storeDomain: (match) => {
|
|
190
|
+
const m = match.match(/https?:\/\/([a-z0-9-]+\.myshopify\.com)/);
|
|
191
|
+
return m?.[1] ?? null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
cloudinary: {
|
|
196
|
+
packages: ["cloudinary", "@cloudinary/url-gen", "@cloudinary/react"],
|
|
197
|
+
urlPatterns: [/https?:\/\/res\.cloudinary\.com\/([a-z0-9_-]+)/gi],
|
|
198
|
+
paramExtractors: {
|
|
199
|
+
cloudName: (match) => {
|
|
200
|
+
const m = match.match(/res\.cloudinary\.com\/([a-z0-9_-]+)/);
|
|
201
|
+
return m?.[1] ?? null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
imgix: {
|
|
206
|
+
packages: ["@imgix/js-core", "react-imgix", "vue-imgix"],
|
|
207
|
+
urlPatterns: [/https?:\/\/([a-z0-9-]+)\.imgix\.net/gi],
|
|
208
|
+
paramExtractors: {
|
|
209
|
+
imgixDomain: (match) => {
|
|
210
|
+
const m = match.match(/https?:\/\/([a-z0-9-]+\.imgix\.net)/);
|
|
211
|
+
return m?.[1] ?? null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var CMS_DETECTION_ORDER = [
|
|
217
|
+
"prismic",
|
|
218
|
+
"contentful",
|
|
219
|
+
"sanity",
|
|
220
|
+
"shopify",
|
|
221
|
+
"cloudinary",
|
|
222
|
+
"imgix"
|
|
223
|
+
];
|
|
224
|
+
var SOURCE_GLOBS = [
|
|
225
|
+
"**/*.ts",
|
|
226
|
+
"**/*.tsx",
|
|
227
|
+
"**/*.js",
|
|
228
|
+
"**/*.jsx",
|
|
229
|
+
"**/*.vue",
|
|
230
|
+
"**/*.svelte",
|
|
231
|
+
"**/*.astro",
|
|
232
|
+
"**/*.mjs",
|
|
233
|
+
"**/*.mts"
|
|
234
|
+
];
|
|
235
|
+
var IGNORE_DIRS = [
|
|
236
|
+
"node_modules",
|
|
237
|
+
".nuxt",
|
|
238
|
+
".next",
|
|
239
|
+
".output",
|
|
240
|
+
".svelte-kit",
|
|
241
|
+
"dist",
|
|
242
|
+
"build",
|
|
243
|
+
".git",
|
|
244
|
+
"coverage",
|
|
245
|
+
".cache"
|
|
246
|
+
];
|
|
247
|
+
function detectCms(root) {
|
|
248
|
+
const pkgPath = join2(root, "package.json");
|
|
249
|
+
let allDeps = {};
|
|
250
|
+
if (existsSync2(pkgPath)) {
|
|
251
|
+
try {
|
|
252
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
253
|
+
allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
for (const cmsName of CMS_DETECTION_ORDER) {
|
|
258
|
+
const sig = CMS_SIGNATURES[cmsName];
|
|
259
|
+
if (sig.packages.some((p) => p in allDeps)) {
|
|
260
|
+
const params = extractParamsFromSource(root, cmsName);
|
|
261
|
+
return {
|
|
262
|
+
type: cmsName,
|
|
263
|
+
params,
|
|
264
|
+
detectedFrom: ["package.json"]
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const sourceFiles = fg.sync(SOURCE_GLOBS, {
|
|
269
|
+
cwd: root,
|
|
270
|
+
ignore: IGNORE_DIRS.map((d) => `${d}/**`),
|
|
271
|
+
absolute: true,
|
|
272
|
+
onlyFiles: true,
|
|
273
|
+
deep: 6
|
|
274
|
+
});
|
|
275
|
+
for (const cmsName of CMS_DETECTION_ORDER) {
|
|
276
|
+
const sig = CMS_SIGNATURES[cmsName];
|
|
277
|
+
const matchingFiles = [];
|
|
278
|
+
const foundParams = {};
|
|
279
|
+
for (const file of sourceFiles) {
|
|
280
|
+
let content;
|
|
281
|
+
try {
|
|
282
|
+
content = readFileSync2(file, "utf-8");
|
|
283
|
+
} catch {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
for (const pattern of sig.urlPatterns) {
|
|
287
|
+
pattern.lastIndex = 0;
|
|
288
|
+
const matches = content.match(pattern);
|
|
289
|
+
if (matches && matches.length > 0) {
|
|
290
|
+
matchingFiles.push(relative(root, file));
|
|
291
|
+
for (const m of matches) {
|
|
292
|
+
for (const [paramName, extractor] of Object.entries(sig.paramExtractors)) {
|
|
293
|
+
if (!foundParams[paramName]) {
|
|
294
|
+
const val = extractor(m);
|
|
295
|
+
if (val) foundParams[paramName] = val;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (matchingFiles.length > 0) {
|
|
303
|
+
return {
|
|
304
|
+
type: cmsName,
|
|
305
|
+
params: foundParams,
|
|
306
|
+
detectedFrom: [...new Set(matchingFiles)]
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const genericPatterns = [
|
|
311
|
+
/https?:\/\/[a-z0-9-]+\.s3[.-][a-z0-9-]*\.amazonaws\.com/gi,
|
|
312
|
+
/https?:\/\/storage\.googleapis\.com\/[a-z0-9-]+/gi,
|
|
313
|
+
/https?:\/\/[a-z0-9-]+\.r2\.cloudflarestorage\.com/gi
|
|
314
|
+
];
|
|
315
|
+
for (const file of sourceFiles) {
|
|
316
|
+
let content;
|
|
317
|
+
try {
|
|
318
|
+
content = readFileSync2(file, "utf-8");
|
|
319
|
+
} catch {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
for (const pattern of genericPatterns) {
|
|
323
|
+
pattern.lastIndex = 0;
|
|
324
|
+
const m = content.match(pattern);
|
|
325
|
+
if (m && m.length > 0) {
|
|
326
|
+
return {
|
|
327
|
+
type: "generic",
|
|
328
|
+
params: { originUrl: m[0] },
|
|
329
|
+
detectedFrom: [relative(root, file)]
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return { type: "unknown", params: {}, detectedFrom: [] };
|
|
335
|
+
}
|
|
336
|
+
var CONFIG_FILES = [
|
|
337
|
+
"slicemachine.config.json",
|
|
338
|
+
"prismicio.config.ts",
|
|
339
|
+
"prismicio.config.js",
|
|
340
|
+
"sm.json",
|
|
341
|
+
".slicemachine.config.json",
|
|
342
|
+
"nuxt.config.ts",
|
|
343
|
+
"nuxt.config.js",
|
|
344
|
+
"next.config.js",
|
|
345
|
+
"next.config.mjs",
|
|
346
|
+
"next.config.ts",
|
|
347
|
+
"sanity.config.ts",
|
|
348
|
+
"sanity.config.js",
|
|
349
|
+
"sanity.cli.ts",
|
|
350
|
+
"sanity.cli.js"
|
|
351
|
+
];
|
|
352
|
+
var CONFIG_PARAM_PATTERNS = {
|
|
353
|
+
prismic: [
|
|
354
|
+
{ param: "repository", patterns: [
|
|
355
|
+
/["']?repositoryName["']?\s*[:=]\s*["']([a-z0-9-]+)["']/i,
|
|
356
|
+
/https?:\/\/([a-z0-9-]+)\.cdn\.prismic\.io/i,
|
|
357
|
+
/["']?apiEndpoint["']?\s*[:=]\s*["']https?:\/\/([a-z0-9-]+)\.cdn\.prismic\.io/i
|
|
358
|
+
] }
|
|
359
|
+
],
|
|
360
|
+
contentful: [
|
|
361
|
+
{ param: "spaceId", patterns: [
|
|
362
|
+
/["']?space["']?\s*[:=]\s*["']([a-z0-9]+)["']/i,
|
|
363
|
+
/["']?spaceId["']?\s*[:=]\s*["']([a-z0-9]+)["']/i
|
|
364
|
+
] }
|
|
365
|
+
],
|
|
366
|
+
sanity: [
|
|
367
|
+
{ param: "projectId", patterns: [
|
|
368
|
+
/["']?projectId["']?\s*[:=]\s*["']([a-z0-9]+)["']/i
|
|
369
|
+
] },
|
|
370
|
+
{ param: "dataset", patterns: [
|
|
371
|
+
/["']?dataset["']?\s*[:=]\s*["']([a-z0-9-]+)["']/i
|
|
372
|
+
] }
|
|
373
|
+
],
|
|
374
|
+
shopify: [
|
|
375
|
+
{ param: "storeDomain", patterns: [
|
|
376
|
+
/["']?storeDomain["']?\s*[:=]\s*["']([a-z0-9-]+\.myshopify\.com)["']/i
|
|
377
|
+
] }
|
|
378
|
+
],
|
|
379
|
+
cloudinary: [
|
|
380
|
+
{ param: "cloudName", patterns: [
|
|
381
|
+
/["']?cloud_name["']?\s*[:=]\s*["']([a-z0-9_-]+)["']/i,
|
|
382
|
+
/["']?cloudName["']?\s*[:=]\s*["']([a-z0-9_-]+)["']/i
|
|
383
|
+
] }
|
|
384
|
+
],
|
|
385
|
+
imgix: [
|
|
386
|
+
{ param: "imgixDomain", patterns: [
|
|
387
|
+
/["']?domain["']?\s*[:=]\s*["']([a-z0-9-]+\.imgix\.net)["']/i
|
|
388
|
+
] }
|
|
389
|
+
]
|
|
390
|
+
};
|
|
391
|
+
function extractParamsFromSource(root, cmsName) {
|
|
392
|
+
const sig = CMS_SIGNATURES[cmsName];
|
|
393
|
+
if (!sig) return {};
|
|
394
|
+
const params = {};
|
|
395
|
+
const configPatterns = CONFIG_PARAM_PATTERNS[cmsName];
|
|
396
|
+
if (configPatterns) {
|
|
397
|
+
for (const cfgFile of CONFIG_FILES) {
|
|
398
|
+
const cfgPath = join2(root, cfgFile);
|
|
399
|
+
if (!existsSync2(cfgPath)) continue;
|
|
400
|
+
let content;
|
|
401
|
+
try {
|
|
402
|
+
content = readFileSync2(cfgPath, "utf-8");
|
|
403
|
+
} catch {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
for (const { param, patterns } of configPatterns) {
|
|
407
|
+
if (params[param]) continue;
|
|
408
|
+
for (const pattern of patterns) {
|
|
409
|
+
const m = content.match(pattern);
|
|
410
|
+
if (m?.[1]) {
|
|
411
|
+
params[param] = m[1];
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (Object.keys(params).length === Object.keys(sig.paramExtractors).length) {
|
|
418
|
+
return params;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const sourceFiles = fg.sync(SOURCE_GLOBS, {
|
|
422
|
+
cwd: root,
|
|
423
|
+
ignore: IGNORE_DIRS.map((d) => `${d}/**`),
|
|
424
|
+
absolute: true,
|
|
425
|
+
onlyFiles: true,
|
|
426
|
+
deep: 6
|
|
427
|
+
});
|
|
428
|
+
for (const file of sourceFiles) {
|
|
429
|
+
let content;
|
|
430
|
+
try {
|
|
431
|
+
content = readFileSync2(file, "utf-8");
|
|
432
|
+
} catch {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
for (const pattern of sig.urlPatterns) {
|
|
436
|
+
pattern.lastIndex = 0;
|
|
437
|
+
const matches = content.match(pattern);
|
|
438
|
+
if (!matches) continue;
|
|
439
|
+
for (const m of matches) {
|
|
440
|
+
for (const [paramName, extractor] of Object.entries(sig.paramExtractors)) {
|
|
441
|
+
if (!params[paramName]) {
|
|
442
|
+
const val = extractor(m);
|
|
443
|
+
if (val) params[paramName] = val;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (Object.keys(params).length === Object.keys(sig.paramExtractors).length) {
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return params;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/scanner/detectPackageManager.ts
|
|
456
|
+
import { existsSync as existsSync3 } from "fs";
|
|
457
|
+
import { join as join3 } from "path";
|
|
458
|
+
var LOCKFILE_MAP = [
|
|
459
|
+
["bun.lockb", "bun"],
|
|
460
|
+
["bun.lock", "bun"],
|
|
461
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
462
|
+
["yarn.lock", "yarn"],
|
|
463
|
+
["package-lock.json", "npm"]
|
|
464
|
+
];
|
|
465
|
+
function detectPackageManager(root) {
|
|
466
|
+
for (const [lockfile, pm] of LOCKFILE_MAP) {
|
|
467
|
+
if (existsSync3(join3(root, lockfile))) return pm;
|
|
468
|
+
}
|
|
469
|
+
return "npm";
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/scanner/index.ts
|
|
473
|
+
function scan(projectRoot) {
|
|
474
|
+
const root = resolve(projectRoot ?? process.cwd());
|
|
475
|
+
const framework = detectFramework(root);
|
|
476
|
+
const cms = detectCms(root);
|
|
477
|
+
const packageManager = detectPackageManager(root);
|
|
478
|
+
const injectionPoints = findInjectionPoints(root, framework.name, cms);
|
|
479
|
+
return {
|
|
480
|
+
framework,
|
|
481
|
+
cms,
|
|
482
|
+
injectionPoints,
|
|
483
|
+
packageManager,
|
|
484
|
+
projectRoot: root
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/planner/index.ts
|
|
489
|
+
import { existsSync as existsSync4 } from "fs";
|
|
490
|
+
import { join as join4 } from "path";
|
|
491
|
+
function resolveInstallCommand(scan2) {
|
|
492
|
+
const pkg = "@synchronized-studio/response-transformer";
|
|
493
|
+
switch (scan2.packageManager) {
|
|
494
|
+
case "pnpm":
|
|
495
|
+
return `pnpm add ${pkg}`;
|
|
496
|
+
case "yarn":
|
|
497
|
+
return `yarn add ${pkg}`;
|
|
498
|
+
case "bun":
|
|
499
|
+
return `bun add ${pkg}`;
|
|
500
|
+
default:
|
|
501
|
+
return `npm install ${pkg}`;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function resolveEnvFiles(root) {
|
|
505
|
+
const candidates = [".env", ".env.local", ".env.example", ".env.development"];
|
|
506
|
+
return candidates.filter((f) => existsSync4(join4(root, f)));
|
|
507
|
+
}
|
|
508
|
+
function createPlan(scan2) {
|
|
509
|
+
const patches = [];
|
|
510
|
+
for (const candidate of scan2.injectionPoints) {
|
|
511
|
+
const template = WRAP_TEMPLATES[candidate.type];
|
|
512
|
+
if (!template) continue;
|
|
513
|
+
const importStatement = getImportStatement(scan2.cms.type);
|
|
514
|
+
const originalCode = candidate.targetCode;
|
|
515
|
+
if (!originalCode) continue;
|
|
516
|
+
if (/\.push\s*\(/.test(originalCode.trim())) continue;
|
|
517
|
+
const transformedCode = template.transform(
|
|
518
|
+
originalCode,
|
|
519
|
+
scan2.cms.type,
|
|
520
|
+
scan2.cms.params
|
|
521
|
+
);
|
|
522
|
+
if (transformedCode === originalCode) continue;
|
|
523
|
+
patches.push({
|
|
524
|
+
filePath: candidate.filePath,
|
|
525
|
+
description: template.description(scan2.cms.type),
|
|
526
|
+
importToAdd: importStatement,
|
|
527
|
+
wrapTarget: {
|
|
528
|
+
line: candidate.line,
|
|
529
|
+
originalCode,
|
|
530
|
+
transformedCode
|
|
531
|
+
},
|
|
532
|
+
confidence: candidate.confidence,
|
|
533
|
+
reasons: candidate.reasons
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
const envFiles = resolveEnvFiles(scan2.projectRoot);
|
|
537
|
+
return {
|
|
538
|
+
schemaVersion: "1.0",
|
|
539
|
+
scan: scan2,
|
|
540
|
+
install: {
|
|
541
|
+
package: "@synchronized-studio/response-transformer",
|
|
542
|
+
command: resolveInstallCommand(scan2)
|
|
543
|
+
},
|
|
544
|
+
env: {
|
|
545
|
+
key: "CMS_ASSETS_URL",
|
|
546
|
+
placeholder: "https://YOUR-SLUG.cmsassets.com",
|
|
547
|
+
files: envFiles
|
|
548
|
+
},
|
|
549
|
+
patches,
|
|
550
|
+
policies: {
|
|
551
|
+
maxFilesAutoApply: 20,
|
|
552
|
+
allowLlmFallback: false,
|
|
553
|
+
llmFallbackForAll: false,
|
|
554
|
+
llmOnly: false,
|
|
555
|
+
verifyProfile: "quick"
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/patcher/index.ts
|
|
561
|
+
import { appendFileSync, existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
562
|
+
import { join as join8 } from "path";
|
|
563
|
+
import consola from "consola";
|
|
564
|
+
|
|
565
|
+
// src/patcher/astPatcher.ts
|
|
566
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
567
|
+
import { readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
568
|
+
import { join as join5, extname } from "path";
|
|
569
|
+
function applyAstPatch(root, patch, dryRun) {
|
|
570
|
+
const absPath = join5(root, patch.filePath);
|
|
571
|
+
const ext = extname(patch.filePath);
|
|
572
|
+
if ([".vue", ".svelte", ".astro"].includes(ext)) {
|
|
573
|
+
return applyStringPatch(root, patch, dryRun);
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
const project = new Project({ useInMemoryFileSystem: false });
|
|
577
|
+
const sourceFile = project.addSourceFileAtPath(absPath);
|
|
578
|
+
const originalText = sourceFile.getFullText();
|
|
579
|
+
const existingImports = sourceFile.getImportDeclarations();
|
|
580
|
+
const hasImport = existingImports.some(
|
|
581
|
+
(i) => i.getModuleSpecifierValue() === "@synchronized-studio/response-transformer"
|
|
582
|
+
);
|
|
583
|
+
if (!hasImport && patch.importToAdd) {
|
|
584
|
+
const importMatch = patch.importToAdd.match(
|
|
585
|
+
/import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/
|
|
586
|
+
);
|
|
587
|
+
if (importMatch) {
|
|
588
|
+
const namedImports = importMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
589
|
+
sourceFile.addImportDeclaration({
|
|
590
|
+
moduleSpecifier: importMatch[2],
|
|
591
|
+
namedImports
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const targetLine = patch.wrapTarget.line;
|
|
596
|
+
const original = patch.wrapTarget.originalCode.trim();
|
|
597
|
+
const transformed = patch.wrapTarget.transformedCode.trim();
|
|
598
|
+
if (original && transformed && original !== transformed) {
|
|
599
|
+
let patched = false;
|
|
600
|
+
const returnStatements = sourceFile.getDescendantsOfKind(
|
|
601
|
+
SyntaxKind.ReturnStatement
|
|
602
|
+
);
|
|
603
|
+
for (const ret of returnStatements) {
|
|
604
|
+
const retLine = ret.getStartLineNumber();
|
|
605
|
+
if (retLine !== targetLine) continue;
|
|
606
|
+
const retText = ret.getText().trim();
|
|
607
|
+
if (retText.includes(original) || original.startsWith("return") && retText.startsWith("return") && retText.replace(/^return\s+/, "").replace(/;$/, "").replace(/\s+/g, " ").trim() === original.replace(/^return\s+/, "").replace(/;$/, "").replace(/\s+/g, " ").trim()) {
|
|
608
|
+
ret.replaceWithText(transformed);
|
|
609
|
+
patched = true;
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (!patched) {
|
|
614
|
+
const expressions = sourceFile.getDescendantsOfKind(
|
|
615
|
+
SyntaxKind.ExpressionStatement
|
|
616
|
+
);
|
|
617
|
+
for (const expr of expressions) {
|
|
618
|
+
const exprLine = expr.getStartLineNumber();
|
|
619
|
+
if (exprLine !== targetLine) continue;
|
|
620
|
+
const exprText = expr.getText().trim();
|
|
621
|
+
if (exprText.includes("res.json") || exprText.includes("c.json")) {
|
|
622
|
+
expr.replaceWithText(transformed);
|
|
623
|
+
patched = true;
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (!patched) {
|
|
629
|
+
for (const ret of returnStatements) {
|
|
630
|
+
const retLine = ret.getStartLineNumber();
|
|
631
|
+
if (Math.abs(retLine - targetLine) > 1) continue;
|
|
632
|
+
const retText = ret.getText().trim();
|
|
633
|
+
if (original.startsWith("return") && retText.startsWith("return")) {
|
|
634
|
+
const normalizeReturnExpr = (value) => value.replace(/^return\s+/, "").replace(/;$/, "").replace(/\s+/g, " ").trim();
|
|
635
|
+
if (normalizeReturnExpr(retText) !== normalizeReturnExpr(original)) continue;
|
|
636
|
+
ret.replaceWithText(transformed);
|
|
637
|
+
patched = true;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (!patched) {
|
|
643
|
+
const currentText = sourceFile.getFullText();
|
|
644
|
+
const lines = currentText.split("\n");
|
|
645
|
+
const targetLineContent = lines[targetLine - 1];
|
|
646
|
+
if (targetLineContent && targetLineContent.trim() === original) {
|
|
647
|
+
const indent = targetLineContent.match(/^(\s*)/)?.[1] ?? "";
|
|
648
|
+
const newContent = currentText.replace(
|
|
649
|
+
targetLineContent,
|
|
650
|
+
indent + transformed
|
|
651
|
+
);
|
|
652
|
+
if (newContent !== currentText) {
|
|
653
|
+
sourceFile.replaceWithText(newContent);
|
|
654
|
+
patched = true;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (!patched) {
|
|
659
|
+
const currentText = sourceFile.getFullText();
|
|
660
|
+
if (original.startsWith("return ") && transformed.startsWith("return ")) {
|
|
661
|
+
const lines = currentText.split("\n");
|
|
662
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
663
|
+
const line = lines[idx];
|
|
664
|
+
const trimmed = line.trim();
|
|
665
|
+
if (trimmed === original || trimmed === original.replace(/;\s*$/, "")) {
|
|
666
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? "";
|
|
667
|
+
lines[idx] = indent + transformed;
|
|
668
|
+
sourceFile.replaceWithText(lines.join("\n"));
|
|
669
|
+
patched = true;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (!patched && currentText.includes(original)) {
|
|
675
|
+
const newContent = currentText.replace(original, transformed);
|
|
676
|
+
if (newContent !== currentText) {
|
|
677
|
+
sourceFile.replaceWithText(newContent);
|
|
678
|
+
patched = true;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (!patched) {
|
|
683
|
+
return { applied: false, reason: `Could not locate target code at line ${targetLine}` };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const newText = sourceFile.getFullText();
|
|
687
|
+
if (newText === originalText) {
|
|
688
|
+
return { applied: false, reason: "No changes needed (idempotent)" };
|
|
689
|
+
}
|
|
690
|
+
if (!dryRun) {
|
|
691
|
+
sourceFile.saveSync();
|
|
692
|
+
}
|
|
693
|
+
return { applied: true };
|
|
694
|
+
} catch (err) {
|
|
695
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
696
|
+
return { applied: false, reason: `AST patch failed: ${msg}` };
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function applyStringPatch(root, patch, dryRun) {
|
|
700
|
+
const absPath = join5(root, patch.filePath);
|
|
701
|
+
let content;
|
|
702
|
+
try {
|
|
703
|
+
content = readFileSync3(absPath, "utf-8");
|
|
704
|
+
} catch (err) {
|
|
705
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
706
|
+
return { applied: false, reason: `Cannot read file: ${msg}` };
|
|
707
|
+
}
|
|
708
|
+
const original = content;
|
|
709
|
+
const { originalCode, transformedCode } = patch.wrapTarget;
|
|
710
|
+
if (patch.importToAdd && !content.includes("@synchronized-studio/response-transformer")) {
|
|
711
|
+
const scriptMatch = content.match(
|
|
712
|
+
/(<script[^>]*>)\s*\n/
|
|
713
|
+
);
|
|
714
|
+
if (scriptMatch) {
|
|
715
|
+
const insertAfter = scriptMatch[0];
|
|
716
|
+
content = content.replace(
|
|
717
|
+
insertAfter,
|
|
718
|
+
`${insertAfter}${patch.importToAdd}
|
|
719
|
+
`
|
|
720
|
+
);
|
|
721
|
+
} else if (patch.filePath.endsWith(".astro")) {
|
|
722
|
+
const fmMatch = content.match(/^(---\n)/);
|
|
723
|
+
if (fmMatch) {
|
|
724
|
+
content = content.replace(
|
|
725
|
+
"---\n",
|
|
726
|
+
`---
|
|
727
|
+
${patch.importToAdd}
|
|
728
|
+
`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (originalCode && transformedCode && originalCode !== transformedCode) {
|
|
734
|
+
if (content.includes(originalCode.trim())) {
|
|
735
|
+
content = content.replace(originalCode.trim(), transformedCode.trim());
|
|
736
|
+
} else {
|
|
737
|
+
return {
|
|
738
|
+
applied: false,
|
|
739
|
+
reason: "Original code not found in file (string match)"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (content === original) {
|
|
744
|
+
return { applied: false, reason: "No changes needed (idempotent)" };
|
|
745
|
+
}
|
|
746
|
+
if (!dryRun) {
|
|
747
|
+
writeFileSync(absPath, content, "utf-8");
|
|
748
|
+
}
|
|
749
|
+
return { applied: true };
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/patcher/llmFallback.ts
|
|
753
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
754
|
+
import { join as join6 } from "path";
|
|
755
|
+
function buildPrompt(fileContent, filePath, cms, framework) {
|
|
756
|
+
const fn = getTransformFunctionName(cms.type);
|
|
757
|
+
const opts = buildCmsOptions(cms.type, cms.params);
|
|
758
|
+
return `You are a code integration agent. Given the following file, add the
|
|
759
|
+
@synchronized-studio/response-transformer to transform CMS asset URLs.
|
|
760
|
+
|
|
761
|
+
CMS: ${cms.type}
|
|
762
|
+
Transform function: ${fn}
|
|
763
|
+
Options: ${opts}
|
|
764
|
+
Framework: ${framework}
|
|
765
|
+
|
|
766
|
+
File: ${filePath}
|
|
767
|
+
\`\`\`
|
|
768
|
+
${fileContent}
|
|
769
|
+
\`\`\`
|
|
770
|
+
|
|
771
|
+
Return ONLY a unified diff that:
|
|
772
|
+
1. Adds the import: import { ${fn} } from '@synchronized-studio/response-transformer'
|
|
773
|
+
2. Wraps the CMS data return/assignment with ${fn}(data, ${opts})
|
|
774
|
+
3. Does NOT change any other code
|
|
775
|
+
|
|
776
|
+
Output ONLY the diff, nothing else.`;
|
|
777
|
+
}
|
|
778
|
+
async function applyLlmPatch(root, patch, cms, framework) {
|
|
779
|
+
const absPath = join6(root, patch.filePath);
|
|
780
|
+
let fileContent;
|
|
781
|
+
try {
|
|
782
|
+
fileContent = readFileSync4(absPath, "utf-8");
|
|
783
|
+
} catch (err) {
|
|
784
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
785
|
+
return { applied: false, reason: `Cannot read file: ${msg}`, requiresReview: true };
|
|
786
|
+
}
|
|
787
|
+
const prompt = buildPrompt(fileContent, patch.filePath, cms, framework);
|
|
788
|
+
const result = await chatCompletion(prompt, { model: "gpt-4o-mini", maxTokens: 2e3 });
|
|
789
|
+
if (isOpenAiError(result)) {
|
|
790
|
+
return { applied: false, reason: result.error, requiresReview: true };
|
|
791
|
+
}
|
|
792
|
+
const diff = result.content;
|
|
793
|
+
if (!diff) {
|
|
794
|
+
return { applied: false, reason: "LLM returned empty response", requiresReview: true };
|
|
795
|
+
}
|
|
796
|
+
const validation = validateDiff(diff, [patch.filePath]);
|
|
797
|
+
if (!validation.valid) {
|
|
798
|
+
return {
|
|
799
|
+
applied: false,
|
|
800
|
+
reason: `LLM diff failed validation: ${validation.errors.join("; ")}`,
|
|
801
|
+
diff,
|
|
802
|
+
requiresReview: true
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
applied: false,
|
|
807
|
+
reason: "LLM patch generated, requires manual review before applying",
|
|
808
|
+
diff,
|
|
809
|
+
requiresReview: true
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// src/patcher/idempotency.ts
|
|
814
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
815
|
+
import { join as join7 } from "path";
|
|
816
|
+
var TRANSFORMER_IMPORTS = [
|
|
817
|
+
"@synchronized-studio/response-transformer",
|
|
818
|
+
"transformCmsAssetUrls",
|
|
819
|
+
"transformPrismicAssetUrls",
|
|
820
|
+
"transformContentfulAssetUrls",
|
|
821
|
+
"transformSanityAssetUrls",
|
|
822
|
+
"transformShopifyAssetUrls",
|
|
823
|
+
"transformCloudinaryAssetUrls",
|
|
824
|
+
"transformImgixAssetUrls",
|
|
825
|
+
"transformGenericAssetUrls"
|
|
826
|
+
];
|
|
827
|
+
function isAlreadyTransformed(root, filePath) {
|
|
828
|
+
const absPath = join7(root, filePath);
|
|
829
|
+
let content;
|
|
830
|
+
try {
|
|
831
|
+
content = readFileSync5(absPath, "utf-8");
|
|
832
|
+
} catch {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
return TRANSFORMER_IMPORTS.some((sig) => content.includes(sig));
|
|
836
|
+
}
|
|
837
|
+
function isTestOrFixtureFile(filePath) {
|
|
838
|
+
return /\.(test|spec)\.[jt]sx?$/.test(filePath) || /__tests__\//.test(filePath) || /(^|\/)tests?\//.test(filePath) || /(^|\/)fixtures?\//.test(filePath) || /(^|\/)mocks?\//.test(filePath) || /\.stories\.[jt]sx?$/.test(filePath);
|
|
839
|
+
}
|
|
840
|
+
function isGeneratedFile(filePath) {
|
|
841
|
+
return /\.generated\.[jt]sx?$/.test(filePath) || /\.g\.[jt]sx?$/.test(filePath) || /(^|\/)\.nuxt\//.test(filePath) || /(^|\/)\.next\//.test(filePath) || /(^|\/)\.svelte-kit\//.test(filePath) || /(^|\/)dist\//.test(filePath) || /(^|\/)build\//.test(filePath);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/patcher/index.ts
|
|
845
|
+
async function applyPlan(plan, opts = {}) {
|
|
846
|
+
const startTime = Date.now();
|
|
847
|
+
const root = plan.scan.projectRoot;
|
|
848
|
+
const dryRun = opts.dryRun ?? false;
|
|
849
|
+
const includeTests = opts.includeTests ?? false;
|
|
850
|
+
const maxFiles = opts.maxFiles ?? plan.policies.maxFilesAutoApply;
|
|
851
|
+
const files = [];
|
|
852
|
+
let appliedCount = 0;
|
|
853
|
+
const patchedFiles = /* @__PURE__ */ new Set();
|
|
854
|
+
const alreadyTransformedAtStart = new Set(
|
|
855
|
+
plan.patches.map((patch) => patch.filePath).filter((filePath, index, all) => all.indexOf(filePath) === index).filter((filePath) => isAlreadyTransformed(root, filePath))
|
|
856
|
+
);
|
|
857
|
+
for (const patch of plan.patches) {
|
|
858
|
+
const fileStart = Date.now();
|
|
859
|
+
if (alreadyTransformedAtStart.has(patch.filePath)) {
|
|
860
|
+
files.push({
|
|
861
|
+
filePath: patch.filePath,
|
|
862
|
+
applied: false,
|
|
863
|
+
method: "skipped",
|
|
864
|
+
reason: "Transformer already present in file",
|
|
865
|
+
durationMs: Date.now() - fileStart
|
|
866
|
+
});
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (!includeTests && isTestOrFixtureFile(patch.filePath)) {
|
|
870
|
+
files.push({
|
|
871
|
+
filePath: patch.filePath,
|
|
872
|
+
applied: false,
|
|
873
|
+
method: "skipped",
|
|
874
|
+
reason: "Test/fixture file excluded (use --include-tests to include)",
|
|
875
|
+
durationMs: Date.now() - fileStart
|
|
876
|
+
});
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
if (isGeneratedFile(patch.filePath)) {
|
|
880
|
+
files.push({
|
|
881
|
+
filePath: patch.filePath,
|
|
882
|
+
applied: false,
|
|
883
|
+
method: "skipped",
|
|
884
|
+
reason: "Generated file excluded",
|
|
885
|
+
durationMs: Date.now() - fileStart
|
|
886
|
+
});
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (!patchedFiles.has(patch.filePath) && appliedCount >= maxFiles) {
|
|
890
|
+
files.push({
|
|
891
|
+
filePath: patch.filePath,
|
|
892
|
+
applied: false,
|
|
893
|
+
method: "skipped",
|
|
894
|
+
reason: `Max files threshold reached (${maxFiles})`,
|
|
895
|
+
durationMs: Date.now() - fileStart
|
|
896
|
+
});
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (plan.policies.llmOnly && plan.policies.allowLlmFallback) {
|
|
900
|
+
consola.info(`LLM-only mode: ${patch.filePath}...`);
|
|
901
|
+
const llmResult = await applyLlmPatch(
|
|
902
|
+
root,
|
|
903
|
+
patch,
|
|
904
|
+
plan.scan.cms,
|
|
905
|
+
plan.scan.framework.name
|
|
906
|
+
);
|
|
907
|
+
files.push({
|
|
908
|
+
filePath: patch.filePath,
|
|
909
|
+
applied: llmResult.applied,
|
|
910
|
+
method: "llm",
|
|
911
|
+
reason: llmResult.reason,
|
|
912
|
+
durationMs: Date.now() - fileStart
|
|
913
|
+
});
|
|
914
|
+
if (llmResult.applied && !patchedFiles.has(patch.filePath)) {
|
|
915
|
+
patchedFiles.add(patch.filePath);
|
|
916
|
+
appliedCount++;
|
|
917
|
+
}
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const astResult = applyAstPatch(root, patch, dryRun);
|
|
921
|
+
if (astResult.applied) {
|
|
922
|
+
if (!patchedFiles.has(patch.filePath)) {
|
|
923
|
+
patchedFiles.add(patch.filePath);
|
|
924
|
+
appliedCount++;
|
|
925
|
+
}
|
|
926
|
+
files.push({
|
|
927
|
+
filePath: patch.filePath,
|
|
928
|
+
applied: true,
|
|
929
|
+
method: "ast",
|
|
930
|
+
durationMs: Date.now() - fileStart
|
|
931
|
+
});
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
const shouldTryLlm = plan.policies.allowLlmFallback && (plan.policies.llmFallbackForAll || patch.confidence === "low");
|
|
935
|
+
if (shouldTryLlm) {
|
|
936
|
+
consola.info(`AST failed for ${patch.filePath}, trying LLM fallback...`);
|
|
937
|
+
const llmResult = await applyLlmPatch(
|
|
938
|
+
root,
|
|
939
|
+
patch,
|
|
940
|
+
plan.scan.cms,
|
|
941
|
+
plan.scan.framework.name
|
|
942
|
+
);
|
|
943
|
+
files.push({
|
|
944
|
+
filePath: patch.filePath,
|
|
945
|
+
applied: llmResult.applied,
|
|
946
|
+
method: "llm",
|
|
947
|
+
reason: llmResult.reason,
|
|
948
|
+
durationMs: Date.now() - fileStart
|
|
949
|
+
});
|
|
950
|
+
if (llmResult.applied && !patchedFiles.has(patch.filePath)) {
|
|
951
|
+
patchedFiles.add(patch.filePath);
|
|
952
|
+
appliedCount++;
|
|
953
|
+
}
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
files.push({
|
|
957
|
+
filePath: patch.filePath,
|
|
958
|
+
applied: false,
|
|
959
|
+
method: "skipped",
|
|
960
|
+
reason: astResult.reason ?? "AST patch could not be applied",
|
|
961
|
+
durationMs: Date.now() - fileStart
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
let envUpdated = false;
|
|
965
|
+
if (!dryRun) {
|
|
966
|
+
for (const envFile of plan.env.files) {
|
|
967
|
+
const envPath = join8(root, envFile);
|
|
968
|
+
if (existsSync5(envPath)) {
|
|
969
|
+
const content = readFileSync6(envPath, "utf-8");
|
|
970
|
+
if (!content.includes(plan.env.key)) {
|
|
971
|
+
appendFileSync(envPath, `
|
|
972
|
+
${plan.env.key}=${plan.env.placeholder}
|
|
973
|
+
`);
|
|
974
|
+
envUpdated = true;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (plan.env.files.length === 0) {
|
|
979
|
+
const envExamplePath = join8(root, ".env.example");
|
|
980
|
+
writeFileSync2(
|
|
981
|
+
envExamplePath,
|
|
982
|
+
`${plan.env.key}=${plan.env.placeholder}
|
|
983
|
+
`,
|
|
984
|
+
"utf-8"
|
|
985
|
+
);
|
|
986
|
+
envUpdated = true;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
schemaVersion: "1.0",
|
|
991
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
992
|
+
projectRoot: root,
|
|
993
|
+
gitBranch: null,
|
|
994
|
+
gitCommit: null,
|
|
995
|
+
installed: false,
|
|
996
|
+
envUpdated,
|
|
997
|
+
files,
|
|
998
|
+
totalDurationMs: Date.now() - startTime
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/types.ts
|
|
1003
|
+
import { z } from "zod";
|
|
1004
|
+
var FrameworkName = z.enum([
|
|
1005
|
+
"nuxt",
|
|
1006
|
+
"nuxt2",
|
|
1007
|
+
"next",
|
|
1008
|
+
"remix",
|
|
1009
|
+
"astro",
|
|
1010
|
+
"sveltekit",
|
|
1011
|
+
"express",
|
|
1012
|
+
"hono",
|
|
1013
|
+
"fastify",
|
|
1014
|
+
"unknown"
|
|
1015
|
+
]);
|
|
1016
|
+
var CmsType = z.enum([
|
|
1017
|
+
"prismic",
|
|
1018
|
+
"contentful",
|
|
1019
|
+
"sanity",
|
|
1020
|
+
"shopify",
|
|
1021
|
+
"cloudinary",
|
|
1022
|
+
"imgix",
|
|
1023
|
+
"generic",
|
|
1024
|
+
"unknown"
|
|
1025
|
+
]);
|
|
1026
|
+
var PackageManager = z.enum(["npm", "yarn", "pnpm", "bun"]);
|
|
1027
|
+
var Confidence = z.enum(["high", "medium", "low"]);
|
|
1028
|
+
var InjectionType = z.enum([
|
|
1029
|
+
"return",
|
|
1030
|
+
"res.json",
|
|
1031
|
+
"assignment",
|
|
1032
|
+
"useFetch-transform",
|
|
1033
|
+
"useAsyncData-transform",
|
|
1034
|
+
"loader-return",
|
|
1035
|
+
"getServerSideProps-return",
|
|
1036
|
+
"getStaticProps-return",
|
|
1037
|
+
"load-return",
|
|
1038
|
+
"frontmatter-assignment",
|
|
1039
|
+
"asyncData-return",
|
|
1040
|
+
"vuex-action-return"
|
|
1041
|
+
]);
|
|
1042
|
+
var VerifyProfile = z.enum(["quick", "full"]);
|
|
1043
|
+
var FrameworkInfo = z.object({
|
|
1044
|
+
name: FrameworkName,
|
|
1045
|
+
version: z.string(),
|
|
1046
|
+
configFile: z.string().nullable()
|
|
1047
|
+
});
|
|
1048
|
+
var CmsInfo = z.object({
|
|
1049
|
+
type: CmsType,
|
|
1050
|
+
params: z.record(z.string()),
|
|
1051
|
+
detectedFrom: z.array(z.string())
|
|
1052
|
+
});
|
|
1053
|
+
var InjectionCandidate = z.object({
|
|
1054
|
+
filePath: z.string(),
|
|
1055
|
+
line: z.number(),
|
|
1056
|
+
type: InjectionType,
|
|
1057
|
+
score: z.number().min(0).max(100),
|
|
1058
|
+
confidence: Confidence,
|
|
1059
|
+
targetCode: z.string(),
|
|
1060
|
+
context: z.string(),
|
|
1061
|
+
reasons: z.array(z.string())
|
|
1062
|
+
});
|
|
1063
|
+
var ScanResult = z.object({
|
|
1064
|
+
framework: FrameworkInfo,
|
|
1065
|
+
cms: CmsInfo,
|
|
1066
|
+
injectionPoints: z.array(InjectionCandidate),
|
|
1067
|
+
packageManager: PackageManager,
|
|
1068
|
+
projectRoot: z.string()
|
|
1069
|
+
});
|
|
1070
|
+
var FilePatch = z.object({
|
|
1071
|
+
filePath: z.string(),
|
|
1072
|
+
description: z.string(),
|
|
1073
|
+
importToAdd: z.string(),
|
|
1074
|
+
wrapTarget: z.object({
|
|
1075
|
+
line: z.number(),
|
|
1076
|
+
originalCode: z.string(),
|
|
1077
|
+
transformedCode: z.string()
|
|
1078
|
+
}),
|
|
1079
|
+
confidence: Confidence,
|
|
1080
|
+
reasons: z.array(z.string())
|
|
1081
|
+
});
|
|
1082
|
+
var PatchPlan = z.object({
|
|
1083
|
+
schemaVersion: z.literal("1.0"),
|
|
1084
|
+
scan: ScanResult,
|
|
1085
|
+
install: z.object({
|
|
1086
|
+
package: z.literal("@synchronized-studio/response-transformer"),
|
|
1087
|
+
command: z.string()
|
|
1088
|
+
}),
|
|
1089
|
+
env: z.object({
|
|
1090
|
+
key: z.literal("CMS_ASSETS_URL"),
|
|
1091
|
+
placeholder: z.string(),
|
|
1092
|
+
files: z.array(z.string())
|
|
1093
|
+
}),
|
|
1094
|
+
patches: z.array(FilePatch),
|
|
1095
|
+
policies: z.object({
|
|
1096
|
+
maxFilesAutoApply: z.number().default(20),
|
|
1097
|
+
allowLlmFallback: z.boolean().default(false),
|
|
1098
|
+
/** When true, try LLM for any failed AST patch (not just low confidence). Useful for testing. */
|
|
1099
|
+
llmFallbackForAll: z.boolean().default(false),
|
|
1100
|
+
/** When true, skip AST entirely and use LLM for all patches (testing only). */
|
|
1101
|
+
llmOnly: z.boolean().default(false),
|
|
1102
|
+
verifyProfile: VerifyProfile.default("quick")
|
|
1103
|
+
})
|
|
1104
|
+
});
|
|
1105
|
+
var PatchedFileReport = z.object({
|
|
1106
|
+
filePath: z.string(),
|
|
1107
|
+
applied: z.boolean(),
|
|
1108
|
+
method: z.enum(["ast", "llm", "skipped"]),
|
|
1109
|
+
reason: z.string().optional(),
|
|
1110
|
+
durationMs: z.number()
|
|
1111
|
+
});
|
|
1112
|
+
var ApplyReport = z.object({
|
|
1113
|
+
schemaVersion: z.literal("1.0"),
|
|
1114
|
+
timestamp: z.string(),
|
|
1115
|
+
projectRoot: z.string(),
|
|
1116
|
+
gitBranch: z.string().nullable(),
|
|
1117
|
+
gitCommit: z.string().nullable(),
|
|
1118
|
+
installed: z.boolean(),
|
|
1119
|
+
envUpdated: z.boolean(),
|
|
1120
|
+
files: z.array(PatchedFileReport),
|
|
1121
|
+
totalDurationMs: z.number()
|
|
1122
|
+
});
|
|
1123
|
+
var VerifyReport = z.object({
|
|
1124
|
+
schemaVersion: z.literal("1.0"),
|
|
1125
|
+
profile: VerifyProfile,
|
|
1126
|
+
lintPassed: z.boolean().nullable(),
|
|
1127
|
+
buildPassed: z.boolean().nullable(),
|
|
1128
|
+
testsPassed: z.boolean().nullable(),
|
|
1129
|
+
patchedFiles: z.array(z.string()),
|
|
1130
|
+
warnings: z.array(z.string()),
|
|
1131
|
+
durationMs: z.number()
|
|
1132
|
+
});
|
|
1133
|
+
var AiFileResult = z.object({
|
|
1134
|
+
filePath: z.string(),
|
|
1135
|
+
passed: z.boolean(),
|
|
1136
|
+
issues: z.array(z.string()),
|
|
1137
|
+
fixAttempted: z.boolean(),
|
|
1138
|
+
fixApplied: z.boolean(),
|
|
1139
|
+
iterations: z.number()
|
|
1140
|
+
});
|
|
1141
|
+
var AiReviewReport = z.object({
|
|
1142
|
+
schemaVersion: z.literal("1.0"),
|
|
1143
|
+
filesReviewed: z.number(),
|
|
1144
|
+
filesPassed: z.number(),
|
|
1145
|
+
filesFixed: z.number(),
|
|
1146
|
+
filesFailed: z.number(),
|
|
1147
|
+
results: z.array(AiFileResult),
|
|
1148
|
+
totalDurationMs: z.number(),
|
|
1149
|
+
tokensUsed: z.number()
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
// src/reporting/index.ts
|
|
1153
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync } from "fs";
|
|
1154
|
+
import { join as join9 } from "path";
|
|
1155
|
+
import consola2 from "consola";
|
|
1156
|
+
function createReport(parts) {
|
|
1157
|
+
return {
|
|
1158
|
+
version: "1.0",
|
|
1159
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1160
|
+
...parts
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function saveReport(root, report) {
|
|
1164
|
+
const dir = join9(root, ".cmsassets-agent");
|
|
1165
|
+
if (!existsSync6(dir)) {
|
|
1166
|
+
mkdirSync(dir, { recursive: true });
|
|
1167
|
+
}
|
|
1168
|
+
const filename = `report-${Date.now()}.json`;
|
|
1169
|
+
const filePath = join9(dir, filename);
|
|
1170
|
+
writeFileSync3(filePath, JSON.stringify(report, null, 2), "utf-8");
|
|
1171
|
+
consola2.info(`Report saved to ${filePath}`);
|
|
1172
|
+
return filePath;
|
|
1173
|
+
}
|
|
1174
|
+
function savePlanFile(root, plan) {
|
|
1175
|
+
const filePath = join9(root, "cmsassets-agent.plan.json");
|
|
1176
|
+
writeFileSync3(filePath, JSON.stringify(plan, null, 2), "utf-8");
|
|
1177
|
+
consola2.info(`Plan saved to ${filePath}`);
|
|
1178
|
+
return filePath;
|
|
1179
|
+
}
|
|
1180
|
+
function loadPlanFile(filePath) {
|
|
1181
|
+
try {
|
|
1182
|
+
const raw = JSON.parse(readFileSync7(filePath, "utf-8"));
|
|
1183
|
+
const result = PatchPlan.safeParse(raw);
|
|
1184
|
+
if (result.success) return result.data;
|
|
1185
|
+
consola2.error("Plan file validation failed:", result.error.issues);
|
|
1186
|
+
return null;
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1189
|
+
consola2.error(`Failed to load plan file: ${msg}`);
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// src/git/index.ts
|
|
1195
|
+
import { execSync } from "child_process";
|
|
1196
|
+
import consola3 from "consola";
|
|
1197
|
+
function exec(cmd, cwd) {
|
|
1198
|
+
try {
|
|
1199
|
+
return execSync(cmd, { cwd, encoding: "utf-8", stdio: "pipe" }).trim();
|
|
1200
|
+
} catch {
|
|
1201
|
+
return "";
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
var gitOps = {
|
|
1205
|
+
isGitRepo(cwd) {
|
|
1206
|
+
return exec("git rev-parse --is-inside-work-tree", cwd) === "true";
|
|
1207
|
+
},
|
|
1208
|
+
isClean(cwd) {
|
|
1209
|
+
return exec("git diff --quiet && git diff --cached --quiet && echo clean", cwd) === "clean";
|
|
1210
|
+
},
|
|
1211
|
+
getCurrentBranch(cwd) {
|
|
1212
|
+
const branch = exec("git branch --show-current", cwd);
|
|
1213
|
+
return branch || null;
|
|
1214
|
+
},
|
|
1215
|
+
getHeadCommit(cwd) {
|
|
1216
|
+
const hash = exec("git rev-parse --short HEAD", cwd);
|
|
1217
|
+
return hash || null;
|
|
1218
|
+
},
|
|
1219
|
+
createBranch(cwd, name) {
|
|
1220
|
+
try {
|
|
1221
|
+
execSync(`git checkout -b ${name}`, { cwd, stdio: "pipe" });
|
|
1222
|
+
return true;
|
|
1223
|
+
} catch {
|
|
1224
|
+
consola3.warn(`Failed to create branch: ${name}`);
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
},
|
|
1228
|
+
stageAll(cwd) {
|
|
1229
|
+
execSync("git add -A", { cwd, stdio: "pipe" });
|
|
1230
|
+
},
|
|
1231
|
+
commit(cwd, message) {
|
|
1232
|
+
try {
|
|
1233
|
+
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
|
|
1234
|
+
cwd,
|
|
1235
|
+
stdio: "pipe"
|
|
1236
|
+
});
|
|
1237
|
+
return exec("git rev-parse --short HEAD", cwd);
|
|
1238
|
+
} catch {
|
|
1239
|
+
consola3.warn("Failed to create commit");
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
},
|
|
1243
|
+
rollbackToCommit(cwd, commitHash) {
|
|
1244
|
+
try {
|
|
1245
|
+
execSync(`git reset --hard ${commitHash}`, { cwd, stdio: "pipe" });
|
|
1246
|
+
return true;
|
|
1247
|
+
} catch {
|
|
1248
|
+
consola3.error(`Failed to rollback to ${commitHash}`);
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
},
|
|
1252
|
+
getLastCommitByAgent(cwd) {
|
|
1253
|
+
const log = exec(
|
|
1254
|
+
'git log --oneline --all --grep="cmsassets-agent" -n 1 --format="%H"',
|
|
1255
|
+
cwd
|
|
1256
|
+
);
|
|
1257
|
+
return log || null;
|
|
1258
|
+
},
|
|
1259
|
+
getCommitBefore(cwd, commitHash) {
|
|
1260
|
+
const parent = exec(`git rev-parse ${commitHash}~1`, cwd);
|
|
1261
|
+
return parent || null;
|
|
1262
|
+
},
|
|
1263
|
+
push(cwd, remote = "origin") {
|
|
1264
|
+
try {
|
|
1265
|
+
const branch = exec("git branch --show-current", cwd);
|
|
1266
|
+
execSync(`git push -u ${remote} ${branch}`, { cwd, stdio: "pipe" });
|
|
1267
|
+
return true;
|
|
1268
|
+
} catch {
|
|
1269
|
+
consola3.warn("Failed to push to remote");
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
// src/verifier/index.ts
|
|
1276
|
+
import { execSync as execSync2 } from "child_process";
|
|
1277
|
+
import consola4 from "consola";
|
|
1278
|
+
|
|
1279
|
+
// src/verifier/profiles.ts
|
|
1280
|
+
function getQuickSteps(framework) {
|
|
1281
|
+
const steps = [];
|
|
1282
|
+
switch (framework) {
|
|
1283
|
+
case "nuxt":
|
|
1284
|
+
case "nuxt2":
|
|
1285
|
+
steps.push({
|
|
1286
|
+
name: "nuxt-prepare",
|
|
1287
|
+
command: "npx nuxt prepare",
|
|
1288
|
+
required: false
|
|
1289
|
+
});
|
|
1290
|
+
break;
|
|
1291
|
+
case "next":
|
|
1292
|
+
steps.push({
|
|
1293
|
+
name: "tsc-noEmit",
|
|
1294
|
+
command: "npx tsc --noEmit",
|
|
1295
|
+
required: false
|
|
1296
|
+
});
|
|
1297
|
+
break;
|
|
1298
|
+
case "astro":
|
|
1299
|
+
steps.push({
|
|
1300
|
+
name: "astro-check",
|
|
1301
|
+
command: "npx astro check",
|
|
1302
|
+
required: false
|
|
1303
|
+
});
|
|
1304
|
+
break;
|
|
1305
|
+
case "sveltekit":
|
|
1306
|
+
steps.push({
|
|
1307
|
+
name: "svelte-check",
|
|
1308
|
+
command: "npx svelte-check",
|
|
1309
|
+
required: false
|
|
1310
|
+
});
|
|
1311
|
+
break;
|
|
1312
|
+
default:
|
|
1313
|
+
steps.push({
|
|
1314
|
+
name: "tsc-noEmit",
|
|
1315
|
+
command: "npx tsc --noEmit",
|
|
1316
|
+
required: false
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
return steps;
|
|
1320
|
+
}
|
|
1321
|
+
function getFullSteps(framework) {
|
|
1322
|
+
const steps = getQuickSteps(framework);
|
|
1323
|
+
steps.push({
|
|
1324
|
+
name: "lint",
|
|
1325
|
+
command: "npm run lint",
|
|
1326
|
+
required: false
|
|
1327
|
+
});
|
|
1328
|
+
switch (framework) {
|
|
1329
|
+
case "nuxt":
|
|
1330
|
+
case "nuxt2":
|
|
1331
|
+
steps.push({ name: "build", command: "npm run build", required: true });
|
|
1332
|
+
break;
|
|
1333
|
+
case "next":
|
|
1334
|
+
steps.push({ name: "build", command: "npm run build", required: true });
|
|
1335
|
+
break;
|
|
1336
|
+
case "astro":
|
|
1337
|
+
steps.push({ name: "build", command: "npm run build", required: true });
|
|
1338
|
+
break;
|
|
1339
|
+
case "sveltekit":
|
|
1340
|
+
steps.push({ name: "build", command: "npm run build", required: true });
|
|
1341
|
+
break;
|
|
1342
|
+
default:
|
|
1343
|
+
steps.push({ name: "build", command: "npm run build", required: false });
|
|
1344
|
+
}
|
|
1345
|
+
steps.push({
|
|
1346
|
+
name: "test",
|
|
1347
|
+
command: "npm test",
|
|
1348
|
+
required: false
|
|
1349
|
+
});
|
|
1350
|
+
return steps;
|
|
1351
|
+
}
|
|
1352
|
+
function getVerifySteps(profile, framework) {
|
|
1353
|
+
return profile === "full" ? getFullSteps(framework) : getQuickSteps(framework);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// src/verifier/index.ts
|
|
1357
|
+
function verify(root, opts = {}) {
|
|
1358
|
+
const startTime = Date.now();
|
|
1359
|
+
const profile = opts.profile ?? "quick";
|
|
1360
|
+
const framework = opts.framework ?? "unknown";
|
|
1361
|
+
const patchedFiles = opts.patchedFiles ?? [];
|
|
1362
|
+
const warnings = [];
|
|
1363
|
+
const steps = getVerifySteps(profile, framework);
|
|
1364
|
+
let lintPassed = null;
|
|
1365
|
+
let buildPassed = null;
|
|
1366
|
+
let testsPassed = null;
|
|
1367
|
+
for (const step of steps) {
|
|
1368
|
+
consola4.info(`Running: ${step.name} (${step.command})`);
|
|
1369
|
+
try {
|
|
1370
|
+
execSync2(step.command, {
|
|
1371
|
+
cwd: root,
|
|
1372
|
+
stdio: "pipe",
|
|
1373
|
+
timeout: 12e4
|
|
1374
|
+
});
|
|
1375
|
+
if (step.name === "lint") lintPassed = true;
|
|
1376
|
+
if (step.name === "build") buildPassed = true;
|
|
1377
|
+
if (step.name === "test") testsPassed = true;
|
|
1378
|
+
consola4.success(`${step.name} passed`);
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1381
|
+
const truncated = message.substring(0, 500);
|
|
1382
|
+
if (step.name === "lint") lintPassed = false;
|
|
1383
|
+
if (step.name === "build") buildPassed = false;
|
|
1384
|
+
if (step.name === "test") testsPassed = false;
|
|
1385
|
+
if (step.required) {
|
|
1386
|
+
consola4.error(`${step.name} FAILED: ${truncated}`);
|
|
1387
|
+
warnings.push(`${step.name} failed (required): ${truncated}`);
|
|
1388
|
+
} else {
|
|
1389
|
+
consola4.warn(`${step.name} failed (optional): ${truncated}`);
|
|
1390
|
+
warnings.push(`${step.name} failed (optional): ${truncated}`);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return {
|
|
1395
|
+
schemaVersion: "1.0",
|
|
1396
|
+
profile,
|
|
1397
|
+
lintPassed,
|
|
1398
|
+
buildPassed,
|
|
1399
|
+
testsPassed,
|
|
1400
|
+
patchedFiles,
|
|
1401
|
+
warnings,
|
|
1402
|
+
durationMs: Date.now() - startTime
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
export {
|
|
1407
|
+
scan,
|
|
1408
|
+
createPlan,
|
|
1409
|
+
applyPlan,
|
|
1410
|
+
FrameworkName,
|
|
1411
|
+
CmsType,
|
|
1412
|
+
PackageManager,
|
|
1413
|
+
Confidence,
|
|
1414
|
+
InjectionType,
|
|
1415
|
+
VerifyProfile,
|
|
1416
|
+
FrameworkInfo,
|
|
1417
|
+
CmsInfo,
|
|
1418
|
+
InjectionCandidate,
|
|
1419
|
+
ScanResult,
|
|
1420
|
+
FilePatch,
|
|
1421
|
+
PatchPlan,
|
|
1422
|
+
PatchedFileReport,
|
|
1423
|
+
ApplyReport,
|
|
1424
|
+
VerifyReport,
|
|
1425
|
+
AiFileResult,
|
|
1426
|
+
AiReviewReport,
|
|
1427
|
+
createReport,
|
|
1428
|
+
saveReport,
|
|
1429
|
+
savePlanFile,
|
|
1430
|
+
loadPlanFile,
|
|
1431
|
+
gitOps,
|
|
1432
|
+
verify
|
|
1433
|
+
};
|