@configjs/cli 1.1.16 → 1.1.18
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/{angular-command-XN26G6L3.js → angular-command-EOREU45Q.js} +8 -8
- package/dist/{angular-installer-FY43HE72.js → angular-installer-TKZDPFLD.js} +9 -1
- package/dist/angular-setup-QDTWXOB4.js +30 -0
- package/dist/check-KAPRT4FM.js +168 -0
- package/dist/{chunk-JYWGJJ4M.js → chunk-D7IWYKUX.js} +476 -28
- package/dist/chunk-EDCNW4UO.js +92 -0
- package/dist/{chunk-TN27AX4L.js → chunk-FJLN62L4.js} +797 -18
- package/dist/{chunk-FIB2J36N.js → chunk-HI7RYD6W.js} +161 -36
- package/dist/{chunk-NYCK4R4K.js → chunk-RIJNUJDC.js} +361 -96
- package/dist/chunk-V2IBYLVH.js +932 -0
- package/dist/chunk-VN4XTFDK.js +315 -0
- package/dist/{chunk-UKEHW2LH.js → chunk-Y4XYC7QV.js} +17 -3
- package/dist/cli.js +31 -21
- package/dist/{installed-D6CUYQM5.js → installed-QMJZIZNC.js} +4 -4
- package/dist/{list-VZDUWV5O.js → list-5T6VDDAO.js} +4 -4
- package/dist/{nextjs-command-WKKOAY7I.js → nextjs-command-C6PM7A5C.js} +8 -9
- package/dist/{nextjs-installer-2ZC5IWJ6.js → nextjs-installer-OFY5BQC4.js} +9 -2
- package/dist/{nextjs-setup-DYOFF72S.js → nextjs-setup-JIKPIJCX.js} +21 -9
- package/dist/{react-command-2T6IOTHB.js → react-command-JMK6VM4Q.js} +8 -9
- package/dist/{remove-ZY3MLPGN.js → remove-4ZNQR6ZR.js} +4 -4
- package/dist/{svelte-command-B2DNNQ5Z.js → svelte-command-YUSD55NO.js} +8 -8
- package/dist/svelte-installer-UP3KDZSY.js +105 -0
- package/dist/{svelte-setup-FWXLXJAC.js → svelte-setup-33E46IBT.js} +16 -5
- package/dist/{vite-installer-Y6VMFHIM.js → vite-installer-EE2LE76G.js} +9 -2
- package/dist/{vite-setup-JRELX6K2.js → vite-setup-VO5BOI46.js} +16 -4
- package/dist/{vue-command-IOTC32AI.js → vue-command-3CYWLLFQ.js} +8 -9
- package/dist/{vue-installer-DGBBVF6F.js → vue-installer-LEGLVD77.js} +9 -2
- package/dist/{vue-setup-G44DLT2U.js → vue-setup-FK5QT7AY.js} +16 -4
- package/package.json +12 -4
- package/dist/angular-setup-Z6TCKNBG.js +0 -18
- package/dist/check-KNGZSCMM.js +0 -131
- package/dist/chunk-6GV4NKUX.js +0 -122
- package/dist/chunk-QPEUT7QG.js +0 -157
- package/dist/svelte-installer-EOSC3EP3.js +0 -65
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installPackages
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V2IBYLVH.js";
|
|
4
4
|
import {
|
|
5
5
|
checkPathExists,
|
|
6
6
|
ensureDirectory,
|
|
7
7
|
normalizePath,
|
|
8
8
|
readFileContent,
|
|
9
|
-
readPackageJson,
|
|
10
9
|
writeFileContent,
|
|
11
10
|
writePackageJson
|
|
12
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-HI7RYD6W.js";
|
|
13
12
|
import {
|
|
14
13
|
getModuleLogger,
|
|
15
14
|
logger
|
|
16
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VN4XTFDK.js";
|
|
17
16
|
|
|
18
17
|
// src/types/index.ts
|
|
19
18
|
var Category = /* @__PURE__ */ ((Category2) => {
|
|
@@ -31,6 +30,426 @@ var Category = /* @__PURE__ */ ((Category2) => {
|
|
|
31
30
|
return Category2;
|
|
32
31
|
})(Category || {});
|
|
33
32
|
|
|
33
|
+
// src/core/config-sanitizer.ts
|
|
34
|
+
import { parse } from "@babel/parser";
|
|
35
|
+
import { parseDocument } from "yaml";
|
|
36
|
+
import { parse as parseToml } from "@iarna/toml";
|
|
37
|
+
var ConfigSanitizer = class {
|
|
38
|
+
/**
|
|
39
|
+
* Validates and sanitizes JSON configuration
|
|
40
|
+
* Rejects if:
|
|
41
|
+
* - Invalid JSON syntax
|
|
42
|
+
* - Circular references
|
|
43
|
+
* - Prototype pollution attempts
|
|
44
|
+
*
|
|
45
|
+
* @param content - Raw JSON content
|
|
46
|
+
* @returns Parsed and validated JSON object
|
|
47
|
+
* @throws Error if JSON is invalid or contains injection attempts
|
|
48
|
+
*/
|
|
49
|
+
static validateJSON(content) {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
if (this.hasPrototypePollution(parsed)) {
|
|
53
|
+
throw new Error("Prototype pollution detected in JSON");
|
|
54
|
+
}
|
|
55
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
56
|
+
throw new Error("JSON must be an object");
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error instanceof SyntaxError) {
|
|
61
|
+
throw new Error(`Invalid JSON: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validates JavaScript configuration file
|
|
68
|
+
* Rejects if:
|
|
69
|
+
* - Invalid JavaScript syntax
|
|
70
|
+
* - Contains eval/Function/require calls (except static imports)
|
|
71
|
+
* - Contains suspicious patterns
|
|
72
|
+
*
|
|
73
|
+
* @param content - Raw JavaScript content
|
|
74
|
+
* @returns Validated content (still as string, needs parsing via AST)
|
|
75
|
+
* @throws Error if JavaScript is invalid or contains suspicious code
|
|
76
|
+
*/
|
|
77
|
+
static validateJavaScript(content) {
|
|
78
|
+
const ast = this.parseJavaScriptAst(content);
|
|
79
|
+
const blockedModules = /* @__PURE__ */ new Set([
|
|
80
|
+
"child_process",
|
|
81
|
+
"node:child_process",
|
|
82
|
+
"fs",
|
|
83
|
+
"node:fs",
|
|
84
|
+
"fs/promises",
|
|
85
|
+
"node:fs/promises"
|
|
86
|
+
]);
|
|
87
|
+
this.walkAst(ast, (node, parent, parentKey) => {
|
|
88
|
+
if (node["type"] === "CallExpression") {
|
|
89
|
+
const callee = node["callee"];
|
|
90
|
+
if (callee?.["type"] === "Identifier" && callee["name"] === "eval") {
|
|
91
|
+
throw new Error("Dangerous pattern detected in JavaScript: eval()");
|
|
92
|
+
}
|
|
93
|
+
if (callee?.["type"] === "Identifier" && callee["name"] === "require") {
|
|
94
|
+
throw new Error("Dangerous pattern detected in JavaScript: require()");
|
|
95
|
+
}
|
|
96
|
+
const requireObject = callee?.["type"] === "MemberExpression" ? callee["object"] : void 0;
|
|
97
|
+
if (callee?.["type"] === "MemberExpression" && requireObject?.["type"] === "Identifier" && requireObject["name"] === "require") {
|
|
98
|
+
throw new Error("Dangerous pattern detected in JavaScript: require()");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (node["type"] === "NewExpression") {
|
|
102
|
+
const callee = node["callee"];
|
|
103
|
+
if (callee?.["type"] === "Identifier" && callee["name"] === "Function") {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"Dangerous pattern detected in JavaScript: new Function()"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (node["type"] === "ImportExpression") {
|
|
110
|
+
throw new Error("Dangerous pattern detected in JavaScript: import()");
|
|
111
|
+
}
|
|
112
|
+
if (node["type"] === "ImportDeclaration") {
|
|
113
|
+
const source = node["source"];
|
|
114
|
+
const sourceValue = source?.["value"];
|
|
115
|
+
if (typeof sourceValue === "string" && blockedModules.has(sourceValue)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Dangerous pattern detected in JavaScript: import ${sourceValue}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (node["type"] === "MemberExpression") {
|
|
122
|
+
const objectNode = node["object"];
|
|
123
|
+
if (objectNode?.["type"] === "Identifier" && objectNode["name"] === "process") {
|
|
124
|
+
throw new Error(
|
|
125
|
+
"Dangerous pattern detected in JavaScript: process access"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (node["type"] === "Identifier") {
|
|
130
|
+
const identifierName = node["name"];
|
|
131
|
+
const isObjectKey = parent?.["type"] === "ObjectProperty" && parentKey === "key" && parent["computed"] === false;
|
|
132
|
+
if (!isObjectKey && identifierName === "__dirname") {
|
|
133
|
+
throw new Error("Dangerous pattern detected in JavaScript: __dirname");
|
|
134
|
+
}
|
|
135
|
+
if (!isObjectKey && identifierName === "__filename") {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"Dangerous pattern detected in JavaScript: __filename"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (node["type"] === "TemplateLiteral") {
|
|
142
|
+
const expressions = node["expressions"];
|
|
143
|
+
if (Array.isArray(expressions) && expressions.length > 0) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"Dangerous pattern detected in JavaScript: template literal"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return content;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Validates YAML configuration
|
|
154
|
+
* Rejects if:
|
|
155
|
+
* - Invalid YAML syntax
|
|
156
|
+
* - Contains dangerous tags (!!, !!python, !!env)
|
|
157
|
+
* - Suspicious merge keys
|
|
158
|
+
*
|
|
159
|
+
* @param content - Raw YAML content
|
|
160
|
+
* @returns Validated content
|
|
161
|
+
* @throws Error if YAML is invalid or contains injection attempts
|
|
162
|
+
*/
|
|
163
|
+
static validateYAML(content) {
|
|
164
|
+
const document = parseDocument(content, {
|
|
165
|
+
schema: "core"
|
|
166
|
+
});
|
|
167
|
+
if (document.errors.length > 0) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Invalid YAML: ${document.errors[0]?.message ?? "unknown"}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
this.validateYamlNodes(document.contents);
|
|
173
|
+
const parsed = document.toJS({ maxAliasCount: 50 });
|
|
174
|
+
if (this.hasPrototypePollution(parsed)) {
|
|
175
|
+
throw new Error("Prototype pollution detected in YAML");
|
|
176
|
+
}
|
|
177
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
178
|
+
throw new Error("YAML must be an object");
|
|
179
|
+
}
|
|
180
|
+
if (this.hasSuspiciousStringValue(parsed)) {
|
|
181
|
+
throw new Error("Template syntax detected in YAML");
|
|
182
|
+
}
|
|
183
|
+
if (this.hasDangerousKey(parsed, /* @__PURE__ */ new Set(["exec", "eval", "require"]))) {
|
|
184
|
+
throw new Error("Dangerous key detected in YAML");
|
|
185
|
+
}
|
|
186
|
+
return content;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Validates TOML configuration
|
|
190
|
+
* Rejects if:
|
|
191
|
+
* - Invalid TOML syntax
|
|
192
|
+
* - Suspicious key names
|
|
193
|
+
* - Values that look like code injection
|
|
194
|
+
*
|
|
195
|
+
* @param content - Raw TOML content
|
|
196
|
+
* @returns Validated content
|
|
197
|
+
* @throws Error if TOML is invalid or contains injection attempts
|
|
198
|
+
*/
|
|
199
|
+
static validateTOML(content) {
|
|
200
|
+
let parsed;
|
|
201
|
+
try {
|
|
202
|
+
parsed = parseToml(content);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
205
|
+
throw new Error(`Invalid TOML: ${errorMessage}`);
|
|
206
|
+
}
|
|
207
|
+
if (this.hasPrototypePollution(parsed)) {
|
|
208
|
+
throw new Error("Prototype pollution detected in TOML");
|
|
209
|
+
}
|
|
210
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
211
|
+
throw new Error("TOML must be an object");
|
|
212
|
+
}
|
|
213
|
+
if (this.hasSuspiciousStringValue(parsed)) {
|
|
214
|
+
throw new Error("Template syntax detected in TOML");
|
|
215
|
+
}
|
|
216
|
+
if (this.hasDangerousKey(parsed, /* @__PURE__ */ new Set(["exec", "eval", "require"]))) {
|
|
217
|
+
throw new Error("Dangerous key detected in TOML");
|
|
218
|
+
}
|
|
219
|
+
return content;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Escapes string values to prevent injection
|
|
223
|
+
* Used when inserting values into configs
|
|
224
|
+
*
|
|
225
|
+
* @param value - Value to escape
|
|
226
|
+
* @param format - Config format (json, js, yaml, toml)
|
|
227
|
+
* @returns Escaped value safe for insertion
|
|
228
|
+
*/
|
|
229
|
+
static escapeValue(value, format = "json") {
|
|
230
|
+
if (!value) return value;
|
|
231
|
+
switch (format) {
|
|
232
|
+
case "json":
|
|
233
|
+
return JSON.stringify(value);
|
|
234
|
+
case "js":
|
|
235
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
236
|
+
case "yaml":
|
|
237
|
+
if (value.includes(":") || value.includes("#") || value.includes("{")) {
|
|
238
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
239
|
+
}
|
|
240
|
+
return value;
|
|
241
|
+
case "toml":
|
|
242
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
243
|
+
default:
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Detects prototype pollution attempts in objects
|
|
249
|
+
* Checks for dangerous keys like __proto__, constructor, prototype
|
|
250
|
+
*
|
|
251
|
+
* @param obj - Object to check
|
|
252
|
+
* @returns True if prototype pollution attempt detected
|
|
253
|
+
*/
|
|
254
|
+
static hasPrototypePollution(obj) {
|
|
255
|
+
if (typeof obj !== "object" || obj === null) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
const dangerousKeys = ["__proto__", "constructor", "prototype"];
|
|
259
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
260
|
+
if (dangerousKeys.includes(key)) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
if (typeof value === "object" && value !== null) {
|
|
264
|
+
if (this.hasPrototypePollution(value)) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
static parseJavaScriptAst(content) {
|
|
272
|
+
try {
|
|
273
|
+
return parse(content, {
|
|
274
|
+
sourceType: "module",
|
|
275
|
+
plugins: ["typescript", "jsx"]
|
|
276
|
+
});
|
|
277
|
+
} catch {
|
|
278
|
+
try {
|
|
279
|
+
return parse(content, {
|
|
280
|
+
sourceType: "script",
|
|
281
|
+
plugins: ["typescript", "jsx"]
|
|
282
|
+
});
|
|
283
|
+
} catch (error) {
|
|
284
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
285
|
+
throw new Error(`Invalid JavaScript: ${errorMessage}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
static walkAst(node, visitor, parent, parentKey) {
|
|
290
|
+
if (!node || typeof node !== "object") {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const currentNode = node;
|
|
294
|
+
if (typeof currentNode["type"] !== "string") {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
visitor(currentNode, parent, parentKey);
|
|
298
|
+
for (const [key, value] of Object.entries(currentNode)) {
|
|
299
|
+
if (Array.isArray(value)) {
|
|
300
|
+
for (const item of value) {
|
|
301
|
+
this.walkAst(item, visitor, currentNode, key);
|
|
302
|
+
}
|
|
303
|
+
} else if (value && typeof value === "object") {
|
|
304
|
+
this.walkAst(value, visitor, currentNode, key);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
static validateYamlNodes(node) {
|
|
309
|
+
if (!node || typeof node !== "object") {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const currentNode = node;
|
|
313
|
+
if (currentNode.tag) {
|
|
314
|
+
const allowedTags = /* @__PURE__ */ new Set([
|
|
315
|
+
"tag:yaml.org,2002:map",
|
|
316
|
+
"tag:yaml.org,2002:seq",
|
|
317
|
+
"tag:yaml.org,2002:str",
|
|
318
|
+
"tag:yaml.org,2002:int",
|
|
319
|
+
"tag:yaml.org,2002:float",
|
|
320
|
+
"tag:yaml.org,2002:bool",
|
|
321
|
+
"tag:yaml.org,2002:null"
|
|
322
|
+
]);
|
|
323
|
+
if (!allowedTags.has(currentNode.tag)) {
|
|
324
|
+
throw new Error(`Dangerous YAML tag detected: ${currentNode.tag}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (Array.isArray(currentNode.items)) {
|
|
328
|
+
for (const item of currentNode.items) {
|
|
329
|
+
const pair = item;
|
|
330
|
+
const keyValue = pair?.key?.value;
|
|
331
|
+
if (keyValue === "<<") {
|
|
332
|
+
throw new Error("Dangerous YAML merge key detected");
|
|
333
|
+
}
|
|
334
|
+
this.validateYamlNodes(pair?.key);
|
|
335
|
+
this.validateYamlNodes(pair?.value);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
this.validateYamlNodes(currentNode.key);
|
|
339
|
+
this.validateYamlNodes(currentNode.value);
|
|
340
|
+
}
|
|
341
|
+
static hasSuspiciousStringValue(value) {
|
|
342
|
+
if (typeof value === "string") {
|
|
343
|
+
return value.includes("${") || value.includes("`");
|
|
344
|
+
}
|
|
345
|
+
if (Array.isArray(value)) {
|
|
346
|
+
return value.some((item) => this.hasSuspiciousStringValue(item));
|
|
347
|
+
}
|
|
348
|
+
if (value && typeof value === "object") {
|
|
349
|
+
return Object.values(value).some(
|
|
350
|
+
(item) => this.hasSuspiciousStringValue(item)
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
static hasDangerousKey(value, dangerousKeys) {
|
|
356
|
+
if (Array.isArray(value)) {
|
|
357
|
+
return value.some((item) => this.hasDangerousKey(item, dangerousKeys));
|
|
358
|
+
}
|
|
359
|
+
if (value && typeof value === "object") {
|
|
360
|
+
for (const [key, item] of Object.entries(value)) {
|
|
361
|
+
if (dangerousKeys.has(key)) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
if (this.hasDangerousKey(item, dangerousKeys)) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Validates that a config file can be safely modified
|
|
373
|
+
* Checks structure and ensures it matches expected format
|
|
374
|
+
*
|
|
375
|
+
* @param content - Config file content
|
|
376
|
+
* @param format - Expected config format
|
|
377
|
+
* @returns True if config is valid and safe to modify
|
|
378
|
+
* @throws Error if config is invalid
|
|
379
|
+
*/
|
|
380
|
+
static canSafelyModify(content, format) {
|
|
381
|
+
try {
|
|
382
|
+
switch (format) {
|
|
383
|
+
case "json":
|
|
384
|
+
this.validateJSON(content);
|
|
385
|
+
break;
|
|
386
|
+
case "js":
|
|
387
|
+
this.validateJavaScript(content);
|
|
388
|
+
break;
|
|
389
|
+
case "yaml":
|
|
390
|
+
this.validateYAML(content);
|
|
391
|
+
break;
|
|
392
|
+
case "toml":
|
|
393
|
+
this.validateTOML(content);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
} catch {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Merges configurations safely
|
|
403
|
+
* Prevents injection by validating before merge
|
|
404
|
+
*
|
|
405
|
+
* @param original - Original config object
|
|
406
|
+
* @param updates - Updates to merge (user-provided)
|
|
407
|
+
* @param format - Config format
|
|
408
|
+
* @returns Merged configuration
|
|
409
|
+
* @throws Error if updates contain injection attempts
|
|
410
|
+
*/
|
|
411
|
+
static mergeSafely(original, updates, format = "json") {
|
|
412
|
+
if (this.hasPrototypePollution(updates)) {
|
|
413
|
+
throw new Error("Prototype pollution detected in updates");
|
|
414
|
+
}
|
|
415
|
+
const result = { ...original };
|
|
416
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
417
|
+
if (!this.isValidKey(key)) {
|
|
418
|
+
throw new Error(`Invalid key name: ${key}`);
|
|
419
|
+
}
|
|
420
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
421
|
+
result[key] = this.mergeSafely(
|
|
422
|
+
original[key] || {},
|
|
423
|
+
value,
|
|
424
|
+
format
|
|
425
|
+
);
|
|
426
|
+
} else {
|
|
427
|
+
result[key] = value;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Validates if a key name is safe to use
|
|
434
|
+
* Prevents prototype pollution and suspicious keys
|
|
435
|
+
*
|
|
436
|
+
* @param key - Key name to validate
|
|
437
|
+
* @returns True if key is valid and safe
|
|
438
|
+
*/
|
|
439
|
+
static isValidKey(key) {
|
|
440
|
+
const dangerousKeys = [
|
|
441
|
+
"__proto__",
|
|
442
|
+
"constructor",
|
|
443
|
+
"prototype",
|
|
444
|
+
"constructor.prototype"
|
|
445
|
+
];
|
|
446
|
+
if (dangerousKeys.includes(key)) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
return /^[a-zA-Z0-9_-]+$/.test(key);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
34
453
|
// src/plugins/animation/framer-motion.ts
|
|
35
454
|
import { join } from "path";
|
|
36
455
|
|
|
@@ -258,6 +677,7 @@ var BackupManager = class {
|
|
|
258
677
|
|
|
259
678
|
// src/core/config-writer.ts
|
|
260
679
|
import { resolve as resolve2, dirname } from "path";
|
|
680
|
+
import { createHash } from "crypto";
|
|
261
681
|
var ConfigWriter = class {
|
|
262
682
|
/**
|
|
263
683
|
* @param backupManager - Gestionnaire de backups à utiliser
|
|
@@ -268,6 +688,7 @@ var ConfigWriter = class {
|
|
|
268
688
|
this.fsAdapter = fsAdapter;
|
|
269
689
|
}
|
|
270
690
|
logger = getModuleLogger();
|
|
691
|
+
jsonCache = /* @__PURE__ */ new Map();
|
|
271
692
|
/**
|
|
272
693
|
* Écrit ou modifie un fichier avec backup automatique
|
|
273
694
|
*
|
|
@@ -359,24 +780,32 @@ var ConfigWriter = class {
|
|
|
359
780
|
*/
|
|
360
781
|
async modifyPackageJson(projectRoot, modifier) {
|
|
361
782
|
const fullPath = resolve2(projectRoot);
|
|
783
|
+
const packageJsonPath = resolve2(fullPath, "package.json");
|
|
362
784
|
let pkg;
|
|
785
|
+
let existingContent;
|
|
363
786
|
try {
|
|
364
|
-
|
|
787
|
+
existingContent = await readFileContent(
|
|
788
|
+
packageJsonPath,
|
|
789
|
+
"utf-8",
|
|
790
|
+
this.fsAdapter
|
|
791
|
+
);
|
|
365
792
|
} catch (error) {
|
|
366
793
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
367
794
|
throw new Error(
|
|
368
795
|
`Failed to read package.json: ${errorMessage}. Make sure you're in a valid project directory.`
|
|
369
796
|
);
|
|
370
797
|
}
|
|
371
|
-
|
|
798
|
+
try {
|
|
799
|
+
pkg = this.parseJsonWithCache(packageJsonPath, existingContent);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
802
|
+
throw new Error(
|
|
803
|
+
`Failed to read package.json: ${errorMessage}. File may be invalid JSON.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
372
806
|
if (this.backupManager.hasBackup(packageJsonPath)) {
|
|
373
807
|
} else {
|
|
374
808
|
try {
|
|
375
|
-
const existingContent = await readFileContent(
|
|
376
|
-
packageJsonPath,
|
|
377
|
-
"utf-8",
|
|
378
|
-
this.fsAdapter
|
|
379
|
-
);
|
|
380
809
|
this.backupManager.backup(packageJsonPath, existingContent);
|
|
381
810
|
} catch (error) {
|
|
382
811
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -389,6 +818,7 @@ var ConfigWriter = class {
|
|
|
389
818
|
try {
|
|
390
819
|
await writePackageJson(fullPath, modifiedPkg, this.fsAdapter);
|
|
391
820
|
this.logger.debug(`Modified package.json: ${packageJsonPath}`);
|
|
821
|
+
this.jsonCache.delete(packageJsonPath);
|
|
392
822
|
} catch (error) {
|
|
393
823
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
394
824
|
throw new Error(
|
|
@@ -497,6 +927,16 @@ var ConfigWriter = class {
|
|
|
497
927
|
});
|
|
498
928
|
this.logger.debug(`Injected import into ${fullPath}`);
|
|
499
929
|
}
|
|
930
|
+
parseJsonWithCache(filePath, content) {
|
|
931
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
932
|
+
const cached = this.jsonCache.get(filePath);
|
|
933
|
+
if (cached?.hash === hash) {
|
|
934
|
+
return cached.parsed;
|
|
935
|
+
}
|
|
936
|
+
const parsed = JSON.parse(content);
|
|
937
|
+
this.jsonCache.set(filePath, { hash, parsed });
|
|
938
|
+
return parsed;
|
|
939
|
+
}
|
|
500
940
|
};
|
|
501
941
|
|
|
502
942
|
// src/plugins/utils/plugin-services.ts
|
|
@@ -4707,6 +5147,12 @@ var nextjsImageOptimizationPlugin = {
|
|
|
4707
5147
|
"utf-8",
|
|
4708
5148
|
ctx.fsAdapter
|
|
4709
5149
|
);
|
|
5150
|
+
const configFormat = extension === "ts" ? "js" : "js";
|
|
5151
|
+
if (!ConfigSanitizer.canSafelyModify(existingContent, configFormat)) {
|
|
5152
|
+
throw new Error(
|
|
5153
|
+
`Invalid or unsafe next.config.${extension} - cannot safely modify`
|
|
5154
|
+
);
|
|
5155
|
+
}
|
|
4710
5156
|
const updatedContent = injectImageConfig(existingContent, extension);
|
|
4711
5157
|
if (updatedContent !== existingContent) {
|
|
4712
5158
|
await writer.writeFile(nextConfigPath, updatedContent, {
|
|
@@ -4802,6 +5248,9 @@ function injectImageConfig(content, _extension) {
|
|
|
4802
5248
|
logger17.warn("Image configuration already exists in next.config");
|
|
4803
5249
|
return content;
|
|
4804
5250
|
}
|
|
5251
|
+
if (!ConfigSanitizer.canSafelyModify(content, "js")) {
|
|
5252
|
+
throw new Error("Invalid JavaScript in next.config file");
|
|
5253
|
+
}
|
|
4805
5254
|
let modifiedContent = content;
|
|
4806
5255
|
const configRegex = /(const\s+nextConfig\s*=\s*\{|nextConfig\s*=\s*\{)([\s\S]*?)(\})/m;
|
|
4807
5256
|
if (configRegex.test(modifiedContent)) {
|
|
@@ -4811,24 +5260,29 @@ function injectImageConfig(content, _extension) {
|
|
|
4811
5260
|
if (configContent.includes("images:")) {
|
|
4812
5261
|
return match;
|
|
4813
5262
|
}
|
|
5263
|
+
const imageConfig = buildImageConfig();
|
|
4814
5264
|
const trimmed = configContent.trim();
|
|
4815
5265
|
const hasTrailingComma = trimmed.endsWith(",");
|
|
4816
|
-
const imageConfig = ` images: {
|
|
4817
|
-
remotePatterns: [
|
|
4818
|
-
{
|
|
4819
|
-
protocol: 'https',
|
|
4820
|
-
hostname: '**',
|
|
4821
|
-
},
|
|
4822
|
-
],
|
|
4823
|
-
},${hasTrailingComma ? "" : "\n"}`;
|
|
4824
5266
|
return `${start}${trimmed}${hasTrailingComma ? "" : ","}
|
|
4825
5267
|
${imageConfig}
|
|
4826
5268
|
}`;
|
|
4827
5269
|
}
|
|
4828
5270
|
);
|
|
4829
5271
|
} else {
|
|
4830
|
-
const imageConfig =
|
|
4831
|
-
|
|
5272
|
+
const imageConfig = buildImageConfig();
|
|
5273
|
+
modifiedContent = modifiedContent.replace(
|
|
5274
|
+
/(\}\s*)$/,
|
|
5275
|
+
(match) => ` ${imageConfig}
|
|
5276
|
+
${match}`
|
|
5277
|
+
);
|
|
5278
|
+
}
|
|
5279
|
+
if (!ConfigSanitizer.canSafelyModify(modifiedContent, "js")) {
|
|
5280
|
+
throw new Error("Failed to safely inject image configuration");
|
|
5281
|
+
}
|
|
5282
|
+
return modifiedContent;
|
|
5283
|
+
}
|
|
5284
|
+
function buildImageConfig() {
|
|
5285
|
+
return ` images: {
|
|
4832
5286
|
remotePatterns: [
|
|
4833
5287
|
{
|
|
4834
5288
|
protocol: 'https',
|
|
@@ -4836,13 +5290,6 @@ ${imageConfig}
|
|
|
4836
5290
|
},
|
|
4837
5291
|
],
|
|
4838
5292
|
},`;
|
|
4839
|
-
modifiedContent = modifiedContent.replace(
|
|
4840
|
-
/(\}\s*)$/,
|
|
4841
|
-
(match) => `${imageConfig}
|
|
4842
|
-
${match}`
|
|
4843
|
-
);
|
|
4844
|
-
}
|
|
4845
|
-
return modifiedContent;
|
|
4846
5293
|
}
|
|
4847
5294
|
|
|
4848
5295
|
// src/plugins/nextjs/middleware.ts
|
|
@@ -15321,6 +15768,7 @@ function getRecommendedPlugins(ctx) {
|
|
|
15321
15768
|
export {
|
|
15322
15769
|
BackupManager,
|
|
15323
15770
|
ConfigWriter,
|
|
15771
|
+
ConfigSanitizer,
|
|
15324
15772
|
pluginRegistry,
|
|
15325
15773
|
getPluginsByCategory,
|
|
15326
15774
|
getRecommendedPlugins
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/core/input-validator.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var projectNameSchema = z.string().min(1, "Project name cannot be empty").max(100, "Project name cannot exceed 100 characters").regex(
|
|
4
|
+
/^[a-zA-Z0-9._-]+$/,
|
|
5
|
+
"Project name can only contain letters, numbers, dots, dashes, and underscores"
|
|
6
|
+
).refine(
|
|
7
|
+
(name) => !name.includes(".."),
|
|
8
|
+
"Project name cannot contain path traversal (..) patterns"
|
|
9
|
+
).refine(
|
|
10
|
+
(name) => !name.includes("/") && !name.includes("\\"),
|
|
11
|
+
"Project name cannot contain path separators"
|
|
12
|
+
).transform((name) => name.trim());
|
|
13
|
+
var svelteSetupSchema = z.object({
|
|
14
|
+
projectName: projectNameSchema,
|
|
15
|
+
useTypeScript: z.boolean()
|
|
16
|
+
});
|
|
17
|
+
var angularSetupSchema = z.object({
|
|
18
|
+
projectName: projectNameSchema,
|
|
19
|
+
useTypeScript: z.boolean(),
|
|
20
|
+
useRouting: z.boolean(),
|
|
21
|
+
useStylesheet: z.enum(["css", "scss", "sass", "less"])
|
|
22
|
+
});
|
|
23
|
+
var vueSetupSchema = z.object({
|
|
24
|
+
projectName: projectNameSchema,
|
|
25
|
+
typescript: z.boolean()
|
|
26
|
+
});
|
|
27
|
+
var nextjsSetupSchema = z.object({
|
|
28
|
+
projectName: projectNameSchema,
|
|
29
|
+
typescript: z.boolean(),
|
|
30
|
+
eslint: z.boolean(),
|
|
31
|
+
tailwind: z.boolean(),
|
|
32
|
+
srcDir: z.boolean(),
|
|
33
|
+
appRouter: z.boolean(),
|
|
34
|
+
importAlias: z.string().min(1, "Import alias cannot be empty").max(50, "Import alias cannot exceed 50 characters").regex(
|
|
35
|
+
/^@[a-zA-Z0-9_/*-]+$/,
|
|
36
|
+
"Import alias must start with @ and contain only valid characters"
|
|
37
|
+
).default("@/*")
|
|
38
|
+
});
|
|
39
|
+
var viteSetupSchema = z.object({
|
|
40
|
+
projectName: projectNameSchema,
|
|
41
|
+
template: z.enum([
|
|
42
|
+
"react",
|
|
43
|
+
"react-ts",
|
|
44
|
+
"vue",
|
|
45
|
+
"vue-ts",
|
|
46
|
+
"svelte",
|
|
47
|
+
"svelte-ts"
|
|
48
|
+
])
|
|
49
|
+
});
|
|
50
|
+
function validateInput(schema, data) {
|
|
51
|
+
try {
|
|
52
|
+
return schema.parse(data);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof z.ZodError) {
|
|
55
|
+
const messages = error.flatten().fieldErrors;
|
|
56
|
+
const errorMessages = [];
|
|
57
|
+
for (const [field, msgs] of Object.entries(messages)) {
|
|
58
|
+
if (msgs && Array.isArray(msgs)) {
|
|
59
|
+
errorMessages.push(`${field}: ${msgs.join(", ")}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Input validation failed: ${errorMessages.join("; ")}`);
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function getValidationErrorMessage(error) {
|
|
68
|
+
if (error instanceof z.ZodError) {
|
|
69
|
+
const messages = error.flatten().fieldErrors;
|
|
70
|
+
const errorMessages = [];
|
|
71
|
+
for (const [field, msgs] of Object.entries(messages)) {
|
|
72
|
+
if (msgs && Array.isArray(msgs)) {
|
|
73
|
+
errorMessages.push(`${field}: ${msgs.join(", ")}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return errorMessages.join("; ");
|
|
77
|
+
}
|
|
78
|
+
if (error instanceof Error) {
|
|
79
|
+
return error.message;
|
|
80
|
+
}
|
|
81
|
+
return "Validation failed";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
svelteSetupSchema,
|
|
86
|
+
angularSetupSchema,
|
|
87
|
+
vueSetupSchema,
|
|
88
|
+
nextjsSetupSchema,
|
|
89
|
+
viteSetupSchema,
|
|
90
|
+
validateInput,
|
|
91
|
+
getValidationErrorMessage
|
|
92
|
+
};
|