@ankurhalder/codebase-snapshot 2.0.0 → 2.1.1
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 +2399 -372
- package/dist/{chunk-RGDVLQTT.js → chunk-SUOJL5EX.js} +2247 -355
- package/dist/cli.cjs +2417 -397
- package/dist/cli.d.cts +9 -1
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +160 -31
- package/dist/index.cjs +2260 -354
- 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 +150 -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,6 +1026,47 @@ var NEXTJS_KINDS = [
|
|
|
342
1026
|
}
|
|
343
1027
|
];
|
|
344
1028
|
|
|
1029
|
+
// src/presets/node.ts
|
|
1030
|
+
var NODE_LAYERS = [
|
|
1031
|
+
{ name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
|
|
1032
|
+
{ name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
|
|
1033
|
+
{ name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
|
|
1034
|
+
{ name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
|
|
1035
|
+
{ name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
|
|
1036
|
+
{ name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
|
|
1037
|
+
{ name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
|
|
1038
|
+
{ name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
|
|
1039
|
+
{ name: "scripts", match: (p) => p.startsWith("scripts/") },
|
|
1040
|
+
{ name: "docs", match: (p) => p.startsWith("docs/") },
|
|
1041
|
+
{
|
|
1042
|
+
name: "config",
|
|
1043
|
+
match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
|
|
1044
|
+
},
|
|
1045
|
+
{ name: "root", match: (p) => !p.includes("/") }
|
|
1046
|
+
];
|
|
1047
|
+
var NODE_KINDS = [
|
|
1048
|
+
{ kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
|
|
1049
|
+
{ kind: "route", match: (p) => p.includes("/routes/") || p.includes("route.") || p.includes("/api/") },
|
|
1050
|
+
{ kind: "controller", match: (p) => p.includes("controller") },
|
|
1051
|
+
{ kind: "service", match: (p) => p.includes("service") },
|
|
1052
|
+
{ kind: "provider", match: (p) => p.includes("provider") },
|
|
1053
|
+
{ kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
|
|
1054
|
+
{ kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
|
|
1055
|
+
{ kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
|
|
1056
|
+
{ kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
|
|
1057
|
+
{ kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
|
|
1058
|
+
{ kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
|
|
1059
|
+
{ kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
|
|
1060
|
+
{ kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
|
|
1061
|
+
{
|
|
1062
|
+
kind: "config",
|
|
1063
|
+
match: (p) => {
|
|
1064
|
+
const base = p.split("/").pop() || "";
|
|
1065
|
+
return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
];
|
|
1069
|
+
|
|
345
1070
|
// src/presets/vite.ts
|
|
346
1071
|
var VITE_LAYERS = [
|
|
347
1072
|
{ name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
|
|
@@ -389,44 +1114,419 @@ var VITE_KINDS = [
|
|
|
389
1114
|
}
|
|
390
1115
|
];
|
|
391
1116
|
|
|
392
|
-
// src/presets/
|
|
393
|
-
var
|
|
394
|
-
|
|
395
|
-
{
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
+
},
|
|
404
1265
|
{
|
|
405
|
-
name: "
|
|
406
|
-
|
|
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"
|
|
407
1273
|
},
|
|
408
|
-
{
|
|
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
|
+
}
|
|
409
1325
|
];
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{ kind: "provider", match: (p) => p.includes("provider") },
|
|
416
|
-
{ kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
|
|
417
|
-
{ kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
|
|
418
|
-
{ kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
|
|
419
|
-
{ kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
|
|
420
|
-
{ kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
|
|
421
|
-
{ kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
|
|
422
|
-
{ kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
|
|
423
|
-
{ kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
|
|
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 = [
|
|
424
1331
|
{
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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"
|
|
430
1530
|
}
|
|
431
1531
|
];
|
|
432
1532
|
|
|
@@ -472,57 +1572,118 @@ 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
|
-
const
|
|
1681
|
+
const reported = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
|
|
522
1682
|
cwd,
|
|
523
1683
|
stdio: ["ignore", "pipe", "ignore"],
|
|
524
1684
|
encoding: "utf8"
|
|
525
|
-
}).trim()
|
|
1685
|
+
}).trim();
|
|
1686
|
+
const branch = !reported || reported === "HEAD" ? fallbackBranch : reported;
|
|
526
1687
|
const commit = childProcess.execSync("git rev-parse HEAD", {
|
|
527
1688
|
cwd,
|
|
528
1689
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -650,10 +1811,10 @@ function matchesExclude(relativePath, pattern) {
|
|
|
650
1811
|
return posixPath === cleaned || posixPath.startsWith(`${cleaned}/`) || posixPath.includes(`/${cleaned}/`) || posixPath.endsWith(`/${cleaned}`);
|
|
651
1812
|
}
|
|
652
1813
|
function readGitignoreMatcher(dir) {
|
|
653
|
-
const gitignorePath =
|
|
1814
|
+
const gitignorePath = path10.join(dir, ".gitignore");
|
|
654
1815
|
let raw;
|
|
655
1816
|
try {
|
|
656
|
-
raw =
|
|
1817
|
+
raw = fs9.readFileSync(gitignorePath, "utf8");
|
|
657
1818
|
} catch {
|
|
658
1819
|
return () => false;
|
|
659
1820
|
}
|
|
@@ -756,7 +1917,7 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
756
1917
|
const results = { files: [], skipped: [] };
|
|
757
1918
|
let realDir;
|
|
758
1919
|
try {
|
|
759
|
-
realDir =
|
|
1920
|
+
realDir = fs9.realpathSync(dir);
|
|
760
1921
|
} catch {
|
|
761
1922
|
realDir = dir;
|
|
762
1923
|
}
|
|
@@ -765,21 +1926,21 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
765
1926
|
const ignoredByGitignore = dir === baseDir ? readGitignoreMatcher(baseDir) : () => false;
|
|
766
1927
|
let entries;
|
|
767
1928
|
try {
|
|
768
|
-
entries =
|
|
1929
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
769
1930
|
} catch {
|
|
770
1931
|
results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
|
|
771
1932
|
return results;
|
|
772
1933
|
}
|
|
773
1934
|
for (const entry of entries) {
|
|
774
1935
|
if (DEFAULT_IGNORED_DIRS.includes(entry.name)) continue;
|
|
775
|
-
const fullPath =
|
|
776
|
-
const relPath =
|
|
1936
|
+
const fullPath = path10.join(dir, entry.name);
|
|
1937
|
+
const relPath = path10.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
777
1938
|
if (ignoredByGitignore(relPath)) continue;
|
|
778
1939
|
let isDirectory = entry.isDirectory();
|
|
779
1940
|
let isFile = entry.isFile();
|
|
780
1941
|
if (entry.isSymbolicLink()) {
|
|
781
1942
|
try {
|
|
782
|
-
const stats =
|
|
1943
|
+
const stats = fs9.statSync(fullPath);
|
|
783
1944
|
isDirectory = stats.isDirectory();
|
|
784
1945
|
isFile = stats.isFile();
|
|
785
1946
|
if (!isDirectory && !isFile) {
|
|
@@ -1044,6 +2205,8 @@ function normalizeResolvedConfig(config) {
|
|
|
1044
2205
|
config.includeMonolith === true
|
|
1045
2206
|
),
|
|
1046
2207
|
include: resolveInclude(config.include),
|
|
2208
|
+
preset: typeof config.preset === "string" ? config.preset : "generic",
|
|
2209
|
+
presetExclude: Array.isArray(config.presetExclude) ? config.presetExclude : [],
|
|
1047
2210
|
layersMode: config.layersMode === "replace" ? "replace" : "merge",
|
|
1048
2211
|
budgets: config.budgets && typeof config.budgets === "object" ? config.budgets : { default: config.maxPartBytes, byLayer: {} },
|
|
1049
2212
|
hooks: resolveHooks(config.hooks),
|
|
@@ -1091,18 +2254,22 @@ function parseJsonish(raw, file) {
|
|
|
1091
2254
|
}
|
|
1092
2255
|
async function loadConfigFileDetailed(cwd) {
|
|
1093
2256
|
const warnings = [];
|
|
2257
|
+
const errors = [];
|
|
1094
2258
|
for (const filename of CONFIG_FILENAMES) {
|
|
1095
|
-
const fullPath =
|
|
1096
|
-
if (!
|
|
2259
|
+
const fullPath = path10.join(cwd, filename);
|
|
2260
|
+
if (!fs9.existsSync(fullPath)) continue;
|
|
1097
2261
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
1098
2262
|
try {
|
|
1099
2263
|
return {
|
|
1100
|
-
config: parseJsonish(
|
|
2264
|
+
config: parseJsonish(fs9.readFileSync(fullPath, "utf8"), fullPath),
|
|
1101
2265
|
file: fullPath,
|
|
1102
|
-
warnings
|
|
2266
|
+
warnings,
|
|
2267
|
+
errors
|
|
1103
2268
|
};
|
|
1104
2269
|
} catch (err) {
|
|
1105
|
-
|
|
2270
|
+
const message = err.message;
|
|
2271
|
+
warnings.push(message);
|
|
2272
|
+
errors.push(message);
|
|
1106
2273
|
continue;
|
|
1107
2274
|
}
|
|
1108
2275
|
}
|
|
@@ -1116,28 +2283,37 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
1116
2283
|
const imported = await import(pathToFileURL(fullPath).href);
|
|
1117
2284
|
const config = imported.default ?? imported;
|
|
1118
2285
|
if (!config || typeof config !== "object") {
|
|
1119
|
-
|
|
2286
|
+
const message = `${fullPath} does not export a configuration object.`;
|
|
2287
|
+
warnings.push(message);
|
|
2288
|
+
errors.push(message);
|
|
1120
2289
|
continue;
|
|
1121
2290
|
}
|
|
1122
|
-
return { config, file: fullPath, warnings };
|
|
2291
|
+
return { config, file: fullPath, warnings, errors };
|
|
1123
2292
|
} catch (err) {
|
|
1124
|
-
|
|
2293
|
+
const message = `Could not load ${fullPath}: ${err.message}`;
|
|
2294
|
+
warnings.push(message);
|
|
2295
|
+
errors.push(message);
|
|
1125
2296
|
continue;
|
|
1126
2297
|
}
|
|
1127
2298
|
}
|
|
1128
|
-
const pkgJsonPath =
|
|
1129
|
-
if (
|
|
2299
|
+
const pkgJsonPath = path10.join(cwd, "package.json");
|
|
2300
|
+
if (fs9.existsSync(pkgJsonPath)) {
|
|
1130
2301
|
try {
|
|
1131
|
-
const parsed = parseJsonish(
|
|
2302
|
+
const parsed = parseJsonish(fs9.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
|
|
1132
2303
|
const snapshotKey = parsed.snapshot;
|
|
1133
2304
|
if (snapshotKey && typeof snapshotKey === "object") {
|
|
1134
|
-
return { config: snapshotKey, file: pkgJsonPath, warnings };
|
|
2305
|
+
return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
|
|
2306
|
+
}
|
|
2307
|
+
if (snapshotKey !== void 0) {
|
|
2308
|
+
warnings.push(`${pkgJsonPath}: the "snapshot" key must be an object; it was ignored.`);
|
|
1135
2309
|
}
|
|
1136
2310
|
} catch (err) {
|
|
1137
|
-
|
|
2311
|
+
const message = err.message;
|
|
2312
|
+
warnings.push(message);
|
|
2313
|
+
errors.push(message);
|
|
1138
2314
|
}
|
|
1139
2315
|
}
|
|
1140
|
-
return { config: null, file: null, warnings };
|
|
2316
|
+
return { config: null, file: null, warnings, errors };
|
|
1141
2317
|
}
|
|
1142
2318
|
async function loadConfigFile(cwd) {
|
|
1143
2319
|
const loaded = await loadConfigFileDetailed(cwd);
|
|
@@ -1236,7 +2412,9 @@ function boundedBytes(value, fallback, minimum, label) {
|
|
|
1236
2412
|
`${label} must be a byte count such as 120000, "120k" or "1.5mb" (received ${describeBytes(value)}).`
|
|
1237
2413
|
);
|
|
1238
2414
|
}
|
|
1239
|
-
if (parsed <= 0)
|
|
2415
|
+
if (parsed <= 0) {
|
|
2416
|
+
throw new Error(`${label} must be a positive byte count (received ${describeBytes(value)}).`);
|
|
2417
|
+
}
|
|
1240
2418
|
if (parsed < minimum) {
|
|
1241
2419
|
throw new Error(`${label} must be at least ${minimum} bytes (received ${describeBytes(value)}).`);
|
|
1242
2420
|
}
|
|
@@ -1276,12 +2454,12 @@ function resolveBudgets(budgets, fallback) {
|
|
|
1276
2454
|
if (typeof budgets.byLayer !== "object" || budgets.byLayer === null) {
|
|
1277
2455
|
throw new Error("`budgets.byLayer` must be an object of layer name to byte size.");
|
|
1278
2456
|
}
|
|
1279
|
-
for (const [
|
|
1280
|
-
byLayer[
|
|
2457
|
+
for (const [layer2, value] of Object.entries(budgets.byLayer)) {
|
|
2458
|
+
byLayer[layer2] = boundedBytes(
|
|
1281
2459
|
value,
|
|
1282
2460
|
fallback,
|
|
1283
2461
|
MIN_MAX_PART_BYTES,
|
|
1284
|
-
`budgets.byLayer["${
|
|
2462
|
+
`budgets.byLayer["${layer2}"]`
|
|
1285
2463
|
);
|
|
1286
2464
|
}
|
|
1287
2465
|
}
|
|
@@ -1302,18 +2480,22 @@ function resolveHooks(hooks) {
|
|
|
1302
2480
|
}
|
|
1303
2481
|
function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
|
|
1304
2482
|
const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
|
|
1305
|
-
const resolved =
|
|
1306
|
-
if (!
|
|
2483
|
+
const resolved = path10.resolve(candidate);
|
|
2484
|
+
if (!fs9.existsSync(resolved)) {
|
|
1307
2485
|
throw new Error(`Working directory does not exist: ${resolved}`);
|
|
1308
2486
|
}
|
|
1309
|
-
if (!
|
|
2487
|
+
if (!fs9.statSync(resolved).isDirectory()) {
|
|
1310
2488
|
throw new Error(`Working directory is not a directory: ${resolved}`);
|
|
1311
2489
|
}
|
|
1312
2490
|
return resolved;
|
|
1313
2491
|
}
|
|
1314
2492
|
function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
1315
2493
|
const cwd = resolveCandidateCwd(userConfig ?? null, cliOptions, baseDir);
|
|
1316
|
-
const
|
|
2494
|
+
const forcedPreset = userConfig?.preset;
|
|
2495
|
+
if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
|
|
2496
|
+
throw new Error(unknownPresetMessage(forcedPreset));
|
|
2497
|
+
}
|
|
2498
|
+
const preset = detectPreset(cwd, forcedPreset);
|
|
1317
2499
|
const layersMode = resolveLayersMode(userConfig?.layersMode);
|
|
1318
2500
|
const layers = userConfig?.layers && userConfig.layers.length > 0 ? layersMode === "replace" ? userConfig.layers : [...userConfig.layers, ...preset.layers] : preset.layers;
|
|
1319
2501
|
const kinds = userConfig?.kinds && userConfig.kinds.length > 0 ? layersMode === "replace" ? userConfig.kinds : [...userConfig.kinds, ...preset.kinds] : preset.kinds;
|
|
@@ -1349,6 +2531,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
1349
2531
|
),
|
|
1350
2532
|
includeSensitiveFiles: cliOptions?.includeSensitiveFiles ?? userConfig?.includeSensitiveFiles ?? false,
|
|
1351
2533
|
include: resolveInclude(userConfig?.include),
|
|
2534
|
+
preset: preset.presetName,
|
|
2535
|
+
presetExclude: [...preset.exclude],
|
|
1352
2536
|
artifacts,
|
|
1353
2537
|
layersMode,
|
|
1354
2538
|
budgets,
|
|
@@ -1383,8 +2567,8 @@ function matchRule(filePath, matcher) {
|
|
|
1383
2567
|
return false;
|
|
1384
2568
|
}
|
|
1385
2569
|
function classifyLayer(filePath, layers) {
|
|
1386
|
-
for (const
|
|
1387
|
-
if (matchRule(filePath,
|
|
2570
|
+
for (const layer2 of layers) {
|
|
2571
|
+
if (matchRule(filePath, layer2.match)) return layer2.name;
|
|
1388
2572
|
}
|
|
1389
2573
|
return "other";
|
|
1390
2574
|
}
|
|
@@ -1465,11 +2649,11 @@ function generateAuditReport(manifest, secretFindings) {
|
|
|
1465
2649
|
"| :--- | :--- | :--- |"
|
|
1466
2650
|
);
|
|
1467
2651
|
const totalFiles = manifest.counts.totalFiles || 1;
|
|
1468
|
-
for (const [
|
|
2652
|
+
for (const [layer2, count] of Object.entries(manifest.layers).sort(
|
|
1469
2653
|
(a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
1470
2654
|
)) {
|
|
1471
2655
|
const pct = (count / totalFiles * 100).toFixed(1);
|
|
1472
|
-
lines.push(`| \`${
|
|
2656
|
+
lines.push(`| \`${layer2}\` | ${count} | ${pct}% |`);
|
|
1473
2657
|
}
|
|
1474
2658
|
lines.push("");
|
|
1475
2659
|
lines.push(
|
|
@@ -1577,21 +2761,21 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
1577
2761
|
section.byteSize = Buffer.byteLength(section.text, "utf8");
|
|
1578
2762
|
continue;
|
|
1579
2763
|
}
|
|
1580
|
-
const
|
|
1581
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
2764
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
2765
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1582
2766
|
const clipped = truncateToBytes(rendered[oversized].markdown, target);
|
|
1583
|
-
rendered[oversized] = rehashPart(rendered[oversized], clipped.content +
|
|
2767
|
+
rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
|
|
1584
2768
|
return rendered;
|
|
1585
2769
|
}
|
|
1586
2770
|
return enforceBudget(renderGroups(groups, contentFingerprint), budget);
|
|
1587
2771
|
}
|
|
1588
2772
|
function enforceBudget(parts, budget) {
|
|
1589
|
-
const
|
|
1590
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
2773
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
2774
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1591
2775
|
return parts.map((part) => {
|
|
1592
2776
|
if (part.metadata.byteSize <= budget) return part;
|
|
1593
2777
|
const clipped = truncateToBytes(part.markdown, target);
|
|
1594
|
-
return rehashPart(part, clipped.content +
|
|
2778
|
+
return rehashPart(part, clipped.content + marker2);
|
|
1595
2779
|
});
|
|
1596
2780
|
}
|
|
1597
2781
|
function renumberParts(parts, contentFingerprint) {
|
|
@@ -1599,10 +2783,10 @@ function renumberParts(parts, contentFingerprint) {
|
|
|
1599
2783
|
if (total === 0) return [];
|
|
1600
2784
|
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
1601
2785
|
const nameFor = (position) => `part-${zeroPad(position)}.md`;
|
|
1602
|
-
const
|
|
2786
|
+
const marker2 = "## Contents\n";
|
|
1603
2787
|
return parts.map((part, index) => {
|
|
1604
|
-
const markerIndex = part.markdown.indexOf(
|
|
1605
|
-
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex +
|
|
2788
|
+
const markerIndex = part.markdown.indexOf(marker2);
|
|
2789
|
+
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
|
|
1606
2790
|
const included = part.metadata.includedFiles;
|
|
1607
2791
|
if (markerIndex === -1) {
|
|
1608
2792
|
throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
|
|
@@ -1660,9 +2844,9 @@ function topEntries(counts, limit) {
|
|
|
1660
2844
|
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
2845
|
}
|
|
1662
2846
|
function prepareSection(file, sectionBudget) {
|
|
1663
|
-
let
|
|
1664
|
-
let byteSize = Buffer.byteLength(
|
|
1665
|
-
if (byteSize <= sectionBudget) return { file, text, byteSize };
|
|
2847
|
+
let text2 = renderFileSection(file);
|
|
2848
|
+
let byteSize = Buffer.byteLength(text2, "utf8");
|
|
2849
|
+
if (byteSize <= sectionBudget) return { file, text: text2, byteSize };
|
|
1666
2850
|
const shrinkBody = () => {
|
|
1667
2851
|
const content = file.content ?? "";
|
|
1668
2852
|
const overhead = byteSize - Buffer.byteLength(content, "utf8");
|
|
@@ -1671,24 +2855,24 @@ function prepareSection(file, sectionBudget) {
|
|
|
1671
2855
|
const truncated = truncateToBytes(content, allowed);
|
|
1672
2856
|
if (!truncated.isTruncated) return;
|
|
1673
2857
|
applyTruncatedContent(file, truncated.content);
|
|
1674
|
-
|
|
1675
|
-
byteSize = Buffer.byteLength(
|
|
2858
|
+
text2 = renderFileSection(file);
|
|
2859
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1676
2860
|
};
|
|
1677
2861
|
shrinkBody();
|
|
1678
2862
|
if (byteSize > sectionBudget && file.summary) {
|
|
1679
2863
|
file.summary = void 0;
|
|
1680
2864
|
file.summaryOmitted = true;
|
|
1681
|
-
|
|
1682
|
-
byteSize = Buffer.byteLength(
|
|
2865
|
+
text2 = renderFileSection(file);
|
|
2866
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1683
2867
|
shrinkBody();
|
|
1684
2868
|
}
|
|
1685
2869
|
if (byteSize > sectionBudget) {
|
|
1686
2870
|
const target = sectionBudget - Buffer.byteLength(SECTION_TRUNCATION_NOTICE, "utf8");
|
|
1687
|
-
const truncated = truncateToBytes(
|
|
1688
|
-
|
|
1689
|
-
byteSize = Buffer.byteLength(
|
|
2871
|
+
const truncated = truncateToBytes(text2, Math.max(0, target));
|
|
2872
|
+
text2 = truncated.content + SECTION_TRUNCATION_NOTICE;
|
|
2873
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1690
2874
|
}
|
|
1691
|
-
return { file, text, byteSize };
|
|
2875
|
+
return { file, text: text2, byteSize };
|
|
1692
2876
|
}
|
|
1693
2877
|
function applyTruncatedContent(file, content) {
|
|
1694
2878
|
file.content = content;
|
|
@@ -1810,6 +2994,7 @@ function renderFileSection(file) {
|
|
|
1810
2994
|
`- Source Lines: ${file.originalLines}`,
|
|
1811
2995
|
`- Included Lines: ${file.includedLines}`,
|
|
1812
2996
|
`- SHA-256: ${file.hashSha256}`,
|
|
2997
|
+
...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
|
|
1813
2998
|
...renderSummary(file.summary),
|
|
1814
2999
|
...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
|
|
1815
3000
|
"",
|
|
@@ -2057,7 +3242,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2057
3242
|
".so"
|
|
2058
3243
|
]);
|
|
2059
3244
|
function isBinaryExtension(filePath) {
|
|
2060
|
-
const ext =
|
|
3245
|
+
const ext = path10.extname(filePath).toLowerCase();
|
|
2061
3246
|
return BINARY_EXTENSIONS.has(ext);
|
|
2062
3247
|
}
|
|
2063
3248
|
function isBinaryBuffer(buffer) {
|
|
@@ -2071,9 +3256,9 @@ function isBinaryFile(absolutePath) {
|
|
|
2071
3256
|
if (isBinaryExtension(absolutePath)) return true;
|
|
2072
3257
|
let fd;
|
|
2073
3258
|
try {
|
|
2074
|
-
fd =
|
|
3259
|
+
fd = fs9.openSync(absolutePath, "r");
|
|
2075
3260
|
const buffer = Buffer.alloc(8192);
|
|
2076
|
-
const bytesRead =
|
|
3261
|
+
const bytesRead = fs9.readSync(fd, buffer, 0, 8192, 0);
|
|
2077
3262
|
if (bytesRead === 0) return false;
|
|
2078
3263
|
return isBinaryBuffer(buffer.subarray(0, bytesRead));
|
|
2079
3264
|
} catch {
|
|
@@ -2081,7 +3266,7 @@ function isBinaryFile(absolutePath) {
|
|
|
2081
3266
|
} finally {
|
|
2082
3267
|
if (fd !== void 0) {
|
|
2083
3268
|
try {
|
|
2084
|
-
|
|
3269
|
+
fs9.closeSync(fd);
|
|
2085
3270
|
} catch {
|
|
2086
3271
|
}
|
|
2087
3272
|
}
|
|
@@ -2141,10 +3326,10 @@ var LANGUAGE_BY_BASENAME = {
|
|
|
2141
3326
|
};
|
|
2142
3327
|
function detectLanguage(filePath) {
|
|
2143
3328
|
const normalized = filePath.replace(/\\/g, "/");
|
|
2144
|
-
const base =
|
|
3329
|
+
const base = path10.posix.basename(normalized);
|
|
2145
3330
|
if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
|
|
2146
3331
|
if (base === ".env" || base.startsWith(".env.")) return "dotenv";
|
|
2147
|
-
const ext =
|
|
3332
|
+
const ext = path10.posix.extname(base).toLowerCase();
|
|
2148
3333
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
2149
3334
|
}
|
|
2150
3335
|
function emptyDrift() {
|
|
@@ -2163,8 +3348,8 @@ function sha256Hex(buffer) {
|
|
|
2163
3348
|
function validateSnapshot(configInput) {
|
|
2164
3349
|
assertUsableConfigInput(configInput);
|
|
2165
3350
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
|
|
2166
|
-
const manifestPath =
|
|
2167
|
-
if (!
|
|
3351
|
+
const manifestPath = path10.resolve(config.cwd, config.outDir, "manifest.json");
|
|
3352
|
+
if (!fs9.existsSync(manifestPath)) {
|
|
2168
3353
|
return {
|
|
2169
3354
|
valid: false,
|
|
2170
3355
|
manifestPresent: false,
|
|
@@ -2175,7 +3360,7 @@ function validateSnapshot(configInput) {
|
|
|
2175
3360
|
}
|
|
2176
3361
|
let manifest;
|
|
2177
3362
|
try {
|
|
2178
|
-
manifest = JSON.parse(
|
|
3363
|
+
manifest = JSON.parse(fs9.readFileSync(manifestPath, "utf8"));
|
|
2179
3364
|
} catch (err) {
|
|
2180
3365
|
return {
|
|
2181
3366
|
valid: false,
|
|
@@ -2198,7 +3383,7 @@ function validateSnapshot(configInput) {
|
|
|
2198
3383
|
}
|
|
2199
3384
|
const drift = emptyDrift();
|
|
2200
3385
|
const warnings = [];
|
|
2201
|
-
const snapshotDir =
|
|
3386
|
+
const snapshotDir = path10.resolve(config.cwd, config.outDir);
|
|
2202
3387
|
const manifestFileMap = /* @__PURE__ */ new Map();
|
|
2203
3388
|
for (const file of manifest.files) {
|
|
2204
3389
|
if (file && typeof file.relativePath === "string") {
|
|
@@ -2210,7 +3395,7 @@ function validateSnapshot(configInput) {
|
|
|
2210
3395
|
);
|
|
2211
3396
|
const collection = collectFiles(config.cwd, {
|
|
2212
3397
|
includePatterns: config.include,
|
|
2213
|
-
excludePatterns: config.excludePatterns,
|
|
3398
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2214
3399
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2215
3400
|
});
|
|
2216
3401
|
for (const diskFile of collection.files) {
|
|
@@ -2222,7 +3407,7 @@ function validateSnapshot(configInput) {
|
|
|
2222
3407
|
}
|
|
2223
3408
|
let diskHash;
|
|
2224
3409
|
try {
|
|
2225
|
-
diskHash = sha256Hex(
|
|
3410
|
+
diskHash = sha256Hex(fs9.readFileSync(path10.join(config.cwd, diskFile)));
|
|
2226
3411
|
} catch {
|
|
2227
3412
|
drift.missingOnDisk.push(diskFile);
|
|
2228
3413
|
continue;
|
|
@@ -2232,10 +3417,11 @@ function validateSnapshot(configInput) {
|
|
|
2232
3417
|
}
|
|
2233
3418
|
}
|
|
2234
3419
|
for (const snapshotFile of manifestFileMap.keys()) {
|
|
2235
|
-
if (!
|
|
3420
|
+
if (!fs9.existsSync(path10.join(config.cwd, snapshotFile))) {
|
|
2236
3421
|
drift.missingOnDisk.push(snapshotFile);
|
|
2237
3422
|
}
|
|
2238
3423
|
}
|
|
3424
|
+
drift.missingOnDisk = [...new Set(drift.missingOnDisk)];
|
|
2239
3425
|
const maxEffectiveBudget = Math.max(
|
|
2240
3426
|
config.maxPartBytes,
|
|
2241
3427
|
config.budgets.default,
|
|
@@ -2246,10 +3432,10 @@ function validateSnapshot(configInput) {
|
|
|
2246
3432
|
if (!part || typeof part.filename !== "string") continue;
|
|
2247
3433
|
partNames.add(part.filename);
|
|
2248
3434
|
if (!config.artifacts.parts) continue;
|
|
2249
|
-
const partPath =
|
|
3435
|
+
const partPath = path10.join(snapshotDir, "parts", part.filename);
|
|
2250
3436
|
let buffer;
|
|
2251
3437
|
try {
|
|
2252
|
-
buffer =
|
|
3438
|
+
buffer = fs9.readFileSync(partPath);
|
|
2253
3439
|
} catch {
|
|
2254
3440
|
drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
|
|
2255
3441
|
continue;
|
|
@@ -2336,6 +3522,11 @@ function validateSnapshot(configInput) {
|
|
|
2336
3522
|
}
|
|
2337
3523
|
}
|
|
2338
3524
|
}
|
|
3525
|
+
if (recorded?.preset !== void 0 && recorded.preset !== config.preset) {
|
|
3526
|
+
drift.configMismatches.push(
|
|
3527
|
+
`preset: manifest ${recorded.preset} vs current ${config.preset}`
|
|
3528
|
+
);
|
|
3529
|
+
}
|
|
2339
3530
|
if (recorded?.layersMode !== void 0 && recorded.layersMode !== config.layersMode) {
|
|
2340
3531
|
drift.configMismatches.push(
|
|
2341
3532
|
`layersMode: manifest ${recorded.layersMode} vs current ${config.layersMode}`
|
|
@@ -2353,10 +3544,10 @@ function validateSnapshot(configInput) {
|
|
|
2353
3544
|
...Object.keys(recordedByLayer),
|
|
2354
3545
|
...Object.keys(config.budgets.byLayer)
|
|
2355
3546
|
]);
|
|
2356
|
-
for (const
|
|
2357
|
-
if (recordedByLayer[
|
|
3547
|
+
for (const layer2 of Array.from(layerNames).sort()) {
|
|
3548
|
+
if (recordedByLayer[layer2] !== config.budgets.byLayer[layer2]) {
|
|
2358
3549
|
drift.configMismatches.push(
|
|
2359
|
-
`budgets.byLayer.${
|
|
3550
|
+
`budgets.byLayer.${layer2}: manifest ${recordedByLayer[layer2]} vs current ${config.budgets.byLayer[layer2]}`
|
|
2360
3551
|
);
|
|
2361
3552
|
}
|
|
2362
3553
|
}
|
|
@@ -2439,9 +3630,9 @@ function validateSnapshot(configInput) {
|
|
|
2439
3630
|
["full-codebase.md", manifest.artifacts?.fullMarkdown]
|
|
2440
3631
|
]) {
|
|
2441
3632
|
if (!artifact) continue;
|
|
2442
|
-
const artifactPath =
|
|
3633
|
+
const artifactPath = path10.join(snapshotDir, key);
|
|
2443
3634
|
try {
|
|
2444
|
-
const buffer =
|
|
3635
|
+
const buffer = fs9.readFileSync(artifactPath);
|
|
2445
3636
|
if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
|
|
2446
3637
|
drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
|
|
2447
3638
|
}
|
|
@@ -2497,35 +3688,35 @@ function describeMatcher(matcher) {
|
|
|
2497
3688
|
return String(matcher);
|
|
2498
3689
|
}
|
|
2499
3690
|
function normalizeTarget(cwd, target) {
|
|
2500
|
-
const absolute =
|
|
2501
|
-
const relative3 =
|
|
2502
|
-
if (relative3.startsWith("..") ||
|
|
2503
|
-
return relative3.split(
|
|
3691
|
+
const absolute = path10.resolve(cwd, target);
|
|
3692
|
+
const relative3 = path10.relative(cwd, absolute);
|
|
3693
|
+
if (relative3.startsWith("..") || path10.isAbsolute(relative3)) return target;
|
|
3694
|
+
return relative3.split(path10.sep).join("/");
|
|
2504
3695
|
}
|
|
2505
3696
|
function findRuleMatch(rules, relativePath) {
|
|
2506
3697
|
return rules.find(
|
|
2507
3698
|
(rule) => matchRule(relativePath, rule.match)
|
|
2508
3699
|
);
|
|
2509
3700
|
}
|
|
2510
|
-
function
|
|
2511
|
-
const manifestPath =
|
|
3701
|
+
function readManifest2(config) {
|
|
3702
|
+
const manifestPath = path10.resolve(config.cwd, config.outDir, "manifest.json");
|
|
2512
3703
|
try {
|
|
2513
|
-
return JSON.parse(
|
|
3704
|
+
return JSON.parse(fs9.readFileSync(manifestPath, "utf8"));
|
|
2514
3705
|
} catch {
|
|
2515
3706
|
return null;
|
|
2516
3707
|
}
|
|
2517
3708
|
}
|
|
2518
3709
|
function explainPath(config, target) {
|
|
2519
3710
|
const relative3 = normalizeTarget(config.cwd, target);
|
|
2520
|
-
const absolute =
|
|
3711
|
+
const absolute = path10.join(config.cwd, relative3);
|
|
2521
3712
|
const result = {
|
|
2522
3713
|
path: relative3,
|
|
2523
|
-
exists:
|
|
3714
|
+
exists: fs9.existsSync(absolute),
|
|
2524
3715
|
included: false
|
|
2525
3716
|
};
|
|
2526
3717
|
const collection = collectFiles(config.cwd, {
|
|
2527
3718
|
includePatterns: config.include,
|
|
2528
|
-
excludePatterns: config.excludePatterns,
|
|
3719
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2529
3720
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2530
3721
|
});
|
|
2531
3722
|
if (!collection.files.includes(relative3)) {
|
|
@@ -2550,16 +3741,16 @@ function explainPath(config, target) {
|
|
|
2550
3741
|
if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
|
|
2551
3742
|
const kindRule = findRuleMatch(config.kinds, relative3);
|
|
2552
3743
|
if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
|
|
2553
|
-
if (
|
|
2554
|
-
const buffer =
|
|
2555
|
-
const
|
|
3744
|
+
if (fs9.existsSync(absolute) && fs9.statSync(absolute).isFile()) {
|
|
3745
|
+
const buffer = fs9.readFileSync(absolute);
|
|
3746
|
+
const text2 = buffer.toString("utf8");
|
|
2556
3747
|
result.bytes = buffer.length;
|
|
2557
|
-
result.lines = countLines(
|
|
2558
|
-
const { findings } = scanAndRedactSecrets(
|
|
3748
|
+
result.lines = countLines(text2);
|
|
3749
|
+
const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative3);
|
|
2559
3750
|
result.secretCount = findings.length;
|
|
2560
3751
|
result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
|
|
2561
3752
|
}
|
|
2562
|
-
const manifest =
|
|
3753
|
+
const manifest = readManifest2(config);
|
|
2563
3754
|
const entry = manifest?.files.find((file) => file.relativePath === relative3);
|
|
2564
3755
|
result.part = entry?.part ?? null;
|
|
2565
3756
|
result.inclusionMode = entry?.inclusionMode;
|
|
@@ -2642,10 +3833,16 @@ function compareManifests(before, after) {
|
|
|
2642
3833
|
};
|
|
2643
3834
|
}
|
|
2644
3835
|
function loadManifest(target) {
|
|
2645
|
-
|
|
3836
|
+
let isDirectory;
|
|
3837
|
+
try {
|
|
3838
|
+
isDirectory = fs9.statSync(target).isDirectory();
|
|
3839
|
+
} catch {
|
|
3840
|
+
throw new Error(`No snapshot found at ${target}: the path does not exist.`);
|
|
3841
|
+
}
|
|
3842
|
+
const manifestPath = isDirectory ? path10.join(target, "manifest.json") : target;
|
|
2646
3843
|
let raw;
|
|
2647
3844
|
try {
|
|
2648
|
-
raw =
|
|
3845
|
+
raw = fs9.readFileSync(manifestPath, "utf8");
|
|
2649
3846
|
} catch {
|
|
2650
3847
|
throw new Error(`Could not read a manifest at ${manifestPath}.`);
|
|
2651
3848
|
}
|
|
@@ -2658,6 +3855,9 @@ function loadManifest(target) {
|
|
|
2658
3855
|
if (!parsed || !Array.isArray(parsed.files)) {
|
|
2659
3856
|
throw new Error(`${manifestPath} is not a snapshot manifest (no "files" array).`);
|
|
2660
3857
|
}
|
|
3858
|
+
if (!parsed.counts || typeof parsed.counts !== "object") {
|
|
3859
|
+
throw new Error(`${manifestPath} is not a snapshot manifest (no "counts" object).`);
|
|
3860
|
+
}
|
|
2661
3861
|
return parsed;
|
|
2662
3862
|
}
|
|
2663
3863
|
function renderDiffText(diff) {
|
|
@@ -2681,14 +3881,659 @@ function renderDiffText(diff) {
|
|
|
2681
3881
|
return lines.join("\n");
|
|
2682
3882
|
}
|
|
2683
3883
|
|
|
3884
|
+
// src/wizard/config-template.ts
|
|
3885
|
+
var DEFAULT_ANSWERS = {
|
|
3886
|
+
preset: "generic",
|
|
3887
|
+
maxPartBytes: "300k",
|
|
3888
|
+
largeFileBytes: "200k",
|
|
3889
|
+
include: [],
|
|
3890
|
+
excludePatterns: [],
|
|
3891
|
+
artifacts: {
|
|
3892
|
+
readme: true,
|
|
3893
|
+
manifest: true,
|
|
3894
|
+
parts: true,
|
|
3895
|
+
navigation: true,
|
|
3896
|
+
projectTree: true,
|
|
3897
|
+
audit: true,
|
|
3898
|
+
monolith: false
|
|
3899
|
+
},
|
|
3900
|
+
includeSensitiveFiles: false,
|
|
3901
|
+
outDir: "snapshot"
|
|
3902
|
+
};
|
|
3903
|
+
function list(values) {
|
|
3904
|
+
if (values.length === 0) return "[]";
|
|
3905
|
+
return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
3906
|
+
}
|
|
3907
|
+
function size(value) {
|
|
3908
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
3909
|
+
}
|
|
3910
|
+
var PRESET_NAMES = [
|
|
3911
|
+
"nextjs \xB7 react-vite \xB7 vue-vite \xB7 nuxt \xB7 sveltekit \xB7 angular \xB7 astro",
|
|
3912
|
+
"remix \xB7 solid \xB7 nest \xB7 node-express \xB7 node-lib",
|
|
3913
|
+
"python-django \xB7 python-fastapi \xB7 python-lib \xB7 go \xB7 rust",
|
|
3914
|
+
"java-spring \xB7 dotnet \xB7 php-laravel \xB7 ruby-rails",
|
|
3915
|
+
"monorepo \xB7 docs \xB7 infra \xB7 generic"
|
|
3916
|
+
];
|
|
3917
|
+
function renderConfig(answers) {
|
|
3918
|
+
const { artifacts } = answers;
|
|
3919
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
3920
|
+
|
|
3921
|
+
export default defineConfig({
|
|
3922
|
+
// \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
|
|
3923
|
+
// Which project shape to use. Detected from the repo when omitted.
|
|
3924
|
+
// ${PRESET_NAMES.join("\n // ")}
|
|
3925
|
+
preset: ${JSON.stringify(answers.preset)},
|
|
3926
|
+
|
|
3927
|
+
// Recorded in the manifest when git cannot report a branch.
|
|
3928
|
+
branch: "main",
|
|
3929
|
+
|
|
3930
|
+
// \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
|
|
3931
|
+
// Where the snapshot is written.
|
|
3932
|
+
outDir: ${JSON.stringify(answers.outDir)},
|
|
3933
|
+
// Scratch directory; the new snapshot is renamed into place atomically.
|
|
3934
|
+
tmpDir: "snapshot.tmp",
|
|
3935
|
+
|
|
3936
|
+
// \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
|
|
3937
|
+
// Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
|
|
3938
|
+
maxPartBytes: ${size(answers.maxPartBytes)},
|
|
3939
|
+
// Files above this size get a head/tail preview instead of the full body.
|
|
3940
|
+
largeFileBytes: ${size(answers.largeFileBytes)},
|
|
3941
|
+
previewHeadLines: 80,
|
|
3942
|
+
previewTailLines: 40,
|
|
3943
|
+
// Different ceilings per layer. Unlisted layers use \`default\`.
|
|
3944
|
+
budgets: {
|
|
3945
|
+
default: ${size(answers.maxPartBytes)},
|
|
3946
|
+
byLayer: {},
|
|
3947
|
+
},
|
|
3948
|
+
|
|
3949
|
+
// \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
|
|
3950
|
+
// Allow-list. Empty means "everything that is not ignored".
|
|
3951
|
+
// Example: ["src/**", "packages/*/src/**"]
|
|
3952
|
+
include: ${list(answers.include)},
|
|
3953
|
+
// Globs or path segments to drop. Example: ["**/*.test.ts", "docs"]
|
|
3954
|
+
excludePatterns: ${list(answers.excludePatterns)},
|
|
3955
|
+
|
|
3956
|
+
// \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
|
|
3957
|
+
// "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
|
|
3958
|
+
layersMode: "merge",
|
|
3959
|
+
// [{ name: "billing", match: "src/features/billing/**" }]
|
|
3960
|
+
layers: [],
|
|
3961
|
+
// [{ kind: "route", match: "src/app/api/**" }]
|
|
3962
|
+
kinds: [],
|
|
3963
|
+
|
|
3964
|
+
// \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
|
|
3965
|
+
// .env, *.pem, .npmrc, .ssh/ and friends are skipped unless this is true.
|
|
3966
|
+
includeSensitiveFiles: ${answers.includeSensitiveFiles},
|
|
3967
|
+
secrets: {
|
|
3968
|
+
// [{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ }]
|
|
3969
|
+
customPatterns: [],
|
|
3970
|
+
// Set true to drop the 21 built-in rules.
|
|
3971
|
+
disableDefaultPatterns: false,
|
|
3972
|
+
},
|
|
3973
|
+
|
|
3974
|
+
// \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
|
|
3975
|
+
artifacts: {
|
|
3976
|
+
readme: ${artifacts.readme}, // README.md
|
|
3977
|
+
manifest: ${artifacts.manifest}, // manifest.json \u2014 required by validate and report
|
|
3978
|
+
parts: ${artifacts.parts}, // parts/part-XXX.md
|
|
3979
|
+
navigation: ${artifacts.navigation}, // reports/navigation.md
|
|
3980
|
+
projectTree: ${artifacts.projectTree}, // reports/project-tree.md
|
|
3981
|
+
audit: ${artifacts.audit}, // reports/audit.md
|
|
3982
|
+
monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
|
|
3983
|
+
},
|
|
3984
|
+
// Skip the monolith above this size. null = no limit.
|
|
3985
|
+
maxMonolithBytes: null,
|
|
3986
|
+
|
|
3987
|
+
// \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
|
|
3988
|
+
// afterGenerate: (manifest) => { /* write your own artifact */ }
|
|
3989
|
+
hooks: {},
|
|
3990
|
+
});
|
|
3991
|
+
`;
|
|
3992
|
+
}
|
|
3993
|
+
function renderMinimalConfig(answers) {
|
|
3994
|
+
const lines = [];
|
|
3995
|
+
if (answers.preset !== DEFAULT_ANSWERS.preset) lines.push(` preset: ${JSON.stringify(answers.preset)},`);
|
|
3996
|
+
if (answers.maxPartBytes !== DEFAULT_ANSWERS.maxPartBytes) {
|
|
3997
|
+
lines.push(` maxPartBytes: ${size(answers.maxPartBytes)},`);
|
|
3998
|
+
}
|
|
3999
|
+
if (answers.largeFileBytes !== DEFAULT_ANSWERS.largeFileBytes) {
|
|
4000
|
+
lines.push(` largeFileBytes: ${size(answers.largeFileBytes)},`);
|
|
4001
|
+
}
|
|
4002
|
+
if (answers.include.length > 0) lines.push(` include: ${list(answers.include)},`);
|
|
4003
|
+
if (answers.excludePatterns.length > 0) {
|
|
4004
|
+
lines.push(` excludePatterns: ${list(answers.excludePatterns)},`);
|
|
4005
|
+
}
|
|
4006
|
+
if (answers.includeSensitiveFiles) lines.push(" includeSensitiveFiles: true,");
|
|
4007
|
+
if (answers.outDir !== DEFAULT_ANSWERS.outDir) lines.push(` outDir: ${JSON.stringify(answers.outDir)},`);
|
|
4008
|
+
const changed = Object.keys(answers.artifacts).filter(
|
|
4009
|
+
(name) => answers.artifacts[name] !== DEFAULT_ANSWERS.artifacts[name]
|
|
4010
|
+
);
|
|
4011
|
+
if (changed.length > 0) {
|
|
4012
|
+
const entries = changed.map((name) => ` ${name}: ${answers.artifacts[name]}`).join(",\n");
|
|
4013
|
+
lines.push(` artifacts: {
|
|
4014
|
+
${entries},
|
|
4015
|
+
},`);
|
|
4016
|
+
}
|
|
4017
|
+
if (lines.length === 0) {
|
|
4018
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
4019
|
+
|
|
4020
|
+
export default defineConfig({});
|
|
4021
|
+
`;
|
|
4022
|
+
}
|
|
4023
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
4024
|
+
|
|
4025
|
+
export default defineConfig({
|
|
4026
|
+
${lines.join("\n")}
|
|
4027
|
+
});
|
|
4028
|
+
`;
|
|
4029
|
+
}
|
|
4030
|
+
var PromptCancelled = class extends Error {
|
|
4031
|
+
constructor() {
|
|
4032
|
+
super("Prompt cancelled");
|
|
4033
|
+
this.name = "PromptCancelled";
|
|
4034
|
+
}
|
|
4035
|
+
};
|
|
4036
|
+
function defaultIO() {
|
|
4037
|
+
return { input: process.stdin, output: process.stdout };
|
|
4038
|
+
}
|
|
4039
|
+
function isInteractive(io) {
|
|
4040
|
+
return Boolean(io.input.isTTY && io.output.isTTY);
|
|
4041
|
+
}
|
|
4042
|
+
function moveSelection(current, delta, count) {
|
|
4043
|
+
if (count <= 0) return 0;
|
|
4044
|
+
return ((current + delta) % count + count) % count;
|
|
4045
|
+
}
|
|
4046
|
+
function toggleIndex(selected, index) {
|
|
4047
|
+
const next = new Set(selected);
|
|
4048
|
+
if (next.has(index)) next.delete(index);
|
|
4049
|
+
else next.add(index);
|
|
4050
|
+
return next;
|
|
4051
|
+
}
|
|
4052
|
+
function marker(active) {
|
|
4053
|
+
return active ? "\u276F" : " ";
|
|
4054
|
+
}
|
|
4055
|
+
function choiceLine(choice, active) {
|
|
4056
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
4057
|
+
return ` ${marker(active)} ${choice.label}${hint}`;
|
|
4058
|
+
}
|
|
4059
|
+
function renderSelect(message, choices, index) {
|
|
4060
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
4061
|
+
choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
|
|
4062
|
+
lines.push("", " \u2191\u2193 move \xB7 enter select");
|
|
4063
|
+
return lines.join("\n");
|
|
4064
|
+
}
|
|
4065
|
+
function renderMultiSelect(message, choices, index, selected) {
|
|
4066
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
4067
|
+
choices.forEach((choice, position) => {
|
|
4068
|
+
const box = selected.has(position) ? "\u25FC" : "\u25FB";
|
|
4069
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
4070
|
+
lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
|
|
4071
|
+
});
|
|
4072
|
+
lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
|
|
4073
|
+
return lines.join("\n");
|
|
4074
|
+
}
|
|
4075
|
+
function renderConfirm(message, value) {
|
|
4076
|
+
return [
|
|
4077
|
+
`\u25C6 ${message}`,
|
|
4078
|
+
"",
|
|
4079
|
+
` ${marker(value)} Yes`,
|
|
4080
|
+
` ${marker(!value)} No`,
|
|
4081
|
+
"",
|
|
4082
|
+
" \u2190\u2192 or y/n \xB7 enter confirm"
|
|
4083
|
+
].join("\n");
|
|
4084
|
+
}
|
|
4085
|
+
function renderText(message, value, error) {
|
|
4086
|
+
const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
|
|
4087
|
+
if (error) lines.push("", ` \u2716 ${error}`);
|
|
4088
|
+
return lines.join("\n");
|
|
4089
|
+
}
|
|
4090
|
+
function keyReader(input) {
|
|
4091
|
+
const queue = [];
|
|
4092
|
+
const waiters = [];
|
|
4093
|
+
const wasRaw = input.isRaw;
|
|
4094
|
+
const onKey = (_text, key) => {
|
|
4095
|
+
const waiter = waiters.shift();
|
|
4096
|
+
if (waiter) waiter(key);
|
|
4097
|
+
else queue.push(key);
|
|
4098
|
+
};
|
|
4099
|
+
readline.emitKeypressEvents(input);
|
|
4100
|
+
if (input.isTTY) input.setRawMode(true);
|
|
4101
|
+
input.resume();
|
|
4102
|
+
input.on("keypress", onKey);
|
|
4103
|
+
return {
|
|
4104
|
+
next() {
|
|
4105
|
+
const queued = queue.shift();
|
|
4106
|
+
if (queued) return Promise.resolve(queued);
|
|
4107
|
+
return new Promise((resolve5) => waiters.push(resolve5));
|
|
4108
|
+
},
|
|
4109
|
+
close() {
|
|
4110
|
+
input.off("keypress", onKey);
|
|
4111
|
+
if (input.isTTY) input.setRawMode(wasRaw ?? false);
|
|
4112
|
+
input.pause();
|
|
4113
|
+
}
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
var Frame = class {
|
|
4117
|
+
constructor(output) {
|
|
4118
|
+
this.output = output;
|
|
4119
|
+
}
|
|
4120
|
+
output;
|
|
4121
|
+
drawn = 0;
|
|
4122
|
+
draw(text2) {
|
|
4123
|
+
this.erase();
|
|
4124
|
+
this.output.write(`${text2}
|
|
4125
|
+
`);
|
|
4126
|
+
this.drawn = text2.split("\n").length;
|
|
4127
|
+
}
|
|
4128
|
+
erase() {
|
|
4129
|
+
if (this.drawn > 0) this.output.write(`\x1B[${this.drawn}A\x1B[0J`);
|
|
4130
|
+
this.drawn = 0;
|
|
4131
|
+
}
|
|
4132
|
+
};
|
|
4133
|
+
function isCancel(key) {
|
|
4134
|
+
return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
|
|
4135
|
+
}
|
|
4136
|
+
async function select(io, message, choices, options = {}) {
|
|
4137
|
+
if (choices.length === 0) throw new Error("select() requires at least one choice.");
|
|
4138
|
+
const fallbackIndex = Math.max(
|
|
4139
|
+
0,
|
|
4140
|
+
choices.findIndex((choice) => choice.value === options.defaultValue)
|
|
4141
|
+
);
|
|
4142
|
+
if (!isInteractive(io)) {
|
|
4143
|
+
io.output.write(`${renderSelect(message, choices, fallbackIndex)}
|
|
4144
|
+
`);
|
|
4145
|
+
return choices[fallbackIndex].value;
|
|
4146
|
+
}
|
|
4147
|
+
const frame = new Frame(io.output);
|
|
4148
|
+
const reader = keyReader(io.input);
|
|
4149
|
+
let index = fallbackIndex;
|
|
4150
|
+
try {
|
|
4151
|
+
for (; ; ) {
|
|
4152
|
+
frame.draw(renderSelect(message, choices, index));
|
|
4153
|
+
const key = await reader.next();
|
|
4154
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4155
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
4156
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
4157
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
4158
|
+
frame.erase();
|
|
4159
|
+
return choices[index].value;
|
|
4160
|
+
} else if (key.name === "j") index = moveSelection(index, 1, choices.length);
|
|
4161
|
+
else if (key.name === "k") index = moveSelection(index, -1, choices.length);
|
|
4162
|
+
}
|
|
4163
|
+
} finally {
|
|
4164
|
+
reader.close();
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
async function multiSelect(io, message, choices, options = {}) {
|
|
4168
|
+
const selected = /* @__PURE__ */ new Set();
|
|
4169
|
+
choices.forEach((choice, position) => {
|
|
4170
|
+
if (options.defaults?.includes(choice.value)) selected.add(position);
|
|
4171
|
+
});
|
|
4172
|
+
const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
|
|
4173
|
+
if (!isInteractive(io)) {
|
|
4174
|
+
io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
|
|
4175
|
+
`);
|
|
4176
|
+
return result();
|
|
4177
|
+
}
|
|
4178
|
+
const frame = new Frame(io.output);
|
|
4179
|
+
const reader = keyReader(io.input);
|
|
4180
|
+
let index = 0;
|
|
4181
|
+
try {
|
|
4182
|
+
for (; ; ) {
|
|
4183
|
+
frame.draw(renderMultiSelect(message, choices, index, selected));
|
|
4184
|
+
const key = await reader.next();
|
|
4185
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4186
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
4187
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
4188
|
+
else if (key.name === "space") {
|
|
4189
|
+
const next = toggleIndex(selected, index);
|
|
4190
|
+
selected.clear();
|
|
4191
|
+
for (const position of next) selected.add(position);
|
|
4192
|
+
} else if (key.name === "a") {
|
|
4193
|
+
if (selected.size === choices.length) selected.clear();
|
|
4194
|
+
else choices.forEach((_, position) => selected.add(position));
|
|
4195
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
4196
|
+
frame.erase();
|
|
4197
|
+
return result();
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
} finally {
|
|
4201
|
+
reader.close();
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
async function confirm(io, message, options = {}) {
|
|
4205
|
+
let value = options.defaultValue ?? true;
|
|
4206
|
+
if (!isInteractive(io)) {
|
|
4207
|
+
io.output.write(`${renderConfirm(message, value)}
|
|
4208
|
+
`);
|
|
4209
|
+
return value;
|
|
4210
|
+
}
|
|
4211
|
+
const frame = new Frame(io.output);
|
|
4212
|
+
const reader = keyReader(io.input);
|
|
4213
|
+
try {
|
|
4214
|
+
for (; ; ) {
|
|
4215
|
+
frame.draw(renderConfirm(message, value));
|
|
4216
|
+
const key = await reader.next();
|
|
4217
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4218
|
+
if (key.name === "up" || key.name === "left") value = true;
|
|
4219
|
+
else if (key.name === "down" || key.name === "right") value = false;
|
|
4220
|
+
else if (key.name === "y") value = true;
|
|
4221
|
+
else if (key.name === "n") value = false;
|
|
4222
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
4223
|
+
frame.erase();
|
|
4224
|
+
return value;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
} finally {
|
|
4228
|
+
reader.close();
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
async function text(io, message, options = {}) {
|
|
4232
|
+
const fallback = options.defaultValue ?? "";
|
|
4233
|
+
if (!isInteractive(io)) {
|
|
4234
|
+
io.output.write(`${renderText(message, fallback)}
|
|
4235
|
+
`);
|
|
4236
|
+
return fallback;
|
|
4237
|
+
}
|
|
4238
|
+
const frame = new Frame(io.output);
|
|
4239
|
+
const reader = keyReader(io.input);
|
|
4240
|
+
let value = fallback;
|
|
4241
|
+
let error;
|
|
4242
|
+
try {
|
|
4243
|
+
for (; ; ) {
|
|
4244
|
+
frame.draw(renderText(message, value, error));
|
|
4245
|
+
const key = await reader.next();
|
|
4246
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
4247
|
+
error = void 0;
|
|
4248
|
+
if (key.name === "return" || key.name === "enter") {
|
|
4249
|
+
const problem = options.validate ? options.validate(value) : null;
|
|
4250
|
+
if (problem) {
|
|
4251
|
+
error = problem;
|
|
4252
|
+
continue;
|
|
4253
|
+
}
|
|
4254
|
+
frame.erase();
|
|
4255
|
+
return value;
|
|
4256
|
+
}
|
|
4257
|
+
if (key.name === "backspace") {
|
|
4258
|
+
value = value.slice(0, -1);
|
|
4259
|
+
continue;
|
|
4260
|
+
}
|
|
4261
|
+
if (key.name === "escape") {
|
|
4262
|
+
value = "";
|
|
4263
|
+
continue;
|
|
4264
|
+
}
|
|
4265
|
+
if (_isPrintable(key)) value += _keyText(key);
|
|
4266
|
+
}
|
|
4267
|
+
} finally {
|
|
4268
|
+
reader.close();
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
function _isPrintable(key) {
|
|
4272
|
+
return typeof key.sequence === "string" && key.sequence.length === 1 && key.sequence >= " ";
|
|
4273
|
+
}
|
|
4274
|
+
function _keyText(key) {
|
|
4275
|
+
return key.sequence ?? "";
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
// src/wizard/init.ts
|
|
4279
|
+
var CONFIG_CANDIDATES = [
|
|
4280
|
+
"snapshot.config.ts",
|
|
4281
|
+
"snapshot.config.js",
|
|
4282
|
+
"snapshot.config.mjs",
|
|
4283
|
+
"snapshot.config.cjs",
|
|
4284
|
+
"snapshot.config.json",
|
|
4285
|
+
".snapshotrc.json",
|
|
4286
|
+
".snapshotrc"
|
|
4287
|
+
];
|
|
4288
|
+
var SIZE_CHOICES = [
|
|
4289
|
+
{ value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
|
|
4290
|
+
{ value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
|
|
4291
|
+
{ value: "300k", label: "Large", hint: "300 KB \xB7 ~75k tokens \xB7 the default" },
|
|
4292
|
+
{ value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
|
|
4293
|
+
];
|
|
4294
|
+
function existingConfig(cwd) {
|
|
4295
|
+
for (const name of CONFIG_CANDIDATES) {
|
|
4296
|
+
if (fs9.existsSync(path10.join(cwd, name))) return name;
|
|
4297
|
+
}
|
|
4298
|
+
return null;
|
|
4299
|
+
}
|
|
4300
|
+
function artifactAnswers(options) {
|
|
4301
|
+
const artifacts = { ...DEFAULT_ANSWERS.artifacts };
|
|
4302
|
+
for (const name of options.skipArtifacts ?? []) {
|
|
4303
|
+
if (ARTIFACT_NAMES.includes(name)) {
|
|
4304
|
+
artifacts[name] = false;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
if (options.onlyArtifacts && options.onlyArtifacts.length > 0) {
|
|
4308
|
+
const keep = new Set(options.onlyArtifacts);
|
|
4309
|
+
for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
|
|
4310
|
+
}
|
|
4311
|
+
return artifacts;
|
|
4312
|
+
}
|
|
4313
|
+
function topLevelDirs(cwd) {
|
|
4314
|
+
try {
|
|
4315
|
+
return fs9.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
|
|
4316
|
+
} catch {
|
|
4317
|
+
return [];
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
function addScripts(cwd, notes) {
|
|
4321
|
+
const packagePath = path10.join(cwd, "package.json");
|
|
4322
|
+
if (!fs9.existsSync(packagePath)) {
|
|
4323
|
+
notes.push("No package.json found, so npm scripts were not added.");
|
|
4324
|
+
return [];
|
|
4325
|
+
}
|
|
4326
|
+
let parsed;
|
|
4327
|
+
try {
|
|
4328
|
+
parsed = JSON.parse(fs9.readFileSync(packagePath, "utf8"));
|
|
4329
|
+
} catch {
|
|
4330
|
+
notes.push("Could not parse package.json; npm scripts were not added.");
|
|
4331
|
+
return [];
|
|
4332
|
+
}
|
|
4333
|
+
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
4334
|
+
const added = [];
|
|
4335
|
+
if (!scripts.snapshot) {
|
|
4336
|
+
scripts.snapshot = "codebase-snapshot generate";
|
|
4337
|
+
added.push("snapshot");
|
|
4338
|
+
}
|
|
4339
|
+
if (!scripts["snapshot:validate"]) {
|
|
4340
|
+
scripts["snapshot:validate"] = "codebase-snapshot validate";
|
|
4341
|
+
added.push("snapshot:validate");
|
|
4342
|
+
}
|
|
4343
|
+
if (added.length === 0) return [];
|
|
4344
|
+
parsed.scripts = scripts;
|
|
4345
|
+
fs9.writeFileSync(packagePath, `${JSON.stringify(parsed, null, 2)}
|
|
4346
|
+
`, "utf8");
|
|
4347
|
+
return added;
|
|
4348
|
+
}
|
|
4349
|
+
function addGitignore(cwd) {
|
|
4350
|
+
const ignorePath = path10.join(cwd, ".gitignore");
|
|
4351
|
+
const existing = fs9.existsSync(ignorePath) ? fs9.readFileSync(ignorePath, "utf8") : "";
|
|
4352
|
+
const wanted = ["snapshot/", "snapshot.tmp/"];
|
|
4353
|
+
const added = wanted.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
4354
|
+
if (added.length === 0) return [];
|
|
4355
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
4356
|
+
fs9.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
|
|
4357
|
+
`, "utf8");
|
|
4358
|
+
return added;
|
|
4359
|
+
}
|
|
4360
|
+
function resolvePresetChoice(answers, presetName) {
|
|
4361
|
+
return {
|
|
4362
|
+
value: presetName,
|
|
4363
|
+
label: presetName,
|
|
4364
|
+
hint: presetName === answers.preset ? "detected" : void 0
|
|
4365
|
+
};
|
|
4366
|
+
}
|
|
4367
|
+
async function runInit(options) {
|
|
4368
|
+
const io = options.io ?? defaultIO();
|
|
4369
|
+
const detected = detectPreset(options.cwd, options.preset);
|
|
4370
|
+
const notes = [];
|
|
4371
|
+
const answers = {
|
|
4372
|
+
preset: detected.presetName,
|
|
4373
|
+
maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
|
|
4374
|
+
largeFileBytes: DEFAULT_ANSWERS.largeFileBytes,
|
|
4375
|
+
include: options.include ?? [],
|
|
4376
|
+
excludePatterns: options.exclude ?? [],
|
|
4377
|
+
artifacts: artifactAnswers(options),
|
|
4378
|
+
includeSensitiveFiles: options.includeSensitiveFiles ?? DEFAULT_ANSWERS.includeSensitiveFiles,
|
|
4379
|
+
outDir: options.outDir ?? DEFAULT_ANSWERS.outDir
|
|
4380
|
+
};
|
|
4381
|
+
const interactive = isInteractive(io) && options.yes !== true;
|
|
4382
|
+
if (interactive) {
|
|
4383
|
+
io.output.write("\n @ankurhalder/codebase-snapshot\n");
|
|
4384
|
+
io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
|
|
4385
|
+
io.output.write(` Detected ${detected.presetName}
|
|
4386
|
+
`);
|
|
4387
|
+
io.output.write(` Directory ${options.cwd}
|
|
4388
|
+
|
|
4389
|
+
`);
|
|
4390
|
+
const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
|
|
4391
|
+
defaultValue: true
|
|
4392
|
+
});
|
|
4393
|
+
if (!useDetected) {
|
|
4394
|
+
answers.preset = await select(
|
|
4395
|
+
io,
|
|
4396
|
+
"What kind of project is this?",
|
|
4397
|
+
listPresetNames().map((name) => resolvePresetChoice(answers, name))
|
|
4398
|
+
);
|
|
4399
|
+
const chosen = detectPreset(options.cwd, answers.preset);
|
|
4400
|
+
answers.preset = chosen.presetName;
|
|
4401
|
+
}
|
|
4402
|
+
const scope = await select(
|
|
4403
|
+
io,
|
|
4404
|
+
"What should go into the snapshot?",
|
|
4405
|
+
[
|
|
4406
|
+
{ value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
|
|
4407
|
+
{ value: "folders", label: "Only specific folders\u2026" },
|
|
4408
|
+
{ value: "no-tests", label: "Everything, but skip tests" },
|
|
4409
|
+
{ value: "custom", label: "Let me write glob patterns" }
|
|
4410
|
+
],
|
|
4411
|
+
{ defaultValue: "all" }
|
|
4412
|
+
);
|
|
4413
|
+
if (scope === "folders") {
|
|
4414
|
+
const dirs = topLevelDirs(options.cwd).slice(0, 40);
|
|
4415
|
+
if (dirs.length === 0) {
|
|
4416
|
+
notes.push("No top-level directories found; the snapshot will include everything.");
|
|
4417
|
+
} else {
|
|
4418
|
+
const chosen = await multiSelect(
|
|
4419
|
+
io,
|
|
4420
|
+
"Select folders",
|
|
4421
|
+
dirs.map((name) => ({ value: name, label: `${name}/` })),
|
|
4422
|
+
{ defaults: dirs }
|
|
4423
|
+
);
|
|
4424
|
+
answers.include = chosen.map((name) => `${name}/**`);
|
|
4425
|
+
}
|
|
4426
|
+
} else if (scope === "no-tests") {
|
|
4427
|
+
answers.excludePatterns = [
|
|
4428
|
+
...answers.excludePatterns,
|
|
4429
|
+
"**/*.test.*",
|
|
4430
|
+
"**/*.spec.*",
|
|
4431
|
+
"tests",
|
|
4432
|
+
"test"
|
|
4433
|
+
];
|
|
4434
|
+
} else if (scope === "custom") {
|
|
4435
|
+
const raw = await text(io, "Glob patterns to include (comma separated)", {
|
|
4436
|
+
defaultValue: ""
|
|
4437
|
+
});
|
|
4438
|
+
answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
4439
|
+
}
|
|
4440
|
+
const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
|
|
4441
|
+
defaultValue: "120k"
|
|
4442
|
+
});
|
|
4443
|
+
if (sizeChoice === "custom") {
|
|
4444
|
+
const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
|
|
4445
|
+
defaultValue: String(answers.maxPartBytes),
|
|
4446
|
+
validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
|
|
4447
|
+
});
|
|
4448
|
+
answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
|
|
4449
|
+
} else {
|
|
4450
|
+
answers.maxPartBytes = sizeChoice;
|
|
4451
|
+
}
|
|
4452
|
+
const artifactChoices = [
|
|
4453
|
+
{ value: "readme", label: "README.md", hint: "orientation page" },
|
|
4454
|
+
{ value: "manifest", label: "manifest.json", hint: "required by validate and report" },
|
|
4455
|
+
{ value: "parts", label: "parts/", hint: "the code payload" },
|
|
4456
|
+
{ value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
|
|
4457
|
+
{ value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
|
|
4458
|
+
{ value: "audit", label: "reports/audit.md", hint: "security and health findings" },
|
|
4459
|
+
{ value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
|
|
4460
|
+
];
|
|
4461
|
+
const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
|
|
4462
|
+
const selectedArtifacts = await multiSelect(
|
|
4463
|
+
io,
|
|
4464
|
+
"Which artifacts should be generated?",
|
|
4465
|
+
artifactChoices,
|
|
4466
|
+
{ defaults: presetEnabled }
|
|
4467
|
+
);
|
|
4468
|
+
for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
|
|
4469
|
+
const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
|
|
4470
|
+
answers.includeSensitiveFiles = await confirm(
|
|
4471
|
+
io,
|
|
4472
|
+
"Include credential files (.env, *.pem, .npmrc, .ssh/)?",
|
|
4473
|
+
{ defaultValue: false }
|
|
4474
|
+
);
|
|
4475
|
+
io.output.write("\n Review\n\n");
|
|
4476
|
+
io.output.write(` project ${answers.preset}
|
|
4477
|
+
`);
|
|
4478
|
+
io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
|
|
4479
|
+
`);
|
|
4480
|
+
io.output.write(` part size ${String(answers.maxPartBytes)}
|
|
4481
|
+
`);
|
|
4482
|
+
io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
|
|
4483
|
+
`);
|
|
4484
|
+
io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
|
|
4485
|
+
|
|
4486
|
+
`);
|
|
4487
|
+
io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
|
|
4488
|
+
io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
|
|
4489
|
+
const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
|
|
4490
|
+
if (!proceed) throw new PromptCancelled();
|
|
4491
|
+
}
|
|
4492
|
+
const configText = options.minimal ? renderMinimalConfig(answers) : renderConfig(answers);
|
|
4493
|
+
const configName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
4494
|
+
const configPath = path10.join(options.cwd, configName);
|
|
4495
|
+
if (options.dryRun) {
|
|
4496
|
+
return {
|
|
4497
|
+
configPath,
|
|
4498
|
+
configText,
|
|
4499
|
+
written: false,
|
|
4500
|
+
preset: answers.preset,
|
|
4501
|
+
scripts: [],
|
|
4502
|
+
gitignoreEntries: [],
|
|
4503
|
+
notes
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4506
|
+
const present = existingConfig(options.cwd);
|
|
4507
|
+
if (present && present !== configName && !options.force) {
|
|
4508
|
+
throw new Error(
|
|
4509
|
+
`A configuration already exists (${present}). Re-run with --force to replace it.`
|
|
4510
|
+
);
|
|
4511
|
+
}
|
|
4512
|
+
if (fs9.existsSync(configPath) && !options.force) {
|
|
4513
|
+
throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
|
|
4514
|
+
}
|
|
4515
|
+
fs9.writeFileSync(configPath, configText, "utf8");
|
|
4516
|
+
const scripts = addScripts(options.cwd, notes);
|
|
4517
|
+
const gitignoreEntries = addGitignore(options.cwd);
|
|
4518
|
+
return {
|
|
4519
|
+
configPath,
|
|
4520
|
+
configText,
|
|
4521
|
+
written: true,
|
|
4522
|
+
preset: answers.preset,
|
|
4523
|
+
scripts,
|
|
4524
|
+
gitignoreEntries,
|
|
4525
|
+
notes
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
|
|
2684
4529
|
// src/index.ts
|
|
2685
4530
|
var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
|
|
2686
|
-
var GENERATOR_VERSION = "2.
|
|
4531
|
+
var GENERATOR_VERSION = "2.1.1";
|
|
2687
4532
|
var SCHEMA_VERSION = "3.0.0";
|
|
2688
4533
|
function sha256Hex2(input) {
|
|
2689
4534
|
return crypto.createHash("sha256").update(input).digest("hex");
|
|
2690
4535
|
}
|
|
2691
|
-
var
|
|
4536
|
+
var TS_EXTENSIONS = [
|
|
2692
4537
|
".ts",
|
|
2693
4538
|
".mts",
|
|
2694
4539
|
".cts",
|
|
@@ -2696,24 +4541,21 @@ var SUMMARY_EXTENSIONS = [
|
|
|
2696
4541
|
".js",
|
|
2697
4542
|
".mjs",
|
|
2698
4543
|
".cjs",
|
|
2699
|
-
".jsx"
|
|
2700
|
-
".json",
|
|
2701
|
-
".jsonc",
|
|
2702
|
-
".md",
|
|
2703
|
-
".mdx"
|
|
4544
|
+
".jsx"
|
|
2704
4545
|
];
|
|
2705
4546
|
function summarizeFile(relativePath, redactedContent) {
|
|
2706
4547
|
const lower = relativePath.toLowerCase();
|
|
2707
|
-
if (!SUMMARY_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
2708
|
-
return void 0;
|
|
2709
|
-
}
|
|
2710
4548
|
if (lower.endsWith(".json") || lower.endsWith(".jsonc")) {
|
|
2711
|
-
return summarizeJson(redactedContent);
|
|
4549
|
+
return { summary: summarizeJson(redactedContent), quality: "structural" };
|
|
2712
4550
|
}
|
|
2713
4551
|
if (lower.endsWith(".md") || lower.endsWith(".mdx")) {
|
|
2714
|
-
return summarizeMarkdown(redactedContent);
|
|
4552
|
+
return { summary: summarizeMarkdown(redactedContent), quality: "structural" };
|
|
4553
|
+
}
|
|
4554
|
+
if (TS_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
4555
|
+
return { summary: summarizeTypeScriptSource(relativePath, redactedContent), quality: "ast" };
|
|
2715
4556
|
}
|
|
2716
|
-
|
|
4557
|
+
const profiled = summarizeWithProfile(relativePath, redactedContent);
|
|
4558
|
+
return profiled ? { summary: profiled.summary, quality: profiled.quality } : void 0;
|
|
2717
4559
|
}
|
|
2718
4560
|
function isSourceSummary(summary) {
|
|
2719
4561
|
return Boolean(summary && typeof summary === "object" && "imports" in summary);
|
|
@@ -2739,8 +4581,8 @@ var IMPORT_CANDIDATE_SUFFIXES = [
|
|
|
2739
4581
|
];
|
|
2740
4582
|
function resolveSpecifier(fromFile, specifier, knownFiles) {
|
|
2741
4583
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
2742
|
-
const baseDir =
|
|
2743
|
-
const joined =
|
|
4584
|
+
const baseDir = path10.posix.dirname(fromFile);
|
|
4585
|
+
const joined = path10.posix.normalize(path10.posix.join(baseDir, specifier));
|
|
2744
4586
|
const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
|
|
2745
4587
|
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
2746
4588
|
const withSuffix = `${joined}${suffix}`;
|
|
@@ -2832,11 +4674,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
2832
4674
|
return errors;
|
|
2833
4675
|
}
|
|
2834
4676
|
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}${
|
|
4677
|
+
const cwd = path10.resolve(config.cwd);
|
|
4678
|
+
const outDir = path10.resolve(cwd, config.outDir);
|
|
4679
|
+
const tmpDir = path10.resolve(cwd, config.tmpDir);
|
|
4680
|
+
const root = path10.parse(cwd).root;
|
|
4681
|
+
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path10.sep}`);
|
|
2840
4682
|
const reject = (reason) => {
|
|
2841
4683
|
throw new Error(
|
|
2842
4684
|
`Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
|
|
@@ -2855,26 +4697,66 @@ function resolveSafeOutputDirs(config) {
|
|
|
2855
4697
|
if (contains(tmpDir, outDir)) reject("outDir is inside tmpDir");
|
|
2856
4698
|
return { outDir, tmpDir };
|
|
2857
4699
|
}
|
|
4700
|
+
var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
|
|
4701
|
+
var OWNERSHIP_MARKER = ".codebase-snapshot";
|
|
4702
|
+
function writeOwnershipMarker(dir) {
|
|
4703
|
+
const marker2 = {
|
|
4704
|
+
generator: GENERATOR_NAME,
|
|
4705
|
+
version: GENERATOR_VERSION,
|
|
4706
|
+
schemaVersion: SCHEMA_VERSION
|
|
4707
|
+
};
|
|
4708
|
+
fs9.writeFileSync(path10.join(dir, OWNERSHIP_MARKER), `${JSON.stringify(marker2, null, 2)}
|
|
4709
|
+
`, "utf8");
|
|
4710
|
+
}
|
|
4711
|
+
function isOwnOutput(dir) {
|
|
4712
|
+
if (fs9.existsSync(path10.join(dir, OWNERSHIP_MARKER))) return true;
|
|
4713
|
+
const manifestPath = path10.join(dir, "manifest.json");
|
|
4714
|
+
if (!fs9.existsSync(manifestPath)) return false;
|
|
4715
|
+
try {
|
|
4716
|
+
const parsed = JSON.parse(fs9.readFileSync(manifestPath, "utf8"));
|
|
4717
|
+
const generator = parsed.generator;
|
|
4718
|
+
return typeof parsed.schemaVersion === "string" && Array.isArray(parsed.files) && typeof generator === "object" && generator !== null && generator.name === GENERATOR_NAME;
|
|
4719
|
+
} catch {
|
|
4720
|
+
return false;
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
function assertReplaceable(target, label) {
|
|
4724
|
+
if (!fs9.existsSync(target)) return;
|
|
4725
|
+
if (!fs9.statSync(target).isDirectory()) {
|
|
4726
|
+
throw new Error(
|
|
4727
|
+
`Refusing to write into ${label} ${target}: it exists but is not a directory.`
|
|
4728
|
+
);
|
|
4729
|
+
}
|
|
4730
|
+
const entries = fs9.readdirSync(target);
|
|
4731
|
+
const meaningful = entries.filter((name) => !PLACEHOLDER_ENTRIES.has(name));
|
|
4732
|
+
if (meaningful.length === 0) return;
|
|
4733
|
+
if (isOwnOutput(target)) return;
|
|
4734
|
+
const preview = meaningful.slice(0, 5).join(", ");
|
|
4735
|
+
const more = meaningful.length > 5 ? `, and ${meaningful.length - 5} more` : "";
|
|
4736
|
+
throw new Error(
|
|
4737
|
+
`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.`
|
|
4738
|
+
);
|
|
4739
|
+
}
|
|
2858
4740
|
function swapIntoPlace(tmpDir, outDir) {
|
|
2859
4741
|
const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
|
|
2860
|
-
const hadPrevious =
|
|
2861
|
-
if (hadPrevious)
|
|
4742
|
+
const hadPrevious = fs9.existsSync(outDir);
|
|
4743
|
+
if (hadPrevious) fs9.renameSync(outDir, backupDir);
|
|
2862
4744
|
try {
|
|
2863
4745
|
try {
|
|
2864
|
-
|
|
4746
|
+
fs9.renameSync(tmpDir, outDir);
|
|
2865
4747
|
} catch (err) {
|
|
2866
4748
|
if (err.code === "EXDEV") {
|
|
2867
|
-
|
|
2868
|
-
|
|
4749
|
+
fs9.cpSync(tmpDir, outDir, { recursive: true });
|
|
4750
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
2869
4751
|
} else {
|
|
2870
4752
|
throw err;
|
|
2871
4753
|
}
|
|
2872
4754
|
}
|
|
2873
|
-
if (hadPrevious)
|
|
4755
|
+
if (hadPrevious) fs9.rmSync(backupDir, { recursive: true, force: true });
|
|
2874
4756
|
} catch (err) {
|
|
2875
|
-
if (hadPrevious &&
|
|
2876
|
-
if (
|
|
2877
|
-
|
|
4757
|
+
if (hadPrevious && fs9.existsSync(backupDir)) {
|
|
4758
|
+
if (fs9.existsSync(outDir)) fs9.rmSync(outDir, { recursive: true, force: true });
|
|
4759
|
+
fs9.renameSync(backupDir, outDir);
|
|
2878
4760
|
}
|
|
2879
4761
|
throw err;
|
|
2880
4762
|
}
|
|
@@ -2890,7 +4772,9 @@ function artifactWarnings(artifacts) {
|
|
|
2890
4772
|
);
|
|
2891
4773
|
}
|
|
2892
4774
|
if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
|
|
2893
|
-
messages.push(
|
|
4775
|
+
messages.push(
|
|
4776
|
+
"every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
|
|
4777
|
+
);
|
|
2894
4778
|
}
|
|
2895
4779
|
return messages;
|
|
2896
4780
|
}
|
|
@@ -2938,7 +4822,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2938
4822
|
const gitInfo = getGitInfo(config.cwd, config.branch);
|
|
2939
4823
|
const collection = collectFiles(config.cwd, {
|
|
2940
4824
|
includePatterns: config.include,
|
|
2941
|
-
excludePatterns: config.excludePatterns,
|
|
4825
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2942
4826
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2943
4827
|
});
|
|
2944
4828
|
const files = [];
|
|
@@ -2951,10 +4835,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2951
4835
|
let totalSizeBytes = 0;
|
|
2952
4836
|
let largeFileCount = 0;
|
|
2953
4837
|
for (const relativePath of collection.files) {
|
|
2954
|
-
const absolutePath =
|
|
4838
|
+
const absolutePath = path10.join(config.cwd, relativePath);
|
|
2955
4839
|
let stats;
|
|
2956
4840
|
try {
|
|
2957
|
-
stats =
|
|
4841
|
+
stats = fs9.statSync(absolutePath);
|
|
2958
4842
|
if (!stats.isFile()) {
|
|
2959
4843
|
skipped.push({ path: relativePath, reason: "not-file" });
|
|
2960
4844
|
continue;
|
|
@@ -2969,7 +4853,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2969
4853
|
}
|
|
2970
4854
|
let buffer;
|
|
2971
4855
|
try {
|
|
2972
|
-
buffer =
|
|
4856
|
+
buffer = fs9.readFileSync(absolutePath);
|
|
2973
4857
|
} catch {
|
|
2974
4858
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
2975
4859
|
continue;
|
|
@@ -2979,10 +4863,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2979
4863
|
const lineCount = countLines(rawContent);
|
|
2980
4864
|
totalLines += lineCount;
|
|
2981
4865
|
totalSizeBytes += stats.size;
|
|
2982
|
-
const
|
|
4866
|
+
const layer2 = classifyLayer(relativePath, config.layers);
|
|
2983
4867
|
const kind = classifyKind(relativePath, config.kinds);
|
|
2984
4868
|
const language = detectLanguage(relativePath);
|
|
2985
|
-
bump(layerCounts,
|
|
4869
|
+
bump(layerCounts, layer2);
|
|
2986
4870
|
bump(kindCounts, kind);
|
|
2987
4871
|
const { redacted, findings } = scanAndRedactSecrets(
|
|
2988
4872
|
rawContent,
|
|
@@ -3006,8 +4890,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3006
4890
|
includedLines = preview.includedLines;
|
|
3007
4891
|
}
|
|
3008
4892
|
let summary;
|
|
4893
|
+
let summaryQuality;
|
|
3009
4894
|
try {
|
|
3010
|
-
|
|
4895
|
+
const outcome = summarizeFile(relativePath, redacted);
|
|
4896
|
+
summary = outcome?.summary;
|
|
4897
|
+
summaryQuality = outcome?.quality;
|
|
3011
4898
|
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
3012
4899
|
warnings.push(
|
|
3013
4900
|
`Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
|
|
@@ -3024,7 +4911,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3024
4911
|
lines: lineCount,
|
|
3025
4912
|
hashSha256,
|
|
3026
4913
|
part: "",
|
|
3027
|
-
layer,
|
|
4914
|
+
layer: layer2,
|
|
3028
4915
|
kind,
|
|
3029
4916
|
language,
|
|
3030
4917
|
inclusionMode: isPreviewTruncated ? "preview" : "full",
|
|
@@ -3034,6 +4921,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3034
4921
|
includedLines,
|
|
3035
4922
|
includedBytes: Buffer.byteLength(finalContent, "utf8"),
|
|
3036
4923
|
summary,
|
|
4924
|
+
summaryQuality,
|
|
3037
4925
|
content: finalContent
|
|
3038
4926
|
});
|
|
3039
4927
|
}
|
|
@@ -3074,10 +4962,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3074
4962
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
3075
4963
|
artifacts: { ...config.artifacts },
|
|
3076
4964
|
include: [...config.include],
|
|
4965
|
+
preset: config.preset,
|
|
3077
4966
|
layersMode: config.layersMode,
|
|
3078
4967
|
budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
|
|
3079
4968
|
excludePatterns: [...config.excludePatterns],
|
|
3080
|
-
layers: config.layers.map((
|
|
4969
|
+
layers: config.layers.map((layer2) => layer2.name),
|
|
3081
4970
|
kinds: config.kinds.map((kind) => kind.kind)
|
|
3082
4971
|
},
|
|
3083
4972
|
counts: {
|
|
@@ -3164,45 +5053,48 @@ function generateSnapshot(configInput, options) {
|
|
|
3164
5053
|
return manifest;
|
|
3165
5054
|
}
|
|
3166
5055
|
const { outDir, tmpDir } = resolveSafeOutputDirs(config);
|
|
5056
|
+
assertReplaceable(tmpDir, "tmpDir");
|
|
5057
|
+
assertReplaceable(outDir, "outDir");
|
|
3167
5058
|
const { artifacts } = config;
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
const
|
|
3172
|
-
|
|
5059
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
5060
|
+
fs9.mkdirSync(tmpDir, { recursive: true });
|
|
5061
|
+
writeOwnershipMarker(tmpDir);
|
|
5062
|
+
const partsDir = path10.join(tmpDir, "parts");
|
|
5063
|
+
const reportsDir = path10.join(tmpDir, "reports");
|
|
5064
|
+
if (artifacts.parts) fs9.mkdirSync(partsDir, { recursive: true });
|
|
3173
5065
|
if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
|
|
3174
|
-
|
|
5066
|
+
fs9.mkdirSync(reportsDir, { recursive: true });
|
|
3175
5067
|
}
|
|
3176
5068
|
if (artifacts.parts) {
|
|
3177
5069
|
for (const part of parts) {
|
|
3178
|
-
|
|
5070
|
+
fs9.writeFileSync(path10.join(partsDir, part.filename), part.markdown, "utf8");
|
|
3179
5071
|
}
|
|
3180
5072
|
}
|
|
3181
5073
|
if (artifacts.projectTree) {
|
|
3182
5074
|
const projectTree = generateProjectTree(files.map((file) => file.relativePath));
|
|
3183
|
-
|
|
5075
|
+
fs9.writeFileSync(path10.join(reportsDir, "project-tree.md"), projectTree, "utf8");
|
|
3184
5076
|
}
|
|
3185
5077
|
if (artifacts.navigation) {
|
|
3186
|
-
|
|
5078
|
+
fs9.writeFileSync(path10.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
|
|
3187
5079
|
}
|
|
3188
5080
|
if (artifacts.audit) {
|
|
3189
|
-
|
|
3190
|
-
|
|
5081
|
+
fs9.writeFileSync(
|
|
5082
|
+
path10.join(reportsDir, "audit.md"),
|
|
3191
5083
|
generateAuditReport(manifest, secretFindings),
|
|
3192
5084
|
"utf8"
|
|
3193
5085
|
);
|
|
3194
5086
|
}
|
|
3195
5087
|
if (artifacts.readme) {
|
|
3196
|
-
|
|
5088
|
+
fs9.writeFileSync(path10.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
|
|
3197
5089
|
}
|
|
3198
5090
|
if (artifacts.manifest) {
|
|
3199
|
-
|
|
5091
|
+
fs9.writeFileSync(path10.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3200
5092
|
}
|
|
3201
5093
|
if (monolithJson !== null) {
|
|
3202
|
-
|
|
5094
|
+
fs9.writeFileSync(path10.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
|
|
3203
5095
|
}
|
|
3204
5096
|
if (monolithMarkdown !== null) {
|
|
3205
|
-
|
|
5097
|
+
fs9.writeFileSync(path10.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
|
|
3206
5098
|
}
|
|
3207
5099
|
swapIntoPlace(tmpDir, outDir);
|
|
3208
5100
|
runAfterGenerate(config, manifest);
|
|
@@ -3212,6 +5104,6 @@ async function generateSnapshotAsync(configInput, options) {
|
|
|
3212
5104
|
return generateSnapshot(configInput, options);
|
|
3213
5105
|
}
|
|
3214
5106
|
|
|
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-
|
|
5107
|
+
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 };
|
|
5108
|
+
//# sourceMappingURL=chunk-SUOJL5EX.js.map
|
|
5109
|
+
//# sourceMappingURL=chunk-SUOJL5EX.js.map
|