@ankurhalder/codebase-snapshot 2.0.0 → 2.1.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 +42 -0
- package/dist/bin.js +2330 -367
- package/dist/{chunk-RGDVLQTT.js → chunk-QEIOXPMU.js} +2242 -366
- package/dist/cli.cjs +2346 -390
- package/dist/cli.d.cts +9 -1
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +108 -27
- package/dist/index.cjs +2255 -365
- package/dist/index.d.cts +183 -6
- package/dist/index.d.ts +183 -6
- package/dist/index.js +1 -1
- package/dist/{types-S-5eRuRc.d.cts → types-CJqkePM4.d.cts} +20 -1
- package/dist/{types-S-5eRuRc.d.ts → types-CJqkePM4.d.ts} +20 -1
- package/docs/configuration.md +97 -1
- package/docs/install.md +136 -0
- package/package.json +3 -2
|
@@ -1,134 +1,497 @@
|
|
|
1
1
|
import * as crypto from 'crypto';
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as fs9 from 'fs';
|
|
3
|
+
import * as path10 from 'path';
|
|
4
4
|
import ts from 'typescript';
|
|
5
5
|
import { pathToFileURL } from 'url';
|
|
6
6
|
import * as childProcess from 'child_process';
|
|
7
|
+
import * as readline from 'readline';
|
|
7
8
|
|
|
8
9
|
// src/index.ts
|
|
9
10
|
|
|
10
|
-
// src/ast/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
// src/ast/profiles/shared.ts
|
|
12
|
+
var MAX_SUMMARY_CHARS = 1e6;
|
|
13
|
+
function emptySummary() {
|
|
14
|
+
return {
|
|
15
|
+
directives: [],
|
|
16
|
+
imports: [],
|
|
17
|
+
exports: [],
|
|
18
|
+
reExports: [],
|
|
19
|
+
functions: [],
|
|
20
|
+
classes: [],
|
|
21
|
+
interfaces: [],
|
|
22
|
+
types: [],
|
|
23
|
+
enums: [],
|
|
24
|
+
variables: [],
|
|
25
|
+
reactComponents: [],
|
|
26
|
+
hooks: [],
|
|
27
|
+
routeHandlers: []
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function dedupeSorted(values) {
|
|
31
|
+
return Array.from(new Set(values.filter(Boolean))).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
32
|
+
}
|
|
33
|
+
function finalizeSummary(summary) {
|
|
34
|
+
return {
|
|
35
|
+
directives: dedupeSorted(summary.directives),
|
|
36
|
+
imports: dedupeSorted(summary.imports),
|
|
37
|
+
exports: dedupeSorted(summary.exports),
|
|
38
|
+
reExports: dedupeSorted(summary.reExports),
|
|
39
|
+
functions: dedupeSorted(summary.functions),
|
|
40
|
+
classes: dedupeSorted(summary.classes),
|
|
41
|
+
interfaces: dedupeSorted(summary.interfaces),
|
|
42
|
+
types: dedupeSorted(summary.types),
|
|
43
|
+
enums: dedupeSorted(summary.enums),
|
|
44
|
+
variables: dedupeSorted(summary.variables),
|
|
45
|
+
reactComponents: dedupeSorted(summary.reactComponents),
|
|
46
|
+
hooks: dedupeSorted(summary.hooks),
|
|
47
|
+
routeHandlers: dedupeSorted(summary.routeHandlers)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function eachLine(content, visit) {
|
|
51
|
+
const text2 = content.length > MAX_SUMMARY_CHARS ? content.slice(0, MAX_SUMMARY_CHARS) : content;
|
|
52
|
+
for (const line of text2.split(/\r?\n/)) visit(line);
|
|
53
|
+
}
|
|
54
|
+
function isTopLevel(line) {
|
|
55
|
+
return !/^[\s]/.test(line);
|
|
56
|
+
}
|
|
57
|
+
function isPublicName(name) {
|
|
58
|
+
return name.length > 0 && !name.startsWith("_") && /^[A-Za-z]/.test(name);
|
|
59
|
+
}
|
|
60
|
+
function isCapitalizedName(name) {
|
|
61
|
+
return /^[A-Z]/.test(name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/ast/profiles/cfamily.ts
|
|
65
|
+
var INCLUDE = /^#\s*include\s*[<"]([^>"]+)[>"]/;
|
|
66
|
+
var DEFINE = /^#\s*define\s+([A-Za-z_]\w*)/;
|
|
67
|
+
var USING = /^using\s+(?:namespace\s+)?([\w:]+)\s*;/;
|
|
68
|
+
var TYPE_DECL = /^(?:typedef\s+)?(?:struct|union|class|enum)\s+([A-Za-z_]\w*)/;
|
|
69
|
+
var TYPEDEF = /^typedef\b[^;{}]*?\b([A-Za-z_]\w*)\s*;/;
|
|
70
|
+
var FUNCTION = /^[A-Za-z_][\w \t*&:<>,]*?\b([A-Za-z_]\w*)\s*\([^;]*\)\s*\{?\s*$/;
|
|
71
|
+
var NOT_A_FUNCTION = /^(?:if|for|while|switch|return|else|do|case|sizeof|catch|throw)\b/;
|
|
72
|
+
var cfamilyProfile = {
|
|
73
|
+
id: "cfamily",
|
|
74
|
+
extensions: [".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hxx", ".m", ".mm"],
|
|
75
|
+
summarize(_path, content) {
|
|
76
|
+
const summary = emptySummary();
|
|
77
|
+
eachLine(content, (line) => {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
80
|
+
return;
|
|
24
81
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
inBlockComment = false;
|
|
30
|
-
i += 1;
|
|
82
|
+
const included = INCLUDE.exec(trimmed);
|
|
83
|
+
if (included) {
|
|
84
|
+
summary.imports.push(included[1]);
|
|
85
|
+
return;
|
|
31
86
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
87
|
+
const used = USING.exec(trimmed);
|
|
88
|
+
if (used) {
|
|
89
|
+
summary.imports.push(used[1]);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const macro = DEFINE.exec(trimmed);
|
|
93
|
+
if (macro) {
|
|
94
|
+
summary.variables.push(macro[1]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!isTopLevel(line)) return;
|
|
98
|
+
const declared = TYPE_DECL.exec(line);
|
|
99
|
+
if (declared) {
|
|
100
|
+
const kind = /^typedef\s+enum/.test(line) || /^enum\b/.test(trimmed) ? "enum" : "type";
|
|
101
|
+
if (kind === "enum") summary.enums.push(declared[1]);
|
|
102
|
+
else summary.classes.push(declared[1]);
|
|
103
|
+
summary.exports.push(declared[1]);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const aliased = TYPEDEF.exec(line);
|
|
107
|
+
if (aliased) {
|
|
108
|
+
summary.types.push(aliased[1]);
|
|
109
|
+
summary.exports.push(aliased[1]);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (NOT_A_FUNCTION.test(trimmed)) return;
|
|
113
|
+
const fn = FUNCTION.exec(line);
|
|
114
|
+
if (fn) {
|
|
115
|
+
summary.functions.push(fn[1]);
|
|
116
|
+
summary.exports.push(fn[1]);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/ast/profiles/dotnet.ts
|
|
124
|
+
var USING2 = /^using\s+([\w.]+)\s*;/;
|
|
125
|
+
var TYPE_DECL2 = /^\s*(?:public\s+|internal\s+|private\s+|protected\s+|sealed\s+|abstract\s+|static\s+|partial\s+|readonly\s+)*(class|interface|struct|record|enum)\s+([A-Za-z_]\w*)/;
|
|
126
|
+
var METHOD = /^\s*(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:virtual\s+|override\s+|sealed\s+)?[\w<>[\],?]+\s+([A-Za-z_]\w*)\s*[<(]/;
|
|
127
|
+
var dotnetProfile = {
|
|
128
|
+
id: "dotnet",
|
|
129
|
+
extensions: [".cs"],
|
|
130
|
+
summarize(_path, content) {
|
|
131
|
+
const summary = emptySummary();
|
|
132
|
+
eachLine(content, (line) => {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const used = USING2.exec(trimmed);
|
|
138
|
+
if (used) {
|
|
139
|
+
summary.imports.push(used[1]);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const declaration = TYPE_DECL2.exec(line);
|
|
143
|
+
if (declaration) {
|
|
144
|
+
const kind = declaration[1];
|
|
145
|
+
const name = declaration[2];
|
|
146
|
+
if (kind === "interface") summary.interfaces.push(name);
|
|
147
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
148
|
+
else summary.classes.push(name);
|
|
149
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(name);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const method = METHOD.exec(line);
|
|
153
|
+
if (method) {
|
|
154
|
+
summary.functions.push(method[1]);
|
|
155
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/ast/profiles/go.ts
|
|
163
|
+
var FUNC = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/;
|
|
164
|
+
var TYPE = /^type\s+([A-Za-z_]\w*)\s+(.*)$/;
|
|
165
|
+
var VAR_CONST = /^(?:var|const)\s+([A-Za-z_]\w*)/;
|
|
166
|
+
var IMPORT_SINGLE = /^import\s+(?:[\w.]+\s+)?"([^"]+)"/;
|
|
167
|
+
var IMPORT_OPEN = /^import\s*\(/;
|
|
168
|
+
var IMPORT_ENTRY = /^\s*(?:[\w.]+\s+)?"([^"]+)"/;
|
|
169
|
+
var goProfile = {
|
|
170
|
+
id: "go",
|
|
171
|
+
extensions: [".go"],
|
|
172
|
+
summarize(_path, content) {
|
|
173
|
+
const summary = emptySummary();
|
|
174
|
+
let inImportBlock = false;
|
|
175
|
+
eachLine(content, (line) => {
|
|
176
|
+
const trimmed = line.trim();
|
|
177
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
178
|
+
if (inImportBlock) {
|
|
179
|
+
if (trimmed.startsWith(")")) {
|
|
180
|
+
inImportBlock = false;
|
|
181
|
+
return;
|
|
40
182
|
}
|
|
41
|
-
|
|
42
|
-
|
|
183
|
+
const entry = IMPORT_ENTRY.exec(line);
|
|
184
|
+
if (entry) summary.imports.push(entry[1]);
|
|
185
|
+
return;
|
|
43
186
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
187
|
+
if (IMPORT_OPEN.test(trimmed)) {
|
|
188
|
+
inImportBlock = true;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const single = IMPORT_SINGLE.exec(trimmed);
|
|
192
|
+
if (single) {
|
|
193
|
+
summary.imports.push(single[1]);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!isTopLevel(line)) return;
|
|
197
|
+
const method = FUNC.exec(line);
|
|
198
|
+
if (method) {
|
|
199
|
+
const name = method[1];
|
|
200
|
+
summary.functions.push(name);
|
|
201
|
+
if (isCapitalizedName(name)) summary.exports.push(name);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const declared = TYPE.exec(line);
|
|
205
|
+
if (declared) {
|
|
206
|
+
const name = declared[1];
|
|
207
|
+
const body = declared[2].trim();
|
|
208
|
+
if (body.startsWith("struct")) summary.classes.push(name);
|
|
209
|
+
else if (body.startsWith("interface")) summary.interfaces.push(name);
|
|
210
|
+
else summary.types.push(name);
|
|
211
|
+
if (isCapitalizedName(name)) summary.exports.push(name);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const value = VAR_CONST.exec(line);
|
|
215
|
+
if (value) {
|
|
216
|
+
summary.variables.push(value[1]);
|
|
217
|
+
if (isCapitalizedName(value[1])) summary.exports.push(value[1]);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
62
221
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/ast/profiles/jvm.ts
|
|
225
|
+
var IMPORT = /^import\s+(?:static\s+)?([\w.]+?)(?:\.\*)?;?\s*$/;
|
|
226
|
+
var TYPE_DECL3 = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:final\s+|abstract\s+|open\s+|sealed\s+|data\s+|static\s+|partial\s+)*(class|interface|enum|record|object|trait)\s+(?:class\s+)?([A-Za-z_]\w*)/;
|
|
227
|
+
var KOTLIN_FUN = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+|override\s+|suspend\s+|inline\s+|open\s+|operator\s+)*fun\s+(?:<[^>]*>\s*)?(?:[\w.]+\.)?([A-Za-z_]\w*)/;
|
|
228
|
+
var SCALA_DEF = /^\s*def\s+([A-Za-z_]\w*)/;
|
|
229
|
+
var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?[\w<>[\],.?]+\s+([A-Za-z_]\w*)\s*\(/;
|
|
230
|
+
var jvmProfile = {
|
|
231
|
+
id: "jvm",
|
|
232
|
+
extensions: [".java", ".kt", ".kts", ".scala"],
|
|
233
|
+
summarize(path11, content) {
|
|
234
|
+
const summary = emptySummary();
|
|
235
|
+
const lower = path11.toLowerCase();
|
|
236
|
+
const isJava = lower.endsWith(".java");
|
|
237
|
+
const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
|
|
238
|
+
eachLine(content, (line) => {
|
|
239
|
+
const trimmed = line.trim();
|
|
240
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const imported = IMPORT.exec(trimmed);
|
|
244
|
+
if (imported) {
|
|
245
|
+
summary.imports.push(imported[1]);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const declaration = TYPE_DECL3.exec(line);
|
|
249
|
+
if (declaration) {
|
|
250
|
+
const kind = declaration[1];
|
|
251
|
+
const name = declaration[2];
|
|
252
|
+
if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
|
|
253
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
254
|
+
else summary.classes.push(name);
|
|
255
|
+
const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
|
|
256
|
+
if (isJava ? /^\s*public\b/.test(line) : !hidden) summary.exports.push(name);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const kotlinFunction = KOTLIN_FUN.exec(line);
|
|
260
|
+
if (kotlinFunction && isKotlin) {
|
|
261
|
+
summary.functions.push(kotlinFunction[1]);
|
|
262
|
+
const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
|
|
263
|
+
if (!hidden) summary.exports.push(kotlinFunction[1]);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const scalaFunction = SCALA_DEF.exec(line);
|
|
267
|
+
if (scalaFunction && !isJava && !isKotlin) {
|
|
268
|
+
summary.functions.push(scalaFunction[1]);
|
|
269
|
+
const hidden = /^\s*(?:private|protected)\b/.test(line);
|
|
270
|
+
if (!hidden) summary.exports.push(scalaFunction[1]);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (isJava) {
|
|
274
|
+
const method = JAVA_METHOD.exec(line);
|
|
275
|
+
if (method) {
|
|
276
|
+
summary.functions.push(method[1]);
|
|
277
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
100
282
|
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
continue;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/ast/profiles/php.ts
|
|
286
|
+
var USE = /^use\s+([\w\\]+)\s*(?:as\s+\w+)?;/;
|
|
287
|
+
var DECLARE = /^(?:abstract\s+|final\s+|readonly\s+)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/;
|
|
288
|
+
var FUNCTION2 = /^(?:public\s+|protected\s+|private\s+|static\s+|final\s+|abstract\s+)*function\s+([A-Za-z_]\w*)/;
|
|
289
|
+
var phpProfile = {
|
|
290
|
+
id: "php",
|
|
291
|
+
extensions: [".php"],
|
|
292
|
+
summarize(_path, content) {
|
|
293
|
+
const summary = emptySummary();
|
|
294
|
+
eachLine(content, (line) => {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*")) {
|
|
297
|
+
return;
|
|
117
298
|
}
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
299
|
+
const used = USE.exec(trimmed);
|
|
300
|
+
if (used) {
|
|
301
|
+
summary.imports.push(used[1].replace(/\\\\/g, "\\"));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const declaration = DECLARE.exec(trimmed);
|
|
305
|
+
if (declaration) {
|
|
306
|
+
const kind = declaration[1];
|
|
307
|
+
const name = declaration[2];
|
|
308
|
+
if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
|
|
309
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
310
|
+
else summary.classes.push(name);
|
|
311
|
+
summary.exports.push(name);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const fn = FUNCTION2.exec(trimmed);
|
|
315
|
+
if (fn) {
|
|
316
|
+
summary.functions.push(fn[1]);
|
|
317
|
+
if (!/^private\b/.test(trimmed)) summary.exports.push(fn[1]);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
126
321
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// src/ast/profiles/python.ts
|
|
325
|
+
var IMPORT2 = /^import\s+(.+)$/;
|
|
326
|
+
var FROM_IMPORT = /^from\s+([\w.]+)\s+import\s+(.+)$/;
|
|
327
|
+
var CLASS = /^class\s+([A-Za-z_]\w*)/;
|
|
328
|
+
var DEF = /^(?:async\s+)?def\s+([A-Za-z_]\w*)/;
|
|
329
|
+
var CONSTANT = /^([A-Z_][A-Z0-9_]*)\s*(?::[^=]+)?=/;
|
|
330
|
+
var ALL_DUNDER = /^__all__\s*=\s*\[([^\]]*)\]/;
|
|
331
|
+
var ROUTE_DECORATOR = /^\s*@[\w.]*\b(?:get|post|put|patch|delete|head|options|route)\s*\(/i;
|
|
332
|
+
var pythonProfile = {
|
|
333
|
+
id: "python",
|
|
334
|
+
extensions: [".py", ".pyi"],
|
|
335
|
+
summarize(_path, content) {
|
|
336
|
+
const summary = emptySummary();
|
|
337
|
+
let routePending = false;
|
|
338
|
+
eachLine(content, (line) => {
|
|
339
|
+
const trimmed = line.trim();
|
|
340
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
341
|
+
if (ROUTE_DECORATOR.test(line)) {
|
|
342
|
+
routePending = true;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const from = FROM_IMPORT.exec(trimmed);
|
|
346
|
+
if (from) {
|
|
347
|
+
summary.imports.push(from[1]);
|
|
348
|
+
routePending = false;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const imported = IMPORT2.exec(trimmed);
|
|
352
|
+
if (imported) {
|
|
353
|
+
for (const part of imported[1].split(",")) {
|
|
354
|
+
const name = part.trim().split(/\s+as\s+/)[0].trim();
|
|
355
|
+
if (name) summary.imports.push(name);
|
|
356
|
+
}
|
|
357
|
+
routePending = false;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (isTopLevel(line)) {
|
|
361
|
+
const klass = CLASS.exec(line);
|
|
362
|
+
if (klass) {
|
|
363
|
+
summary.classes.push(klass[1]);
|
|
364
|
+
if (isPublicName(klass[1])) summary.exports.push(klass[1]);
|
|
365
|
+
routePending = false;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const definition = DEF.exec(line);
|
|
369
|
+
if (definition) {
|
|
370
|
+
const name = definition[1];
|
|
371
|
+
summary.functions.push(name);
|
|
372
|
+
if (routePending) {
|
|
373
|
+
summary.routeHandlers.push(name);
|
|
374
|
+
routePending = false;
|
|
375
|
+
}
|
|
376
|
+
if (isPublicName(name)) summary.exports.push(name);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const all = ALL_DUNDER.exec(trimmed);
|
|
380
|
+
if (all) {
|
|
381
|
+
for (const entry of all[1].split(",")) {
|
|
382
|
+
const name = entry.trim().replace(/['"]/g, "").trim();
|
|
383
|
+
if (name) summary.exports.push(name);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const constant = CONSTANT.exec(line);
|
|
388
|
+
if (constant) summary.variables.push(constant[1]);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/ast/profiles/ruby.ts
|
|
396
|
+
var REQUIRE = /^require(?:_relative)?\s+["']([^"']+)["']/;
|
|
397
|
+
var CLASS_OR_MODULE = /^(?:class|module)\s+([A-Z][\w:]*)/;
|
|
398
|
+
var METHOD2 = /^def\s+(?:self\.)?([A-Za-z_]\w*[?!=]?)/;
|
|
399
|
+
var rubyProfile = {
|
|
400
|
+
id: "ruby",
|
|
401
|
+
extensions: [".rb", ".rake", ".gemspec"],
|
|
402
|
+
summarize(_path, content) {
|
|
403
|
+
const summary = emptySummary();
|
|
404
|
+
eachLine(content, (line) => {
|
|
405
|
+
const trimmed = line.trim();
|
|
406
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
407
|
+
const required = REQUIRE.exec(trimmed);
|
|
408
|
+
if (required) {
|
|
409
|
+
summary.imports.push(required[1]);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (!isTopLevel(line)) return;
|
|
413
|
+
const declaration = CLASS_OR_MODULE.exec(line);
|
|
414
|
+
if (declaration) {
|
|
415
|
+
summary.classes.push(declaration[1]);
|
|
416
|
+
summary.exports.push(declaration[1]);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const method = METHOD2.exec(line);
|
|
420
|
+
if (method) {
|
|
421
|
+
summary.functions.push(method[1]);
|
|
422
|
+
summary.exports.push(method[1]);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/ast/profiles/rust.ts
|
|
430
|
+
var USE2 = /^use\s+([^;]+);/;
|
|
431
|
+
var FN = /^(pub(?:\([^)]*\))?\s+)?(?:const\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+([A-Za-z_]\w*)/;
|
|
432
|
+
var STRUCT = /^(pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)/;
|
|
433
|
+
var TRAIT = /^(pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?trait\s+([A-Za-z_]\w*)/;
|
|
434
|
+
var ENUM = /^(pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)/;
|
|
435
|
+
var TYPE2 = /^(pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)/;
|
|
436
|
+
var MOD = /^(pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)/;
|
|
437
|
+
var IMPL = /^impl(?:<[^>]*>)?\s+(?:[\w:<>]+\s+for\s+)?([A-Za-z_]\w*)/;
|
|
438
|
+
var rustProfile = {
|
|
439
|
+
id: "rust",
|
|
440
|
+
extensions: [".rs"],
|
|
441
|
+
summarize(_path, content) {
|
|
442
|
+
const summary = emptySummary();
|
|
443
|
+
eachLine(content, (line) => {
|
|
444
|
+
const trimmed = line.trim();
|
|
445
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
446
|
+
const used = USE2.exec(trimmed);
|
|
447
|
+
if (used) {
|
|
448
|
+
summary.imports.push(used[1].trim());
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (!isTopLevel(line)) return;
|
|
452
|
+
const isPublic = /^pub\b/.test(trimmed);
|
|
453
|
+
const fn = FN.exec(line);
|
|
454
|
+
if (fn) {
|
|
455
|
+
summary.functions.push(fn[2]);
|
|
456
|
+
if (isPublic) summary.exports.push(fn[2]);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const struct = STRUCT.exec(line);
|
|
460
|
+
if (struct) {
|
|
461
|
+
summary.classes.push(struct[2]);
|
|
462
|
+
if (isPublic) summary.exports.push(struct[2]);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const trait = TRAIT.exec(line);
|
|
466
|
+
if (trait) {
|
|
467
|
+
summary.interfaces.push(trait[2]);
|
|
468
|
+
if (isPublic) summary.exports.push(trait[2]);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const enumeration = ENUM.exec(line);
|
|
472
|
+
if (enumeration) {
|
|
473
|
+
summary.enums.push(enumeration[2]);
|
|
474
|
+
if (isPublic) summary.exports.push(enumeration[2]);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const alias = TYPE2.exec(line);
|
|
478
|
+
if (alias) {
|
|
479
|
+
summary.types.push(alias[2]);
|
|
480
|
+
if (isPublic) summary.exports.push(alias[2]);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const module = MOD.exec(line);
|
|
484
|
+
if (module) {
|
|
485
|
+
summary.variables.push(module[2]);
|
|
486
|
+
if (isPublic) summary.exports.push(module[2]);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const implementation = IMPL.exec(line);
|
|
490
|
+
if (implementation) summary.classes.push(implementation[1]);
|
|
491
|
+
});
|
|
492
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
493
|
+
}
|
|
494
|
+
};
|
|
132
495
|
var ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
133
496
|
"GET",
|
|
134
497
|
"POST",
|
|
@@ -168,9 +531,9 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
168
531
|
if (parseErrors > 0) summary.parseErrors = parseErrors;
|
|
169
532
|
for (const statement of sourceFile.statements) {
|
|
170
533
|
if (ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)) {
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
summary.directives.push(
|
|
534
|
+
const text2 = statement.expression.text;
|
|
535
|
+
if (text2 === "use client" || text2 === "use server") {
|
|
536
|
+
summary.directives.push(text2);
|
|
174
537
|
}
|
|
175
538
|
continue;
|
|
176
539
|
}
|
|
@@ -270,23 +633,344 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
270
633
|
stack.push(children[index]);
|
|
271
634
|
}
|
|
272
635
|
}
|
|
273
|
-
const
|
|
274
|
-
summary.directives =
|
|
275
|
-
summary.imports =
|
|
276
|
-
summary.exports =
|
|
277
|
-
summary.reExports =
|
|
278
|
-
summary.functions =
|
|
279
|
-
summary.classes =
|
|
280
|
-
summary.interfaces =
|
|
281
|
-
summary.types =
|
|
282
|
-
summary.enums =
|
|
283
|
-
summary.variables =
|
|
284
|
-
summary.reactComponents =
|
|
285
|
-
summary.hooks =
|
|
286
|
-
summary.routeHandlers =
|
|
636
|
+
const dedupeSorted2 = (values) => Array.from(new Set(values)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
637
|
+
summary.directives = dedupeSorted2(summary.directives);
|
|
638
|
+
summary.imports = dedupeSorted2(summary.imports);
|
|
639
|
+
summary.exports = dedupeSorted2(summary.exports);
|
|
640
|
+
summary.reExports = dedupeSorted2(summary.reExports);
|
|
641
|
+
summary.functions = dedupeSorted2(summary.functions);
|
|
642
|
+
summary.classes = dedupeSorted2(summary.classes);
|
|
643
|
+
summary.interfaces = dedupeSorted2(summary.interfaces);
|
|
644
|
+
summary.types = dedupeSorted2(summary.types);
|
|
645
|
+
summary.enums = dedupeSorted2(summary.enums);
|
|
646
|
+
summary.variables = dedupeSorted2(summary.variables);
|
|
647
|
+
summary.reactComponents = dedupeSorted2(summary.reactComponents);
|
|
648
|
+
summary.hooks = dedupeSorted2(summary.hooks);
|
|
649
|
+
summary.routeHandlers = dedupeSorted2(summary.routeHandlers);
|
|
287
650
|
return summary;
|
|
288
651
|
}
|
|
289
652
|
|
|
653
|
+
// src/ast/profiles/sfc.ts
|
|
654
|
+
var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
655
|
+
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
656
|
+
function componentName(path11) {
|
|
657
|
+
const base = path11.split("/").pop() ?? path11;
|
|
658
|
+
return base.replace(/\.[^.]+$/, "");
|
|
659
|
+
}
|
|
660
|
+
var sfcProfile = {
|
|
661
|
+
id: "sfc",
|
|
662
|
+
extensions: [".vue", ".svelte", ".astro"],
|
|
663
|
+
summarize(path11, content) {
|
|
664
|
+
let script = "";
|
|
665
|
+
if (path11.toLowerCase().endsWith(".astro")) {
|
|
666
|
+
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
667
|
+
if (frontmatter) script += `${frontmatter[1]}
|
|
668
|
+
`;
|
|
669
|
+
}
|
|
670
|
+
for (const match of content.matchAll(SCRIPT_BLOCK)) {
|
|
671
|
+
script += `${match[1]}
|
|
672
|
+
`;
|
|
673
|
+
}
|
|
674
|
+
const name = componentName(path11);
|
|
675
|
+
if (!script.trim()) {
|
|
676
|
+
const summary2 = emptySummary();
|
|
677
|
+
if (name) {
|
|
678
|
+
summary2.exports.push(name);
|
|
679
|
+
summary2.reactComponents.push(name);
|
|
680
|
+
}
|
|
681
|
+
return { summary: finalizeSummary(summary2), quality: "heuristic" };
|
|
682
|
+
}
|
|
683
|
+
const summary = summarizeTypeScriptSource(`${name}.ts`, script);
|
|
684
|
+
summary.reactComponents.push(name);
|
|
685
|
+
summary.exports.push(name);
|
|
686
|
+
return { summary, quality: "ast" };
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
// src/ast/profiles/index.ts
|
|
691
|
+
var LANGUAGE_PROFILES = [
|
|
692
|
+
pythonProfile,
|
|
693
|
+
goProfile,
|
|
694
|
+
rustProfile,
|
|
695
|
+
jvmProfile,
|
|
696
|
+
dotnetProfile,
|
|
697
|
+
phpProfile,
|
|
698
|
+
rubyProfile,
|
|
699
|
+
cfamilyProfile,
|
|
700
|
+
sfcProfile
|
|
701
|
+
];
|
|
702
|
+
var BY_EXTENSION = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const profile of LANGUAGE_PROFILES) {
|
|
704
|
+
for (const extension of profile.extensions) {
|
|
705
|
+
BY_EXTENSION.set(extension.toLowerCase(), profile);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function profileForPath(path11) {
|
|
709
|
+
const lower = path11.toLowerCase();
|
|
710
|
+
const dot = lower.lastIndexOf(".");
|
|
711
|
+
if (dot === -1) return null;
|
|
712
|
+
return BY_EXTENSION.get(lower.slice(dot)) ?? null;
|
|
713
|
+
}
|
|
714
|
+
function summarizeWithProfile(path11, content) {
|
|
715
|
+
const profile = profileForPath(path11);
|
|
716
|
+
return profile ? profile.summarize(path11, content) : null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/ast/structured.ts
|
|
720
|
+
function stripJsonComments(content) {
|
|
721
|
+
const text2 = content.replace(/^\uFEFF/, "");
|
|
722
|
+
let out = "";
|
|
723
|
+
let inString = false;
|
|
724
|
+
let inLineComment = false;
|
|
725
|
+
let inBlockComment = false;
|
|
726
|
+
for (let i = 0; i < text2.length; i++) {
|
|
727
|
+
const char = text2[i];
|
|
728
|
+
const next = text2[i + 1];
|
|
729
|
+
if (inLineComment) {
|
|
730
|
+
if (char === "\n") {
|
|
731
|
+
inLineComment = false;
|
|
732
|
+
out += char;
|
|
733
|
+
}
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (inBlockComment) {
|
|
737
|
+
if (char === "*" && next === "/") {
|
|
738
|
+
inBlockComment = false;
|
|
739
|
+
i += 1;
|
|
740
|
+
}
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (inString) {
|
|
744
|
+
out += char;
|
|
745
|
+
if (char === "\\") {
|
|
746
|
+
if (next !== void 0) {
|
|
747
|
+
out += next;
|
|
748
|
+
i += 1;
|
|
749
|
+
}
|
|
750
|
+
} else if (char === '"') {
|
|
751
|
+
inString = false;
|
|
752
|
+
}
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (char === '"') {
|
|
756
|
+
inString = true;
|
|
757
|
+
out += char;
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (char === "/" && next === "/") {
|
|
761
|
+
inLineComment = true;
|
|
762
|
+
i += 1;
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (char === "/" && next === "*") {
|
|
766
|
+
inBlockComment = true;
|
|
767
|
+
i += 1;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
out += char;
|
|
771
|
+
}
|
|
772
|
+
return out;
|
|
773
|
+
}
|
|
774
|
+
function summarizeJson(content) {
|
|
775
|
+
try {
|
|
776
|
+
const parsed = JSON.parse(stripJsonComments(content));
|
|
777
|
+
if (Array.isArray(parsed)) {
|
|
778
|
+
const sample = parsed[0];
|
|
779
|
+
const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
|
|
780
|
+
return {
|
|
781
|
+
shape: "array",
|
|
782
|
+
itemCount: parsed.length,
|
|
783
|
+
keys: [],
|
|
784
|
+
sampleKeys
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
if (parsed && typeof parsed === "object") {
|
|
788
|
+
const keys = Object.keys(parsed).sort();
|
|
789
|
+
return {
|
|
790
|
+
shape: "object",
|
|
791
|
+
itemCount: keys.length,
|
|
792
|
+
keys: keys.slice(0, 30),
|
|
793
|
+
sampleKeys: []
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
shape: typeof parsed,
|
|
798
|
+
itemCount: null,
|
|
799
|
+
keys: [],
|
|
800
|
+
sampleKeys: []
|
|
801
|
+
};
|
|
802
|
+
} catch {
|
|
803
|
+
return {
|
|
804
|
+
shape: "invalid-json",
|
|
805
|
+
itemCount: null,
|
|
806
|
+
keys: [],
|
|
807
|
+
sampleKeys: []
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function summarizeMarkdown(content) {
|
|
812
|
+
const headings = [];
|
|
813
|
+
const frontmatterKeys = [];
|
|
814
|
+
const lines = content.replace(/^\uFEFF/, "").split("\n");
|
|
815
|
+
let inFrontmatter = false;
|
|
816
|
+
for (let index = 0; index < lines.length; index++) {
|
|
817
|
+
const line = lines[index];
|
|
818
|
+
if (index === 0 && line.trim() === "---") {
|
|
819
|
+
inFrontmatter = true;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
if (inFrontmatter) {
|
|
823
|
+
if (line.trim() === "---") {
|
|
824
|
+
inFrontmatter = false;
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
const match = /^([a-zA-Z0-9_-]+):/.exec(line);
|
|
828
|
+
if (match) frontmatterKeys.push(match[1]);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
|
|
832
|
+
if (headingMatch) {
|
|
833
|
+
headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
headings: headings.slice(0, 40),
|
|
838
|
+
frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
var MANIFEST_FILES = [
|
|
842
|
+
"package.json",
|
|
843
|
+
"pyproject.toml",
|
|
844
|
+
"requirements.txt",
|
|
845
|
+
"setup.py",
|
|
846
|
+
"go.mod",
|
|
847
|
+
"Cargo.toml",
|
|
848
|
+
"pom.xml",
|
|
849
|
+
"build.gradle",
|
|
850
|
+
"build.gradle.kts",
|
|
851
|
+
"composer.json",
|
|
852
|
+
"Gemfile"
|
|
853
|
+
];
|
|
854
|
+
var MANIFEST_MAX_BYTES = 1e5;
|
|
855
|
+
function readManifest(cwd, name) {
|
|
856
|
+
const full = path10.join(cwd, name);
|
|
857
|
+
try {
|
|
858
|
+
const stats = fs9.statSync(full);
|
|
859
|
+
if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
|
|
860
|
+
return fs9.readFileSync(full, "utf8");
|
|
861
|
+
} catch {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function collectDependencies(manifests) {
|
|
866
|
+
const names = /* @__PURE__ */ new Set();
|
|
867
|
+
const raw = manifests["package.json"];
|
|
868
|
+
if (!raw) return names;
|
|
869
|
+
try {
|
|
870
|
+
const parsed = JSON.parse(raw);
|
|
871
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
872
|
+
const block = parsed[section];
|
|
873
|
+
if (block && typeof block === "object") {
|
|
874
|
+
for (const name of Object.keys(block)) names.add(name);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
return names;
|
|
880
|
+
}
|
|
881
|
+
function buildPresetContext(cwd) {
|
|
882
|
+
const files = /* @__PURE__ */ new Set();
|
|
883
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
884
|
+
try {
|
|
885
|
+
for (const entry of fs9.readdirSync(cwd, { withFileTypes: true })) {
|
|
886
|
+
if (entry.isDirectory()) dirs.add(entry.name);
|
|
887
|
+
else if (entry.isFile()) files.add(entry.name);
|
|
888
|
+
}
|
|
889
|
+
} catch {
|
|
890
|
+
}
|
|
891
|
+
const manifests = {};
|
|
892
|
+
for (const name of MANIFEST_FILES) {
|
|
893
|
+
const content = readManifest(cwd, name);
|
|
894
|
+
if (content !== null) manifests[name] = content;
|
|
895
|
+
}
|
|
896
|
+
return {
|
|
897
|
+
cwd,
|
|
898
|
+
files,
|
|
899
|
+
dirs,
|
|
900
|
+
dependencies: collectDependencies(manifests),
|
|
901
|
+
manifests
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function hasFile(context, name) {
|
|
905
|
+
return context.files.has(name);
|
|
906
|
+
}
|
|
907
|
+
function hasFileStartingWith(context, prefix) {
|
|
908
|
+
for (const file of context.files) {
|
|
909
|
+
if (file.startsWith(prefix)) return true;
|
|
910
|
+
}
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
function manifestContains(context, manifest, needle) {
|
|
914
|
+
const content = context.manifests[manifest];
|
|
915
|
+
if (!content) return false;
|
|
916
|
+
return content.toLowerCase().includes(needle.toLowerCase());
|
|
917
|
+
}
|
|
918
|
+
function hasDependency(context, ...names) {
|
|
919
|
+
return names.some((name) => context.dependencies.has(name));
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/presets/helpers.ts
|
|
923
|
+
function layer(name, ...prefixes) {
|
|
924
|
+
return {
|
|
925
|
+
name,
|
|
926
|
+
match: (filePath) => prefixes.some((prefix) => filePath.startsWith(prefix))
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function kindOf(name, ...needles) {
|
|
930
|
+
return {
|
|
931
|
+
kind: name,
|
|
932
|
+
match: (filePath) => needles.some((needle) => filePath.includes(needle))
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function suffixKind(name, ...suffixes) {
|
|
936
|
+
return {
|
|
937
|
+
kind: name,
|
|
938
|
+
match: (filePath) => suffixes.some((suffix) => filePath.endsWith(suffix))
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function commonLayers() {
|
|
942
|
+
return [
|
|
943
|
+
layer("scripts", "scripts/", "tools/"),
|
|
944
|
+
layer("docs", "docs/", "documentation/"),
|
|
945
|
+
{
|
|
946
|
+
name: "config",
|
|
947
|
+
match: (filePath) => filePath.startsWith(".github/") || filePath.startsWith(".husky/") || filePath.startsWith(".vscode/") || filePath.startsWith(".") || filePath.includes("config")
|
|
948
|
+
},
|
|
949
|
+
{ name: "root", match: (filePath) => !filePath.includes("/") }
|
|
950
|
+
];
|
|
951
|
+
}
|
|
952
|
+
function commonKinds() {
|
|
953
|
+
return [
|
|
954
|
+
{
|
|
955
|
+
kind: "test",
|
|
956
|
+
match: (filePath) => {
|
|
957
|
+
const lower = filePath.toLowerCase();
|
|
958
|
+
return lower.includes(".test.") || lower.includes(".spec.") || lower.startsWith("test/") || lower.startsWith("tests/") || lower.includes("_test.") || lower.endsWith("_test.go") || lower.includes("__tests__");
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
suffixKind("doc", ".md", ".mdx", ".rst", ".txt"),
|
|
962
|
+
suffixKind("data", ".json", ".yaml", ".yml", ".csv", ".toml"),
|
|
963
|
+
{
|
|
964
|
+
kind: "script",
|
|
965
|
+
match: (filePath) => filePath.startsWith("scripts/") || /\.(sh|ps1|bat)$/i.test(filePath)
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
kind: "config",
|
|
969
|
+
match: (filePath) => filePath.startsWith(".") || filePath.includes("config") || filePath === "package.json" || filePath === "tsconfig.json"
|
|
970
|
+
}
|
|
971
|
+
];
|
|
972
|
+
}
|
|
973
|
+
|
|
290
974
|
// src/presets/nextjs.ts
|
|
291
975
|
var NEXTJS_LAYERS = [
|
|
292
976
|
{ name: "app", match: (p) => p.startsWith("src/app/") || p.startsWith("app/") },
|
|
@@ -342,53 +1026,6 @@ var NEXTJS_KINDS = [
|
|
|
342
1026
|
}
|
|
343
1027
|
];
|
|
344
1028
|
|
|
345
|
-
// src/presets/vite.ts
|
|
346
|
-
var VITE_LAYERS = [
|
|
347
|
-
{ name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
|
|
348
|
-
{ name: "components", match: (p) => p.startsWith("src/components/") },
|
|
349
|
-
{ name: "hooks", match: (p) => p.startsWith("src/hooks/") },
|
|
350
|
-
{ name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("src/api/") },
|
|
351
|
-
{ name: "shared", match: (p) => p.startsWith("src/shared/") || p.startsWith("src/common/") },
|
|
352
|
-
{ name: "styles", match: (p) => p.startsWith("src/styles/") || p.startsWith("src/assets/") },
|
|
353
|
-
{ name: "scripts", match: (p) => p.startsWith("scripts/") },
|
|
354
|
-
{ name: "docs", match: (p) => p.startsWith("docs/") },
|
|
355
|
-
{
|
|
356
|
-
name: "config",
|
|
357
|
-
match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
|
|
358
|
-
},
|
|
359
|
-
{ name: "root", match: (p) => !p.includes("/") }
|
|
360
|
-
];
|
|
361
|
-
var VITE_KINDS = [
|
|
362
|
-
{ kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
|
|
363
|
-
{
|
|
364
|
-
kind: "component",
|
|
365
|
-
match: (p) => {
|
|
366
|
-
const base = p.split("/").pop() || "";
|
|
367
|
-
return p.includes("/components/") || base.endsWith(".tsx") || base.endsWith(".jsx") || base.endsWith(".vue");
|
|
368
|
-
}
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
kind: "hook",
|
|
372
|
-
match: (p) => {
|
|
373
|
-
const base = p.split("/").pop() || "";
|
|
374
|
-
return p.includes("/hooks/") || /^use[A-Z]/.test(base);
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
{ kind: "service", match: (p) => p.includes("service") || p.includes("api") },
|
|
378
|
-
{ kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types") },
|
|
379
|
-
{ kind: "style", match: (p) => p.endsWith(".css") || p.endsWith(".scss") },
|
|
380
|
-
{ kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
|
|
381
|
-
{ kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
|
|
382
|
-
{ kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
|
|
383
|
-
{
|
|
384
|
-
kind: "config",
|
|
385
|
-
match: (p) => {
|
|
386
|
-
const base = p.split("/").pop() || "";
|
|
387
|
-
return ["package.json", "tsconfig.json", "vite.config.ts", "vite.config.js"].includes(base) || base.startsWith(".");
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
];
|
|
391
|
-
|
|
392
1029
|
// src/presets/node.ts
|
|
393
1030
|
var NODE_LAYERS = [
|
|
394
1031
|
{ name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
|
|
@@ -425,11 +1062,474 @@ var NODE_KINDS = [
|
|
|
425
1062
|
kind: "config",
|
|
426
1063
|
match: (p) => {
|
|
427
1064
|
const base = p.split("/").pop() || "";
|
|
428
|
-
return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
|
|
1065
|
+
return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
];
|
|
1069
|
+
|
|
1070
|
+
// src/presets/vite.ts
|
|
1071
|
+
var VITE_LAYERS = [
|
|
1072
|
+
{ name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
|
|
1073
|
+
{ name: "components", match: (p) => p.startsWith("src/components/") },
|
|
1074
|
+
{ name: "hooks", match: (p) => p.startsWith("src/hooks/") },
|
|
1075
|
+
{ name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("src/api/") },
|
|
1076
|
+
{ name: "shared", match: (p) => p.startsWith("src/shared/") || p.startsWith("src/common/") },
|
|
1077
|
+
{ name: "styles", match: (p) => p.startsWith("src/styles/") || p.startsWith("src/assets/") },
|
|
1078
|
+
{ name: "scripts", match: (p) => p.startsWith("scripts/") },
|
|
1079
|
+
{ name: "docs", match: (p) => p.startsWith("docs/") },
|
|
1080
|
+
{
|
|
1081
|
+
name: "config",
|
|
1082
|
+
match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
|
|
1083
|
+
},
|
|
1084
|
+
{ name: "root", match: (p) => !p.includes("/") }
|
|
1085
|
+
];
|
|
1086
|
+
var VITE_KINDS = [
|
|
1087
|
+
{ kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
|
|
1088
|
+
{
|
|
1089
|
+
kind: "component",
|
|
1090
|
+
match: (p) => {
|
|
1091
|
+
const base = p.split("/").pop() || "";
|
|
1092
|
+
return p.includes("/components/") || base.endsWith(".tsx") || base.endsWith(".jsx") || base.endsWith(".vue");
|
|
1093
|
+
}
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
kind: "hook",
|
|
1097
|
+
match: (p) => {
|
|
1098
|
+
const base = p.split("/").pop() || "";
|
|
1099
|
+
return p.includes("/hooks/") || /^use[A-Z]/.test(base);
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
{ kind: "service", match: (p) => p.includes("service") || p.includes("api") },
|
|
1103
|
+
{ kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types") },
|
|
1104
|
+
{ kind: "style", match: (p) => p.endsWith(".css") || p.endsWith(".scss") },
|
|
1105
|
+
{ kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
|
|
1106
|
+
{ kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
|
|
1107
|
+
{ kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
|
|
1108
|
+
{
|
|
1109
|
+
kind: "config",
|
|
1110
|
+
match: (p) => {
|
|
1111
|
+
const base = p.split("/").pop() || "";
|
|
1112
|
+
return ["package.json", "tsconfig.json", "vite.config.ts", "vite.config.js"].includes(base) || base.startsWith(".");
|
|
429
1113
|
}
|
|
430
1114
|
}
|
|
431
1115
|
];
|
|
432
1116
|
|
|
1117
|
+
// src/presets/js-presets.ts
|
|
1118
|
+
var NEXT_LIKE_EXCLUDES = [".svelte-kit", ".astro", ".vite", ".output"];
|
|
1119
|
+
var JS_PRESETS = [
|
|
1120
|
+
{
|
|
1121
|
+
name: "nextjs",
|
|
1122
|
+
priority: 100,
|
|
1123
|
+
detect: (context) => hasFileStartingWith(context, "next.config") || hasDependency(context, "next"),
|
|
1124
|
+
layers: NEXTJS_LAYERS,
|
|
1125
|
+
kinds: NEXTJS_KINDS,
|
|
1126
|
+
exclude: NEXT_LIKE_EXCLUDES,
|
|
1127
|
+
recommendedBudget: "300k"
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
name: "nuxt",
|
|
1131
|
+
priority: 98,
|
|
1132
|
+
detect: (context) => hasFileStartingWith(context, "nuxt.config") || hasDependency(context, "nuxt", "nuxt3"),
|
|
1133
|
+
layers: [
|
|
1134
|
+
layer("pages", "pages/", "src/pages/"),
|
|
1135
|
+
layer("components", "components/", "src/components/"),
|
|
1136
|
+
layer("composables", "composables/", "src/composables/"),
|
|
1137
|
+
layer("server", "server/", "src/server/", "api/"),
|
|
1138
|
+
layer("stores", "stores/", "src/stores/"),
|
|
1139
|
+
layer("layouts", "layouts/", "src/layouts/"),
|
|
1140
|
+
...commonLayers()
|
|
1141
|
+
],
|
|
1142
|
+
kinds: [
|
|
1143
|
+
kindOf("api-route", "server/api/", "server/routes/"),
|
|
1144
|
+
suffixKind("component", ".vue"),
|
|
1145
|
+
kindOf("composable", "composables/"),
|
|
1146
|
+
kindOf("store", "stores/", "store/"),
|
|
1147
|
+
kindOf("layout", "layouts/"),
|
|
1148
|
+
...commonKinds()
|
|
1149
|
+
],
|
|
1150
|
+
exclude: [".nuxt", ".output"],
|
|
1151
|
+
recommendedBudget: "200k"
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
name: "sveltekit",
|
|
1155
|
+
priority: 96,
|
|
1156
|
+
detect: (context) => hasFileStartingWith(context, "svelte.config") || hasDependency(context, "@sveltejs/kit", "svelte"),
|
|
1157
|
+
layers: [
|
|
1158
|
+
layer("routes", "src/routes/", "routes/"),
|
|
1159
|
+
layer("lib", "src/lib/", "lib/"),
|
|
1160
|
+
layer("components", "src/lib/components/", "src/components/"),
|
|
1161
|
+
layer("stores", "src/lib/stores/", "src/stores/"),
|
|
1162
|
+
...commonLayers()
|
|
1163
|
+
],
|
|
1164
|
+
kinds: [
|
|
1165
|
+
kindOf("route", "+page.", "+server.", "+layout."),
|
|
1166
|
+
suffixKind("component", ".svelte"),
|
|
1167
|
+
kindOf("store", "stores/"),
|
|
1168
|
+
kindOf("params", "params/"),
|
|
1169
|
+
...commonKinds()
|
|
1170
|
+
],
|
|
1171
|
+
exclude: [".svelte-kit", ".vercel"],
|
|
1172
|
+
recommendedBudget: "200k"
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
name: "remix",
|
|
1176
|
+
priority: 94,
|
|
1177
|
+
detect: (context) => hasDependency(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
|
|
1178
|
+
layers: [
|
|
1179
|
+
layer("routes", "app/routes/", "routes/"),
|
|
1180
|
+
layer("components", "app/components/"),
|
|
1181
|
+
layer("lib", "app/lib/", "app/utils/"),
|
|
1182
|
+
...commonLayers()
|
|
1183
|
+
],
|
|
1184
|
+
kinds: [kindOf("route", "routes/"), ...commonKinds()],
|
|
1185
|
+
exclude: [".cache", "public/build"],
|
|
1186
|
+
recommendedBudget: "200k"
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
name: "astro",
|
|
1190
|
+
priority: 92,
|
|
1191
|
+
detect: (context) => hasFileStartingWith(context, "astro.config") || hasDependency(context, "astro"),
|
|
1192
|
+
layers: [
|
|
1193
|
+
layer("pages", "src/pages/"),
|
|
1194
|
+
layer("layouts", "src/layouts/"),
|
|
1195
|
+
layer("components", "src/components/"),
|
|
1196
|
+
layer("content", "src/content/"),
|
|
1197
|
+
...commonLayers()
|
|
1198
|
+
],
|
|
1199
|
+
kinds: [suffixKind("page", ".astro"), kindOf("layout", "layouts/"), ...commonKinds()],
|
|
1200
|
+
exclude: [".astro", "dist"],
|
|
1201
|
+
recommendedBudget: "150k"
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
name: "angular",
|
|
1205
|
+
priority: 90,
|
|
1206
|
+
detect: (context) => hasFile(context, "angular.json") || hasDependency(context, "@angular/core"),
|
|
1207
|
+
layers: [
|
|
1208
|
+
layer("app", "src/app/"),
|
|
1209
|
+
layer("features", "src/app/features/"),
|
|
1210
|
+
layer("shared", "src/app/shared/"),
|
|
1211
|
+
layer("core", "src/app/core/"),
|
|
1212
|
+
layer("assets", "src/assets/"),
|
|
1213
|
+
...commonLayers()
|
|
1214
|
+
],
|
|
1215
|
+
kinds: [
|
|
1216
|
+
suffixKind("component", ".component.ts"),
|
|
1217
|
+
suffixKind("service", ".service.ts"),
|
|
1218
|
+
suffixKind("module", ".module.ts"),
|
|
1219
|
+
suffixKind("guard", ".guard.ts"),
|
|
1220
|
+
suffixKind("pipe", ".pipe.ts"),
|
|
1221
|
+
suffixKind("directive", ".directive.ts"),
|
|
1222
|
+
...commonKinds()
|
|
1223
|
+
],
|
|
1224
|
+
exclude: [".angular", "dist"],
|
|
1225
|
+
recommendedBudget: "200k"
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
name: "solid",
|
|
1229
|
+
priority: 88,
|
|
1230
|
+
detect: (context) => hasDependency(context, "solid-js", "solid-start"),
|
|
1231
|
+
layers: [
|
|
1232
|
+
layer("pages", "src/routes/", "src/pages/"),
|
|
1233
|
+
layer("components", "src/components/"),
|
|
1234
|
+
...commonLayers()
|
|
1235
|
+
],
|
|
1236
|
+
kinds: [kindOf("route", "routes/"), ...commonKinds()],
|
|
1237
|
+
exclude: [".solid", "dist"],
|
|
1238
|
+
recommendedBudget: "120k"
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
name: "vue-vite",
|
|
1242
|
+
priority: 86,
|
|
1243
|
+
detect: (context) => hasDependency(context, "vue") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
|
|
1244
|
+
layers: [
|
|
1245
|
+
layer("pages", "src/pages/", "src/views/"),
|
|
1246
|
+
layer("components", "src/components/"),
|
|
1247
|
+
layer("composables", "src/composables/", "src/hooks/"),
|
|
1248
|
+
layer("stores", "src/stores/", "src/store/"),
|
|
1249
|
+
layer("services", "src/services/", "src/api/"),
|
|
1250
|
+
layer("shared", "src/shared/", "src/common/"),
|
|
1251
|
+
layer("styles", "src/styles/", "src/assets/"),
|
|
1252
|
+
...commonLayers()
|
|
1253
|
+
],
|
|
1254
|
+
kinds: [
|
|
1255
|
+
suffixKind("component", ".vue"),
|
|
1256
|
+
kindOf("composable", "composables/", "use"),
|
|
1257
|
+
kindOf("store", "stores/", "store/"),
|
|
1258
|
+
kindOf("service", "services/", "api/"),
|
|
1259
|
+
suffixKind("style", ".css", ".scss", ".sass", ".less"),
|
|
1260
|
+
...commonKinds()
|
|
1261
|
+
],
|
|
1262
|
+
exclude: [".vite", "dist"],
|
|
1263
|
+
recommendedBudget: "120k"
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
name: "react-vite",
|
|
1267
|
+
priority: 84,
|
|
1268
|
+
detect: (context) => hasDependency(context, "react") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
|
|
1269
|
+
layers: VITE_LAYERS,
|
|
1270
|
+
kinds: VITE_KINDS,
|
|
1271
|
+
exclude: [".vite", "dist"],
|
|
1272
|
+
recommendedBudget: "120k"
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
name: "nest",
|
|
1276
|
+
priority: 80,
|
|
1277
|
+
detect: (context) => hasDependency(context, "@nestjs/core"),
|
|
1278
|
+
layers: [
|
|
1279
|
+
layer("modules", "src/modules/", "modules/"),
|
|
1280
|
+
layer("controllers", "src/controllers/", "controllers/"),
|
|
1281
|
+
layer("providers", "src/providers/", "providers/"),
|
|
1282
|
+
layer("core", "src/core/"),
|
|
1283
|
+
layer("security", "src/security/", "src/auth/"),
|
|
1284
|
+
layer("db", "src/db/", "src/database/", "prisma/"),
|
|
1285
|
+
...commonLayers()
|
|
1286
|
+
],
|
|
1287
|
+
kinds: [
|
|
1288
|
+
suffixKind("controller", ".controller.ts"),
|
|
1289
|
+
suffixKind("service", ".service.ts"),
|
|
1290
|
+
suffixKind("module", ".module.ts"),
|
|
1291
|
+
suffixKind("guard", ".guard.ts"),
|
|
1292
|
+
suffixKind("dto", ".dto.ts"),
|
|
1293
|
+
suffixKind("entity", ".entity.ts"),
|
|
1294
|
+
kindOf("migration", "migrations/"),
|
|
1295
|
+
...commonKinds()
|
|
1296
|
+
],
|
|
1297
|
+
exclude: ["dist", "node_modules"],
|
|
1298
|
+
recommendedBudget: "200k"
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
name: "node-express",
|
|
1302
|
+
priority: 78,
|
|
1303
|
+
detect: (context) => hasDependency(context, "express", "fastify", "koa", "@hapi/hapi"),
|
|
1304
|
+
layers: NODE_LAYERS,
|
|
1305
|
+
kinds: NODE_KINDS,
|
|
1306
|
+
exclude: ["dist"],
|
|
1307
|
+
recommendedBudget: "200k"
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
name: "node-lib",
|
|
1311
|
+
// A bare package.json is often just tooling around a non-JS project, so it
|
|
1312
|
+
// ranks below every definitive language manifest.
|
|
1313
|
+
priority: 45,
|
|
1314
|
+
detect: (context) => hasFile(context, "package.json"),
|
|
1315
|
+
layers: [
|
|
1316
|
+
layer("src", "src/"),
|
|
1317
|
+
layer("lib", "lib/"),
|
|
1318
|
+
layer("test", "test/", "tests/", "__tests__/"),
|
|
1319
|
+
...commonLayers()
|
|
1320
|
+
],
|
|
1321
|
+
kinds: [suffixKind("type", ".d.ts"), ...commonKinds()],
|
|
1322
|
+
exclude: ["dist"],
|
|
1323
|
+
recommendedBudget: "120k"
|
|
1324
|
+
}
|
|
1325
|
+
];
|
|
1326
|
+
|
|
1327
|
+
// src/presets/lang-presets.ts
|
|
1328
|
+
var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
|
|
1329
|
+
var DIR = (name) => `${name}/`;
|
|
1330
|
+
var LANGUAGE_PRESETS = [
|
|
1331
|
+
{
|
|
1332
|
+
name: "python-django",
|
|
1333
|
+
priority: 56,
|
|
1334
|
+
detect: (context) => hasFile(context, "manage.py") || manifestContains(context, "pyproject.toml", "django") || manifestContains(context, "requirements.txt", "django"),
|
|
1335
|
+
layers: [
|
|
1336
|
+
layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
|
|
1337
|
+
layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
|
|
1338
|
+
layer("serializers", DIR("serializers")),
|
|
1339
|
+
layer("urls", "urls.py"),
|
|
1340
|
+
layer("migrations", DIR("migrations")),
|
|
1341
|
+
layer("settings", "settings.py", "settings/"),
|
|
1342
|
+
layer("templates", DIR("templates")),
|
|
1343
|
+
...commonLayers()
|
|
1344
|
+
],
|
|
1345
|
+
kinds: [
|
|
1346
|
+
suffixKind("model", "models.py"),
|
|
1347
|
+
suffixKind("view", "views.py"),
|
|
1348
|
+
suffixKind("serializer", "serializers.py"),
|
|
1349
|
+
suffixKind("urlconf", "urls.py"),
|
|
1350
|
+
kindOf("migration", "migrations/"),
|
|
1351
|
+
suffixKind("settings", "settings.py"),
|
|
1352
|
+
...commonKinds()
|
|
1353
|
+
],
|
|
1354
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs", "staticfiles", "media"],
|
|
1355
|
+
recommendedBudget: "150k"
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
name: "python-fastapi",
|
|
1359
|
+
priority: 54,
|
|
1360
|
+
detect: (context) => manifestContains(context, "requirements.txt", "fastapi") || manifestContains(context, "requirements.txt", "flask") || manifestContains(context, "pyproject.toml", "fastapi") || manifestContains(context, "pyproject.toml", "flask"),
|
|
1361
|
+
layers: [
|
|
1362
|
+
layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
|
|
1363
|
+
layer("models", DIR("models"), DIR("db")),
|
|
1364
|
+
layer("schemas", DIR("schemas")),
|
|
1365
|
+
layer("services", DIR("services")),
|
|
1366
|
+
layer("core", DIR("core")),
|
|
1367
|
+
layer("tests", DIR("tests")),
|
|
1368
|
+
...commonLayers()
|
|
1369
|
+
],
|
|
1370
|
+
kinds: [
|
|
1371
|
+
kindOf("router", "routers/", "routes/", "api/"),
|
|
1372
|
+
kindOf("model", "models/"),
|
|
1373
|
+
kindOf("schema", "schemas/"),
|
|
1374
|
+
kindOf("service", "services/"),
|
|
1375
|
+
...commonKinds()
|
|
1376
|
+
],
|
|
1377
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
|
|
1378
|
+
recommendedBudget: "150k"
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
name: "python-lib",
|
|
1382
|
+
// Below node-lib: a Node repo with a stray .py file is still a Node repo.
|
|
1383
|
+
priority: 30,
|
|
1384
|
+
detect: (context) => hasFile(context, "pyproject.toml") || hasFile(context, "setup.py") || hasFile(context, "requirements.txt") || anyFileEndingWith(context, ".py"),
|
|
1385
|
+
layers: [
|
|
1386
|
+
layer("package", DIR("src"), DIR("lib")),
|
|
1387
|
+
layer("tests", DIR("tests"), DIR("test")),
|
|
1388
|
+
...commonLayers()
|
|
1389
|
+
],
|
|
1390
|
+
kinds: [suffixKind("module", ".py"), ...commonKinds()],
|
|
1391
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
|
|
1392
|
+
recommendedBudget: "120k"
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
name: "go",
|
|
1396
|
+
priority: 50,
|
|
1397
|
+
detect: (context) => hasFile(context, "go.mod"),
|
|
1398
|
+
layers: [
|
|
1399
|
+
layer("cmd", "cmd/"),
|
|
1400
|
+
layer("internal", "internal/"),
|
|
1401
|
+
layer("pkg", "pkg/"),
|
|
1402
|
+
layer("api", "api/", "proto/"),
|
|
1403
|
+
layer("migrations", "migrations/"),
|
|
1404
|
+
...commonLayers()
|
|
1405
|
+
],
|
|
1406
|
+
kinds: [
|
|
1407
|
+
kindOf("main", "cmd/"),
|
|
1408
|
+
kindOf("handler", "handler", "server", "http"),
|
|
1409
|
+
kindOf("service", "service"),
|
|
1410
|
+
kindOf("repository", "repository", "store", "repo"),
|
|
1411
|
+
kindOf("model", "model", "entity"),
|
|
1412
|
+
suffixKind("test", "_test.go"),
|
|
1413
|
+
...commonKinds()
|
|
1414
|
+
],
|
|
1415
|
+
exclude: ["vendor", "testdata"],
|
|
1416
|
+
recommendedBudget: "200k"
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
name: "rust",
|
|
1420
|
+
priority: 48,
|
|
1421
|
+
detect: (context) => hasFile(context, "Cargo.toml"),
|
|
1422
|
+
layers: [
|
|
1423
|
+
layer("src", "src/"),
|
|
1424
|
+
layer("crates", "crates/"),
|
|
1425
|
+
layer("bins", "src/bin/"),
|
|
1426
|
+
layer("tests", "tests/"),
|
|
1427
|
+
layer("benches", "benches/"),
|
|
1428
|
+
...commonLayers()
|
|
1429
|
+
],
|
|
1430
|
+
kinds: [suffixKind("module", ".rs"), suffixKind("test", "_test.rs"), ...commonKinds()],
|
|
1431
|
+
exclude: ["target"],
|
|
1432
|
+
recommendedBudget: "200k"
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
name: "java-spring",
|
|
1436
|
+
priority: 46,
|
|
1437
|
+
detect: (context) => hasFile(context, "pom.xml") || hasFile(context, "build.gradle") || hasFile(context, "build.gradle.kts"),
|
|
1438
|
+
layers: [
|
|
1439
|
+
layer("api", "src/main/java/"),
|
|
1440
|
+
layer("test", "src/test/java/"),
|
|
1441
|
+
layer("resources", "src/main/resources/"),
|
|
1442
|
+
layer("migrations", "src/main/resources/db/"),
|
|
1443
|
+
...commonLayers()
|
|
1444
|
+
],
|
|
1445
|
+
kinds: [
|
|
1446
|
+
kindOf("controller", "controller"),
|
|
1447
|
+
kindOf("service", "service"),
|
|
1448
|
+
kindOf("repository", "repository", "dao"),
|
|
1449
|
+
kindOf("entity", "entity", "model"),
|
|
1450
|
+
kindOf("dto", "dto", "request", "response"),
|
|
1451
|
+
kindOf("migration", "migration", "db/migration"),
|
|
1452
|
+
...commonKinds()
|
|
1453
|
+
],
|
|
1454
|
+
exclude: [".gradle", "out", "target"],
|
|
1455
|
+
recommendedBudget: "200k"
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
name: "dotnet",
|
|
1459
|
+
priority: 44,
|
|
1460
|
+
detect: (context) => anyFileEndingWith(context, ".csproj") || anyFileEndingWith(context, ".sln") || hasFile(context, "global.json"),
|
|
1461
|
+
layers: [
|
|
1462
|
+
layer("web", "src/", "Web/"),
|
|
1463
|
+
layer("domain", "Domain/", "Core/"),
|
|
1464
|
+
layer("infrastructure", "Infrastructure/", "Data/"),
|
|
1465
|
+
layer("tests", "Tests/", "test/"),
|
|
1466
|
+
...commonLayers()
|
|
1467
|
+
],
|
|
1468
|
+
kinds: [
|
|
1469
|
+
kindOf("controller", "Controller"),
|
|
1470
|
+
kindOf("service", "Service"),
|
|
1471
|
+
kindOf("repository", "Repository"),
|
|
1472
|
+
kindOf("model", "Model", "Entity"),
|
|
1473
|
+
kindOf("dto", "Dto", "Request", "Response"),
|
|
1474
|
+
suffixKind("test", "Tests.cs"),
|
|
1475
|
+
...commonKinds()
|
|
1476
|
+
],
|
|
1477
|
+
exclude: ["bin", "obj", ".vs"],
|
|
1478
|
+
recommendedBudget: "200k"
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
name: "php-laravel",
|
|
1482
|
+
priority: 42,
|
|
1483
|
+
detect: (context) => hasFile(context, "artisan") || manifestContains(context, "composer.json", "laravel") || hasFile(context, "composer.json"),
|
|
1484
|
+
layers: [
|
|
1485
|
+
layer("app", "app/"),
|
|
1486
|
+
layer("routes", "routes/"),
|
|
1487
|
+
layer("http", "app/Http/"),
|
|
1488
|
+
layer("models", "app/Models/"),
|
|
1489
|
+
layer("providers", "app/Providers/"),
|
|
1490
|
+
layer("database", "database/"),
|
|
1491
|
+
layer("tests", "tests/"),
|
|
1492
|
+
...commonLayers()
|
|
1493
|
+
],
|
|
1494
|
+
kinds: [
|
|
1495
|
+
kindOf("controller", "Http/Controllers/"),
|
|
1496
|
+
kindOf("model", "Models/"),
|
|
1497
|
+
kindOf("migration", "migrations/"),
|
|
1498
|
+
kindOf("middleware", "Middleware/"),
|
|
1499
|
+
kindOf("request", "Requests/"),
|
|
1500
|
+
kindOf("provider", "Providers/"),
|
|
1501
|
+
...commonKinds()
|
|
1502
|
+
],
|
|
1503
|
+
exclude: ["vendor", "storage", "bootstrap/cache", "public/build"],
|
|
1504
|
+
recommendedBudget: "150k"
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
name: "ruby-rails",
|
|
1508
|
+
priority: 40,
|
|
1509
|
+
detect: (context) => hasFile(context, "Gemfile") || hasFile(context, "config.ru") || hasFile(context, "Rakefile"),
|
|
1510
|
+
layers: [
|
|
1511
|
+
layer("app", "app/"),
|
|
1512
|
+
layer("controllers", "app/controllers/"),
|
|
1513
|
+
layer("models", "app/models/"),
|
|
1514
|
+
layer("config", "config/"),
|
|
1515
|
+
layer("db", "db/"),
|
|
1516
|
+
layer("spec", "spec/", "test/"),
|
|
1517
|
+
...commonLayers()
|
|
1518
|
+
],
|
|
1519
|
+
kinds: [
|
|
1520
|
+
kindOf("controller", "controllers/"),
|
|
1521
|
+
kindOf("model", "models/"),
|
|
1522
|
+
kindOf("migration", "migrate/"),
|
|
1523
|
+
kindOf("job", "jobs/"),
|
|
1524
|
+
kindOf("mailer", "mailers/"),
|
|
1525
|
+
suffixKind("test", "_spec.rb", "_test.rb"),
|
|
1526
|
+
...commonKinds()
|
|
1527
|
+
],
|
|
1528
|
+
exclude: [".bundle", "log", "tmp", "vendor/bundle", "public/assets"],
|
|
1529
|
+
recommendedBudget: "150k"
|
|
1530
|
+
}
|
|
1531
|
+
];
|
|
1532
|
+
|
|
433
1533
|
// src/presets/default.ts
|
|
434
1534
|
var DEFAULT_LAYERS = [
|
|
435
1535
|
{ name: "src", match: (p) => p.startsWith("src/") },
|
|
@@ -472,50 +1572,110 @@ var DEFAULT_KINDS = [
|
|
|
472
1572
|
}
|
|
473
1573
|
];
|
|
474
1574
|
|
|
475
|
-
// src/presets/
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1575
|
+
// src/presets/shape-presets.ts
|
|
1576
|
+
var SHAPE_PRESETS = [
|
|
1577
|
+
{
|
|
1578
|
+
name: "monorepo",
|
|
1579
|
+
priority: 70,
|
|
1580
|
+
detect: (context) => hasFile(context, "pnpm-workspace.yaml") || hasFile(context, "nx.json") || hasFile(context, "turbo.json") || hasFile(context, "lerna.json") || manifestContains(context, "package.json", '"workspaces"'),
|
|
1581
|
+
layers: [
|
|
1582
|
+
layer("apps", "apps/"),
|
|
1583
|
+
layer("packages", "packages/"),
|
|
1584
|
+
layer("tooling", "tooling/", "tools/", "scripts/"),
|
|
1585
|
+
...commonLayers()
|
|
1586
|
+
],
|
|
1587
|
+
kinds: commonKinds(),
|
|
1588
|
+
exclude: [".turbo", ".nx"],
|
|
1589
|
+
recommendedBudget: "200k"
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
name: "infra",
|
|
1593
|
+
priority: 24,
|
|
1594
|
+
detect: (context) => Array.from(context.files).some((file) => file.endsWith(".tf")) || hasFile(context, "Chart.yaml") || hasFile(context, "kustomization.yaml") || hasFile(context, "docker-compose.yml") || hasFile(context, "docker-compose.yaml"),
|
|
1595
|
+
layers: [
|
|
1596
|
+
layer("modules", "modules/"),
|
|
1597
|
+
layer("environments", "env/", "environments/"),
|
|
1598
|
+
layer("manifests", "k8s/", "helm/", "charts/", "deploy/"),
|
|
1599
|
+
...commonLayers()
|
|
1600
|
+
],
|
|
1601
|
+
kinds: [
|
|
1602
|
+
{ kind: "terraform", match: (filePath) => filePath.endsWith(".tf") },
|
|
1603
|
+
{ kind: "kubernetes", match: (filePath) => /\.(ya?ml)$/i.test(filePath) && /k8s|helm|charts|deploy/.test(filePath) },
|
|
1604
|
+
...commonKinds()
|
|
1605
|
+
],
|
|
1606
|
+
exclude: [".terraform", ".terragrunt-cache"],
|
|
1607
|
+
recommendedBudget: "100k"
|
|
1608
|
+
},
|
|
1609
|
+
{
|
|
1610
|
+
name: "docs",
|
|
1611
|
+
priority: 22,
|
|
1612
|
+
detect: (context) => {
|
|
1613
|
+
const markdown = Array.from(context.files).filter((file) => /\.mdx?$/i.test(file)).length;
|
|
1614
|
+
return markdown >= 3 && !hasFile(context, "package.json");
|
|
1615
|
+
},
|
|
1616
|
+
layers: [
|
|
1617
|
+
layer("content", "content/", "docs/"),
|
|
1618
|
+
layer("assets", "assets/", "static/", "public/"),
|
|
1619
|
+
...commonLayers()
|
|
1620
|
+
],
|
|
1621
|
+
kinds: commonKinds(),
|
|
1622
|
+
exclude: [".docusaurus", ".vitepress/dist", "site"],
|
|
1623
|
+
recommendedBudget: "100k"
|
|
1624
|
+
},
|
|
1625
|
+
{
|
|
1626
|
+
name: "generic",
|
|
1627
|
+
priority: 0,
|
|
1628
|
+
detect: () => true,
|
|
1629
|
+
layers: DEFAULT_LAYERS,
|
|
1630
|
+
kinds: DEFAULT_KINDS,
|
|
1631
|
+
exclude: [],
|
|
1632
|
+
recommendedBudget: "120k"
|
|
498
1633
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1634
|
+
];
|
|
1635
|
+
|
|
1636
|
+
// src/presets/registry.ts
|
|
1637
|
+
function compare(a, b) {
|
|
1638
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
1639
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
1640
|
+
}
|
|
1641
|
+
var PRESETS = [
|
|
1642
|
+
...JS_PRESETS,
|
|
1643
|
+
...LANGUAGE_PRESETS,
|
|
1644
|
+
...SHAPE_PRESETS
|
|
1645
|
+
].sort(compare);
|
|
1646
|
+
var BY_NAME = new Map(PRESETS.map((preset) => [preset.name, preset]));
|
|
1647
|
+
function listPresetNames() {
|
|
1648
|
+
return PRESETS.map((preset) => preset.name);
|
|
1649
|
+
}
|
|
1650
|
+
function getPreset(name) {
|
|
1651
|
+
return BY_NAME.get(name);
|
|
1652
|
+
}
|
|
1653
|
+
function unknownPresetMessage(name) {
|
|
1654
|
+
return `Unknown preset "${name}". Expected one of: ${listPresetNames().join(", ")}.`;
|
|
1655
|
+
}
|
|
1656
|
+
function detectPresetFromContext(context, forced) {
|
|
1657
|
+
if (forced !== void 0) {
|
|
1658
|
+
const preset = BY_NAME.get(forced);
|
|
1659
|
+
if (!preset) throw new Error(unknownPresetMessage(forced));
|
|
1660
|
+
return toDetected(preset);
|
|
505
1661
|
}
|
|
506
|
-
|
|
507
|
-
return
|
|
508
|
-
presetName: "node",
|
|
509
|
-
layers: NODE_LAYERS,
|
|
510
|
-
kinds: NODE_KINDS
|
|
511
|
-
};
|
|
1662
|
+
for (const preset of PRESETS) {
|
|
1663
|
+
if (preset.detect(context)) return toDetected(preset);
|
|
512
1664
|
}
|
|
1665
|
+
return toDetected(PRESETS[PRESETS.length - 1]);
|
|
1666
|
+
}
|
|
1667
|
+
function toDetected(preset) {
|
|
513
1668
|
return {
|
|
514
|
-
presetName:
|
|
515
|
-
layers:
|
|
516
|
-
kinds:
|
|
1669
|
+
presetName: preset.name,
|
|
1670
|
+
layers: preset.layers,
|
|
1671
|
+
kinds: preset.kinds,
|
|
1672
|
+
exclude: preset.exclude ? [...preset.exclude] : [],
|
|
1673
|
+
recommendedBudget: preset.recommendedBudget ?? null
|
|
517
1674
|
};
|
|
518
1675
|
}
|
|
1676
|
+
function detectPreset(cwd, forced) {
|
|
1677
|
+
return detectPresetFromContext(buildPresetContext(cwd), forced);
|
|
1678
|
+
}
|
|
519
1679
|
function getGitInfo(cwd, fallbackBranch = "main") {
|
|
520
1680
|
try {
|
|
521
1681
|
const branch = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
|
|
@@ -650,10 +1810,10 @@ function matchesExclude(relativePath, pattern) {
|
|
|
650
1810
|
return posixPath === cleaned || posixPath.startsWith(`${cleaned}/`) || posixPath.includes(`/${cleaned}/`) || posixPath.endsWith(`/${cleaned}`);
|
|
651
1811
|
}
|
|
652
1812
|
function readGitignoreMatcher(dir) {
|
|
653
|
-
const gitignorePath =
|
|
1813
|
+
const gitignorePath = path10.join(dir, ".gitignore");
|
|
654
1814
|
let raw;
|
|
655
1815
|
try {
|
|
656
|
-
raw =
|
|
1816
|
+
raw = fs9.readFileSync(gitignorePath, "utf8");
|
|
657
1817
|
} catch {
|
|
658
1818
|
return () => false;
|
|
659
1819
|
}
|
|
@@ -756,7 +1916,7 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
756
1916
|
const results = { files: [], skipped: [] };
|
|
757
1917
|
let realDir;
|
|
758
1918
|
try {
|
|
759
|
-
realDir =
|
|
1919
|
+
realDir = fs9.realpathSync(dir);
|
|
760
1920
|
} catch {
|
|
761
1921
|
realDir = dir;
|
|
762
1922
|
}
|
|
@@ -765,21 +1925,21 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
765
1925
|
const ignoredByGitignore = dir === baseDir ? readGitignoreMatcher(baseDir) : () => false;
|
|
766
1926
|
let entries;
|
|
767
1927
|
try {
|
|
768
|
-
entries =
|
|
1928
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
769
1929
|
} catch {
|
|
770
1930
|
results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
|
|
771
1931
|
return results;
|
|
772
1932
|
}
|
|
773
1933
|
for (const entry of entries) {
|
|
774
1934
|
if (DEFAULT_IGNORED_DIRS.includes(entry.name)) continue;
|
|
775
|
-
const fullPath =
|
|
776
|
-
const relPath =
|
|
1935
|
+
const fullPath = path10.join(dir, entry.name);
|
|
1936
|
+
const relPath = path10.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
777
1937
|
if (ignoredByGitignore(relPath)) continue;
|
|
778
1938
|
let isDirectory = entry.isDirectory();
|
|
779
1939
|
let isFile = entry.isFile();
|
|
780
1940
|
if (entry.isSymbolicLink()) {
|
|
781
1941
|
try {
|
|
782
|
-
const stats =
|
|
1942
|
+
const stats = fs9.statSync(fullPath);
|
|
783
1943
|
isDirectory = stats.isDirectory();
|
|
784
1944
|
isFile = stats.isFile();
|
|
785
1945
|
if (!isDirectory && !isFile) {
|
|
@@ -1044,6 +2204,8 @@ function normalizeResolvedConfig(config) {
|
|
|
1044
2204
|
config.includeMonolith === true
|
|
1045
2205
|
),
|
|
1046
2206
|
include: resolveInclude(config.include),
|
|
2207
|
+
preset: typeof config.preset === "string" ? config.preset : "generic",
|
|
2208
|
+
presetExclude: Array.isArray(config.presetExclude) ? config.presetExclude : [],
|
|
1047
2209
|
layersMode: config.layersMode === "replace" ? "replace" : "merge",
|
|
1048
2210
|
budgets: config.budgets && typeof config.budgets === "object" ? config.budgets : { default: config.maxPartBytes, byLayer: {} },
|
|
1049
2211
|
hooks: resolveHooks(config.hooks),
|
|
@@ -1091,18 +2253,22 @@ function parseJsonish(raw, file) {
|
|
|
1091
2253
|
}
|
|
1092
2254
|
async function loadConfigFileDetailed(cwd) {
|
|
1093
2255
|
const warnings = [];
|
|
2256
|
+
const errors = [];
|
|
1094
2257
|
for (const filename of CONFIG_FILENAMES) {
|
|
1095
|
-
const fullPath =
|
|
1096
|
-
if (!
|
|
2258
|
+
const fullPath = path10.join(cwd, filename);
|
|
2259
|
+
if (!fs9.existsSync(fullPath)) continue;
|
|
1097
2260
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
1098
2261
|
try {
|
|
1099
2262
|
return {
|
|
1100
|
-
config: parseJsonish(
|
|
2263
|
+
config: parseJsonish(fs9.readFileSync(fullPath, "utf8"), fullPath),
|
|
1101
2264
|
file: fullPath,
|
|
1102
|
-
warnings
|
|
2265
|
+
warnings,
|
|
2266
|
+
errors
|
|
1103
2267
|
};
|
|
1104
2268
|
} catch (err) {
|
|
1105
|
-
|
|
2269
|
+
const message = err.message;
|
|
2270
|
+
warnings.push(message);
|
|
2271
|
+
errors.push(message);
|
|
1106
2272
|
continue;
|
|
1107
2273
|
}
|
|
1108
2274
|
}
|
|
@@ -1116,28 +2282,34 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
1116
2282
|
const imported = await import(pathToFileURL(fullPath).href);
|
|
1117
2283
|
const config = imported.default ?? imported;
|
|
1118
2284
|
if (!config || typeof config !== "object") {
|
|
1119
|
-
|
|
2285
|
+
const message = `${fullPath} does not export a configuration object.`;
|
|
2286
|
+
warnings.push(message);
|
|
2287
|
+
errors.push(message);
|
|
1120
2288
|
continue;
|
|
1121
2289
|
}
|
|
1122
|
-
return { config, file: fullPath, warnings };
|
|
2290
|
+
return { config, file: fullPath, warnings, errors };
|
|
1123
2291
|
} catch (err) {
|
|
1124
|
-
|
|
2292
|
+
const message = `Could not load ${fullPath}: ${err.message}`;
|
|
2293
|
+
warnings.push(message);
|
|
2294
|
+
errors.push(message);
|
|
1125
2295
|
continue;
|
|
1126
2296
|
}
|
|
1127
2297
|
}
|
|
1128
|
-
const pkgJsonPath =
|
|
1129
|
-
if (
|
|
2298
|
+
const pkgJsonPath = path10.join(cwd, "package.json");
|
|
2299
|
+
if (fs9.existsSync(pkgJsonPath)) {
|
|
1130
2300
|
try {
|
|
1131
|
-
const parsed = parseJsonish(
|
|
2301
|
+
const parsed = parseJsonish(fs9.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
|
|
1132
2302
|
const snapshotKey = parsed.snapshot;
|
|
1133
2303
|
if (snapshotKey && typeof snapshotKey === "object") {
|
|
1134
|
-
return { config: snapshotKey, file: pkgJsonPath, warnings };
|
|
2304
|
+
return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
|
|
1135
2305
|
}
|
|
1136
2306
|
} catch (err) {
|
|
1137
|
-
|
|
2307
|
+
const message = err.message;
|
|
2308
|
+
warnings.push(message);
|
|
2309
|
+
errors.push(message);
|
|
1138
2310
|
}
|
|
1139
2311
|
}
|
|
1140
|
-
return { config: null, file: null, warnings };
|
|
2312
|
+
return { config: null, file: null, warnings, errors };
|
|
1141
2313
|
}
|
|
1142
2314
|
async function loadConfigFile(cwd) {
|
|
1143
2315
|
const loaded = await loadConfigFileDetailed(cwd);
|
|
@@ -1236,7 +2408,9 @@ function boundedBytes(value, fallback, minimum, label) {
|
|
|
1236
2408
|
`${label} must be a byte count such as 120000, "120k" or "1.5mb" (received ${describeBytes(value)}).`
|
|
1237
2409
|
);
|
|
1238
2410
|
}
|
|
1239
|
-
if (parsed <= 0)
|
|
2411
|
+
if (parsed <= 0) {
|
|
2412
|
+
throw new Error(`${label} must be a positive byte count (received ${describeBytes(value)}).`);
|
|
2413
|
+
}
|
|
1240
2414
|
if (parsed < minimum) {
|
|
1241
2415
|
throw new Error(`${label} must be at least ${minimum} bytes (received ${describeBytes(value)}).`);
|
|
1242
2416
|
}
|
|
@@ -1276,12 +2450,12 @@ function resolveBudgets(budgets, fallback) {
|
|
|
1276
2450
|
if (typeof budgets.byLayer !== "object" || budgets.byLayer === null) {
|
|
1277
2451
|
throw new Error("`budgets.byLayer` must be an object of layer name to byte size.");
|
|
1278
2452
|
}
|
|
1279
|
-
for (const [
|
|
1280
|
-
byLayer[
|
|
2453
|
+
for (const [layer2, value] of Object.entries(budgets.byLayer)) {
|
|
2454
|
+
byLayer[layer2] = boundedBytes(
|
|
1281
2455
|
value,
|
|
1282
2456
|
fallback,
|
|
1283
2457
|
MIN_MAX_PART_BYTES,
|
|
1284
|
-
`budgets.byLayer["${
|
|
2458
|
+
`budgets.byLayer["${layer2}"]`
|
|
1285
2459
|
);
|
|
1286
2460
|
}
|
|
1287
2461
|
}
|
|
@@ -1302,18 +2476,22 @@ function resolveHooks(hooks) {
|
|
|
1302
2476
|
}
|
|
1303
2477
|
function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
|
|
1304
2478
|
const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
|
|
1305
|
-
const resolved =
|
|
1306
|
-
if (!
|
|
2479
|
+
const resolved = path10.resolve(candidate);
|
|
2480
|
+
if (!fs9.existsSync(resolved)) {
|
|
1307
2481
|
throw new Error(`Working directory does not exist: ${resolved}`);
|
|
1308
2482
|
}
|
|
1309
|
-
if (!
|
|
2483
|
+
if (!fs9.statSync(resolved).isDirectory()) {
|
|
1310
2484
|
throw new Error(`Working directory is not a directory: ${resolved}`);
|
|
1311
2485
|
}
|
|
1312
2486
|
return resolved;
|
|
1313
2487
|
}
|
|
1314
2488
|
function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
1315
2489
|
const cwd = resolveCandidateCwd(userConfig ?? null, cliOptions, baseDir);
|
|
1316
|
-
const
|
|
2490
|
+
const forcedPreset = userConfig?.preset;
|
|
2491
|
+
if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
|
|
2492
|
+
throw new Error(unknownPresetMessage(forcedPreset));
|
|
2493
|
+
}
|
|
2494
|
+
const preset = detectPreset(cwd, forcedPreset);
|
|
1317
2495
|
const layersMode = resolveLayersMode(userConfig?.layersMode);
|
|
1318
2496
|
const layers = userConfig?.layers && userConfig.layers.length > 0 ? layersMode === "replace" ? userConfig.layers : [...userConfig.layers, ...preset.layers] : preset.layers;
|
|
1319
2497
|
const kinds = userConfig?.kinds && userConfig.kinds.length > 0 ? layersMode === "replace" ? userConfig.kinds : [...userConfig.kinds, ...preset.kinds] : preset.kinds;
|
|
@@ -1349,6 +2527,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
1349
2527
|
),
|
|
1350
2528
|
includeSensitiveFiles: cliOptions?.includeSensitiveFiles ?? userConfig?.includeSensitiveFiles ?? false,
|
|
1351
2529
|
include: resolveInclude(userConfig?.include),
|
|
2530
|
+
preset: preset.presetName,
|
|
2531
|
+
presetExclude: [...preset.exclude],
|
|
1352
2532
|
artifacts,
|
|
1353
2533
|
layersMode,
|
|
1354
2534
|
budgets,
|
|
@@ -1383,8 +2563,8 @@ function matchRule(filePath, matcher) {
|
|
|
1383
2563
|
return false;
|
|
1384
2564
|
}
|
|
1385
2565
|
function classifyLayer(filePath, layers) {
|
|
1386
|
-
for (const
|
|
1387
|
-
if (matchRule(filePath,
|
|
2566
|
+
for (const layer2 of layers) {
|
|
2567
|
+
if (matchRule(filePath, layer2.match)) return layer2.name;
|
|
1388
2568
|
}
|
|
1389
2569
|
return "other";
|
|
1390
2570
|
}
|
|
@@ -1465,11 +2645,11 @@ function generateAuditReport(manifest, secretFindings) {
|
|
|
1465
2645
|
"| :--- | :--- | :--- |"
|
|
1466
2646
|
);
|
|
1467
2647
|
const totalFiles = manifest.counts.totalFiles || 1;
|
|
1468
|
-
for (const [
|
|
2648
|
+
for (const [layer2, count] of Object.entries(manifest.layers).sort(
|
|
1469
2649
|
(a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
1470
2650
|
)) {
|
|
1471
2651
|
const pct = (count / totalFiles * 100).toFixed(1);
|
|
1472
|
-
lines.push(`| \`${
|
|
2652
|
+
lines.push(`| \`${layer2}\` | ${count} | ${pct}% |`);
|
|
1473
2653
|
}
|
|
1474
2654
|
lines.push("");
|
|
1475
2655
|
lines.push(
|
|
@@ -1577,21 +2757,21 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
1577
2757
|
section.byteSize = Buffer.byteLength(section.text, "utf8");
|
|
1578
2758
|
continue;
|
|
1579
2759
|
}
|
|
1580
|
-
const
|
|
1581
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
2760
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
2761
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1582
2762
|
const clipped = truncateToBytes(rendered[oversized].markdown, target);
|
|
1583
|
-
rendered[oversized] = rehashPart(rendered[oversized], clipped.content +
|
|
2763
|
+
rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
|
|
1584
2764
|
return rendered;
|
|
1585
2765
|
}
|
|
1586
2766
|
return enforceBudget(renderGroups(groups, contentFingerprint), budget);
|
|
1587
2767
|
}
|
|
1588
2768
|
function enforceBudget(parts, budget) {
|
|
1589
|
-
const
|
|
1590
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
2769
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
2770
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1591
2771
|
return parts.map((part) => {
|
|
1592
2772
|
if (part.metadata.byteSize <= budget) return part;
|
|
1593
2773
|
const clipped = truncateToBytes(part.markdown, target);
|
|
1594
|
-
return rehashPart(part, clipped.content +
|
|
2774
|
+
return rehashPart(part, clipped.content + marker2);
|
|
1595
2775
|
});
|
|
1596
2776
|
}
|
|
1597
2777
|
function renumberParts(parts, contentFingerprint) {
|
|
@@ -1599,10 +2779,10 @@ function renumberParts(parts, contentFingerprint) {
|
|
|
1599
2779
|
if (total === 0) return [];
|
|
1600
2780
|
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
1601
2781
|
const nameFor = (position) => `part-${zeroPad(position)}.md`;
|
|
1602
|
-
const
|
|
2782
|
+
const marker2 = "## Contents\n";
|
|
1603
2783
|
return parts.map((part, index) => {
|
|
1604
|
-
const markerIndex = part.markdown.indexOf(
|
|
1605
|
-
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex +
|
|
2784
|
+
const markerIndex = part.markdown.indexOf(marker2);
|
|
2785
|
+
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
|
|
1606
2786
|
const included = part.metadata.includedFiles;
|
|
1607
2787
|
if (markerIndex === -1) {
|
|
1608
2788
|
throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
|
|
@@ -1660,9 +2840,9 @@ function topEntries(counts, limit) {
|
|
|
1660
2840
|
return Object.entries(counts).sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)).slice(0, limit).map(([key, value]) => `${capMetadata(key)} (${value})`).join(", ");
|
|
1661
2841
|
}
|
|
1662
2842
|
function prepareSection(file, sectionBudget) {
|
|
1663
|
-
let
|
|
1664
|
-
let byteSize = Buffer.byteLength(
|
|
1665
|
-
if (byteSize <= sectionBudget) return { file, text, byteSize };
|
|
2843
|
+
let text2 = renderFileSection(file);
|
|
2844
|
+
let byteSize = Buffer.byteLength(text2, "utf8");
|
|
2845
|
+
if (byteSize <= sectionBudget) return { file, text: text2, byteSize };
|
|
1666
2846
|
const shrinkBody = () => {
|
|
1667
2847
|
const content = file.content ?? "";
|
|
1668
2848
|
const overhead = byteSize - Buffer.byteLength(content, "utf8");
|
|
@@ -1671,24 +2851,24 @@ function prepareSection(file, sectionBudget) {
|
|
|
1671
2851
|
const truncated = truncateToBytes(content, allowed);
|
|
1672
2852
|
if (!truncated.isTruncated) return;
|
|
1673
2853
|
applyTruncatedContent(file, truncated.content);
|
|
1674
|
-
|
|
1675
|
-
byteSize = Buffer.byteLength(
|
|
2854
|
+
text2 = renderFileSection(file);
|
|
2855
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1676
2856
|
};
|
|
1677
2857
|
shrinkBody();
|
|
1678
2858
|
if (byteSize > sectionBudget && file.summary) {
|
|
1679
2859
|
file.summary = void 0;
|
|
1680
2860
|
file.summaryOmitted = true;
|
|
1681
|
-
|
|
1682
|
-
byteSize = Buffer.byteLength(
|
|
2861
|
+
text2 = renderFileSection(file);
|
|
2862
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1683
2863
|
shrinkBody();
|
|
1684
2864
|
}
|
|
1685
2865
|
if (byteSize > sectionBudget) {
|
|
1686
2866
|
const target = sectionBudget - Buffer.byteLength(SECTION_TRUNCATION_NOTICE, "utf8");
|
|
1687
|
-
const truncated = truncateToBytes(
|
|
1688
|
-
|
|
1689
|
-
byteSize = Buffer.byteLength(
|
|
2867
|
+
const truncated = truncateToBytes(text2, Math.max(0, target));
|
|
2868
|
+
text2 = truncated.content + SECTION_TRUNCATION_NOTICE;
|
|
2869
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1690
2870
|
}
|
|
1691
|
-
return { file, text, byteSize };
|
|
2871
|
+
return { file, text: text2, byteSize };
|
|
1692
2872
|
}
|
|
1693
2873
|
function applyTruncatedContent(file, content) {
|
|
1694
2874
|
file.content = content;
|
|
@@ -1810,6 +2990,7 @@ function renderFileSection(file) {
|
|
|
1810
2990
|
`- Source Lines: ${file.originalLines}`,
|
|
1811
2991
|
`- Included Lines: ${file.includedLines}`,
|
|
1812
2992
|
`- SHA-256: ${file.hashSha256}`,
|
|
2993
|
+
...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
|
|
1813
2994
|
...renderSummary(file.summary),
|
|
1814
2995
|
...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
|
|
1815
2996
|
"",
|
|
@@ -2057,7 +3238,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2057
3238
|
".so"
|
|
2058
3239
|
]);
|
|
2059
3240
|
function isBinaryExtension(filePath) {
|
|
2060
|
-
const ext =
|
|
3241
|
+
const ext = path10.extname(filePath).toLowerCase();
|
|
2061
3242
|
return BINARY_EXTENSIONS.has(ext);
|
|
2062
3243
|
}
|
|
2063
3244
|
function isBinaryBuffer(buffer) {
|
|
@@ -2071,9 +3252,9 @@ function isBinaryFile(absolutePath) {
|
|
|
2071
3252
|
if (isBinaryExtension(absolutePath)) return true;
|
|
2072
3253
|
let fd;
|
|
2073
3254
|
try {
|
|
2074
|
-
fd =
|
|
3255
|
+
fd = fs9.openSync(absolutePath, "r");
|
|
2075
3256
|
const buffer = Buffer.alloc(8192);
|
|
2076
|
-
const bytesRead =
|
|
3257
|
+
const bytesRead = fs9.readSync(fd, buffer, 0, 8192, 0);
|
|
2077
3258
|
if (bytesRead === 0) return false;
|
|
2078
3259
|
return isBinaryBuffer(buffer.subarray(0, bytesRead));
|
|
2079
3260
|
} catch {
|
|
@@ -2081,7 +3262,7 @@ function isBinaryFile(absolutePath) {
|
|
|
2081
3262
|
} finally {
|
|
2082
3263
|
if (fd !== void 0) {
|
|
2083
3264
|
try {
|
|
2084
|
-
|
|
3265
|
+
fs9.closeSync(fd);
|
|
2085
3266
|
} catch {
|
|
2086
3267
|
}
|
|
2087
3268
|
}
|
|
@@ -2141,10 +3322,10 @@ var LANGUAGE_BY_BASENAME = {
|
|
|
2141
3322
|
};
|
|
2142
3323
|
function detectLanguage(filePath) {
|
|
2143
3324
|
const normalized = filePath.replace(/\\/g, "/");
|
|
2144
|
-
const base =
|
|
3325
|
+
const base = path10.posix.basename(normalized);
|
|
2145
3326
|
if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
|
|
2146
3327
|
if (base === ".env" || base.startsWith(".env.")) return "dotenv";
|
|
2147
|
-
const ext =
|
|
3328
|
+
const ext = path10.posix.extname(base).toLowerCase();
|
|
2148
3329
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
2149
3330
|
}
|
|
2150
3331
|
function emptyDrift() {
|
|
@@ -2163,8 +3344,8 @@ function sha256Hex(buffer) {
|
|
|
2163
3344
|
function validateSnapshot(configInput) {
|
|
2164
3345
|
assertUsableConfigInput(configInput);
|
|
2165
3346
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
|
|
2166
|
-
const manifestPath =
|
|
2167
|
-
if (!
|
|
3347
|
+
const manifestPath = path10.resolve(config.cwd, config.outDir, "manifest.json");
|
|
3348
|
+
if (!fs9.existsSync(manifestPath)) {
|
|
2168
3349
|
return {
|
|
2169
3350
|
valid: false,
|
|
2170
3351
|
manifestPresent: false,
|
|
@@ -2175,7 +3356,7 @@ function validateSnapshot(configInput) {
|
|
|
2175
3356
|
}
|
|
2176
3357
|
let manifest;
|
|
2177
3358
|
try {
|
|
2178
|
-
manifest = JSON.parse(
|
|
3359
|
+
manifest = JSON.parse(fs9.readFileSync(manifestPath, "utf8"));
|
|
2179
3360
|
} catch (err) {
|
|
2180
3361
|
return {
|
|
2181
3362
|
valid: false,
|
|
@@ -2198,7 +3379,7 @@ function validateSnapshot(configInput) {
|
|
|
2198
3379
|
}
|
|
2199
3380
|
const drift = emptyDrift();
|
|
2200
3381
|
const warnings = [];
|
|
2201
|
-
const snapshotDir =
|
|
3382
|
+
const snapshotDir = path10.resolve(config.cwd, config.outDir);
|
|
2202
3383
|
const manifestFileMap = /* @__PURE__ */ new Map();
|
|
2203
3384
|
for (const file of manifest.files) {
|
|
2204
3385
|
if (file && typeof file.relativePath === "string") {
|
|
@@ -2210,7 +3391,7 @@ function validateSnapshot(configInput) {
|
|
|
2210
3391
|
);
|
|
2211
3392
|
const collection = collectFiles(config.cwd, {
|
|
2212
3393
|
includePatterns: config.include,
|
|
2213
|
-
excludePatterns: config.excludePatterns,
|
|
3394
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2214
3395
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2215
3396
|
});
|
|
2216
3397
|
for (const diskFile of collection.files) {
|
|
@@ -2222,7 +3403,7 @@ function validateSnapshot(configInput) {
|
|
|
2222
3403
|
}
|
|
2223
3404
|
let diskHash;
|
|
2224
3405
|
try {
|
|
2225
|
-
diskHash = sha256Hex(
|
|
3406
|
+
diskHash = sha256Hex(fs9.readFileSync(path10.join(config.cwd, diskFile)));
|
|
2226
3407
|
} catch {
|
|
2227
3408
|
drift.missingOnDisk.push(diskFile);
|
|
2228
3409
|
continue;
|
|
@@ -2232,7 +3413,7 @@ function validateSnapshot(configInput) {
|
|
|
2232
3413
|
}
|
|
2233
3414
|
}
|
|
2234
3415
|
for (const snapshotFile of manifestFileMap.keys()) {
|
|
2235
|
-
if (!
|
|
3416
|
+
if (!fs9.existsSync(path10.join(config.cwd, snapshotFile))) {
|
|
2236
3417
|
drift.missingOnDisk.push(snapshotFile);
|
|
2237
3418
|
}
|
|
2238
3419
|
}
|
|
@@ -2246,10 +3427,10 @@ function validateSnapshot(configInput) {
|
|
|
2246
3427
|
if (!part || typeof part.filename !== "string") continue;
|
|
2247
3428
|
partNames.add(part.filename);
|
|
2248
3429
|
if (!config.artifacts.parts) continue;
|
|
2249
|
-
const partPath =
|
|
3430
|
+
const partPath = path10.join(snapshotDir, "parts", part.filename);
|
|
2250
3431
|
let buffer;
|
|
2251
3432
|
try {
|
|
2252
|
-
buffer =
|
|
3433
|
+
buffer = fs9.readFileSync(partPath);
|
|
2253
3434
|
} catch {
|
|
2254
3435
|
drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
|
|
2255
3436
|
continue;
|
|
@@ -2336,6 +3517,11 @@ function validateSnapshot(configInput) {
|
|
|
2336
3517
|
}
|
|
2337
3518
|
}
|
|
2338
3519
|
}
|
|
3520
|
+
if (recorded?.preset !== void 0 && recorded.preset !== config.preset) {
|
|
3521
|
+
drift.configMismatches.push(
|
|
3522
|
+
`preset: manifest ${recorded.preset} vs current ${config.preset}`
|
|
3523
|
+
);
|
|
3524
|
+
}
|
|
2339
3525
|
if (recorded?.layersMode !== void 0 && recorded.layersMode !== config.layersMode) {
|
|
2340
3526
|
drift.configMismatches.push(
|
|
2341
3527
|
`layersMode: manifest ${recorded.layersMode} vs current ${config.layersMode}`
|
|
@@ -2353,10 +3539,10 @@ function validateSnapshot(configInput) {
|
|
|
2353
3539
|
...Object.keys(recordedByLayer),
|
|
2354
3540
|
...Object.keys(config.budgets.byLayer)
|
|
2355
3541
|
]);
|
|
2356
|
-
for (const
|
|
2357
|
-
if (recordedByLayer[
|
|
3542
|
+
for (const layer2 of Array.from(layerNames).sort()) {
|
|
3543
|
+
if (recordedByLayer[layer2] !== config.budgets.byLayer[layer2]) {
|
|
2358
3544
|
drift.configMismatches.push(
|
|
2359
|
-
`budgets.byLayer.${
|
|
3545
|
+
`budgets.byLayer.${layer2}: manifest ${recordedByLayer[layer2]} vs current ${config.budgets.byLayer[layer2]}`
|
|
2360
3546
|
);
|
|
2361
3547
|
}
|
|
2362
3548
|
}
|
|
@@ -2439,9 +3625,9 @@ function validateSnapshot(configInput) {
|
|
|
2439
3625
|
["full-codebase.md", manifest.artifacts?.fullMarkdown]
|
|
2440
3626
|
]) {
|
|
2441
3627
|
if (!artifact) continue;
|
|
2442
|
-
const artifactPath =
|
|
3628
|
+
const artifactPath = path10.join(snapshotDir, key);
|
|
2443
3629
|
try {
|
|
2444
|
-
const buffer =
|
|
3630
|
+
const buffer = fs9.readFileSync(artifactPath);
|
|
2445
3631
|
if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
|
|
2446
3632
|
drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
|
|
2447
3633
|
}
|
|
@@ -2497,35 +3683,35 @@ function describeMatcher(matcher) {
|
|
|
2497
3683
|
return String(matcher);
|
|
2498
3684
|
}
|
|
2499
3685
|
function normalizeTarget(cwd, target) {
|
|
2500
|
-
const absolute =
|
|
2501
|
-
const relative3 =
|
|
2502
|
-
if (relative3.startsWith("..") ||
|
|
2503
|
-
return relative3.split(
|
|
3686
|
+
const absolute = path10.resolve(cwd, target);
|
|
3687
|
+
const relative3 = path10.relative(cwd, absolute);
|
|
3688
|
+
if (relative3.startsWith("..") || path10.isAbsolute(relative3)) return target;
|
|
3689
|
+
return relative3.split(path10.sep).join("/");
|
|
2504
3690
|
}
|
|
2505
3691
|
function findRuleMatch(rules, relativePath) {
|
|
2506
3692
|
return rules.find(
|
|
2507
3693
|
(rule) => matchRule(relativePath, rule.match)
|
|
2508
3694
|
);
|
|
2509
3695
|
}
|
|
2510
|
-
function
|
|
2511
|
-
const manifestPath =
|
|
3696
|
+
function readManifest2(config) {
|
|
3697
|
+
const manifestPath = path10.resolve(config.cwd, config.outDir, "manifest.json");
|
|
2512
3698
|
try {
|
|
2513
|
-
return JSON.parse(
|
|
3699
|
+
return JSON.parse(fs9.readFileSync(manifestPath, "utf8"));
|
|
2514
3700
|
} catch {
|
|
2515
3701
|
return null;
|
|
2516
3702
|
}
|
|
2517
3703
|
}
|
|
2518
3704
|
function explainPath(config, target) {
|
|
2519
3705
|
const relative3 = normalizeTarget(config.cwd, target);
|
|
2520
|
-
const absolute =
|
|
3706
|
+
const absolute = path10.join(config.cwd, relative3);
|
|
2521
3707
|
const result = {
|
|
2522
3708
|
path: relative3,
|
|
2523
|
-
exists:
|
|
3709
|
+
exists: fs9.existsSync(absolute),
|
|
2524
3710
|
included: false
|
|
2525
3711
|
};
|
|
2526
3712
|
const collection = collectFiles(config.cwd, {
|
|
2527
3713
|
includePatterns: config.include,
|
|
2528
|
-
excludePatterns: config.excludePatterns,
|
|
3714
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2529
3715
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2530
3716
|
});
|
|
2531
3717
|
if (!collection.files.includes(relative3)) {
|
|
@@ -2550,16 +3736,16 @@ function explainPath(config, target) {
|
|
|
2550
3736
|
if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
|
|
2551
3737
|
const kindRule = findRuleMatch(config.kinds, relative3);
|
|
2552
3738
|
if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
|
|
2553
|
-
if (
|
|
2554
|
-
const buffer =
|
|
2555
|
-
const
|
|
3739
|
+
if (fs9.existsSync(absolute) && fs9.statSync(absolute).isFile()) {
|
|
3740
|
+
const buffer = fs9.readFileSync(absolute);
|
|
3741
|
+
const text2 = buffer.toString("utf8");
|
|
2556
3742
|
result.bytes = buffer.length;
|
|
2557
|
-
result.lines = countLines(
|
|
2558
|
-
const { findings } = scanAndRedactSecrets(
|
|
3743
|
+
result.lines = countLines(text2);
|
|
3744
|
+
const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative3);
|
|
2559
3745
|
result.secretCount = findings.length;
|
|
2560
3746
|
result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
|
|
2561
3747
|
}
|
|
2562
|
-
const manifest =
|
|
3748
|
+
const manifest = readManifest2(config);
|
|
2563
3749
|
const entry = manifest?.files.find((file) => file.relativePath === relative3);
|
|
2564
3750
|
result.part = entry?.part ?? null;
|
|
2565
3751
|
result.inclusionMode = entry?.inclusionMode;
|
|
@@ -2642,10 +3828,10 @@ function compareManifests(before, after) {
|
|
|
2642
3828
|
};
|
|
2643
3829
|
}
|
|
2644
3830
|
function loadManifest(target) {
|
|
2645
|
-
const manifestPath =
|
|
3831
|
+
const manifestPath = fs9.statSync(target).isDirectory() ? path10.join(target, "manifest.json") : target;
|
|
2646
3832
|
let raw;
|
|
2647
3833
|
try {
|
|
2648
|
-
raw =
|
|
3834
|
+
raw = fs9.readFileSync(manifestPath, "utf8");
|
|
2649
3835
|
} catch {
|
|
2650
3836
|
throw new Error(`Could not read a manifest at ${manifestPath}.`);
|
|
2651
3837
|
}
|
|
@@ -2681,14 +3867,659 @@ function renderDiffText(diff) {
|
|
|
2681
3867
|
return lines.join("\n");
|
|
2682
3868
|
}
|
|
2683
3869
|
|
|
3870
|
+
// src/wizard/config-template.ts
|
|
3871
|
+
var DEFAULT_ANSWERS = {
|
|
3872
|
+
preset: "generic",
|
|
3873
|
+
maxPartBytes: "300k",
|
|
3874
|
+
largeFileBytes: "200k",
|
|
3875
|
+
include: [],
|
|
3876
|
+
excludePatterns: [],
|
|
3877
|
+
artifacts: {
|
|
3878
|
+
readme: true,
|
|
3879
|
+
manifest: true,
|
|
3880
|
+
parts: true,
|
|
3881
|
+
navigation: true,
|
|
3882
|
+
projectTree: true,
|
|
3883
|
+
audit: true,
|
|
3884
|
+
monolith: false
|
|
3885
|
+
},
|
|
3886
|
+
includeSensitiveFiles: false,
|
|
3887
|
+
outDir: "snapshot"
|
|
3888
|
+
};
|
|
3889
|
+
function list(values) {
|
|
3890
|
+
if (values.length === 0) return "[]";
|
|
3891
|
+
return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
3892
|
+
}
|
|
3893
|
+
function size(value) {
|
|
3894
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
3895
|
+
}
|
|
3896
|
+
var PRESET_NAMES = [
|
|
3897
|
+
"nextjs \xB7 react-vite \xB7 vue-vite \xB7 nuxt \xB7 sveltekit \xB7 angular \xB7 astro",
|
|
3898
|
+
"remix \xB7 solid \xB7 nest \xB7 node-express \xB7 node-lib",
|
|
3899
|
+
"python-django \xB7 python-fastapi \xB7 python-lib \xB7 go \xB7 rust",
|
|
3900
|
+
"java-spring \xB7 dotnet \xB7 php-laravel \xB7 ruby-rails",
|
|
3901
|
+
"monorepo \xB7 docs \xB7 infra \xB7 generic"
|
|
3902
|
+
];
|
|
3903
|
+
function renderConfig(answers) {
|
|
3904
|
+
const { artifacts } = answers;
|
|
3905
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
3906
|
+
|
|
3907
|
+
export default defineConfig({
|
|
3908
|
+
// \u2500\u2500 Project \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3909
|
+
// Which project shape to use. Detected from the repo when omitted.
|
|
3910
|
+
// ${PRESET_NAMES.join("\n // ")}
|
|
3911
|
+
preset: ${JSON.stringify(answers.preset)},
|
|
3912
|
+
|
|
3913
|
+
// Recorded in the manifest when git cannot report a branch.
|
|
3914
|
+
branch: "main",
|
|
3915
|
+
|
|
3916
|
+
// \u2500\u2500 Output \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3917
|
+
// Where the snapshot is written.
|
|
3918
|
+
outDir: ${JSON.stringify(answers.outDir)},
|
|
3919
|
+
// Scratch directory; the new snapshot is renamed into place atomically.
|
|
3920
|
+
tmpDir: "snapshot.tmp",
|
|
3921
|
+
|
|
3922
|
+
// \u2500\u2500 Size \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3923
|
+
// Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
|
|
3924
|
+
maxPartBytes: ${size(answers.maxPartBytes)},
|
|
3925
|
+
// Files above this size get a head/tail preview instead of the full body.
|
|
3926
|
+
largeFileBytes: ${size(answers.largeFileBytes)},
|
|
3927
|
+
previewHeadLines: 80,
|
|
3928
|
+
previewTailLines: 40,
|
|
3929
|
+
// Different ceilings per layer. Unlisted layers use \`default\`.
|
|
3930
|
+
budgets: {
|
|
3931
|
+
default: ${size(answers.maxPartBytes)},
|
|
3932
|
+
byLayer: {},
|
|
3933
|
+
},
|
|
3934
|
+
|
|
3935
|
+
// \u2500\u2500 What goes in \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3936
|
+
// Allow-list. Empty means "everything that is not ignored".
|
|
3937
|
+
// Example: ["src/**", "packages/*/src/**"]
|
|
3938
|
+
include: ${list(answers.include)},
|
|
3939
|
+
// Globs or path segments to drop. Example: ["**/*.test.ts", "docs"]
|
|
3940
|
+
excludePatterns: ${list(answers.excludePatterns)},
|
|
3941
|
+
|
|
3942
|
+
// \u2500\u2500 Classification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3943
|
+
// "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
|
|
3944
|
+
layersMode: "merge",
|
|
3945
|
+
// [{ name: "billing", match: "src/features/billing/**" }]
|
|
3946
|
+
layers: [],
|
|
3947
|
+
// [{ kind: "route", match: "src/app/api/**" }]
|
|
3948
|
+
kinds: [],
|
|
3949
|
+
|
|
3950
|
+
// \u2500\u2500 Safety \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3951
|
+
// .env, *.pem, .npmrc, .ssh/ and friends are skipped unless this is true.
|
|
3952
|
+
includeSensitiveFiles: ${answers.includeSensitiveFiles},
|
|
3953
|
+
secrets: {
|
|
3954
|
+
// [{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ }]
|
|
3955
|
+
customPatterns: [],
|
|
3956
|
+
// Set true to drop the 21 built-in rules.
|
|
3957
|
+
disableDefaultPatterns: false,
|
|
3958
|
+
},
|
|
3959
|
+
|
|
3960
|
+
// \u2500\u2500 Artifacts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3961
|
+
artifacts: {
|
|
3962
|
+
readme: ${artifacts.readme}, // README.md
|
|
3963
|
+
manifest: ${artifacts.manifest}, // manifest.json \u2014 required by validate and report
|
|
3964
|
+
parts: ${artifacts.parts}, // parts/part-XXX.md
|
|
3965
|
+
navigation: ${artifacts.navigation}, // reports/navigation.md
|
|
3966
|
+
projectTree: ${artifacts.projectTree}, // reports/project-tree.md
|
|
3967
|
+
audit: ${artifacts.audit}, // reports/audit.md
|
|
3968
|
+
monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
|
|
3969
|
+
},
|
|
3970
|
+
// Skip the monolith above this size. null = no limit.
|
|
3971
|
+
maxMonolithBytes: null,
|
|
3972
|
+
|
|
3973
|
+
// \u2500\u2500 Advanced \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3974
|
+
// afterGenerate: (manifest) => { /* write your own artifact */ }
|
|
3975
|
+
hooks: {},
|
|
3976
|
+
});
|
|
3977
|
+
`;
|
|
3978
|
+
}
|
|
3979
|
+
function renderMinimalConfig(answers) {
|
|
3980
|
+
const lines = [];
|
|
3981
|
+
if (answers.preset !== DEFAULT_ANSWERS.preset) lines.push(` preset: ${JSON.stringify(answers.preset)},`);
|
|
3982
|
+
if (answers.maxPartBytes !== DEFAULT_ANSWERS.maxPartBytes) {
|
|
3983
|
+
lines.push(` maxPartBytes: ${size(answers.maxPartBytes)},`);
|
|
3984
|
+
}
|
|
3985
|
+
if (answers.largeFileBytes !== DEFAULT_ANSWERS.largeFileBytes) {
|
|
3986
|
+
lines.push(` largeFileBytes: ${size(answers.largeFileBytes)},`);
|
|
3987
|
+
}
|
|
3988
|
+
if (answers.include.length > 0) lines.push(` include: ${list(answers.include)},`);
|
|
3989
|
+
if (answers.excludePatterns.length > 0) {
|
|
3990
|
+
lines.push(` excludePatterns: ${list(answers.excludePatterns)},`);
|
|
3991
|
+
}
|
|
3992
|
+
if (answers.includeSensitiveFiles) lines.push(" includeSensitiveFiles: true,");
|
|
3993
|
+
if (answers.outDir !== DEFAULT_ANSWERS.outDir) lines.push(` outDir: ${JSON.stringify(answers.outDir)},`);
|
|
3994
|
+
const changed = Object.keys(answers.artifacts).filter(
|
|
3995
|
+
(name) => answers.artifacts[name] !== DEFAULT_ANSWERS.artifacts[name]
|
|
3996
|
+
);
|
|
3997
|
+
if (changed.length > 0) {
|
|
3998
|
+
const entries = changed.map((name) => ` ${name}: ${answers.artifacts[name]}`).join(",\n");
|
|
3999
|
+
lines.push(` artifacts: {
|
|
4000
|
+
${entries},
|
|
4001
|
+
},`);
|
|
4002
|
+
}
|
|
4003
|
+
if (lines.length === 0) {
|
|
4004
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
4005
|
+
|
|
4006
|
+
export default defineConfig({});
|
|
4007
|
+
`;
|
|
4008
|
+
}
|
|
4009
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
4010
|
+
|
|
4011
|
+
export default defineConfig({
|
|
4012
|
+
${lines.join("\n")}
|
|
4013
|
+
});
|
|
4014
|
+
`;
|
|
4015
|
+
}
|
|
4016
|
+
var PromptCancelled = class extends Error {
|
|
4017
|
+
constructor() {
|
|
4018
|
+
super("Prompt cancelled");
|
|
4019
|
+
this.name = "PromptCancelled";
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
function defaultIO() {
|
|
4023
|
+
return { input: process.stdin, output: process.stdout };
|
|
4024
|
+
}
|
|
4025
|
+
function isInteractive(io) {
|
|
4026
|
+
return Boolean(io.input.isTTY && io.output.isTTY);
|
|
4027
|
+
}
|
|
4028
|
+
function moveSelection(current, delta, count) {
|
|
4029
|
+
if (count <= 0) return 0;
|
|
4030
|
+
return ((current + delta) % count + count) % count;
|
|
4031
|
+
}
|
|
4032
|
+
function toggleIndex(selected, index) {
|
|
4033
|
+
const next = new Set(selected);
|
|
4034
|
+
if (next.has(index)) next.delete(index);
|
|
4035
|
+
else next.add(index);
|
|
4036
|
+
return next;
|
|
4037
|
+
}
|
|
4038
|
+
function marker(active) {
|
|
4039
|
+
return active ? "\u276F" : " ";
|
|
4040
|
+
}
|
|
4041
|
+
function choiceLine(choice, active) {
|
|
4042
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
4043
|
+
return ` ${marker(active)} ${choice.label}${hint}`;
|
|
4044
|
+
}
|
|
4045
|
+
function renderSelect(message, choices, index) {
|
|
4046
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
4047
|
+
choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
|
|
4048
|
+
lines.push("", " \u2191\u2193 move \xB7 enter select");
|
|
4049
|
+
return lines.join("\n");
|
|
4050
|
+
}
|
|
4051
|
+
function renderMultiSelect(message, choices, index, selected) {
|
|
4052
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
4053
|
+
choices.forEach((choice, position) => {
|
|
4054
|
+
const box = selected.has(position) ? "\u25FC" : "\u25FB";
|
|
4055
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
4056
|
+
lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
|
|
4057
|
+
});
|
|
4058
|
+
lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
|
|
4059
|
+
return lines.join("\n");
|
|
4060
|
+
}
|
|
4061
|
+
function renderConfirm(message, value) {
|
|
4062
|
+
return [
|
|
4063
|
+
`\u25C6 ${message}`,
|
|
4064
|
+
"",
|
|
4065
|
+
` ${marker(value)} Yes`,
|
|
4066
|
+
` ${marker(!value)} No`,
|
|
4067
|
+
"",
|
|
4068
|
+
" \u2190\u2192 or y/n \xB7 enter confirm"
|
|
4069
|
+
].join("\n");
|
|
4070
|
+
}
|
|
4071
|
+
function renderText(message, value, error) {
|
|
4072
|
+
const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
|
|
4073
|
+
if (error) lines.push("", ` \u2716 ${error}`);
|
|
4074
|
+
return lines.join("\n");
|
|
4075
|
+
}
|
|
4076
|
+
function keyReader(input) {
|
|
4077
|
+
const queue = [];
|
|
4078
|
+
const waiters = [];
|
|
4079
|
+
const wasRaw = input.isRaw;
|
|
4080
|
+
const onKey = (_text, key) => {
|
|
4081
|
+
const waiter = waiters.shift();
|
|
4082
|
+
if (waiter) waiter(key);
|
|
4083
|
+
else queue.push(key);
|
|
4084
|
+
};
|
|
4085
|
+
readline.emitKeypressEvents(input);
|
|
4086
|
+
if (input.isTTY) input.setRawMode(true);
|
|
4087
|
+
input.resume();
|
|
4088
|
+
input.on("keypress", onKey);
|
|
4089
|
+
return {
|
|
4090
|
+
next() {
|
|
4091
|
+
const queued = queue.shift();
|
|
4092
|
+
if (queued) return Promise.resolve(queued);
|
|
4093
|
+
return new Promise((resolve5) => waiters.push(resolve5));
|
|
4094
|
+
},
|
|
4095
|
+
close() {
|
|
4096
|
+
input.off("keypress", onKey);
|
|
4097
|
+
if (input.isTTY) input.setRawMode(wasRaw ?? false);
|
|
4098
|
+
input.pause();
|
|
4099
|
+
}
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
var Frame = class {
|
|
4103
|
+
constructor(output) {
|
|
4104
|
+
this.output = output;
|
|
4105
|
+
}
|
|
4106
|
+
output;
|
|
4107
|
+
drawn = 0;
|
|
4108
|
+
draw(text2) {
|
|
4109
|
+
this.erase();
|
|
4110
|
+
this.output.write(`${text2}
|
|
4111
|
+
`);
|
|
4112
|
+
this.drawn = text2.split("\n").length;
|
|
4113
|
+
}
|
|
4114
|
+
erase() {
|
|
4115
|
+
if (this.drawn > 0) this.output.write(`\x1B[${this.drawn}A\x1B[0J`);
|
|
4116
|
+
this.drawn = 0;
|
|
4117
|
+
}
|
|
4118
|
+
};
|
|
4119
|
+
function isCancel(key) {
|
|
4120
|
+
return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
|
|
4121
|
+
}
|
|
4122
|
+
async function select(io, message, choices, options = {}) {
|
|
4123
|
+
if (choices.length === 0) throw new Error("select() requires at least one choice.");
|
|
4124
|
+
const fallbackIndex = Math.max(
|
|
4125
|
+
0,
|
|
4126
|
+
choices.findIndex((choice) => choice.value === options.defaultValue)
|
|
4127
|
+
);
|
|
4128
|
+
if (!isInteractive(io)) {
|
|
4129
|
+
io.output.write(`${renderSelect(message, choices, fallbackIndex)}
|
|
4130
|
+
`);
|
|
4131
|
+
return choices[fallbackIndex].value;
|
|
4132
|
+
}
|
|
4133
|
+
const frame = new Frame(io.output);
|
|
4134
|
+
const reader = keyReader(io.input);
|
|
4135
|
+
let index = fallbackIndex;
|
|
4136
|
+
try {
|
|
4137
|
+
for (; ; ) {
|
|
4138
|
+
frame.draw(renderSelect(message, choices, index));
|
|
4139
|
+
const key = await reader.next();
|
|
4140
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4141
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
4142
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
4143
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
4144
|
+
frame.erase();
|
|
4145
|
+
return choices[index].value;
|
|
4146
|
+
} else if (key.name === "j") index = moveSelection(index, 1, choices.length);
|
|
4147
|
+
else if (key.name === "k") index = moveSelection(index, -1, choices.length);
|
|
4148
|
+
}
|
|
4149
|
+
} finally {
|
|
4150
|
+
reader.close();
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
async function multiSelect(io, message, choices, options = {}) {
|
|
4154
|
+
const selected = /* @__PURE__ */ new Set();
|
|
4155
|
+
choices.forEach((choice, position) => {
|
|
4156
|
+
if (options.defaults?.includes(choice.value)) selected.add(position);
|
|
4157
|
+
});
|
|
4158
|
+
const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
|
|
4159
|
+
if (!isInteractive(io)) {
|
|
4160
|
+
io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
|
|
4161
|
+
`);
|
|
4162
|
+
return result();
|
|
4163
|
+
}
|
|
4164
|
+
const frame = new Frame(io.output);
|
|
4165
|
+
const reader = keyReader(io.input);
|
|
4166
|
+
let index = 0;
|
|
4167
|
+
try {
|
|
4168
|
+
for (; ; ) {
|
|
4169
|
+
frame.draw(renderMultiSelect(message, choices, index, selected));
|
|
4170
|
+
const key = await reader.next();
|
|
4171
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4172
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
4173
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
4174
|
+
else if (key.name === "space") {
|
|
4175
|
+
const next = toggleIndex(selected, index);
|
|
4176
|
+
selected.clear();
|
|
4177
|
+
for (const position of next) selected.add(position);
|
|
4178
|
+
} else if (key.name === "a") {
|
|
4179
|
+
if (selected.size === choices.length) selected.clear();
|
|
4180
|
+
else choices.forEach((_, position) => selected.add(position));
|
|
4181
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
4182
|
+
frame.erase();
|
|
4183
|
+
return result();
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
} finally {
|
|
4187
|
+
reader.close();
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
async function confirm(io, message, options = {}) {
|
|
4191
|
+
let value = options.defaultValue ?? true;
|
|
4192
|
+
if (!isInteractive(io)) {
|
|
4193
|
+
io.output.write(`${renderConfirm(message, value)}
|
|
4194
|
+
`);
|
|
4195
|
+
return value;
|
|
4196
|
+
}
|
|
4197
|
+
const frame = new Frame(io.output);
|
|
4198
|
+
const reader = keyReader(io.input);
|
|
4199
|
+
try {
|
|
4200
|
+
for (; ; ) {
|
|
4201
|
+
frame.draw(renderConfirm(message, value));
|
|
4202
|
+
const key = await reader.next();
|
|
4203
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4204
|
+
if (key.name === "up" || key.name === "left") value = true;
|
|
4205
|
+
else if (key.name === "down" || key.name === "right") value = false;
|
|
4206
|
+
else if (key.name === "y") value = true;
|
|
4207
|
+
else if (key.name === "n") value = false;
|
|
4208
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
4209
|
+
frame.erase();
|
|
4210
|
+
return value;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
} finally {
|
|
4214
|
+
reader.close();
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
async function text(io, message, options = {}) {
|
|
4218
|
+
const fallback = options.defaultValue ?? "";
|
|
4219
|
+
if (!isInteractive(io)) {
|
|
4220
|
+
io.output.write(`${renderText(message, fallback)}
|
|
4221
|
+
`);
|
|
4222
|
+
return fallback;
|
|
4223
|
+
}
|
|
4224
|
+
const frame = new Frame(io.output);
|
|
4225
|
+
const reader = keyReader(io.input);
|
|
4226
|
+
let value = fallback;
|
|
4227
|
+
let error;
|
|
4228
|
+
try {
|
|
4229
|
+
for (; ; ) {
|
|
4230
|
+
frame.draw(renderText(message, value, error));
|
|
4231
|
+
const key = await reader.next();
|
|
4232
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4233
|
+
error = void 0;
|
|
4234
|
+
if (key.name === "return" || key.name === "enter") {
|
|
4235
|
+
const problem = options.validate ? options.validate(value) : null;
|
|
4236
|
+
if (problem) {
|
|
4237
|
+
error = problem;
|
|
4238
|
+
continue;
|
|
4239
|
+
}
|
|
4240
|
+
frame.erase();
|
|
4241
|
+
return value;
|
|
4242
|
+
}
|
|
4243
|
+
if (key.name === "backspace") {
|
|
4244
|
+
value = value.slice(0, -1);
|
|
4245
|
+
continue;
|
|
4246
|
+
}
|
|
4247
|
+
if (key.name === "escape") {
|
|
4248
|
+
value = "";
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
if (_isPrintable(key)) value += _keyText(key);
|
|
4252
|
+
}
|
|
4253
|
+
} finally {
|
|
4254
|
+
reader.close();
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
function _isPrintable(key) {
|
|
4258
|
+
return typeof key.sequence === "string" && key.sequence.length === 1 && key.sequence >= " ";
|
|
4259
|
+
}
|
|
4260
|
+
function _keyText(key) {
|
|
4261
|
+
return key.sequence ?? "";
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
// src/wizard/init.ts
|
|
4265
|
+
var CONFIG_CANDIDATES = [
|
|
4266
|
+
"snapshot.config.ts",
|
|
4267
|
+
"snapshot.config.js",
|
|
4268
|
+
"snapshot.config.mjs",
|
|
4269
|
+
"snapshot.config.cjs",
|
|
4270
|
+
"snapshot.config.json",
|
|
4271
|
+
".snapshotrc.json",
|
|
4272
|
+
".snapshotrc"
|
|
4273
|
+
];
|
|
4274
|
+
var SIZE_CHOICES = [
|
|
4275
|
+
{ value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
|
|
4276
|
+
{ value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
|
|
4277
|
+
{ value: "300k", label: "Large", hint: "300 KB \xB7 ~75k tokens \xB7 the default" },
|
|
4278
|
+
{ value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
|
|
4279
|
+
];
|
|
4280
|
+
function existingConfig(cwd) {
|
|
4281
|
+
for (const name of CONFIG_CANDIDATES) {
|
|
4282
|
+
if (fs9.existsSync(path10.join(cwd, name))) return name;
|
|
4283
|
+
}
|
|
4284
|
+
return null;
|
|
4285
|
+
}
|
|
4286
|
+
function artifactAnswers(options) {
|
|
4287
|
+
const artifacts = { ...DEFAULT_ANSWERS.artifacts };
|
|
4288
|
+
for (const name of options.skipArtifacts ?? []) {
|
|
4289
|
+
if (ARTIFACT_NAMES.includes(name)) {
|
|
4290
|
+
artifacts[name] = false;
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
if (options.onlyArtifacts && options.onlyArtifacts.length > 0) {
|
|
4294
|
+
const keep = new Set(options.onlyArtifacts);
|
|
4295
|
+
for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
|
|
4296
|
+
}
|
|
4297
|
+
return artifacts;
|
|
4298
|
+
}
|
|
4299
|
+
function topLevelDirs(cwd) {
|
|
4300
|
+
try {
|
|
4301
|
+
return fs9.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
|
|
4302
|
+
} catch {
|
|
4303
|
+
return [];
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
function addScripts(cwd, notes) {
|
|
4307
|
+
const packagePath = path10.join(cwd, "package.json");
|
|
4308
|
+
if (!fs9.existsSync(packagePath)) {
|
|
4309
|
+
notes.push("No package.json found, so npm scripts were not added.");
|
|
4310
|
+
return [];
|
|
4311
|
+
}
|
|
4312
|
+
let parsed;
|
|
4313
|
+
try {
|
|
4314
|
+
parsed = JSON.parse(fs9.readFileSync(packagePath, "utf8"));
|
|
4315
|
+
} catch {
|
|
4316
|
+
notes.push("Could not parse package.json; npm scripts were not added.");
|
|
4317
|
+
return [];
|
|
4318
|
+
}
|
|
4319
|
+
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
4320
|
+
const added = [];
|
|
4321
|
+
if (!scripts.snapshot) {
|
|
4322
|
+
scripts.snapshot = "codebase-snapshot generate";
|
|
4323
|
+
added.push("snapshot");
|
|
4324
|
+
}
|
|
4325
|
+
if (!scripts["snapshot:validate"]) {
|
|
4326
|
+
scripts["snapshot:validate"] = "codebase-snapshot validate";
|
|
4327
|
+
added.push("snapshot:validate");
|
|
4328
|
+
}
|
|
4329
|
+
if (added.length === 0) return [];
|
|
4330
|
+
parsed.scripts = scripts;
|
|
4331
|
+
fs9.writeFileSync(packagePath, `${JSON.stringify(parsed, null, 2)}
|
|
4332
|
+
`, "utf8");
|
|
4333
|
+
return added;
|
|
4334
|
+
}
|
|
4335
|
+
function addGitignore(cwd) {
|
|
4336
|
+
const ignorePath = path10.join(cwd, ".gitignore");
|
|
4337
|
+
const existing = fs9.existsSync(ignorePath) ? fs9.readFileSync(ignorePath, "utf8") : "";
|
|
4338
|
+
const wanted = ["snapshot/", "snapshot.tmp/"];
|
|
4339
|
+
const added = wanted.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
4340
|
+
if (added.length === 0) return [];
|
|
4341
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
4342
|
+
fs9.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
|
|
4343
|
+
`, "utf8");
|
|
4344
|
+
return added;
|
|
4345
|
+
}
|
|
4346
|
+
function resolvePresetChoice(answers, presetName) {
|
|
4347
|
+
return {
|
|
4348
|
+
value: presetName,
|
|
4349
|
+
label: presetName,
|
|
4350
|
+
hint: presetName === answers.preset ? "detected" : void 0
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4353
|
+
async function runInit(options) {
|
|
4354
|
+
const io = options.io ?? defaultIO();
|
|
4355
|
+
const detected = detectPreset(options.cwd, options.preset);
|
|
4356
|
+
const notes = [];
|
|
4357
|
+
const answers = {
|
|
4358
|
+
preset: detected.presetName,
|
|
4359
|
+
maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
|
|
4360
|
+
largeFileBytes: DEFAULT_ANSWERS.largeFileBytes,
|
|
4361
|
+
include: options.include ?? [],
|
|
4362
|
+
excludePatterns: options.exclude ?? [],
|
|
4363
|
+
artifacts: artifactAnswers(options),
|
|
4364
|
+
includeSensitiveFiles: options.includeSensitiveFiles ?? DEFAULT_ANSWERS.includeSensitiveFiles,
|
|
4365
|
+
outDir: options.outDir ?? DEFAULT_ANSWERS.outDir
|
|
4366
|
+
};
|
|
4367
|
+
const interactive = isInteractive(io) && options.yes !== true;
|
|
4368
|
+
if (interactive) {
|
|
4369
|
+
io.output.write("\n @ankurhalder/codebase-snapshot\n");
|
|
4370
|
+
io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
|
|
4371
|
+
io.output.write(` Detected ${detected.presetName}
|
|
4372
|
+
`);
|
|
4373
|
+
io.output.write(` Directory ${options.cwd}
|
|
4374
|
+
|
|
4375
|
+
`);
|
|
4376
|
+
const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
|
|
4377
|
+
defaultValue: true
|
|
4378
|
+
});
|
|
4379
|
+
if (!useDetected) {
|
|
4380
|
+
answers.preset = await select(
|
|
4381
|
+
io,
|
|
4382
|
+
"What kind of project is this?",
|
|
4383
|
+
listPresetNames().map((name) => resolvePresetChoice(answers, name))
|
|
4384
|
+
);
|
|
4385
|
+
const chosen = detectPreset(options.cwd, answers.preset);
|
|
4386
|
+
answers.preset = chosen.presetName;
|
|
4387
|
+
}
|
|
4388
|
+
const scope = await select(
|
|
4389
|
+
io,
|
|
4390
|
+
"What should go into the snapshot?",
|
|
4391
|
+
[
|
|
4392
|
+
{ value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
|
|
4393
|
+
{ value: "folders", label: "Only specific folders\u2026" },
|
|
4394
|
+
{ value: "no-tests", label: "Everything, but skip tests" },
|
|
4395
|
+
{ value: "custom", label: "Let me write glob patterns" }
|
|
4396
|
+
],
|
|
4397
|
+
{ defaultValue: "all" }
|
|
4398
|
+
);
|
|
4399
|
+
if (scope === "folders") {
|
|
4400
|
+
const dirs = topLevelDirs(options.cwd).slice(0, 40);
|
|
4401
|
+
if (dirs.length === 0) {
|
|
4402
|
+
notes.push("No top-level directories found; the snapshot will include everything.");
|
|
4403
|
+
} else {
|
|
4404
|
+
const chosen = await multiSelect(
|
|
4405
|
+
io,
|
|
4406
|
+
"Select folders",
|
|
4407
|
+
dirs.map((name) => ({ value: name, label: `${name}/` })),
|
|
4408
|
+
{ defaults: dirs }
|
|
4409
|
+
);
|
|
4410
|
+
answers.include = chosen.map((name) => `${name}/**`);
|
|
4411
|
+
}
|
|
4412
|
+
} else if (scope === "no-tests") {
|
|
4413
|
+
answers.excludePatterns = [
|
|
4414
|
+
...answers.excludePatterns,
|
|
4415
|
+
"**/*.test.*",
|
|
4416
|
+
"**/*.spec.*",
|
|
4417
|
+
"tests",
|
|
4418
|
+
"test"
|
|
4419
|
+
];
|
|
4420
|
+
} else if (scope === "custom") {
|
|
4421
|
+
const raw = await text(io, "Glob patterns to include (comma separated)", {
|
|
4422
|
+
defaultValue: ""
|
|
4423
|
+
});
|
|
4424
|
+
answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
4425
|
+
}
|
|
4426
|
+
const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
|
|
4427
|
+
defaultValue: "120k"
|
|
4428
|
+
});
|
|
4429
|
+
if (sizeChoice === "custom") {
|
|
4430
|
+
const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
|
|
4431
|
+
defaultValue: String(answers.maxPartBytes),
|
|
4432
|
+
validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
|
|
4433
|
+
});
|
|
4434
|
+
answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
|
|
4435
|
+
} else {
|
|
4436
|
+
answers.maxPartBytes = sizeChoice;
|
|
4437
|
+
}
|
|
4438
|
+
const artifactChoices = [
|
|
4439
|
+
{ value: "readme", label: "README.md", hint: "orientation page" },
|
|
4440
|
+
{ value: "manifest", label: "manifest.json", hint: "required by validate and report" },
|
|
4441
|
+
{ value: "parts", label: "parts/", hint: "the code payload" },
|
|
4442
|
+
{ value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
|
|
4443
|
+
{ value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
|
|
4444
|
+
{ value: "audit", label: "reports/audit.md", hint: "security and health findings" },
|
|
4445
|
+
{ value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
|
|
4446
|
+
];
|
|
4447
|
+
const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
|
|
4448
|
+
const selectedArtifacts = await multiSelect(
|
|
4449
|
+
io,
|
|
4450
|
+
"Which artifacts should be generated?",
|
|
4451
|
+
artifactChoices,
|
|
4452
|
+
{ defaults: presetEnabled }
|
|
4453
|
+
);
|
|
4454
|
+
for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
|
|
4455
|
+
const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
|
|
4456
|
+
answers.includeSensitiveFiles = await confirm(
|
|
4457
|
+
io,
|
|
4458
|
+
"Include credential files (.env, *.pem, .npmrc, .ssh/)?",
|
|
4459
|
+
{ defaultValue: false }
|
|
4460
|
+
);
|
|
4461
|
+
io.output.write("\n Review\n\n");
|
|
4462
|
+
io.output.write(` project ${answers.preset}
|
|
4463
|
+
`);
|
|
4464
|
+
io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
|
|
4465
|
+
`);
|
|
4466
|
+
io.output.write(` part size ${String(answers.maxPartBytes)}
|
|
4467
|
+
`);
|
|
4468
|
+
io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
|
|
4469
|
+
`);
|
|
4470
|
+
io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
|
|
4471
|
+
|
|
4472
|
+
`);
|
|
4473
|
+
io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
|
|
4474
|
+
io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
|
|
4475
|
+
const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
|
|
4476
|
+
if (!proceed) throw new PromptCancelled();
|
|
4477
|
+
}
|
|
4478
|
+
const configText = options.minimal ? renderMinimalConfig(answers) : renderConfig(answers);
|
|
4479
|
+
const configName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
4480
|
+
const configPath = path10.join(options.cwd, configName);
|
|
4481
|
+
if (options.dryRun) {
|
|
4482
|
+
return {
|
|
4483
|
+
configPath,
|
|
4484
|
+
configText,
|
|
4485
|
+
written: false,
|
|
4486
|
+
preset: answers.preset,
|
|
4487
|
+
scripts: [],
|
|
4488
|
+
gitignoreEntries: [],
|
|
4489
|
+
notes
|
|
4490
|
+
};
|
|
4491
|
+
}
|
|
4492
|
+
const present = existingConfig(options.cwd);
|
|
4493
|
+
if (present && present !== configName && !options.force) {
|
|
4494
|
+
throw new Error(
|
|
4495
|
+
`A configuration already exists (${present}). Re-run with --force to replace it.`
|
|
4496
|
+
);
|
|
4497
|
+
}
|
|
4498
|
+
if (fs9.existsSync(configPath) && !options.force) {
|
|
4499
|
+
throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
|
|
4500
|
+
}
|
|
4501
|
+
fs9.writeFileSync(configPath, configText, "utf8");
|
|
4502
|
+
const scripts = addScripts(options.cwd, notes);
|
|
4503
|
+
const gitignoreEntries = addGitignore(options.cwd);
|
|
4504
|
+
return {
|
|
4505
|
+
configPath,
|
|
4506
|
+
configText,
|
|
4507
|
+
written: true,
|
|
4508
|
+
preset: answers.preset,
|
|
4509
|
+
scripts,
|
|
4510
|
+
gitignoreEntries,
|
|
4511
|
+
notes
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
4514
|
+
|
|
2684
4515
|
// src/index.ts
|
|
2685
4516
|
var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
|
|
2686
|
-
var GENERATOR_VERSION = "2.
|
|
4517
|
+
var GENERATOR_VERSION = "2.1.0";
|
|
2687
4518
|
var SCHEMA_VERSION = "3.0.0";
|
|
2688
4519
|
function sha256Hex2(input) {
|
|
2689
4520
|
return crypto.createHash("sha256").update(input).digest("hex");
|
|
2690
4521
|
}
|
|
2691
|
-
var
|
|
4522
|
+
var TS_EXTENSIONS = [
|
|
2692
4523
|
".ts",
|
|
2693
4524
|
".mts",
|
|
2694
4525
|
".cts",
|
|
@@ -2696,24 +4527,21 @@ var SUMMARY_EXTENSIONS = [
|
|
|
2696
4527
|
".js",
|
|
2697
4528
|
".mjs",
|
|
2698
4529
|
".cjs",
|
|
2699
|
-
".jsx"
|
|
2700
|
-
".json",
|
|
2701
|
-
".jsonc",
|
|
2702
|
-
".md",
|
|
2703
|
-
".mdx"
|
|
4530
|
+
".jsx"
|
|
2704
4531
|
];
|
|
2705
4532
|
function summarizeFile(relativePath, redactedContent) {
|
|
2706
4533
|
const lower = relativePath.toLowerCase();
|
|
2707
|
-
if (!SUMMARY_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
2708
|
-
return void 0;
|
|
2709
|
-
}
|
|
2710
4534
|
if (lower.endsWith(".json") || lower.endsWith(".jsonc")) {
|
|
2711
|
-
return summarizeJson(redactedContent);
|
|
4535
|
+
return { summary: summarizeJson(redactedContent), quality: "structural" };
|
|
2712
4536
|
}
|
|
2713
4537
|
if (lower.endsWith(".md") || lower.endsWith(".mdx")) {
|
|
2714
|
-
return summarizeMarkdown(redactedContent);
|
|
4538
|
+
return { summary: summarizeMarkdown(redactedContent), quality: "structural" };
|
|
4539
|
+
}
|
|
4540
|
+
if (TS_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
4541
|
+
return { summary: summarizeTypeScriptSource(relativePath, redactedContent), quality: "ast" };
|
|
2715
4542
|
}
|
|
2716
|
-
|
|
4543
|
+
const profiled = summarizeWithProfile(relativePath, redactedContent);
|
|
4544
|
+
return profiled ? { summary: profiled.summary, quality: profiled.quality } : void 0;
|
|
2717
4545
|
}
|
|
2718
4546
|
function isSourceSummary(summary) {
|
|
2719
4547
|
return Boolean(summary && typeof summary === "object" && "imports" in summary);
|
|
@@ -2739,8 +4567,8 @@ var IMPORT_CANDIDATE_SUFFIXES = [
|
|
|
2739
4567
|
];
|
|
2740
4568
|
function resolveSpecifier(fromFile, specifier, knownFiles) {
|
|
2741
4569
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
2742
|
-
const baseDir =
|
|
2743
|
-
const joined =
|
|
4570
|
+
const baseDir = path10.posix.dirname(fromFile);
|
|
4571
|
+
const joined = path10.posix.normalize(path10.posix.join(baseDir, specifier));
|
|
2744
4572
|
const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
|
|
2745
4573
|
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
2746
4574
|
const withSuffix = `${joined}${suffix}`;
|
|
@@ -2832,11 +4660,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
2832
4660
|
return errors;
|
|
2833
4661
|
}
|
|
2834
4662
|
function resolveSafeOutputDirs(config) {
|
|
2835
|
-
const cwd =
|
|
2836
|
-
const outDir =
|
|
2837
|
-
const tmpDir =
|
|
2838
|
-
const root =
|
|
2839
|
-
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${
|
|
4663
|
+
const cwd = path10.resolve(config.cwd);
|
|
4664
|
+
const outDir = path10.resolve(cwd, config.outDir);
|
|
4665
|
+
const tmpDir = path10.resolve(cwd, config.tmpDir);
|
|
4666
|
+
const root = path10.parse(cwd).root;
|
|
4667
|
+
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path10.sep}`);
|
|
2840
4668
|
const reject = (reason) => {
|
|
2841
4669
|
throw new Error(
|
|
2842
4670
|
`Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
|
|
@@ -2855,26 +4683,66 @@ function resolveSafeOutputDirs(config) {
|
|
|
2855
4683
|
if (contains(tmpDir, outDir)) reject("outDir is inside tmpDir");
|
|
2856
4684
|
return { outDir, tmpDir };
|
|
2857
4685
|
}
|
|
4686
|
+
var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
|
|
4687
|
+
var OWNERSHIP_MARKER = ".codebase-snapshot";
|
|
4688
|
+
function writeOwnershipMarker(dir) {
|
|
4689
|
+
const marker2 = {
|
|
4690
|
+
generator: GENERATOR_NAME,
|
|
4691
|
+
version: GENERATOR_VERSION,
|
|
4692
|
+
schemaVersion: SCHEMA_VERSION
|
|
4693
|
+
};
|
|
4694
|
+
fs9.writeFileSync(path10.join(dir, OWNERSHIP_MARKER), `${JSON.stringify(marker2, null, 2)}
|
|
4695
|
+
`, "utf8");
|
|
4696
|
+
}
|
|
4697
|
+
function isOwnOutput(dir) {
|
|
4698
|
+
if (fs9.existsSync(path10.join(dir, OWNERSHIP_MARKER))) return true;
|
|
4699
|
+
const manifestPath = path10.join(dir, "manifest.json");
|
|
4700
|
+
if (!fs9.existsSync(manifestPath)) return false;
|
|
4701
|
+
try {
|
|
4702
|
+
const parsed = JSON.parse(fs9.readFileSync(manifestPath, "utf8"));
|
|
4703
|
+
const generator = parsed.generator;
|
|
4704
|
+
return typeof parsed.schemaVersion === "string" && Array.isArray(parsed.files) && typeof generator === "object" && generator !== null && generator.name === GENERATOR_NAME;
|
|
4705
|
+
} catch {
|
|
4706
|
+
return false;
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
function assertReplaceable(target, label) {
|
|
4710
|
+
if (!fs9.existsSync(target)) return;
|
|
4711
|
+
if (!fs9.statSync(target).isDirectory()) {
|
|
4712
|
+
throw new Error(
|
|
4713
|
+
`Refusing to write into ${label} ${target}: it exists but is not a directory.`
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4716
|
+
const entries = fs9.readdirSync(target);
|
|
4717
|
+
const meaningful = entries.filter((name) => !PLACEHOLDER_ENTRIES.has(name));
|
|
4718
|
+
if (meaningful.length === 0) return;
|
|
4719
|
+
if (isOwnOutput(target)) return;
|
|
4720
|
+
const preview = meaningful.slice(0, 5).join(", ");
|
|
4721
|
+
const more = meaningful.length > 5 ? `, and ${meaningful.length - 5} more` : "";
|
|
4722
|
+
throw new Error(
|
|
4723
|
+
`Refusing to replace ${label} ${target}: it exists, is not empty, and does not look like a snapshot this generator wrote (found ${preview}${more}). Point ${label} at a new or empty directory, or delete it yourself first.`
|
|
4724
|
+
);
|
|
4725
|
+
}
|
|
2858
4726
|
function swapIntoPlace(tmpDir, outDir) {
|
|
2859
4727
|
const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
|
|
2860
|
-
const hadPrevious =
|
|
2861
|
-
if (hadPrevious)
|
|
4728
|
+
const hadPrevious = fs9.existsSync(outDir);
|
|
4729
|
+
if (hadPrevious) fs9.renameSync(outDir, backupDir);
|
|
2862
4730
|
try {
|
|
2863
4731
|
try {
|
|
2864
|
-
|
|
4732
|
+
fs9.renameSync(tmpDir, outDir);
|
|
2865
4733
|
} catch (err) {
|
|
2866
4734
|
if (err.code === "EXDEV") {
|
|
2867
|
-
|
|
2868
|
-
|
|
4735
|
+
fs9.cpSync(tmpDir, outDir, { recursive: true });
|
|
4736
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
2869
4737
|
} else {
|
|
2870
4738
|
throw err;
|
|
2871
4739
|
}
|
|
2872
4740
|
}
|
|
2873
|
-
if (hadPrevious)
|
|
4741
|
+
if (hadPrevious) fs9.rmSync(backupDir, { recursive: true, force: true });
|
|
2874
4742
|
} catch (err) {
|
|
2875
|
-
if (hadPrevious &&
|
|
2876
|
-
if (
|
|
2877
|
-
|
|
4743
|
+
if (hadPrevious && fs9.existsSync(backupDir)) {
|
|
4744
|
+
if (fs9.existsSync(outDir)) fs9.rmSync(outDir, { recursive: true, force: true });
|
|
4745
|
+
fs9.renameSync(backupDir, outDir);
|
|
2878
4746
|
}
|
|
2879
4747
|
throw err;
|
|
2880
4748
|
}
|
|
@@ -2938,7 +4806,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2938
4806
|
const gitInfo = getGitInfo(config.cwd, config.branch);
|
|
2939
4807
|
const collection = collectFiles(config.cwd, {
|
|
2940
4808
|
includePatterns: config.include,
|
|
2941
|
-
excludePatterns: config.excludePatterns,
|
|
4809
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2942
4810
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2943
4811
|
});
|
|
2944
4812
|
const files = [];
|
|
@@ -2951,10 +4819,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2951
4819
|
let totalSizeBytes = 0;
|
|
2952
4820
|
let largeFileCount = 0;
|
|
2953
4821
|
for (const relativePath of collection.files) {
|
|
2954
|
-
const absolutePath =
|
|
4822
|
+
const absolutePath = path10.join(config.cwd, relativePath);
|
|
2955
4823
|
let stats;
|
|
2956
4824
|
try {
|
|
2957
|
-
stats =
|
|
4825
|
+
stats = fs9.statSync(absolutePath);
|
|
2958
4826
|
if (!stats.isFile()) {
|
|
2959
4827
|
skipped.push({ path: relativePath, reason: "not-file" });
|
|
2960
4828
|
continue;
|
|
@@ -2969,7 +4837,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2969
4837
|
}
|
|
2970
4838
|
let buffer;
|
|
2971
4839
|
try {
|
|
2972
|
-
buffer =
|
|
4840
|
+
buffer = fs9.readFileSync(absolutePath);
|
|
2973
4841
|
} catch {
|
|
2974
4842
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
2975
4843
|
continue;
|
|
@@ -2979,10 +4847,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2979
4847
|
const lineCount = countLines(rawContent);
|
|
2980
4848
|
totalLines += lineCount;
|
|
2981
4849
|
totalSizeBytes += stats.size;
|
|
2982
|
-
const
|
|
4850
|
+
const layer2 = classifyLayer(relativePath, config.layers);
|
|
2983
4851
|
const kind = classifyKind(relativePath, config.kinds);
|
|
2984
4852
|
const language = detectLanguage(relativePath);
|
|
2985
|
-
bump(layerCounts,
|
|
4853
|
+
bump(layerCounts, layer2);
|
|
2986
4854
|
bump(kindCounts, kind);
|
|
2987
4855
|
const { redacted, findings } = scanAndRedactSecrets(
|
|
2988
4856
|
rawContent,
|
|
@@ -3006,8 +4874,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3006
4874
|
includedLines = preview.includedLines;
|
|
3007
4875
|
}
|
|
3008
4876
|
let summary;
|
|
4877
|
+
let summaryQuality;
|
|
3009
4878
|
try {
|
|
3010
|
-
|
|
4879
|
+
const outcome = summarizeFile(relativePath, redacted);
|
|
4880
|
+
summary = outcome?.summary;
|
|
4881
|
+
summaryQuality = outcome?.quality;
|
|
3011
4882
|
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
3012
4883
|
warnings.push(
|
|
3013
4884
|
`Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
|
|
@@ -3024,7 +4895,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3024
4895
|
lines: lineCount,
|
|
3025
4896
|
hashSha256,
|
|
3026
4897
|
part: "",
|
|
3027
|
-
layer,
|
|
4898
|
+
layer: layer2,
|
|
3028
4899
|
kind,
|
|
3029
4900
|
language,
|
|
3030
4901
|
inclusionMode: isPreviewTruncated ? "preview" : "full",
|
|
@@ -3034,6 +4905,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3034
4905
|
includedLines,
|
|
3035
4906
|
includedBytes: Buffer.byteLength(finalContent, "utf8"),
|
|
3036
4907
|
summary,
|
|
4908
|
+
summaryQuality,
|
|
3037
4909
|
content: finalContent
|
|
3038
4910
|
});
|
|
3039
4911
|
}
|
|
@@ -3074,10 +4946,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3074
4946
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
3075
4947
|
artifacts: { ...config.artifacts },
|
|
3076
4948
|
include: [...config.include],
|
|
4949
|
+
preset: config.preset,
|
|
3077
4950
|
layersMode: config.layersMode,
|
|
3078
4951
|
budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
|
|
3079
4952
|
excludePatterns: [...config.excludePatterns],
|
|
3080
|
-
layers: config.layers.map((
|
|
4953
|
+
layers: config.layers.map((layer2) => layer2.name),
|
|
3081
4954
|
kinds: config.kinds.map((kind) => kind.kind)
|
|
3082
4955
|
},
|
|
3083
4956
|
counts: {
|
|
@@ -3164,45 +5037,48 @@ function generateSnapshot(configInput, options) {
|
|
|
3164
5037
|
return manifest;
|
|
3165
5038
|
}
|
|
3166
5039
|
const { outDir, tmpDir } = resolveSafeOutputDirs(config);
|
|
5040
|
+
assertReplaceable(tmpDir, "tmpDir");
|
|
5041
|
+
assertReplaceable(outDir, "outDir");
|
|
3167
5042
|
const { artifacts } = config;
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
const
|
|
3172
|
-
|
|
5043
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
5044
|
+
fs9.mkdirSync(tmpDir, { recursive: true });
|
|
5045
|
+
writeOwnershipMarker(tmpDir);
|
|
5046
|
+
const partsDir = path10.join(tmpDir, "parts");
|
|
5047
|
+
const reportsDir = path10.join(tmpDir, "reports");
|
|
5048
|
+
if (artifacts.parts) fs9.mkdirSync(partsDir, { recursive: true });
|
|
3173
5049
|
if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
|
|
3174
|
-
|
|
5050
|
+
fs9.mkdirSync(reportsDir, { recursive: true });
|
|
3175
5051
|
}
|
|
3176
5052
|
if (artifacts.parts) {
|
|
3177
5053
|
for (const part of parts) {
|
|
3178
|
-
|
|
5054
|
+
fs9.writeFileSync(path10.join(partsDir, part.filename), part.markdown, "utf8");
|
|
3179
5055
|
}
|
|
3180
5056
|
}
|
|
3181
5057
|
if (artifacts.projectTree) {
|
|
3182
5058
|
const projectTree = generateProjectTree(files.map((file) => file.relativePath));
|
|
3183
|
-
|
|
5059
|
+
fs9.writeFileSync(path10.join(reportsDir, "project-tree.md"), projectTree, "utf8");
|
|
3184
5060
|
}
|
|
3185
5061
|
if (artifacts.navigation) {
|
|
3186
|
-
|
|
5062
|
+
fs9.writeFileSync(path10.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
|
|
3187
5063
|
}
|
|
3188
5064
|
if (artifacts.audit) {
|
|
3189
|
-
|
|
3190
|
-
|
|
5065
|
+
fs9.writeFileSync(
|
|
5066
|
+
path10.join(reportsDir, "audit.md"),
|
|
3191
5067
|
generateAuditReport(manifest, secretFindings),
|
|
3192
5068
|
"utf8"
|
|
3193
5069
|
);
|
|
3194
5070
|
}
|
|
3195
5071
|
if (artifacts.readme) {
|
|
3196
|
-
|
|
5072
|
+
fs9.writeFileSync(path10.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
|
|
3197
5073
|
}
|
|
3198
5074
|
if (artifacts.manifest) {
|
|
3199
|
-
|
|
5075
|
+
fs9.writeFileSync(path10.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3200
5076
|
}
|
|
3201
5077
|
if (monolithJson !== null) {
|
|
3202
|
-
|
|
5078
|
+
fs9.writeFileSync(path10.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
|
|
3203
5079
|
}
|
|
3204
5080
|
if (monolithMarkdown !== null) {
|
|
3205
|
-
|
|
5081
|
+
fs9.writeFileSync(path10.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
|
|
3206
5082
|
}
|
|
3207
5083
|
swapIntoPlace(tmpDir, outDir);
|
|
3208
5084
|
runAfterGenerate(config, manifest);
|
|
@@ -3212,6 +5088,6 @@ async function generateSnapshotAsync(configInput, options) {
|
|
|
3212
5088
|
return generateSnapshot(configInput, options);
|
|
3213
5089
|
}
|
|
3214
5090
|
|
|
3215
|
-
export { ARTIFACT_NAMES, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, MIN_MAX_PART_BYTES, SCHEMA_VERSION, anchorForPath, assertUsableConfigInput, collectFiles, compareManifests, countLines, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, generateProjectTree, generateSnapshot, generateSnapshotAsync, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isResolvedConfig, isSensitivePath, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, renderDiffText, renderExplainText, renumberParts, resolveConfig, resolveSafeOutputDirs, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, supportsTypeScriptImports, truncatePreview, truncateToBytes, validateSnapshot };
|
|
3216
|
-
//# sourceMappingURL=chunk-
|
|
3217
|
-
//# sourceMappingURL=chunk-
|
|
5091
|
+
export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countLines, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, validateSnapshot };
|
|
5092
|
+
//# sourceMappingURL=chunk-QEIOXPMU.js.map
|
|
5093
|
+
//# sourceMappingURL=chunk-QEIOXPMU.js.map
|