@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
package/dist/cli.cjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var fs9 = require('fs');
|
|
4
|
+
var path10 = require('path');
|
|
5
5
|
var util = require('util');
|
|
6
6
|
var url = require('url');
|
|
7
7
|
var childProcess = require('child_process');
|
|
8
|
+
var readline = require('readline');
|
|
8
9
|
var crypto = require('crypto');
|
|
9
10
|
var ts = require('typescript');
|
|
10
11
|
|
|
@@ -28,9 +29,10 @@ function _interopNamespace(e) {
|
|
|
28
29
|
return Object.freeze(n);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
var
|
|
32
|
-
var
|
|
32
|
+
var fs9__namespace = /*#__PURE__*/_interopNamespace(fs9);
|
|
33
|
+
var path10__namespace = /*#__PURE__*/_interopNamespace(path10);
|
|
33
34
|
var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
|
|
35
|
+
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
34
36
|
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
35
37
|
var ts__default = /*#__PURE__*/_interopDefault(ts);
|
|
36
38
|
|
|
@@ -79,10 +81,16 @@ function compareManifests(before, after) {
|
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
function loadManifest(target) {
|
|
82
|
-
|
|
84
|
+
let isDirectory;
|
|
85
|
+
try {
|
|
86
|
+
isDirectory = fs9__namespace.statSync(target).isDirectory();
|
|
87
|
+
} catch {
|
|
88
|
+
throw new Error(`No snapshot found at ${target}: the path does not exist.`);
|
|
89
|
+
}
|
|
90
|
+
const manifestPath = isDirectory ? path10__namespace.join(target, "manifest.json") : target;
|
|
83
91
|
let raw;
|
|
84
92
|
try {
|
|
85
|
-
raw =
|
|
93
|
+
raw = fs9__namespace.readFileSync(manifestPath, "utf8");
|
|
86
94
|
} catch {
|
|
87
95
|
throw new Error(`Could not read a manifest at ${manifestPath}.`);
|
|
88
96
|
}
|
|
@@ -95,6 +103,9 @@ function loadManifest(target) {
|
|
|
95
103
|
if (!parsed || !Array.isArray(parsed.files)) {
|
|
96
104
|
throw new Error(`${manifestPath} is not a snapshot manifest (no "files" array).`);
|
|
97
105
|
}
|
|
106
|
+
if (!parsed.counts || typeof parsed.counts !== "object") {
|
|
107
|
+
throw new Error(`${manifestPath} is not a snapshot manifest (no "counts" object).`);
|
|
108
|
+
}
|
|
98
109
|
return parsed;
|
|
99
110
|
}
|
|
100
111
|
function renderDiffText(diff) {
|
|
@@ -117,6 +128,138 @@ function renderDiffText(diff) {
|
|
|
117
128
|
section("changed:", diff.changed);
|
|
118
129
|
return lines.join("\n");
|
|
119
130
|
}
|
|
131
|
+
var MANIFEST_FILES = [
|
|
132
|
+
"package.json",
|
|
133
|
+
"pyproject.toml",
|
|
134
|
+
"requirements.txt",
|
|
135
|
+
"setup.py",
|
|
136
|
+
"go.mod",
|
|
137
|
+
"Cargo.toml",
|
|
138
|
+
"pom.xml",
|
|
139
|
+
"build.gradle",
|
|
140
|
+
"build.gradle.kts",
|
|
141
|
+
"composer.json",
|
|
142
|
+
"Gemfile"
|
|
143
|
+
];
|
|
144
|
+
var MANIFEST_MAX_BYTES = 1e5;
|
|
145
|
+
function readManifest(cwd, name) {
|
|
146
|
+
const full = path10__namespace.join(cwd, name);
|
|
147
|
+
try {
|
|
148
|
+
const stats = fs9__namespace.statSync(full);
|
|
149
|
+
if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
|
|
150
|
+
return fs9__namespace.readFileSync(full, "utf8");
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function collectDependencies(manifests) {
|
|
156
|
+
const names = /* @__PURE__ */ new Set();
|
|
157
|
+
const raw = manifests["package.json"];
|
|
158
|
+
if (!raw) return names;
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(raw);
|
|
161
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
162
|
+
const block = parsed[section];
|
|
163
|
+
if (block && typeof block === "object") {
|
|
164
|
+
for (const name of Object.keys(block)) names.add(name);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
return names;
|
|
170
|
+
}
|
|
171
|
+
function buildPresetContext(cwd) {
|
|
172
|
+
const files = /* @__PURE__ */ new Set();
|
|
173
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
174
|
+
try {
|
|
175
|
+
for (const entry of fs9__namespace.readdirSync(cwd, { withFileTypes: true })) {
|
|
176
|
+
if (entry.isDirectory()) dirs.add(entry.name);
|
|
177
|
+
else if (entry.isFile()) files.add(entry.name);
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
const manifests = {};
|
|
182
|
+
for (const name of MANIFEST_FILES) {
|
|
183
|
+
const content = readManifest(cwd, name);
|
|
184
|
+
if (content !== null) manifests[name] = content;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
cwd,
|
|
188
|
+
files,
|
|
189
|
+
dirs,
|
|
190
|
+
dependencies: collectDependencies(manifests),
|
|
191
|
+
manifests
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function hasFile(context, name) {
|
|
195
|
+
return context.files.has(name);
|
|
196
|
+
}
|
|
197
|
+
function hasFileStartingWith(context, prefix) {
|
|
198
|
+
for (const file of context.files) {
|
|
199
|
+
if (file.startsWith(prefix)) return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
function manifestContains(context, manifest, needle) {
|
|
204
|
+
const content = context.manifests[manifest];
|
|
205
|
+
if (!content) return false;
|
|
206
|
+
return content.toLowerCase().includes(needle.toLowerCase());
|
|
207
|
+
}
|
|
208
|
+
function hasDependency(context, ...names) {
|
|
209
|
+
return names.some((name) => context.dependencies.has(name));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/presets/helpers.ts
|
|
213
|
+
function layer(name, ...prefixes) {
|
|
214
|
+
return {
|
|
215
|
+
name,
|
|
216
|
+
match: (filePath) => prefixes.some((prefix) => filePath.startsWith(prefix))
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function kindOf(name, ...needles) {
|
|
220
|
+
return {
|
|
221
|
+
kind: name,
|
|
222
|
+
match: (filePath) => needles.some((needle) => filePath.includes(needle))
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function suffixKind(name, ...suffixes) {
|
|
226
|
+
return {
|
|
227
|
+
kind: name,
|
|
228
|
+
match: (filePath) => suffixes.some((suffix) => filePath.endsWith(suffix))
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function commonLayers() {
|
|
232
|
+
return [
|
|
233
|
+
layer("scripts", "scripts/", "tools/"),
|
|
234
|
+
layer("docs", "docs/", "documentation/"),
|
|
235
|
+
{
|
|
236
|
+
name: "config",
|
|
237
|
+
match: (filePath) => filePath.startsWith(".github/") || filePath.startsWith(".husky/") || filePath.startsWith(".vscode/") || filePath.startsWith(".") || filePath.includes("config")
|
|
238
|
+
},
|
|
239
|
+
{ name: "root", match: (filePath) => !filePath.includes("/") }
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
function commonKinds() {
|
|
243
|
+
return [
|
|
244
|
+
{
|
|
245
|
+
kind: "test",
|
|
246
|
+
match: (filePath) => {
|
|
247
|
+
const lower = filePath.toLowerCase();
|
|
248
|
+
return lower.includes(".test.") || lower.includes(".spec.") || lower.startsWith("test/") || lower.startsWith("tests/") || lower.includes("_test.") || lower.endsWith("_test.go") || lower.includes("__tests__");
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
suffixKind("doc", ".md", ".mdx", ".rst", ".txt"),
|
|
252
|
+
suffixKind("data", ".json", ".yaml", ".yml", ".csv", ".toml"),
|
|
253
|
+
{
|
|
254
|
+
kind: "script",
|
|
255
|
+
match: (filePath) => filePath.startsWith("scripts/") || /\.(sh|ps1|bat)$/i.test(filePath)
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
kind: "config",
|
|
259
|
+
match: (filePath) => filePath.startsWith(".") || filePath.includes("config") || filePath === "package.json" || filePath === "tsconfig.json"
|
|
260
|
+
}
|
|
261
|
+
];
|
|
262
|
+
}
|
|
120
263
|
|
|
121
264
|
// src/presets/nextjs.ts
|
|
122
265
|
var NEXTJS_LAYERS = [
|
|
@@ -173,6 +316,47 @@ var NEXTJS_KINDS = [
|
|
|
173
316
|
}
|
|
174
317
|
];
|
|
175
318
|
|
|
319
|
+
// src/presets/node.ts
|
|
320
|
+
var NODE_LAYERS = [
|
|
321
|
+
{ name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
|
|
322
|
+
{ name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
|
|
323
|
+
{ name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
|
|
324
|
+
{ name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
|
|
325
|
+
{ name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
|
|
326
|
+
{ name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
|
|
327
|
+
{ name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
|
|
328
|
+
{ name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
|
|
329
|
+
{ name: "scripts", match: (p) => p.startsWith("scripts/") },
|
|
330
|
+
{ name: "docs", match: (p) => p.startsWith("docs/") },
|
|
331
|
+
{
|
|
332
|
+
name: "config",
|
|
333
|
+
match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
|
|
334
|
+
},
|
|
335
|
+
{ name: "root", match: (p) => !p.includes("/") }
|
|
336
|
+
];
|
|
337
|
+
var NODE_KINDS = [
|
|
338
|
+
{ kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
|
|
339
|
+
{ kind: "route", match: (p) => p.includes("/routes/") || p.includes("route.") || p.includes("/api/") },
|
|
340
|
+
{ kind: "controller", match: (p) => p.includes("controller") },
|
|
341
|
+
{ kind: "service", match: (p) => p.includes("service") },
|
|
342
|
+
{ kind: "provider", match: (p) => p.includes("provider") },
|
|
343
|
+
{ kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
|
|
344
|
+
{ kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
|
|
345
|
+
{ kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
|
|
346
|
+
{ kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
|
|
347
|
+
{ kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
|
|
348
|
+
{ kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
|
|
349
|
+
{ kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
|
|
350
|
+
{ kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
|
|
351
|
+
{
|
|
352
|
+
kind: "config",
|
|
353
|
+
match: (p) => {
|
|
354
|
+
const base = p.split("/").pop() || "";
|
|
355
|
+
return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
];
|
|
359
|
+
|
|
176
360
|
// src/presets/vite.ts
|
|
177
361
|
var VITE_LAYERS = [
|
|
178
362
|
{ name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
|
|
@@ -220,44 +404,419 @@ var VITE_KINDS = [
|
|
|
220
404
|
}
|
|
221
405
|
];
|
|
222
406
|
|
|
223
|
-
// src/presets/
|
|
224
|
-
var
|
|
225
|
-
|
|
226
|
-
{ name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
|
|
227
|
-
{ name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
|
|
228
|
-
{ name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
|
|
229
|
-
{ name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
|
|
230
|
-
{ name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
|
|
231
|
-
{ name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
|
|
232
|
-
{ name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
|
|
233
|
-
{ name: "scripts", match: (p) => p.startsWith("scripts/") },
|
|
234
|
-
{ name: "docs", match: (p) => p.startsWith("docs/") },
|
|
407
|
+
// src/presets/js-presets.ts
|
|
408
|
+
var NEXT_LIKE_EXCLUDES = [".svelte-kit", ".astro", ".vite", ".output"];
|
|
409
|
+
var JS_PRESETS = [
|
|
235
410
|
{
|
|
236
|
-
name: "
|
|
237
|
-
|
|
411
|
+
name: "nextjs",
|
|
412
|
+
priority: 100,
|
|
413
|
+
detect: (context) => hasFileStartingWith(context, "next.config") || hasDependency(context, "next"),
|
|
414
|
+
layers: NEXTJS_LAYERS,
|
|
415
|
+
kinds: NEXTJS_KINDS,
|
|
416
|
+
exclude: NEXT_LIKE_EXCLUDES,
|
|
417
|
+
recommendedBudget: "300k"
|
|
238
418
|
},
|
|
239
|
-
{
|
|
419
|
+
{
|
|
420
|
+
name: "nuxt",
|
|
421
|
+
priority: 98,
|
|
422
|
+
detect: (context) => hasFileStartingWith(context, "nuxt.config") || hasDependency(context, "nuxt", "nuxt3"),
|
|
423
|
+
layers: [
|
|
424
|
+
layer("pages", "pages/", "src/pages/"),
|
|
425
|
+
layer("components", "components/", "src/components/"),
|
|
426
|
+
layer("composables", "composables/", "src/composables/"),
|
|
427
|
+
layer("server", "server/", "src/server/", "api/"),
|
|
428
|
+
layer("stores", "stores/", "src/stores/"),
|
|
429
|
+
layer("layouts", "layouts/", "src/layouts/"),
|
|
430
|
+
...commonLayers()
|
|
431
|
+
],
|
|
432
|
+
kinds: [
|
|
433
|
+
kindOf("api-route", "server/api/", "server/routes/"),
|
|
434
|
+
suffixKind("component", ".vue"),
|
|
435
|
+
kindOf("composable", "composables/"),
|
|
436
|
+
kindOf("store", "stores/", "store/"),
|
|
437
|
+
kindOf("layout", "layouts/"),
|
|
438
|
+
...commonKinds()
|
|
439
|
+
],
|
|
440
|
+
exclude: [".nuxt", ".output"],
|
|
441
|
+
recommendedBudget: "200k"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "sveltekit",
|
|
445
|
+
priority: 96,
|
|
446
|
+
detect: (context) => hasFileStartingWith(context, "svelte.config") || hasDependency(context, "@sveltejs/kit", "svelte"),
|
|
447
|
+
layers: [
|
|
448
|
+
layer("routes", "src/routes/", "routes/"),
|
|
449
|
+
layer("lib", "src/lib/", "lib/"),
|
|
450
|
+
layer("components", "src/lib/components/", "src/components/"),
|
|
451
|
+
layer("stores", "src/lib/stores/", "src/stores/"),
|
|
452
|
+
...commonLayers()
|
|
453
|
+
],
|
|
454
|
+
kinds: [
|
|
455
|
+
kindOf("route", "+page.", "+server.", "+layout."),
|
|
456
|
+
suffixKind("component", ".svelte"),
|
|
457
|
+
kindOf("store", "stores/"),
|
|
458
|
+
kindOf("params", "params/"),
|
|
459
|
+
...commonKinds()
|
|
460
|
+
],
|
|
461
|
+
exclude: [".svelte-kit", ".vercel"],
|
|
462
|
+
recommendedBudget: "200k"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "remix",
|
|
466
|
+
priority: 94,
|
|
467
|
+
detect: (context) => hasDependency(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
|
|
468
|
+
layers: [
|
|
469
|
+
layer("routes", "app/routes/", "routes/"),
|
|
470
|
+
layer("components", "app/components/"),
|
|
471
|
+
layer("lib", "app/lib/", "app/utils/"),
|
|
472
|
+
...commonLayers()
|
|
473
|
+
],
|
|
474
|
+
kinds: [kindOf("route", "routes/"), ...commonKinds()],
|
|
475
|
+
exclude: [".cache", "public/build"],
|
|
476
|
+
recommendedBudget: "200k"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "astro",
|
|
480
|
+
priority: 92,
|
|
481
|
+
detect: (context) => hasFileStartingWith(context, "astro.config") || hasDependency(context, "astro"),
|
|
482
|
+
layers: [
|
|
483
|
+
layer("pages", "src/pages/"),
|
|
484
|
+
layer("layouts", "src/layouts/"),
|
|
485
|
+
layer("components", "src/components/"),
|
|
486
|
+
layer("content", "src/content/"),
|
|
487
|
+
...commonLayers()
|
|
488
|
+
],
|
|
489
|
+
kinds: [suffixKind("page", ".astro"), kindOf("layout", "layouts/"), ...commonKinds()],
|
|
490
|
+
exclude: [".astro", "dist"],
|
|
491
|
+
recommendedBudget: "150k"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: "angular",
|
|
495
|
+
priority: 90,
|
|
496
|
+
detect: (context) => hasFile(context, "angular.json") || hasDependency(context, "@angular/core"),
|
|
497
|
+
layers: [
|
|
498
|
+
layer("app", "src/app/"),
|
|
499
|
+
layer("features", "src/app/features/"),
|
|
500
|
+
layer("shared", "src/app/shared/"),
|
|
501
|
+
layer("core", "src/app/core/"),
|
|
502
|
+
layer("assets", "src/assets/"),
|
|
503
|
+
...commonLayers()
|
|
504
|
+
],
|
|
505
|
+
kinds: [
|
|
506
|
+
suffixKind("component", ".component.ts"),
|
|
507
|
+
suffixKind("service", ".service.ts"),
|
|
508
|
+
suffixKind("module", ".module.ts"),
|
|
509
|
+
suffixKind("guard", ".guard.ts"),
|
|
510
|
+
suffixKind("pipe", ".pipe.ts"),
|
|
511
|
+
suffixKind("directive", ".directive.ts"),
|
|
512
|
+
...commonKinds()
|
|
513
|
+
],
|
|
514
|
+
exclude: [".angular", "dist"],
|
|
515
|
+
recommendedBudget: "200k"
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: "solid",
|
|
519
|
+
priority: 88,
|
|
520
|
+
detect: (context) => hasDependency(context, "solid-js", "solid-start"),
|
|
521
|
+
layers: [
|
|
522
|
+
layer("pages", "src/routes/", "src/pages/"),
|
|
523
|
+
layer("components", "src/components/"),
|
|
524
|
+
...commonLayers()
|
|
525
|
+
],
|
|
526
|
+
kinds: [kindOf("route", "routes/"), ...commonKinds()],
|
|
527
|
+
exclude: [".solid", "dist"],
|
|
528
|
+
recommendedBudget: "120k"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: "vue-vite",
|
|
532
|
+
priority: 86,
|
|
533
|
+
detect: (context) => hasDependency(context, "vue") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
|
|
534
|
+
layers: [
|
|
535
|
+
layer("pages", "src/pages/", "src/views/"),
|
|
536
|
+
layer("components", "src/components/"),
|
|
537
|
+
layer("composables", "src/composables/", "src/hooks/"),
|
|
538
|
+
layer("stores", "src/stores/", "src/store/"),
|
|
539
|
+
layer("services", "src/services/", "src/api/"),
|
|
540
|
+
layer("shared", "src/shared/", "src/common/"),
|
|
541
|
+
layer("styles", "src/styles/", "src/assets/"),
|
|
542
|
+
...commonLayers()
|
|
543
|
+
],
|
|
544
|
+
kinds: [
|
|
545
|
+
suffixKind("component", ".vue"),
|
|
546
|
+
kindOf("composable", "composables/", "use"),
|
|
547
|
+
kindOf("store", "stores/", "store/"),
|
|
548
|
+
kindOf("service", "services/", "api/"),
|
|
549
|
+
suffixKind("style", ".css", ".scss", ".sass", ".less"),
|
|
550
|
+
...commonKinds()
|
|
551
|
+
],
|
|
552
|
+
exclude: [".vite", "dist"],
|
|
553
|
+
recommendedBudget: "120k"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "react-vite",
|
|
557
|
+
priority: 84,
|
|
558
|
+
detect: (context) => hasDependency(context, "react") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
|
|
559
|
+
layers: VITE_LAYERS,
|
|
560
|
+
kinds: VITE_KINDS,
|
|
561
|
+
exclude: [".vite", "dist"],
|
|
562
|
+
recommendedBudget: "120k"
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: "nest",
|
|
566
|
+
priority: 80,
|
|
567
|
+
detect: (context) => hasDependency(context, "@nestjs/core"),
|
|
568
|
+
layers: [
|
|
569
|
+
layer("modules", "src/modules/", "modules/"),
|
|
570
|
+
layer("controllers", "src/controllers/", "controllers/"),
|
|
571
|
+
layer("providers", "src/providers/", "providers/"),
|
|
572
|
+
layer("core", "src/core/"),
|
|
573
|
+
layer("security", "src/security/", "src/auth/"),
|
|
574
|
+
layer("db", "src/db/", "src/database/", "prisma/"),
|
|
575
|
+
...commonLayers()
|
|
576
|
+
],
|
|
577
|
+
kinds: [
|
|
578
|
+
suffixKind("controller", ".controller.ts"),
|
|
579
|
+
suffixKind("service", ".service.ts"),
|
|
580
|
+
suffixKind("module", ".module.ts"),
|
|
581
|
+
suffixKind("guard", ".guard.ts"),
|
|
582
|
+
suffixKind("dto", ".dto.ts"),
|
|
583
|
+
suffixKind("entity", ".entity.ts"),
|
|
584
|
+
kindOf("migration", "migrations/"),
|
|
585
|
+
...commonKinds()
|
|
586
|
+
],
|
|
587
|
+
exclude: ["dist", "node_modules"],
|
|
588
|
+
recommendedBudget: "200k"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "node-express",
|
|
592
|
+
priority: 78,
|
|
593
|
+
detect: (context) => hasDependency(context, "express", "fastify", "koa", "@hapi/hapi"),
|
|
594
|
+
layers: NODE_LAYERS,
|
|
595
|
+
kinds: NODE_KINDS,
|
|
596
|
+
exclude: ["dist"],
|
|
597
|
+
recommendedBudget: "200k"
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: "node-lib",
|
|
601
|
+
// A bare package.json is often just tooling around a non-JS project, so it
|
|
602
|
+
// ranks below every definitive language manifest.
|
|
603
|
+
priority: 45,
|
|
604
|
+
detect: (context) => hasFile(context, "package.json"),
|
|
605
|
+
layers: [
|
|
606
|
+
layer("src", "src/"),
|
|
607
|
+
layer("lib", "lib/"),
|
|
608
|
+
layer("test", "test/", "tests/", "__tests__/"),
|
|
609
|
+
...commonLayers()
|
|
610
|
+
],
|
|
611
|
+
kinds: [suffixKind("type", ".d.ts"), ...commonKinds()],
|
|
612
|
+
exclude: ["dist"],
|
|
613
|
+
recommendedBudget: "120k"
|
|
614
|
+
}
|
|
240
615
|
];
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
{ kind: "provider", match: (p) => p.includes("provider") },
|
|
247
|
-
{ kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
|
|
248
|
-
{ kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
|
|
249
|
-
{ kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
|
|
250
|
-
{ kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
|
|
251
|
-
{ kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
|
|
252
|
-
{ kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
|
|
253
|
-
{ kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
|
|
254
|
-
{ kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
|
|
616
|
+
|
|
617
|
+
// src/presets/lang-presets.ts
|
|
618
|
+
var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
|
|
619
|
+
var DIR = (name) => `${name}/`;
|
|
620
|
+
var LANGUAGE_PRESETS = [
|
|
255
621
|
{
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
622
|
+
name: "python-django",
|
|
623
|
+
priority: 56,
|
|
624
|
+
detect: (context) => hasFile(context, "manage.py") || manifestContains(context, "pyproject.toml", "django") || manifestContains(context, "requirements.txt", "django"),
|
|
625
|
+
layers: [
|
|
626
|
+
layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
|
|
627
|
+
layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
|
|
628
|
+
layer("serializers", DIR("serializers")),
|
|
629
|
+
layer("urls", "urls.py"),
|
|
630
|
+
layer("migrations", DIR("migrations")),
|
|
631
|
+
layer("settings", "settings.py", "settings/"),
|
|
632
|
+
layer("templates", DIR("templates")),
|
|
633
|
+
...commonLayers()
|
|
634
|
+
],
|
|
635
|
+
kinds: [
|
|
636
|
+
suffixKind("model", "models.py"),
|
|
637
|
+
suffixKind("view", "views.py"),
|
|
638
|
+
suffixKind("serializer", "serializers.py"),
|
|
639
|
+
suffixKind("urlconf", "urls.py"),
|
|
640
|
+
kindOf("migration", "migrations/"),
|
|
641
|
+
suffixKind("settings", "settings.py"),
|
|
642
|
+
...commonKinds()
|
|
643
|
+
],
|
|
644
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs", "staticfiles", "media"],
|
|
645
|
+
recommendedBudget: "150k"
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: "python-fastapi",
|
|
649
|
+
priority: 54,
|
|
650
|
+
detect: (context) => manifestContains(context, "requirements.txt", "fastapi") || manifestContains(context, "requirements.txt", "flask") || manifestContains(context, "pyproject.toml", "fastapi") || manifestContains(context, "pyproject.toml", "flask"),
|
|
651
|
+
layers: [
|
|
652
|
+
layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
|
|
653
|
+
layer("models", DIR("models"), DIR("db")),
|
|
654
|
+
layer("schemas", DIR("schemas")),
|
|
655
|
+
layer("services", DIR("services")),
|
|
656
|
+
layer("core", DIR("core")),
|
|
657
|
+
layer("tests", DIR("tests")),
|
|
658
|
+
...commonLayers()
|
|
659
|
+
],
|
|
660
|
+
kinds: [
|
|
661
|
+
kindOf("router", "routers/", "routes/", "api/"),
|
|
662
|
+
kindOf("model", "models/"),
|
|
663
|
+
kindOf("schema", "schemas/"),
|
|
664
|
+
kindOf("service", "services/"),
|
|
665
|
+
...commonKinds()
|
|
666
|
+
],
|
|
667
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
|
|
668
|
+
recommendedBudget: "150k"
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
name: "python-lib",
|
|
672
|
+
// Below node-lib: a Node repo with a stray .py file is still a Node repo.
|
|
673
|
+
priority: 30,
|
|
674
|
+
detect: (context) => hasFile(context, "pyproject.toml") || hasFile(context, "setup.py") || hasFile(context, "requirements.txt") || anyFileEndingWith(context, ".py"),
|
|
675
|
+
layers: [
|
|
676
|
+
layer("package", DIR("src"), DIR("lib")),
|
|
677
|
+
layer("tests", DIR("tests"), DIR("test")),
|
|
678
|
+
...commonLayers()
|
|
679
|
+
],
|
|
680
|
+
kinds: [suffixKind("module", ".py"), ...commonKinds()],
|
|
681
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
|
|
682
|
+
recommendedBudget: "120k"
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
name: "go",
|
|
686
|
+
priority: 50,
|
|
687
|
+
detect: (context) => hasFile(context, "go.mod"),
|
|
688
|
+
layers: [
|
|
689
|
+
layer("cmd", "cmd/"),
|
|
690
|
+
layer("internal", "internal/"),
|
|
691
|
+
layer("pkg", "pkg/"),
|
|
692
|
+
layer("api", "api/", "proto/"),
|
|
693
|
+
layer("migrations", "migrations/"),
|
|
694
|
+
...commonLayers()
|
|
695
|
+
],
|
|
696
|
+
kinds: [
|
|
697
|
+
kindOf("main", "cmd/"),
|
|
698
|
+
kindOf("handler", "handler", "server", "http"),
|
|
699
|
+
kindOf("service", "service"),
|
|
700
|
+
kindOf("repository", "repository", "store", "repo"),
|
|
701
|
+
kindOf("model", "model", "entity"),
|
|
702
|
+
suffixKind("test", "_test.go"),
|
|
703
|
+
...commonKinds()
|
|
704
|
+
],
|
|
705
|
+
exclude: ["vendor", "testdata"],
|
|
706
|
+
recommendedBudget: "200k"
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
name: "rust",
|
|
710
|
+
priority: 48,
|
|
711
|
+
detect: (context) => hasFile(context, "Cargo.toml"),
|
|
712
|
+
layers: [
|
|
713
|
+
layer("src", "src/"),
|
|
714
|
+
layer("crates", "crates/"),
|
|
715
|
+
layer("bins", "src/bin/"),
|
|
716
|
+
layer("tests", "tests/"),
|
|
717
|
+
layer("benches", "benches/"),
|
|
718
|
+
...commonLayers()
|
|
719
|
+
],
|
|
720
|
+
kinds: [suffixKind("module", ".rs"), suffixKind("test", "_test.rs"), ...commonKinds()],
|
|
721
|
+
exclude: ["target"],
|
|
722
|
+
recommendedBudget: "200k"
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
name: "java-spring",
|
|
726
|
+
priority: 46,
|
|
727
|
+
detect: (context) => hasFile(context, "pom.xml") || hasFile(context, "build.gradle") || hasFile(context, "build.gradle.kts"),
|
|
728
|
+
layers: [
|
|
729
|
+
layer("api", "src/main/java/"),
|
|
730
|
+
layer("test", "src/test/java/"),
|
|
731
|
+
layer("resources", "src/main/resources/"),
|
|
732
|
+
layer("migrations", "src/main/resources/db/"),
|
|
733
|
+
...commonLayers()
|
|
734
|
+
],
|
|
735
|
+
kinds: [
|
|
736
|
+
kindOf("controller", "controller"),
|
|
737
|
+
kindOf("service", "service"),
|
|
738
|
+
kindOf("repository", "repository", "dao"),
|
|
739
|
+
kindOf("entity", "entity", "model"),
|
|
740
|
+
kindOf("dto", "dto", "request", "response"),
|
|
741
|
+
kindOf("migration", "migration", "db/migration"),
|
|
742
|
+
...commonKinds()
|
|
743
|
+
],
|
|
744
|
+
exclude: [".gradle", "out", "target"],
|
|
745
|
+
recommendedBudget: "200k"
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
name: "dotnet",
|
|
749
|
+
priority: 44,
|
|
750
|
+
detect: (context) => anyFileEndingWith(context, ".csproj") || anyFileEndingWith(context, ".sln") || hasFile(context, "global.json"),
|
|
751
|
+
layers: [
|
|
752
|
+
layer("web", "src/", "Web/"),
|
|
753
|
+
layer("domain", "Domain/", "Core/"),
|
|
754
|
+
layer("infrastructure", "Infrastructure/", "Data/"),
|
|
755
|
+
layer("tests", "Tests/", "test/"),
|
|
756
|
+
...commonLayers()
|
|
757
|
+
],
|
|
758
|
+
kinds: [
|
|
759
|
+
kindOf("controller", "Controller"),
|
|
760
|
+
kindOf("service", "Service"),
|
|
761
|
+
kindOf("repository", "Repository"),
|
|
762
|
+
kindOf("model", "Model", "Entity"),
|
|
763
|
+
kindOf("dto", "Dto", "Request", "Response"),
|
|
764
|
+
suffixKind("test", "Tests.cs"),
|
|
765
|
+
...commonKinds()
|
|
766
|
+
],
|
|
767
|
+
exclude: ["bin", "obj", ".vs"],
|
|
768
|
+
recommendedBudget: "200k"
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
name: "php-laravel",
|
|
772
|
+
priority: 42,
|
|
773
|
+
detect: (context) => hasFile(context, "artisan") || manifestContains(context, "composer.json", "laravel") || hasFile(context, "composer.json"),
|
|
774
|
+
layers: [
|
|
775
|
+
layer("app", "app/"),
|
|
776
|
+
layer("routes", "routes/"),
|
|
777
|
+
layer("http", "app/Http/"),
|
|
778
|
+
layer("models", "app/Models/"),
|
|
779
|
+
layer("providers", "app/Providers/"),
|
|
780
|
+
layer("database", "database/"),
|
|
781
|
+
layer("tests", "tests/"),
|
|
782
|
+
...commonLayers()
|
|
783
|
+
],
|
|
784
|
+
kinds: [
|
|
785
|
+
kindOf("controller", "Http/Controllers/"),
|
|
786
|
+
kindOf("model", "Models/"),
|
|
787
|
+
kindOf("migration", "migrations/"),
|
|
788
|
+
kindOf("middleware", "Middleware/"),
|
|
789
|
+
kindOf("request", "Requests/"),
|
|
790
|
+
kindOf("provider", "Providers/"),
|
|
791
|
+
...commonKinds()
|
|
792
|
+
],
|
|
793
|
+
exclude: ["vendor", "storage", "bootstrap/cache", "public/build"],
|
|
794
|
+
recommendedBudget: "150k"
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "ruby-rails",
|
|
798
|
+
priority: 40,
|
|
799
|
+
detect: (context) => hasFile(context, "Gemfile") || hasFile(context, "config.ru") || hasFile(context, "Rakefile"),
|
|
800
|
+
layers: [
|
|
801
|
+
layer("app", "app/"),
|
|
802
|
+
layer("controllers", "app/controllers/"),
|
|
803
|
+
layer("models", "app/models/"),
|
|
804
|
+
layer("config", "config/"),
|
|
805
|
+
layer("db", "db/"),
|
|
806
|
+
layer("spec", "spec/", "test/"),
|
|
807
|
+
...commonLayers()
|
|
808
|
+
],
|
|
809
|
+
kinds: [
|
|
810
|
+
kindOf("controller", "controllers/"),
|
|
811
|
+
kindOf("model", "models/"),
|
|
812
|
+
kindOf("migration", "migrate/"),
|
|
813
|
+
kindOf("job", "jobs/"),
|
|
814
|
+
kindOf("mailer", "mailers/"),
|
|
815
|
+
suffixKind("test", "_spec.rb", "_test.rb"),
|
|
816
|
+
...commonKinds()
|
|
817
|
+
],
|
|
818
|
+
exclude: [".bundle", "log", "tmp", "vendor/bundle", "public/assets"],
|
|
819
|
+
recommendedBudget: "150k"
|
|
261
820
|
}
|
|
262
821
|
];
|
|
263
822
|
|
|
@@ -303,57 +862,118 @@ var DEFAULT_KINDS = [
|
|
|
303
862
|
}
|
|
304
863
|
];
|
|
305
864
|
|
|
306
|
-
// src/presets/
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
865
|
+
// src/presets/shape-presets.ts
|
|
866
|
+
var SHAPE_PRESETS = [
|
|
867
|
+
{
|
|
868
|
+
name: "monorepo",
|
|
869
|
+
priority: 70,
|
|
870
|
+
detect: (context) => hasFile(context, "pnpm-workspace.yaml") || hasFile(context, "nx.json") || hasFile(context, "turbo.json") || hasFile(context, "lerna.json") || manifestContains(context, "package.json", '"workspaces"'),
|
|
871
|
+
layers: [
|
|
872
|
+
layer("apps", "apps/"),
|
|
873
|
+
layer("packages", "packages/"),
|
|
874
|
+
layer("tooling", "tooling/", "tools/", "scripts/"),
|
|
875
|
+
...commonLayers()
|
|
876
|
+
],
|
|
877
|
+
kinds: commonKinds(),
|
|
878
|
+
exclude: [".turbo", ".nx"],
|
|
879
|
+
recommendedBudget: "200k"
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
name: "infra",
|
|
883
|
+
priority: 24,
|
|
884
|
+
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"),
|
|
885
|
+
layers: [
|
|
886
|
+
layer("modules", "modules/"),
|
|
887
|
+
layer("environments", "env/", "environments/"),
|
|
888
|
+
layer("manifests", "k8s/", "helm/", "charts/", "deploy/"),
|
|
889
|
+
...commonLayers()
|
|
890
|
+
],
|
|
891
|
+
kinds: [
|
|
892
|
+
{ kind: "terraform", match: (filePath) => filePath.endsWith(".tf") },
|
|
893
|
+
{ kind: "kubernetes", match: (filePath) => /\.(ya?ml)$/i.test(filePath) && /k8s|helm|charts|deploy/.test(filePath) },
|
|
894
|
+
...commonKinds()
|
|
895
|
+
],
|
|
896
|
+
exclude: [".terraform", ".terragrunt-cache"],
|
|
897
|
+
recommendedBudget: "100k"
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
name: "docs",
|
|
901
|
+
priority: 22,
|
|
902
|
+
detect: (context) => {
|
|
903
|
+
const markdown = Array.from(context.files).filter((file) => /\.mdx?$/i.test(file)).length;
|
|
904
|
+
return markdown >= 3 && !hasFile(context, "package.json");
|
|
905
|
+
},
|
|
906
|
+
layers: [
|
|
907
|
+
layer("content", "content/", "docs/"),
|
|
908
|
+
layer("assets", "assets/", "static/", "public/"),
|
|
909
|
+
...commonLayers()
|
|
910
|
+
],
|
|
911
|
+
kinds: commonKinds(),
|
|
912
|
+
exclude: [".docusaurus", ".vitepress/dist", "site"],
|
|
913
|
+
recommendedBudget: "100k"
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
name: "generic",
|
|
917
|
+
priority: 0,
|
|
918
|
+
detect: () => true,
|
|
919
|
+
layers: DEFAULT_LAYERS,
|
|
920
|
+
kinds: DEFAULT_KINDS,
|
|
921
|
+
exclude: [],
|
|
922
|
+
recommendedBudget: "120k"
|
|
329
923
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
924
|
+
];
|
|
925
|
+
|
|
926
|
+
// src/presets/registry.ts
|
|
927
|
+
function compare(a, b) {
|
|
928
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
929
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
930
|
+
}
|
|
931
|
+
var PRESETS = [
|
|
932
|
+
...JS_PRESETS,
|
|
933
|
+
...LANGUAGE_PRESETS,
|
|
934
|
+
...SHAPE_PRESETS
|
|
935
|
+
].sort(compare);
|
|
936
|
+
var BY_NAME = new Map(PRESETS.map((preset) => [preset.name, preset]));
|
|
937
|
+
function listPresetNames() {
|
|
938
|
+
return PRESETS.map((preset) => preset.name);
|
|
939
|
+
}
|
|
940
|
+
function getPreset(name) {
|
|
941
|
+
return BY_NAME.get(name);
|
|
942
|
+
}
|
|
943
|
+
function unknownPresetMessage(name) {
|
|
944
|
+
return `Unknown preset "${name}". Expected one of: ${listPresetNames().join(", ")}.`;
|
|
945
|
+
}
|
|
946
|
+
function detectPresetFromContext(context, forced) {
|
|
947
|
+
if (forced !== void 0) {
|
|
948
|
+
const preset = BY_NAME.get(forced);
|
|
949
|
+
if (!preset) throw new Error(unknownPresetMessage(forced));
|
|
950
|
+
return toDetected(preset);
|
|
336
951
|
}
|
|
337
|
-
|
|
338
|
-
return
|
|
339
|
-
presetName: "node",
|
|
340
|
-
layers: NODE_LAYERS,
|
|
341
|
-
kinds: NODE_KINDS
|
|
342
|
-
};
|
|
952
|
+
for (const preset of PRESETS) {
|
|
953
|
+
if (preset.detect(context)) return toDetected(preset);
|
|
343
954
|
}
|
|
955
|
+
return toDetected(PRESETS[PRESETS.length - 1]);
|
|
956
|
+
}
|
|
957
|
+
function toDetected(preset) {
|
|
344
958
|
return {
|
|
345
|
-
presetName:
|
|
346
|
-
layers:
|
|
347
|
-
kinds:
|
|
959
|
+
presetName: preset.name,
|
|
960
|
+
layers: preset.layers,
|
|
961
|
+
kinds: preset.kinds,
|
|
962
|
+
exclude: preset.exclude ? [...preset.exclude] : [],
|
|
963
|
+
recommendedBudget: preset.recommendedBudget ?? null
|
|
348
964
|
};
|
|
349
965
|
}
|
|
966
|
+
function detectPreset(cwd, forced) {
|
|
967
|
+
return detectPresetFromContext(buildPresetContext(cwd), forced);
|
|
968
|
+
}
|
|
350
969
|
function getGitInfo(cwd, fallbackBranch = "main") {
|
|
351
970
|
try {
|
|
352
|
-
const
|
|
971
|
+
const reported = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
|
|
353
972
|
cwd,
|
|
354
973
|
stdio: ["ignore", "pipe", "ignore"],
|
|
355
974
|
encoding: "utf8"
|
|
356
|
-
}).trim()
|
|
975
|
+
}).trim();
|
|
976
|
+
const branch = !reported || reported === "HEAD" ? fallbackBranch : reported;
|
|
357
977
|
const commit = childProcess__namespace.execSync("git rev-parse HEAD", {
|
|
358
978
|
cwd,
|
|
359
979
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -481,10 +1101,10 @@ function matchesExclude(relativePath, pattern) {
|
|
|
481
1101
|
return posixPath === cleaned || posixPath.startsWith(`${cleaned}/`) || posixPath.includes(`/${cleaned}/`) || posixPath.endsWith(`/${cleaned}`);
|
|
482
1102
|
}
|
|
483
1103
|
function readGitignoreMatcher(dir) {
|
|
484
|
-
const gitignorePath =
|
|
1104
|
+
const gitignorePath = path10__namespace.join(dir, ".gitignore");
|
|
485
1105
|
let raw;
|
|
486
1106
|
try {
|
|
487
|
-
raw =
|
|
1107
|
+
raw = fs9__namespace.readFileSync(gitignorePath, "utf8");
|
|
488
1108
|
} catch {
|
|
489
1109
|
return () => false;
|
|
490
1110
|
}
|
|
@@ -587,7 +1207,7 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
587
1207
|
const results = { files: [], skipped: [] };
|
|
588
1208
|
let realDir;
|
|
589
1209
|
try {
|
|
590
|
-
realDir =
|
|
1210
|
+
realDir = fs9__namespace.realpathSync(dir);
|
|
591
1211
|
} catch {
|
|
592
1212
|
realDir = dir;
|
|
593
1213
|
}
|
|
@@ -596,21 +1216,21 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
596
1216
|
const ignoredByGitignore = dir === baseDir ? readGitignoreMatcher(baseDir) : () => false;
|
|
597
1217
|
let entries;
|
|
598
1218
|
try {
|
|
599
|
-
entries =
|
|
1219
|
+
entries = fs9__namespace.readdirSync(dir, { withFileTypes: true });
|
|
600
1220
|
} catch {
|
|
601
1221
|
results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
|
|
602
1222
|
return results;
|
|
603
1223
|
}
|
|
604
1224
|
for (const entry of entries) {
|
|
605
1225
|
if (DEFAULT_IGNORED_DIRS.includes(entry.name)) continue;
|
|
606
|
-
const fullPath =
|
|
607
|
-
const relPath =
|
|
1226
|
+
const fullPath = path10__namespace.join(dir, entry.name);
|
|
1227
|
+
const relPath = path10__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
608
1228
|
if (ignoredByGitignore(relPath)) continue;
|
|
609
1229
|
let isDirectory = entry.isDirectory();
|
|
610
1230
|
let isFile = entry.isFile();
|
|
611
1231
|
if (entry.isSymbolicLink()) {
|
|
612
1232
|
try {
|
|
613
|
-
const stats =
|
|
1233
|
+
const stats = fs9__namespace.statSync(fullPath);
|
|
614
1234
|
isDirectory = stats.isDirectory();
|
|
615
1235
|
isFile = stats.isFile();
|
|
616
1236
|
if (!isDirectory && !isFile) {
|
|
@@ -875,6 +1495,8 @@ function normalizeResolvedConfig(config) {
|
|
|
875
1495
|
config.includeMonolith === true
|
|
876
1496
|
),
|
|
877
1497
|
include: resolveInclude(config.include),
|
|
1498
|
+
preset: typeof config.preset === "string" ? config.preset : "generic",
|
|
1499
|
+
presetExclude: Array.isArray(config.presetExclude) ? config.presetExclude : [],
|
|
878
1500
|
layersMode: config.layersMode === "replace" ? "replace" : "merge",
|
|
879
1501
|
budgets: config.budgets && typeof config.budgets === "object" ? config.budgets : { default: config.maxPartBytes, byLayer: {} },
|
|
880
1502
|
hooks: resolveHooks(config.hooks),
|
|
@@ -922,18 +1544,22 @@ function parseJsonish(raw, file) {
|
|
|
922
1544
|
}
|
|
923
1545
|
async function loadConfigFileDetailed(cwd) {
|
|
924
1546
|
const warnings = [];
|
|
1547
|
+
const errors = [];
|
|
925
1548
|
for (const filename of CONFIG_FILENAMES) {
|
|
926
|
-
const fullPath =
|
|
927
|
-
if (!
|
|
1549
|
+
const fullPath = path10__namespace.join(cwd, filename);
|
|
1550
|
+
if (!fs9__namespace.existsSync(fullPath)) continue;
|
|
928
1551
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
929
1552
|
try {
|
|
930
1553
|
return {
|
|
931
|
-
config: parseJsonish(
|
|
1554
|
+
config: parseJsonish(fs9__namespace.readFileSync(fullPath, "utf8"), fullPath),
|
|
932
1555
|
file: fullPath,
|
|
933
|
-
warnings
|
|
1556
|
+
warnings,
|
|
1557
|
+
errors
|
|
934
1558
|
};
|
|
935
1559
|
} catch (err) {
|
|
936
|
-
|
|
1560
|
+
const message = err.message;
|
|
1561
|
+
warnings.push(message);
|
|
1562
|
+
errors.push(message);
|
|
937
1563
|
continue;
|
|
938
1564
|
}
|
|
939
1565
|
}
|
|
@@ -947,28 +1573,37 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
947
1573
|
const imported = await import(url.pathToFileURL(fullPath).href);
|
|
948
1574
|
const config = imported.default ?? imported;
|
|
949
1575
|
if (!config || typeof config !== "object") {
|
|
950
|
-
|
|
1576
|
+
const message = `${fullPath} does not export a configuration object.`;
|
|
1577
|
+
warnings.push(message);
|
|
1578
|
+
errors.push(message);
|
|
951
1579
|
continue;
|
|
952
1580
|
}
|
|
953
|
-
return { config, file: fullPath, warnings };
|
|
1581
|
+
return { config, file: fullPath, warnings, errors };
|
|
954
1582
|
} catch (err) {
|
|
955
|
-
|
|
1583
|
+
const message = `Could not load ${fullPath}: ${err.message}`;
|
|
1584
|
+
warnings.push(message);
|
|
1585
|
+
errors.push(message);
|
|
956
1586
|
continue;
|
|
957
1587
|
}
|
|
958
1588
|
}
|
|
959
|
-
const pkgJsonPath =
|
|
960
|
-
if (
|
|
1589
|
+
const pkgJsonPath = path10__namespace.join(cwd, "package.json");
|
|
1590
|
+
if (fs9__namespace.existsSync(pkgJsonPath)) {
|
|
961
1591
|
try {
|
|
962
|
-
const parsed = parseJsonish(
|
|
1592
|
+
const parsed = parseJsonish(fs9__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
|
|
963
1593
|
const snapshotKey = parsed.snapshot;
|
|
964
1594
|
if (snapshotKey && typeof snapshotKey === "object") {
|
|
965
|
-
return { config: snapshotKey, file: pkgJsonPath, warnings };
|
|
1595
|
+
return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
|
|
1596
|
+
}
|
|
1597
|
+
if (snapshotKey !== void 0) {
|
|
1598
|
+
warnings.push(`${pkgJsonPath}: the "snapshot" key must be an object; it was ignored.`);
|
|
966
1599
|
}
|
|
967
1600
|
} catch (err) {
|
|
968
|
-
|
|
1601
|
+
const message = err.message;
|
|
1602
|
+
warnings.push(message);
|
|
1603
|
+
errors.push(message);
|
|
969
1604
|
}
|
|
970
1605
|
}
|
|
971
|
-
return { config: null, file: null, warnings };
|
|
1606
|
+
return { config: null, file: null, warnings, errors };
|
|
972
1607
|
}
|
|
973
1608
|
var MIN_MAX_PART_BYTES = 4096;
|
|
974
1609
|
var ARTIFACT_NAMES = [
|
|
@@ -1063,7 +1698,9 @@ function boundedBytes(value, fallback, minimum, label) {
|
|
|
1063
1698
|
`${label} must be a byte count such as 120000, "120k" or "1.5mb" (received ${describeBytes(value)}).`
|
|
1064
1699
|
);
|
|
1065
1700
|
}
|
|
1066
|
-
if (parsed <= 0)
|
|
1701
|
+
if (parsed <= 0) {
|
|
1702
|
+
throw new Error(`${label} must be a positive byte count (received ${describeBytes(value)}).`);
|
|
1703
|
+
}
|
|
1067
1704
|
if (parsed < minimum) {
|
|
1068
1705
|
throw new Error(`${label} must be at least ${minimum} bytes (received ${describeBytes(value)}).`);
|
|
1069
1706
|
}
|
|
@@ -1103,12 +1740,12 @@ function resolveBudgets(budgets, fallback) {
|
|
|
1103
1740
|
if (typeof budgets.byLayer !== "object" || budgets.byLayer === null) {
|
|
1104
1741
|
throw new Error("`budgets.byLayer` must be an object of layer name to byte size.");
|
|
1105
1742
|
}
|
|
1106
|
-
for (const [
|
|
1107
|
-
byLayer[
|
|
1743
|
+
for (const [layer2, value] of Object.entries(budgets.byLayer)) {
|
|
1744
|
+
byLayer[layer2] = boundedBytes(
|
|
1108
1745
|
value,
|
|
1109
1746
|
fallback,
|
|
1110
1747
|
MIN_MAX_PART_BYTES,
|
|
1111
|
-
`budgets.byLayer["${
|
|
1748
|
+
`budgets.byLayer["${layer2}"]`
|
|
1112
1749
|
);
|
|
1113
1750
|
}
|
|
1114
1751
|
}
|
|
@@ -1129,18 +1766,22 @@ function resolveHooks(hooks) {
|
|
|
1129
1766
|
}
|
|
1130
1767
|
function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
|
|
1131
1768
|
const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
|
|
1132
|
-
const resolved =
|
|
1133
|
-
if (!
|
|
1769
|
+
const resolved = path10__namespace.resolve(candidate);
|
|
1770
|
+
if (!fs9__namespace.existsSync(resolved)) {
|
|
1134
1771
|
throw new Error(`Working directory does not exist: ${resolved}`);
|
|
1135
1772
|
}
|
|
1136
|
-
if (!
|
|
1773
|
+
if (!fs9__namespace.statSync(resolved).isDirectory()) {
|
|
1137
1774
|
throw new Error(`Working directory is not a directory: ${resolved}`);
|
|
1138
1775
|
}
|
|
1139
1776
|
return resolved;
|
|
1140
1777
|
}
|
|
1141
1778
|
function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
1142
1779
|
const cwd = resolveCandidateCwd(userConfig ?? null, cliOptions, baseDir);
|
|
1143
|
-
const
|
|
1780
|
+
const forcedPreset = userConfig?.preset;
|
|
1781
|
+
if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
|
|
1782
|
+
throw new Error(unknownPresetMessage(forcedPreset));
|
|
1783
|
+
}
|
|
1784
|
+
const preset = detectPreset(cwd, forcedPreset);
|
|
1144
1785
|
const layersMode = resolveLayersMode(userConfig?.layersMode);
|
|
1145
1786
|
const layers = userConfig?.layers && userConfig.layers.length > 0 ? layersMode === "replace" ? userConfig.layers : [...userConfig.layers, ...preset.layers] : preset.layers;
|
|
1146
1787
|
const kinds = userConfig?.kinds && userConfig.kinds.length > 0 ? layersMode === "replace" ? userConfig.kinds : [...userConfig.kinds, ...preset.kinds] : preset.kinds;
|
|
@@ -1176,6 +1817,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
1176
1817
|
),
|
|
1177
1818
|
includeSensitiveFiles: cliOptions?.includeSensitiveFiles ?? userConfig?.includeSensitiveFiles ?? false,
|
|
1178
1819
|
include: resolveInclude(userConfig?.include),
|
|
1820
|
+
preset: preset.presetName,
|
|
1821
|
+
presetExclude: [...preset.exclude],
|
|
1179
1822
|
artifacts,
|
|
1180
1823
|
layersMode,
|
|
1181
1824
|
budgets,
|
|
@@ -1210,8 +1853,8 @@ function matchRule(filePath, matcher) {
|
|
|
1210
1853
|
return false;
|
|
1211
1854
|
}
|
|
1212
1855
|
function classifyLayer(filePath, layers) {
|
|
1213
|
-
for (const
|
|
1214
|
-
if (matchRule(filePath,
|
|
1856
|
+
for (const layer2 of layers) {
|
|
1857
|
+
if (matchRule(filePath, layer2.match)) return layer2.name;
|
|
1215
1858
|
}
|
|
1216
1859
|
return "other";
|
|
1217
1860
|
}
|
|
@@ -1315,10 +1958,10 @@ var LANGUAGE_BY_BASENAME = {
|
|
|
1315
1958
|
};
|
|
1316
1959
|
function detectLanguage(filePath) {
|
|
1317
1960
|
const normalized = filePath.replace(/\\/g, "/");
|
|
1318
|
-
const base =
|
|
1961
|
+
const base = path10__namespace.posix.basename(normalized);
|
|
1319
1962
|
if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
|
|
1320
1963
|
if (base === ".env" || base.startsWith(".env.")) return "dotenv";
|
|
1321
|
-
const ext =
|
|
1964
|
+
const ext = path10__namespace.posix.extname(base).toLowerCase();
|
|
1322
1965
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
1323
1966
|
}
|
|
1324
1967
|
|
|
@@ -1333,35 +1976,35 @@ function describeMatcher(matcher) {
|
|
|
1333
1976
|
return String(matcher);
|
|
1334
1977
|
}
|
|
1335
1978
|
function normalizeTarget(cwd, target) {
|
|
1336
|
-
const absolute =
|
|
1337
|
-
const relative4 =
|
|
1338
|
-
if (relative4.startsWith("..") ||
|
|
1339
|
-
return relative4.split(
|
|
1979
|
+
const absolute = path10__namespace.resolve(cwd, target);
|
|
1980
|
+
const relative4 = path10__namespace.relative(cwd, absolute);
|
|
1981
|
+
if (relative4.startsWith("..") || path10__namespace.isAbsolute(relative4)) return target;
|
|
1982
|
+
return relative4.split(path10__namespace.sep).join("/");
|
|
1340
1983
|
}
|
|
1341
1984
|
function findRuleMatch(rules, relativePath) {
|
|
1342
1985
|
return rules.find(
|
|
1343
1986
|
(rule) => matchRule(relativePath, rule.match)
|
|
1344
1987
|
);
|
|
1345
1988
|
}
|
|
1346
|
-
function
|
|
1347
|
-
const manifestPath =
|
|
1989
|
+
function readManifest2(config) {
|
|
1990
|
+
const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
|
|
1348
1991
|
try {
|
|
1349
|
-
return JSON.parse(
|
|
1992
|
+
return JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
1350
1993
|
} catch {
|
|
1351
1994
|
return null;
|
|
1352
1995
|
}
|
|
1353
1996
|
}
|
|
1354
1997
|
function explainPath(config, target) {
|
|
1355
1998
|
const relative4 = normalizeTarget(config.cwd, target);
|
|
1356
|
-
const absolute =
|
|
1999
|
+
const absolute = path10__namespace.join(config.cwd, relative4);
|
|
1357
2000
|
const result = {
|
|
1358
2001
|
path: relative4,
|
|
1359
|
-
exists:
|
|
2002
|
+
exists: fs9__namespace.existsSync(absolute),
|
|
1360
2003
|
included: false
|
|
1361
2004
|
};
|
|
1362
2005
|
const collection = collectFiles(config.cwd, {
|
|
1363
2006
|
includePatterns: config.include,
|
|
1364
|
-
excludePatterns: config.excludePatterns,
|
|
2007
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
1365
2008
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
1366
2009
|
});
|
|
1367
2010
|
if (!collection.files.includes(relative4)) {
|
|
@@ -1386,16 +2029,16 @@ function explainPath(config, target) {
|
|
|
1386
2029
|
if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
|
|
1387
2030
|
const kindRule = findRuleMatch(config.kinds, relative4);
|
|
1388
2031
|
if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
|
|
1389
|
-
if (
|
|
1390
|
-
const buffer =
|
|
1391
|
-
const
|
|
2032
|
+
if (fs9__namespace.existsSync(absolute) && fs9__namespace.statSync(absolute).isFile()) {
|
|
2033
|
+
const buffer = fs9__namespace.readFileSync(absolute);
|
|
2034
|
+
const text2 = buffer.toString("utf8");
|
|
1392
2035
|
result.bytes = buffer.length;
|
|
1393
|
-
result.lines = countLines(
|
|
1394
|
-
const { findings } = scanAndRedactSecrets(
|
|
2036
|
+
result.lines = countLines(text2);
|
|
2037
|
+
const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative4);
|
|
1395
2038
|
result.secretCount = findings.length;
|
|
1396
2039
|
result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
|
|
1397
2040
|
}
|
|
1398
|
-
const manifest =
|
|
2041
|
+
const manifest = readManifest2(config);
|
|
1399
2042
|
const entry = manifest?.files.find((file) => file.relativePath === relative4);
|
|
1400
2043
|
result.part = entry?.part ?? null;
|
|
1401
2044
|
result.inclusionMode = entry?.inclusionMode;
|
|
@@ -1435,128 +2078,1133 @@ function renderExplainText(result) {
|
|
|
1435
2078
|
return lines.join("\n");
|
|
1436
2079
|
}
|
|
1437
2080
|
|
|
1438
|
-
// src/
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
}
|
|
1493
|
-
|
|
2081
|
+
// src/wizard/config-template.ts
|
|
2082
|
+
var DEFAULT_ANSWERS = {
|
|
2083
|
+
preset: "generic",
|
|
2084
|
+
maxPartBytes: "300k",
|
|
2085
|
+
largeFileBytes: "200k",
|
|
2086
|
+
artifacts: {
|
|
2087
|
+
readme: true,
|
|
2088
|
+
manifest: true,
|
|
2089
|
+
parts: true,
|
|
2090
|
+
navigation: true,
|
|
2091
|
+
projectTree: true,
|
|
2092
|
+
audit: true,
|
|
2093
|
+
monolith: false
|
|
2094
|
+
},
|
|
2095
|
+
includeSensitiveFiles: false,
|
|
2096
|
+
outDir: "snapshot"
|
|
2097
|
+
};
|
|
2098
|
+
function list(values) {
|
|
2099
|
+
if (values.length === 0) return "[]";
|
|
2100
|
+
return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
2101
|
+
}
|
|
2102
|
+
function size(value) {
|
|
2103
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2104
|
+
}
|
|
2105
|
+
var PRESET_NAMES = [
|
|
2106
|
+
"nextjs \xB7 react-vite \xB7 vue-vite \xB7 nuxt \xB7 sveltekit \xB7 angular \xB7 astro",
|
|
2107
|
+
"remix \xB7 solid \xB7 nest \xB7 node-express \xB7 node-lib",
|
|
2108
|
+
"python-django \xB7 python-fastapi \xB7 python-lib \xB7 go \xB7 rust",
|
|
2109
|
+
"java-spring \xB7 dotnet \xB7 php-laravel \xB7 ruby-rails",
|
|
2110
|
+
"monorepo \xB7 docs \xB7 infra \xB7 generic"
|
|
2111
|
+
];
|
|
2112
|
+
function renderConfig(answers) {
|
|
2113
|
+
const { artifacts } = answers;
|
|
2114
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
2115
|
+
|
|
2116
|
+
export default defineConfig({
|
|
2117
|
+
// \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
|
|
2118
|
+
// Which project shape to use. Detected from the repo when omitted.
|
|
2119
|
+
// ${PRESET_NAMES.join("\n // ")}
|
|
2120
|
+
preset: ${JSON.stringify(answers.preset)},
|
|
2121
|
+
|
|
2122
|
+
// Recorded in the manifest when git cannot report a branch.
|
|
2123
|
+
branch: "main",
|
|
2124
|
+
|
|
2125
|
+
// \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
|
|
2126
|
+
// Where the snapshot is written.
|
|
2127
|
+
outDir: ${JSON.stringify(answers.outDir)},
|
|
2128
|
+
// Scratch directory; the new snapshot is renamed into place atomically.
|
|
2129
|
+
tmpDir: "snapshot.tmp",
|
|
2130
|
+
|
|
2131
|
+
// \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
|
|
2132
|
+
// Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
|
|
2133
|
+
maxPartBytes: ${size(answers.maxPartBytes)},
|
|
2134
|
+
// Files above this size get a head/tail preview instead of the full body.
|
|
2135
|
+
largeFileBytes: ${size(answers.largeFileBytes)},
|
|
2136
|
+
previewHeadLines: 80,
|
|
2137
|
+
previewTailLines: 40,
|
|
2138
|
+
// Different ceilings per layer. Unlisted layers use \`default\`.
|
|
2139
|
+
budgets: {
|
|
2140
|
+
default: ${size(answers.maxPartBytes)},
|
|
2141
|
+
byLayer: {},
|
|
2142
|
+
},
|
|
2143
|
+
|
|
2144
|
+
// \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
|
|
2145
|
+
// Allow-list. Empty means "everything that is not ignored".
|
|
2146
|
+
// Example: ["src/**", "packages/*/src/**"]
|
|
2147
|
+
include: ${list(answers.include)},
|
|
2148
|
+
// Globs or path segments to drop. Example: ["**/*.test.ts", "docs"]
|
|
2149
|
+
excludePatterns: ${list(answers.excludePatterns)},
|
|
2150
|
+
|
|
2151
|
+
// \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
|
|
2152
|
+
// "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
|
|
2153
|
+
layersMode: "merge",
|
|
2154
|
+
// [{ name: "billing", match: "src/features/billing/**" }]
|
|
2155
|
+
layers: [],
|
|
2156
|
+
// [{ kind: "route", match: "src/app/api/**" }]
|
|
2157
|
+
kinds: [],
|
|
2158
|
+
|
|
2159
|
+
// \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
|
|
2160
|
+
// .env, *.pem, .npmrc, .ssh/ and friends are skipped unless this is true.
|
|
2161
|
+
includeSensitiveFiles: ${answers.includeSensitiveFiles},
|
|
2162
|
+
secrets: {
|
|
2163
|
+
// [{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ }]
|
|
2164
|
+
customPatterns: [],
|
|
2165
|
+
// Set true to drop the 21 built-in rules.
|
|
2166
|
+
disableDefaultPatterns: false,
|
|
2167
|
+
},
|
|
2168
|
+
|
|
2169
|
+
// \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
|
|
2170
|
+
artifacts: {
|
|
2171
|
+
readme: ${artifacts.readme}, // README.md
|
|
2172
|
+
manifest: ${artifacts.manifest}, // manifest.json \u2014 required by validate and report
|
|
2173
|
+
parts: ${artifacts.parts}, // parts/part-XXX.md
|
|
2174
|
+
navigation: ${artifacts.navigation}, // reports/navigation.md
|
|
2175
|
+
projectTree: ${artifacts.projectTree}, // reports/project-tree.md
|
|
2176
|
+
audit: ${artifacts.audit}, // reports/audit.md
|
|
2177
|
+
monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
|
|
2178
|
+
},
|
|
2179
|
+
// Skip the monolith above this size. null = no limit.
|
|
2180
|
+
maxMonolithBytes: null,
|
|
2181
|
+
|
|
2182
|
+
// \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
|
|
2183
|
+
// afterGenerate: (manifest) => { /* write your own artifact */ }
|
|
2184
|
+
hooks: {},
|
|
2185
|
+
});
|
|
2186
|
+
`;
|
|
2187
|
+
}
|
|
2188
|
+
function renderMinimalConfig(answers) {
|
|
2189
|
+
const lines = [];
|
|
2190
|
+
if (answers.preset !== DEFAULT_ANSWERS.preset) lines.push(` preset: ${JSON.stringify(answers.preset)},`);
|
|
2191
|
+
if (answers.maxPartBytes !== DEFAULT_ANSWERS.maxPartBytes) {
|
|
2192
|
+
lines.push(` maxPartBytes: ${size(answers.maxPartBytes)},`);
|
|
2193
|
+
}
|
|
2194
|
+
if (answers.largeFileBytes !== DEFAULT_ANSWERS.largeFileBytes) {
|
|
2195
|
+
lines.push(` largeFileBytes: ${size(answers.largeFileBytes)},`);
|
|
2196
|
+
}
|
|
2197
|
+
if (answers.include.length > 0) lines.push(` include: ${list(answers.include)},`);
|
|
2198
|
+
if (answers.excludePatterns.length > 0) {
|
|
2199
|
+
lines.push(` excludePatterns: ${list(answers.excludePatterns)},`);
|
|
2200
|
+
}
|
|
2201
|
+
if (answers.includeSensitiveFiles) lines.push(" includeSensitiveFiles: true,");
|
|
2202
|
+
if (answers.outDir !== DEFAULT_ANSWERS.outDir) lines.push(` outDir: ${JSON.stringify(answers.outDir)},`);
|
|
2203
|
+
const changed = Object.keys(answers.artifacts).filter(
|
|
2204
|
+
(name) => answers.artifacts[name] !== DEFAULT_ANSWERS.artifacts[name]
|
|
2205
|
+
);
|
|
2206
|
+
if (changed.length > 0) {
|
|
2207
|
+
const entries = changed.map((name) => ` ${name}: ${answers.artifacts[name]}`).join(",\n");
|
|
2208
|
+
lines.push(` artifacts: {
|
|
2209
|
+
${entries},
|
|
2210
|
+
},`);
|
|
2211
|
+
}
|
|
2212
|
+
if (lines.length === 0) {
|
|
2213
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
2214
|
+
|
|
2215
|
+
export default defineConfig({});
|
|
2216
|
+
`;
|
|
2217
|
+
}
|
|
2218
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
2219
|
+
|
|
2220
|
+
export default defineConfig({
|
|
2221
|
+
${lines.join("\n")}
|
|
2222
|
+
});
|
|
2223
|
+
`;
|
|
2224
|
+
}
|
|
2225
|
+
var PromptCancelled = class extends Error {
|
|
2226
|
+
constructor() {
|
|
2227
|
+
super("Prompt cancelled");
|
|
2228
|
+
this.name = "PromptCancelled";
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
function defaultIO() {
|
|
2232
|
+
return { input: process.stdin, output: process.stdout };
|
|
2233
|
+
}
|
|
2234
|
+
function isInteractive(io) {
|
|
2235
|
+
return Boolean(io.input.isTTY && io.output.isTTY);
|
|
2236
|
+
}
|
|
2237
|
+
function moveSelection(current, delta, count) {
|
|
2238
|
+
if (count <= 0) return 0;
|
|
2239
|
+
return ((current + delta) % count + count) % count;
|
|
2240
|
+
}
|
|
2241
|
+
function toggleIndex(selected, index) {
|
|
2242
|
+
const next = new Set(selected);
|
|
2243
|
+
if (next.has(index)) next.delete(index);
|
|
2244
|
+
else next.add(index);
|
|
2245
|
+
return next;
|
|
2246
|
+
}
|
|
2247
|
+
function marker(active) {
|
|
2248
|
+
return active ? "\u276F" : " ";
|
|
2249
|
+
}
|
|
2250
|
+
function choiceLine(choice, active) {
|
|
2251
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
2252
|
+
return ` ${marker(active)} ${choice.label}${hint}`;
|
|
2253
|
+
}
|
|
2254
|
+
function renderSelect(message, choices, index) {
|
|
2255
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
2256
|
+
choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
|
|
2257
|
+
lines.push("", " \u2191\u2193 move \xB7 enter select");
|
|
2258
|
+
return lines.join("\n");
|
|
2259
|
+
}
|
|
2260
|
+
function renderMultiSelect(message, choices, index, selected) {
|
|
2261
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
2262
|
+
choices.forEach((choice, position) => {
|
|
2263
|
+
const box = selected.has(position) ? "\u25FC" : "\u25FB";
|
|
2264
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
2265
|
+
lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
|
|
2266
|
+
});
|
|
2267
|
+
lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
|
|
2268
|
+
return lines.join("\n");
|
|
2269
|
+
}
|
|
2270
|
+
function renderConfirm(message, value) {
|
|
2271
|
+
return [
|
|
2272
|
+
`\u25C6 ${message}`,
|
|
2273
|
+
"",
|
|
2274
|
+
` ${marker(value)} Yes`,
|
|
2275
|
+
` ${marker(!value)} No`,
|
|
2276
|
+
"",
|
|
2277
|
+
" \u2190\u2192 or y/n \xB7 enter confirm"
|
|
2278
|
+
].join("\n");
|
|
2279
|
+
}
|
|
2280
|
+
function renderText(message, value, error) {
|
|
2281
|
+
const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
|
|
2282
|
+
if (error) lines.push("", ` \u2716 ${error}`);
|
|
2283
|
+
return lines.join("\n");
|
|
2284
|
+
}
|
|
2285
|
+
function keyReader(input) {
|
|
2286
|
+
const queue = [];
|
|
2287
|
+
const waiters = [];
|
|
2288
|
+
const wasRaw = input.isRaw;
|
|
2289
|
+
const onKey = (_text, key) => {
|
|
2290
|
+
const waiter = waiters.shift();
|
|
2291
|
+
if (waiter) waiter(key);
|
|
2292
|
+
else queue.push(key);
|
|
2293
|
+
};
|
|
2294
|
+
readline__namespace.emitKeypressEvents(input);
|
|
2295
|
+
if (input.isTTY) input.setRawMode(true);
|
|
2296
|
+
input.resume();
|
|
2297
|
+
input.on("keypress", onKey);
|
|
2298
|
+
return {
|
|
2299
|
+
next() {
|
|
2300
|
+
const queued = queue.shift();
|
|
2301
|
+
if (queued) return Promise.resolve(queued);
|
|
2302
|
+
return new Promise((resolve6) => waiters.push(resolve6));
|
|
2303
|
+
},
|
|
2304
|
+
close() {
|
|
2305
|
+
input.off("keypress", onKey);
|
|
2306
|
+
if (input.isTTY) input.setRawMode(wasRaw ?? false);
|
|
2307
|
+
input.pause();
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
var Frame = class {
|
|
2312
|
+
constructor(output) {
|
|
2313
|
+
this.output = output;
|
|
2314
|
+
}
|
|
2315
|
+
output;
|
|
2316
|
+
drawn = 0;
|
|
2317
|
+
draw(text2) {
|
|
2318
|
+
this.erase();
|
|
2319
|
+
this.output.write(`${text2}
|
|
2320
|
+
`);
|
|
2321
|
+
this.drawn = text2.split("\n").length;
|
|
2322
|
+
}
|
|
2323
|
+
erase() {
|
|
2324
|
+
if (this.drawn > 0) this.output.write(`\x1B[${this.drawn}A\x1B[0J`);
|
|
2325
|
+
this.drawn = 0;
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2328
|
+
function isCancel(key) {
|
|
2329
|
+
return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
|
|
2330
|
+
}
|
|
2331
|
+
async function select(io, message, choices, options = {}) {
|
|
2332
|
+
if (choices.length === 0) throw new Error("select() requires at least one choice.");
|
|
2333
|
+
const fallbackIndex = Math.max(
|
|
2334
|
+
0,
|
|
2335
|
+
choices.findIndex((choice) => choice.value === options.defaultValue)
|
|
2336
|
+
);
|
|
2337
|
+
if (!isInteractive(io)) {
|
|
2338
|
+
io.output.write(`${renderSelect(message, choices, fallbackIndex)}
|
|
2339
|
+
`);
|
|
2340
|
+
return choices[fallbackIndex].value;
|
|
2341
|
+
}
|
|
2342
|
+
const frame = new Frame(io.output);
|
|
2343
|
+
const reader = keyReader(io.input);
|
|
2344
|
+
let index = fallbackIndex;
|
|
1494
2345
|
try {
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
};
|
|
2346
|
+
for (; ; ) {
|
|
2347
|
+
frame.draw(renderSelect(message, choices, index));
|
|
2348
|
+
const key = await reader.next();
|
|
2349
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2350
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
2351
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
2352
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
2353
|
+
frame.erase();
|
|
2354
|
+
return choices[index].value;
|
|
2355
|
+
} else if (key.name === "j") index = moveSelection(index, 1, choices.length);
|
|
2356
|
+
else if (key.name === "k") index = moveSelection(index, -1, choices.length);
|
|
1505
2357
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
2358
|
+
} finally {
|
|
2359
|
+
reader.close();
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
async function multiSelect(io, message, choices, options = {}) {
|
|
2363
|
+
const selected = /* @__PURE__ */ new Set();
|
|
2364
|
+
choices.forEach((choice, position) => {
|
|
2365
|
+
if (options.defaults?.includes(choice.value)) selected.add(position);
|
|
2366
|
+
});
|
|
2367
|
+
const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
|
|
2368
|
+
if (!isInteractive(io)) {
|
|
2369
|
+
io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
|
|
2370
|
+
`);
|
|
2371
|
+
return result();
|
|
2372
|
+
}
|
|
2373
|
+
const frame = new Frame(io.output);
|
|
2374
|
+
const reader = keyReader(io.input);
|
|
2375
|
+
let index = 0;
|
|
2376
|
+
try {
|
|
2377
|
+
for (; ; ) {
|
|
2378
|
+
frame.draw(renderMultiSelect(message, choices, index, selected));
|
|
2379
|
+
const key = await reader.next();
|
|
2380
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2381
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
2382
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
2383
|
+
else if (key.name === "space") {
|
|
2384
|
+
const next = toggleIndex(selected, index);
|
|
2385
|
+
selected.clear();
|
|
2386
|
+
for (const position of next) selected.add(position);
|
|
2387
|
+
} else if (key.name === "a") {
|
|
2388
|
+
if (selected.size === choices.length) selected.clear();
|
|
2389
|
+
else choices.forEach((_, position) => selected.add(position));
|
|
2390
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
2391
|
+
frame.erase();
|
|
2392
|
+
return result();
|
|
2393
|
+
}
|
|
1514
2394
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
2395
|
+
} finally {
|
|
2396
|
+
reader.close();
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
async function confirm(io, message, options = {}) {
|
|
2400
|
+
let value = options.defaultValue ?? true;
|
|
2401
|
+
if (!isInteractive(io)) {
|
|
2402
|
+
io.output.write(`${renderConfirm(message, value)}
|
|
2403
|
+
`);
|
|
2404
|
+
return value;
|
|
2405
|
+
}
|
|
2406
|
+
const frame = new Frame(io.output);
|
|
2407
|
+
const reader = keyReader(io.input);
|
|
2408
|
+
try {
|
|
2409
|
+
for (; ; ) {
|
|
2410
|
+
frame.draw(renderConfirm(message, value));
|
|
2411
|
+
const key = await reader.next();
|
|
2412
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2413
|
+
if (key.name === "up" || key.name === "left") value = true;
|
|
2414
|
+
else if (key.name === "down" || key.name === "right") value = false;
|
|
2415
|
+
else if (key.name === "y") value = true;
|
|
2416
|
+
else if (key.name === "n") value = false;
|
|
2417
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
2418
|
+
frame.erase();
|
|
2419
|
+
return value;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
} finally {
|
|
2423
|
+
reader.close();
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
async function text(io, message, options = {}) {
|
|
2427
|
+
const fallback = options.defaultValue ?? "";
|
|
2428
|
+
if (!isInteractive(io)) {
|
|
2429
|
+
io.output.write(`${renderText(message, fallback)}
|
|
2430
|
+
`);
|
|
2431
|
+
return fallback;
|
|
2432
|
+
}
|
|
2433
|
+
const frame = new Frame(io.output);
|
|
2434
|
+
const reader = keyReader(io.input);
|
|
2435
|
+
let value = fallback;
|
|
2436
|
+
let error;
|
|
2437
|
+
try {
|
|
2438
|
+
for (; ; ) {
|
|
2439
|
+
frame.draw(renderText(message, value, error));
|
|
2440
|
+
const key = await reader.next();
|
|
2441
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2442
|
+
error = void 0;
|
|
2443
|
+
if (key.name === "return" || key.name === "enter") {
|
|
2444
|
+
const problem = options.validate ? options.validate(value) : null;
|
|
2445
|
+
if (problem) {
|
|
2446
|
+
error = problem;
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
frame.erase();
|
|
2450
|
+
return value;
|
|
2451
|
+
}
|
|
2452
|
+
if (key.name === "backspace") {
|
|
2453
|
+
value = value.slice(0, -1);
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
if (key.name === "escape") {
|
|
2457
|
+
value = "";
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
if (_isPrintable(key)) value += _keyText(key);
|
|
2461
|
+
}
|
|
2462
|
+
} finally {
|
|
2463
|
+
reader.close();
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
function _isPrintable(key) {
|
|
2467
|
+
return typeof key.sequence === "string" && key.sequence.length === 1 && key.sequence >= " ";
|
|
2468
|
+
}
|
|
2469
|
+
function _keyText(key) {
|
|
2470
|
+
return key.sequence ?? "";
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// src/wizard/init.ts
|
|
2474
|
+
var CONFIG_CANDIDATES = [
|
|
2475
|
+
"snapshot.config.ts",
|
|
2476
|
+
"snapshot.config.js",
|
|
2477
|
+
"snapshot.config.mjs",
|
|
2478
|
+
"snapshot.config.cjs",
|
|
2479
|
+
"snapshot.config.json",
|
|
2480
|
+
".snapshotrc.json",
|
|
2481
|
+
".snapshotrc"
|
|
2482
|
+
];
|
|
2483
|
+
var SIZE_CHOICES = [
|
|
2484
|
+
{ value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
|
|
2485
|
+
{ value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
|
|
2486
|
+
{ value: "300k", label: "Large", hint: "300 KB \xB7 ~75k tokens \xB7 the default" },
|
|
2487
|
+
{ value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
|
|
2488
|
+
];
|
|
2489
|
+
function existingConfig(cwd) {
|
|
2490
|
+
for (const name of CONFIG_CANDIDATES) {
|
|
2491
|
+
if (fs9__namespace.existsSync(path10__namespace.join(cwd, name))) return name;
|
|
2492
|
+
}
|
|
2493
|
+
return null;
|
|
2494
|
+
}
|
|
2495
|
+
function artifactAnswers(options) {
|
|
2496
|
+
const artifacts = { ...DEFAULT_ANSWERS.artifacts };
|
|
2497
|
+
for (const name of options.skipArtifacts ?? []) {
|
|
2498
|
+
if (ARTIFACT_NAMES.includes(name)) {
|
|
2499
|
+
artifacts[name] = false;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
if (options.onlyArtifacts && options.onlyArtifacts.length > 0) {
|
|
2503
|
+
const keep = new Set(options.onlyArtifacts);
|
|
2504
|
+
for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
|
|
2505
|
+
}
|
|
2506
|
+
return artifacts;
|
|
2507
|
+
}
|
|
2508
|
+
function topLevelDirs(cwd) {
|
|
2509
|
+
try {
|
|
2510
|
+
return fs9__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
|
|
1521
2511
|
} catch {
|
|
2512
|
+
return [];
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
function addScripts(cwd, notes) {
|
|
2516
|
+
const packagePath = path10__namespace.join(cwd, "package.json");
|
|
2517
|
+
if (!fs9__namespace.existsSync(packagePath)) {
|
|
2518
|
+
notes.push("No package.json found, so npm scripts were not added.");
|
|
2519
|
+
return [];
|
|
2520
|
+
}
|
|
2521
|
+
let parsed;
|
|
2522
|
+
try {
|
|
2523
|
+
parsed = JSON.parse(fs9__namespace.readFileSync(packagePath, "utf8"));
|
|
2524
|
+
} catch {
|
|
2525
|
+
notes.push("Could not parse package.json; npm scripts were not added.");
|
|
2526
|
+
return [];
|
|
2527
|
+
}
|
|
2528
|
+
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
2529
|
+
const added = [];
|
|
2530
|
+
if (!scripts.snapshot) {
|
|
2531
|
+
scripts.snapshot = "codebase-snapshot generate";
|
|
2532
|
+
added.push("snapshot");
|
|
2533
|
+
}
|
|
2534
|
+
if (!scripts["snapshot:validate"]) {
|
|
2535
|
+
scripts["snapshot:validate"] = "codebase-snapshot validate";
|
|
2536
|
+
added.push("snapshot:validate");
|
|
2537
|
+
}
|
|
2538
|
+
if (added.length === 0) return [];
|
|
2539
|
+
parsed.scripts = scripts;
|
|
2540
|
+
fs9__namespace.writeFileSync(packagePath, `${JSON.stringify(parsed, null, 2)}
|
|
2541
|
+
`, "utf8");
|
|
2542
|
+
return added;
|
|
2543
|
+
}
|
|
2544
|
+
function addGitignore(cwd) {
|
|
2545
|
+
const ignorePath = path10__namespace.join(cwd, ".gitignore");
|
|
2546
|
+
const existing = fs9__namespace.existsSync(ignorePath) ? fs9__namespace.readFileSync(ignorePath, "utf8") : "";
|
|
2547
|
+
const wanted = ["snapshot/", "snapshot.tmp/"];
|
|
2548
|
+
const added = wanted.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
2549
|
+
if (added.length === 0) return [];
|
|
2550
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
2551
|
+
fs9__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
|
|
2552
|
+
`, "utf8");
|
|
2553
|
+
return added;
|
|
2554
|
+
}
|
|
2555
|
+
function resolvePresetChoice(answers, presetName) {
|
|
2556
|
+
return {
|
|
2557
|
+
value: presetName,
|
|
2558
|
+
label: presetName,
|
|
2559
|
+
hint: presetName === answers.preset ? "detected" : void 0
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
async function runInit(options) {
|
|
2563
|
+
const io = options.io ?? defaultIO();
|
|
2564
|
+
const detected = detectPreset(options.cwd, options.preset);
|
|
2565
|
+
const notes = [];
|
|
2566
|
+
const answers = {
|
|
2567
|
+
preset: detected.presetName,
|
|
2568
|
+
maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
|
|
2569
|
+
largeFileBytes: DEFAULT_ANSWERS.largeFileBytes,
|
|
2570
|
+
include: options.include ?? [],
|
|
2571
|
+
excludePatterns: options.exclude ?? [],
|
|
2572
|
+
artifacts: artifactAnswers(options),
|
|
2573
|
+
includeSensitiveFiles: options.includeSensitiveFiles ?? DEFAULT_ANSWERS.includeSensitiveFiles,
|
|
2574
|
+
outDir: options.outDir ?? DEFAULT_ANSWERS.outDir
|
|
2575
|
+
};
|
|
2576
|
+
const interactive = isInteractive(io) && options.yes !== true;
|
|
2577
|
+
if (interactive) {
|
|
2578
|
+
io.output.write("\n @ankurhalder/codebase-snapshot\n");
|
|
2579
|
+
io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
|
|
2580
|
+
io.output.write(` Detected ${detected.presetName}
|
|
2581
|
+
`);
|
|
2582
|
+
io.output.write(` Directory ${options.cwd}
|
|
2583
|
+
|
|
2584
|
+
`);
|
|
2585
|
+
const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
|
|
2586
|
+
defaultValue: true
|
|
2587
|
+
});
|
|
2588
|
+
if (!useDetected) {
|
|
2589
|
+
answers.preset = await select(
|
|
2590
|
+
io,
|
|
2591
|
+
"What kind of project is this?",
|
|
2592
|
+
listPresetNames().map((name) => resolvePresetChoice(answers, name))
|
|
2593
|
+
);
|
|
2594
|
+
const chosen = detectPreset(options.cwd, answers.preset);
|
|
2595
|
+
answers.preset = chosen.presetName;
|
|
2596
|
+
}
|
|
2597
|
+
const scope = await select(
|
|
2598
|
+
io,
|
|
2599
|
+
"What should go into the snapshot?",
|
|
2600
|
+
[
|
|
2601
|
+
{ value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
|
|
2602
|
+
{ value: "folders", label: "Only specific folders\u2026" },
|
|
2603
|
+
{ value: "no-tests", label: "Everything, but skip tests" },
|
|
2604
|
+
{ value: "custom", label: "Let me write glob patterns" }
|
|
2605
|
+
],
|
|
2606
|
+
{ defaultValue: "all" }
|
|
2607
|
+
);
|
|
2608
|
+
if (scope === "folders") {
|
|
2609
|
+
const dirs = topLevelDirs(options.cwd).slice(0, 40);
|
|
2610
|
+
if (dirs.length === 0) {
|
|
2611
|
+
notes.push("No top-level directories found; the snapshot will include everything.");
|
|
2612
|
+
} else {
|
|
2613
|
+
const chosen = await multiSelect(
|
|
2614
|
+
io,
|
|
2615
|
+
"Select folders",
|
|
2616
|
+
dirs.map((name) => ({ value: name, label: `${name}/` })),
|
|
2617
|
+
{ defaults: dirs }
|
|
2618
|
+
);
|
|
2619
|
+
answers.include = chosen.map((name) => `${name}/**`);
|
|
2620
|
+
}
|
|
2621
|
+
} else if (scope === "no-tests") {
|
|
2622
|
+
answers.excludePatterns = [
|
|
2623
|
+
...answers.excludePatterns,
|
|
2624
|
+
"**/*.test.*",
|
|
2625
|
+
"**/*.spec.*",
|
|
2626
|
+
"tests",
|
|
2627
|
+
"test"
|
|
2628
|
+
];
|
|
2629
|
+
} else if (scope === "custom") {
|
|
2630
|
+
const raw = await text(io, "Glob patterns to include (comma separated)", {
|
|
2631
|
+
defaultValue: ""
|
|
2632
|
+
});
|
|
2633
|
+
answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
2634
|
+
}
|
|
2635
|
+
const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
|
|
2636
|
+
defaultValue: "120k"
|
|
2637
|
+
});
|
|
2638
|
+
if (sizeChoice === "custom") {
|
|
2639
|
+
const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
|
|
2640
|
+
defaultValue: String(answers.maxPartBytes),
|
|
2641
|
+
validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
|
|
2642
|
+
});
|
|
2643
|
+
answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
|
|
2644
|
+
} else {
|
|
2645
|
+
answers.maxPartBytes = sizeChoice;
|
|
2646
|
+
}
|
|
2647
|
+
const artifactChoices = [
|
|
2648
|
+
{ value: "readme", label: "README.md", hint: "orientation page" },
|
|
2649
|
+
{ value: "manifest", label: "manifest.json", hint: "required by validate and report" },
|
|
2650
|
+
{ value: "parts", label: "parts/", hint: "the code payload" },
|
|
2651
|
+
{ value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
|
|
2652
|
+
{ value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
|
|
2653
|
+
{ value: "audit", label: "reports/audit.md", hint: "security and health findings" },
|
|
2654
|
+
{ value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
|
|
2655
|
+
];
|
|
2656
|
+
const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
|
|
2657
|
+
const selectedArtifacts = await multiSelect(
|
|
2658
|
+
io,
|
|
2659
|
+
"Which artifacts should be generated?",
|
|
2660
|
+
artifactChoices,
|
|
2661
|
+
{ defaults: presetEnabled }
|
|
2662
|
+
);
|
|
2663
|
+
for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
|
|
2664
|
+
const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
|
|
2665
|
+
answers.includeSensitiveFiles = await confirm(
|
|
2666
|
+
io,
|
|
2667
|
+
"Include credential files (.env, *.pem, .npmrc, .ssh/)?",
|
|
2668
|
+
{ defaultValue: false }
|
|
2669
|
+
);
|
|
2670
|
+
io.output.write("\n Review\n\n");
|
|
2671
|
+
io.output.write(` project ${answers.preset}
|
|
2672
|
+
`);
|
|
2673
|
+
io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
|
|
2674
|
+
`);
|
|
2675
|
+
io.output.write(` part size ${String(answers.maxPartBytes)}
|
|
2676
|
+
`);
|
|
2677
|
+
io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
|
|
2678
|
+
`);
|
|
2679
|
+
io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
|
|
2680
|
+
|
|
2681
|
+
`);
|
|
2682
|
+
io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
|
|
2683
|
+
io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
|
|
2684
|
+
const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
|
|
2685
|
+
if (!proceed) throw new PromptCancelled();
|
|
2686
|
+
}
|
|
2687
|
+
const configText = options.minimal ? renderMinimalConfig(answers) : renderConfig(answers);
|
|
2688
|
+
const configName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
2689
|
+
const configPath = path10__namespace.join(options.cwd, configName);
|
|
2690
|
+
if (options.dryRun) {
|
|
1522
2691
|
return {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
2692
|
+
configPath,
|
|
2693
|
+
configText,
|
|
2694
|
+
written: false,
|
|
2695
|
+
preset: answers.preset,
|
|
2696
|
+
scripts: [],
|
|
2697
|
+
gitignoreEntries: [],
|
|
2698
|
+
notes
|
|
1527
2699
|
};
|
|
1528
2700
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2701
|
+
const present = existingConfig(options.cwd);
|
|
2702
|
+
if (present && present !== configName && !options.force) {
|
|
2703
|
+
throw new Error(
|
|
2704
|
+
`A configuration already exists (${present}). Re-run with --force to replace it.`
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
if (fs9__namespace.existsSync(configPath) && !options.force) {
|
|
2708
|
+
throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
|
|
2709
|
+
}
|
|
2710
|
+
fs9__namespace.writeFileSync(configPath, configText, "utf8");
|
|
2711
|
+
const scripts = addScripts(options.cwd, notes);
|
|
2712
|
+
const gitignoreEntries = addGitignore(options.cwd);
|
|
2713
|
+
return {
|
|
2714
|
+
configPath,
|
|
2715
|
+
configText,
|
|
2716
|
+
written: true,
|
|
2717
|
+
preset: answers.preset,
|
|
2718
|
+
scripts,
|
|
2719
|
+
gitignoreEntries,
|
|
2720
|
+
notes
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// src/ast/profiles/shared.ts
|
|
2725
|
+
var MAX_SUMMARY_CHARS = 1e6;
|
|
2726
|
+
function emptySummary() {
|
|
2727
|
+
return {
|
|
2728
|
+
directives: [],
|
|
2729
|
+
imports: [],
|
|
2730
|
+
exports: [],
|
|
2731
|
+
reExports: [],
|
|
2732
|
+
functions: [],
|
|
2733
|
+
classes: [],
|
|
2734
|
+
interfaces: [],
|
|
2735
|
+
types: [],
|
|
2736
|
+
enums: [],
|
|
2737
|
+
variables: [],
|
|
2738
|
+
reactComponents: [],
|
|
2739
|
+
hooks: [],
|
|
2740
|
+
routeHandlers: []
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
function dedupeSorted(values) {
|
|
2744
|
+
return Array.from(new Set(values.filter(Boolean))).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2745
|
+
}
|
|
2746
|
+
function finalizeSummary(summary) {
|
|
2747
|
+
return {
|
|
2748
|
+
directives: dedupeSorted(summary.directives),
|
|
2749
|
+
imports: dedupeSorted(summary.imports),
|
|
2750
|
+
exports: dedupeSorted(summary.exports),
|
|
2751
|
+
reExports: dedupeSorted(summary.reExports),
|
|
2752
|
+
functions: dedupeSorted(summary.functions),
|
|
2753
|
+
classes: dedupeSorted(summary.classes),
|
|
2754
|
+
interfaces: dedupeSorted(summary.interfaces),
|
|
2755
|
+
types: dedupeSorted(summary.types),
|
|
2756
|
+
enums: dedupeSorted(summary.enums),
|
|
2757
|
+
variables: dedupeSorted(summary.variables),
|
|
2758
|
+
reactComponents: dedupeSorted(summary.reactComponents),
|
|
2759
|
+
hooks: dedupeSorted(summary.hooks),
|
|
2760
|
+
routeHandlers: dedupeSorted(summary.routeHandlers)
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
function eachLine(content, visit) {
|
|
2764
|
+
const text2 = content.length > MAX_SUMMARY_CHARS ? content.slice(0, MAX_SUMMARY_CHARS) : content;
|
|
2765
|
+
for (const line of text2.split(/\r?\n/)) visit(line);
|
|
2766
|
+
}
|
|
2767
|
+
function isTopLevel(line) {
|
|
2768
|
+
return !/^[\s]/.test(line);
|
|
2769
|
+
}
|
|
2770
|
+
function isPublicName(name) {
|
|
2771
|
+
return name.length > 0 && !name.startsWith("_") && /^[A-Za-z]/.test(name);
|
|
2772
|
+
}
|
|
2773
|
+
function isCapitalizedName(name) {
|
|
2774
|
+
return /^[A-Z]/.test(name);
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// src/ast/profiles/cfamily.ts
|
|
2778
|
+
var INCLUDE = /^#\s*include\s*[<"]([^>"]+)[>"]/;
|
|
2779
|
+
var DEFINE = /^#\s*define\s+([A-Za-z_]\w*)/;
|
|
2780
|
+
var USING = /^using\s+(?:namespace\s+)?([\w:]+)\s*;/;
|
|
2781
|
+
var TYPE_DECL = /^(?:typedef\s+)?(?:struct|union|class|enum)\s+([A-Za-z_]\w*)/;
|
|
2782
|
+
var TYPEDEF = /^typedef\b[^;{}]*?\b([A-Za-z_]\w*)\s*;/;
|
|
2783
|
+
var FUNCTION = /^[A-Za-z_][\w \t*&:<>,]*?\b([A-Za-z_]\w*)\s*\([^;]*\)\s*\{?\s*$/;
|
|
2784
|
+
var NOT_A_FUNCTION = /^(?:if|for|while|switch|return|else|do|case|sizeof|catch|throw)\b/;
|
|
2785
|
+
var cfamilyProfile = {
|
|
2786
|
+
id: "cfamily",
|
|
2787
|
+
extensions: [".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hxx", ".m", ".mm"],
|
|
2788
|
+
summarize(_path, content) {
|
|
2789
|
+
const summary = emptySummary();
|
|
2790
|
+
eachLine(content, (line) => {
|
|
2791
|
+
const trimmed = line.trim();
|
|
2792
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
const included = INCLUDE.exec(trimmed);
|
|
2796
|
+
if (included) {
|
|
2797
|
+
summary.imports.push(included[1]);
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
const used = USING.exec(trimmed);
|
|
2801
|
+
if (used) {
|
|
2802
|
+
summary.imports.push(used[1]);
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
const macro = DEFINE.exec(trimmed);
|
|
2806
|
+
if (macro) {
|
|
2807
|
+
summary.variables.push(macro[1]);
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
if (!isTopLevel(line)) return;
|
|
2811
|
+
const declared = TYPE_DECL.exec(line);
|
|
2812
|
+
if (declared) {
|
|
2813
|
+
const kind = /^typedef\s+enum/.test(line) || /^enum\b/.test(trimmed) ? "enum" : "type";
|
|
2814
|
+
if (kind === "enum") summary.enums.push(declared[1]);
|
|
2815
|
+
else summary.classes.push(declared[1]);
|
|
2816
|
+
summary.exports.push(declared[1]);
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
const aliased = TYPEDEF.exec(line);
|
|
2820
|
+
if (aliased) {
|
|
2821
|
+
summary.types.push(aliased[1]);
|
|
2822
|
+
summary.exports.push(aliased[1]);
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
if (NOT_A_FUNCTION.test(trimmed)) return;
|
|
2826
|
+
const fn = FUNCTION.exec(line);
|
|
2827
|
+
if (fn) {
|
|
2828
|
+
summary.functions.push(fn[1]);
|
|
2829
|
+
summary.exports.push(fn[1]);
|
|
2830
|
+
}
|
|
2831
|
+
});
|
|
2832
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2833
|
+
}
|
|
2834
|
+
};
|
|
2835
|
+
|
|
2836
|
+
// src/ast/profiles/dotnet.ts
|
|
2837
|
+
var USING2 = /^using\s+([\w.]+)\s*;/;
|
|
2838
|
+
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*)/;
|
|
2839
|
+
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*[<(]/;
|
|
2840
|
+
var dotnetProfile = {
|
|
2841
|
+
id: "dotnet",
|
|
2842
|
+
extensions: [".cs"],
|
|
2843
|
+
summarize(_path, content) {
|
|
2844
|
+
const summary = emptySummary();
|
|
2845
|
+
eachLine(content, (line) => {
|
|
2846
|
+
const trimmed = line.trim();
|
|
2847
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
const used = USING2.exec(trimmed);
|
|
2851
|
+
if (used) {
|
|
2852
|
+
summary.imports.push(used[1]);
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
const declaration = TYPE_DECL2.exec(line);
|
|
2856
|
+
if (declaration) {
|
|
2857
|
+
const kind = declaration[1];
|
|
2858
|
+
const name = declaration[2];
|
|
2859
|
+
if (kind === "interface") summary.interfaces.push(name);
|
|
2860
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
2861
|
+
else summary.classes.push(name);
|
|
2862
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(name);
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
const method = METHOD.exec(line);
|
|
2866
|
+
if (method) {
|
|
2867
|
+
summary.functions.push(method[1]);
|
|
2868
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
|
|
2869
|
+
}
|
|
2870
|
+
});
|
|
2871
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2872
|
+
}
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2875
|
+
// src/ast/profiles/go.ts
|
|
2876
|
+
var FUNC = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/;
|
|
2877
|
+
var TYPE = /^type\s+([A-Za-z_]\w*)\s+(.*)$/;
|
|
2878
|
+
var VAR_CONST = /^(?:var|const)\s+([A-Za-z_]\w*)/;
|
|
2879
|
+
var IMPORT_SINGLE = /^import\s+(?:[\w.]+\s+)?"([^"]+)"/;
|
|
2880
|
+
var IMPORT_OPEN = /^import\s*\(/;
|
|
2881
|
+
var IMPORT_ENTRY = /^\s*(?:[\w.]+\s+)?"([^"]+)"/;
|
|
2882
|
+
var goProfile = {
|
|
2883
|
+
id: "go",
|
|
2884
|
+
extensions: [".go"],
|
|
2885
|
+
summarize(_path, content) {
|
|
2886
|
+
const summary = emptySummary();
|
|
2887
|
+
let inImportBlock = false;
|
|
2888
|
+
eachLine(content, (line) => {
|
|
2889
|
+
const trimmed = line.trim();
|
|
2890
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
2891
|
+
if (inImportBlock) {
|
|
2892
|
+
if (trimmed.startsWith(")")) {
|
|
2893
|
+
inImportBlock = false;
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
const entry = IMPORT_ENTRY.exec(line);
|
|
2897
|
+
if (entry) summary.imports.push(entry[1]);
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
if (IMPORT_OPEN.test(trimmed)) {
|
|
2901
|
+
inImportBlock = true;
|
|
2902
|
+
return;
|
|
2903
|
+
}
|
|
2904
|
+
const single = IMPORT_SINGLE.exec(trimmed);
|
|
2905
|
+
if (single) {
|
|
2906
|
+
summary.imports.push(single[1]);
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
if (!isTopLevel(line)) return;
|
|
2910
|
+
const method = FUNC.exec(line);
|
|
2911
|
+
if (method) {
|
|
2912
|
+
const name = method[1];
|
|
2913
|
+
summary.functions.push(name);
|
|
2914
|
+
if (isCapitalizedName(name)) summary.exports.push(name);
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
const declared = TYPE.exec(line);
|
|
2918
|
+
if (declared) {
|
|
2919
|
+
const name = declared[1];
|
|
2920
|
+
const body = declared[2].trim();
|
|
2921
|
+
if (body.startsWith("struct")) summary.classes.push(name);
|
|
2922
|
+
else if (body.startsWith("interface")) summary.interfaces.push(name);
|
|
2923
|
+
else summary.types.push(name);
|
|
2924
|
+
if (isCapitalizedName(name)) summary.exports.push(name);
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
const value = VAR_CONST.exec(line);
|
|
2928
|
+
if (value) {
|
|
2929
|
+
summary.variables.push(value[1]);
|
|
2930
|
+
if (isCapitalizedName(value[1])) summary.exports.push(value[1]);
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2933
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2936
|
+
|
|
2937
|
+
// src/ast/profiles/jvm.ts
|
|
2938
|
+
var IMPORT = /^import\s+(?:static\s+)?([\w.]+?)(?:\.\*)?;?\s*$/;
|
|
2939
|
+
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*)/;
|
|
2940
|
+
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*)/;
|
|
2941
|
+
var SCALA_DEF = /^\s*def\s+([A-Za-z_]\w*)/;
|
|
2942
|
+
var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?[\w<>[\],.?]+\s+([A-Za-z_]\w*)\s*\(/;
|
|
2943
|
+
var jvmProfile = {
|
|
2944
|
+
id: "jvm",
|
|
2945
|
+
extensions: [".java", ".kt", ".kts", ".scala"],
|
|
2946
|
+
summarize(path12, content) {
|
|
2947
|
+
const summary = emptySummary();
|
|
2948
|
+
const lower = path12.toLowerCase();
|
|
2949
|
+
const isJava = lower.endsWith(".java");
|
|
2950
|
+
const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
|
|
2951
|
+
eachLine(content, (line) => {
|
|
2952
|
+
const trimmed = line.trim();
|
|
2953
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
const imported = IMPORT.exec(trimmed);
|
|
2957
|
+
if (imported) {
|
|
2958
|
+
summary.imports.push(imported[1]);
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
const declaration = TYPE_DECL3.exec(line);
|
|
2962
|
+
if (declaration) {
|
|
2963
|
+
const kind = declaration[1];
|
|
2964
|
+
const name = declaration[2];
|
|
2965
|
+
if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
|
|
2966
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
2967
|
+
else summary.classes.push(name);
|
|
2968
|
+
const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
|
|
2969
|
+
if (isJava ? /^\s*public\b/.test(line) : !hidden) summary.exports.push(name);
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
const kotlinFunction = KOTLIN_FUN.exec(line);
|
|
2973
|
+
if (kotlinFunction && isKotlin) {
|
|
2974
|
+
summary.functions.push(kotlinFunction[1]);
|
|
2975
|
+
const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
|
|
2976
|
+
if (!hidden) summary.exports.push(kotlinFunction[1]);
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
const scalaFunction = SCALA_DEF.exec(line);
|
|
2980
|
+
if (scalaFunction && !isJava && !isKotlin) {
|
|
2981
|
+
summary.functions.push(scalaFunction[1]);
|
|
2982
|
+
const hidden = /^\s*(?:private|protected)\b/.test(line);
|
|
2983
|
+
if (!hidden) summary.exports.push(scalaFunction[1]);
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
if (isJava) {
|
|
2987
|
+
const method = JAVA_METHOD.exec(line);
|
|
2988
|
+
if (method) {
|
|
2989
|
+
summary.functions.push(method[1]);
|
|
2990
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
});
|
|
2994
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2995
|
+
}
|
|
2996
|
+
};
|
|
2997
|
+
|
|
2998
|
+
// src/ast/profiles/php.ts
|
|
2999
|
+
var USE = /^use\s+([\w\\]+)\s*(?:as\s+\w+)?;/;
|
|
3000
|
+
var DECLARE = /^(?:abstract\s+|final\s+|readonly\s+)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/;
|
|
3001
|
+
var FUNCTION2 = /^(?:public\s+|protected\s+|private\s+|static\s+|final\s+|abstract\s+)*function\s+([A-Za-z_]\w*)/;
|
|
3002
|
+
var phpProfile = {
|
|
3003
|
+
id: "php",
|
|
3004
|
+
extensions: [".php"],
|
|
3005
|
+
summarize(_path, content) {
|
|
3006
|
+
const summary = emptySummary();
|
|
3007
|
+
eachLine(content, (line) => {
|
|
3008
|
+
const trimmed = line.trim();
|
|
3009
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*")) {
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
const used = USE.exec(trimmed);
|
|
3013
|
+
if (used) {
|
|
3014
|
+
summary.imports.push(used[1].replace(/\\\\/g, "\\"));
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
const declaration = DECLARE.exec(trimmed);
|
|
3018
|
+
if (declaration) {
|
|
3019
|
+
const kind = declaration[1];
|
|
3020
|
+
const name = declaration[2];
|
|
3021
|
+
if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
|
|
3022
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
3023
|
+
else summary.classes.push(name);
|
|
3024
|
+
summary.exports.push(name);
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const fn = FUNCTION2.exec(trimmed);
|
|
3028
|
+
if (fn) {
|
|
3029
|
+
summary.functions.push(fn[1]);
|
|
3030
|
+
if (!/^private\b/.test(trimmed)) summary.exports.push(fn[1]);
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
|
|
3037
|
+
// src/ast/profiles/python.ts
|
|
3038
|
+
var IMPORT2 = /^import\s+(.+)$/;
|
|
3039
|
+
var FROM_IMPORT = /^from\s+([\w.]+)\s+import\s+(.+)$/;
|
|
3040
|
+
var CLASS = /^class\s+([A-Za-z_]\w*)/;
|
|
3041
|
+
var DEF = /^(?:async\s+)?def\s+([A-Za-z_]\w*)/;
|
|
3042
|
+
var CONSTANT = /^([A-Z_][A-Z0-9_]*)\s*(?::[^=]+)?=/;
|
|
3043
|
+
var ALL_DUNDER = /^__all__\s*=\s*\[([^\]]*)\]/;
|
|
3044
|
+
var ROUTE_DECORATOR = /^\s*@[\w.]*\b(?:get|post|put|patch|delete|head|options|route)\s*\(/i;
|
|
3045
|
+
var pythonProfile = {
|
|
3046
|
+
id: "python",
|
|
3047
|
+
extensions: [".py", ".pyi"],
|
|
3048
|
+
summarize(_path, content) {
|
|
3049
|
+
const summary = emptySummary();
|
|
3050
|
+
let routePending = false;
|
|
3051
|
+
eachLine(content, (line) => {
|
|
3052
|
+
const trimmed = line.trim();
|
|
3053
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
3054
|
+
if (ROUTE_DECORATOR.test(line)) {
|
|
3055
|
+
routePending = true;
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
const from = FROM_IMPORT.exec(trimmed);
|
|
3059
|
+
if (from) {
|
|
3060
|
+
summary.imports.push(from[1]);
|
|
3061
|
+
routePending = false;
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
const imported = IMPORT2.exec(trimmed);
|
|
3065
|
+
if (imported) {
|
|
3066
|
+
for (const part of imported[1].split(",")) {
|
|
3067
|
+
const name = part.trim().split(/\s+as\s+/)[0].trim();
|
|
3068
|
+
if (name) summary.imports.push(name);
|
|
3069
|
+
}
|
|
3070
|
+
routePending = false;
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
if (isTopLevel(line)) {
|
|
3074
|
+
const klass = CLASS.exec(line);
|
|
3075
|
+
if (klass) {
|
|
3076
|
+
summary.classes.push(klass[1]);
|
|
3077
|
+
if (isPublicName(klass[1])) summary.exports.push(klass[1]);
|
|
3078
|
+
routePending = false;
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
const definition = DEF.exec(line);
|
|
3082
|
+
if (definition) {
|
|
3083
|
+
const name = definition[1];
|
|
3084
|
+
summary.functions.push(name);
|
|
3085
|
+
if (routePending) {
|
|
3086
|
+
summary.routeHandlers.push(name);
|
|
3087
|
+
routePending = false;
|
|
3088
|
+
}
|
|
3089
|
+
if (isPublicName(name)) summary.exports.push(name);
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
const all = ALL_DUNDER.exec(trimmed);
|
|
3093
|
+
if (all) {
|
|
3094
|
+
for (const entry of all[1].split(",")) {
|
|
3095
|
+
const name = entry.trim().replace(/['"]/g, "").trim();
|
|
3096
|
+
if (name) summary.exports.push(name);
|
|
3097
|
+
}
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
const constant = CONSTANT.exec(line);
|
|
3101
|
+
if (constant) summary.variables.push(constant[1]);
|
|
3102
|
+
}
|
|
3103
|
+
});
|
|
3104
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
|
|
3108
|
+
// src/ast/profiles/ruby.ts
|
|
3109
|
+
var REQUIRE = /^require(?:_relative)?\s+["']([^"']+)["']/;
|
|
3110
|
+
var CLASS_OR_MODULE = /^(?:class|module)\s+([A-Z][\w:]*)/;
|
|
3111
|
+
var METHOD2 = /^def\s+(?:self\.)?([A-Za-z_]\w*[?!=]?)/;
|
|
3112
|
+
var rubyProfile = {
|
|
3113
|
+
id: "ruby",
|
|
3114
|
+
extensions: [".rb", ".rake", ".gemspec"],
|
|
3115
|
+
summarize(_path, content) {
|
|
3116
|
+
const summary = emptySummary();
|
|
3117
|
+
eachLine(content, (line) => {
|
|
3118
|
+
const trimmed = line.trim();
|
|
3119
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
3120
|
+
const required = REQUIRE.exec(trimmed);
|
|
3121
|
+
if (required) {
|
|
3122
|
+
summary.imports.push(required[1]);
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
if (!isTopLevel(line)) return;
|
|
3126
|
+
const declaration = CLASS_OR_MODULE.exec(line);
|
|
3127
|
+
if (declaration) {
|
|
3128
|
+
summary.classes.push(declaration[1]);
|
|
3129
|
+
summary.exports.push(declaration[1]);
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
const method = METHOD2.exec(line);
|
|
3133
|
+
if (method) {
|
|
3134
|
+
summary.functions.push(method[1]);
|
|
3135
|
+
summary.exports.push(method[1]);
|
|
3136
|
+
}
|
|
3137
|
+
});
|
|
3138
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
3139
|
+
}
|
|
3140
|
+
};
|
|
3141
|
+
|
|
3142
|
+
// src/ast/profiles/rust.ts
|
|
3143
|
+
var USE2 = /^use\s+([^;]+);/;
|
|
3144
|
+
var FN = /^(pub(?:\([^)]*\))?\s+)?(?:const\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+([A-Za-z_]\w*)/;
|
|
3145
|
+
var STRUCT = /^(pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)/;
|
|
3146
|
+
var TRAIT = /^(pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?trait\s+([A-Za-z_]\w*)/;
|
|
3147
|
+
var ENUM = /^(pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)/;
|
|
3148
|
+
var TYPE2 = /^(pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)/;
|
|
3149
|
+
var MOD = /^(pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)/;
|
|
3150
|
+
var IMPL = /^impl(?:<[^>]*>)?\s+(?:[\w:<>]+\s+for\s+)?([A-Za-z_]\w*)/;
|
|
3151
|
+
var rustProfile = {
|
|
3152
|
+
id: "rust",
|
|
3153
|
+
extensions: [".rs"],
|
|
3154
|
+
summarize(_path, content) {
|
|
3155
|
+
const summary = emptySummary();
|
|
3156
|
+
eachLine(content, (line) => {
|
|
3157
|
+
const trimmed = line.trim();
|
|
3158
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
3159
|
+
const used = USE2.exec(trimmed);
|
|
3160
|
+
if (used) {
|
|
3161
|
+
summary.imports.push(used[1].trim());
|
|
3162
|
+
return;
|
|
1545
3163
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
3164
|
+
if (!isTopLevel(line)) return;
|
|
3165
|
+
const isPublic = /^pub\b/.test(trimmed);
|
|
3166
|
+
const fn = FN.exec(line);
|
|
3167
|
+
if (fn) {
|
|
3168
|
+
summary.functions.push(fn[2]);
|
|
3169
|
+
if (isPublic) summary.exports.push(fn[2]);
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
const struct = STRUCT.exec(line);
|
|
3173
|
+
if (struct) {
|
|
3174
|
+
summary.classes.push(struct[2]);
|
|
3175
|
+
if (isPublic) summary.exports.push(struct[2]);
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
const trait = TRAIT.exec(line);
|
|
3179
|
+
if (trait) {
|
|
3180
|
+
summary.interfaces.push(trait[2]);
|
|
3181
|
+
if (isPublic) summary.exports.push(trait[2]);
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
const enumeration = ENUM.exec(line);
|
|
3185
|
+
if (enumeration) {
|
|
3186
|
+
summary.enums.push(enumeration[2]);
|
|
3187
|
+
if (isPublic) summary.exports.push(enumeration[2]);
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
const alias = TYPE2.exec(line);
|
|
3191
|
+
if (alias) {
|
|
3192
|
+
summary.types.push(alias[2]);
|
|
3193
|
+
if (isPublic) summary.exports.push(alias[2]);
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
const module = MOD.exec(line);
|
|
3197
|
+
if (module) {
|
|
3198
|
+
summary.variables.push(module[2]);
|
|
3199
|
+
if (isPublic) summary.exports.push(module[2]);
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
const implementation = IMPL.exec(line);
|
|
3203
|
+
if (implementation) summary.classes.push(implementation[1]);
|
|
3204
|
+
});
|
|
3205
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
1554
3206
|
}
|
|
1555
|
-
|
|
1556
|
-
headings: headings.slice(0, 40),
|
|
1557
|
-
frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
3207
|
+
};
|
|
1560
3208
|
var ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
1561
3209
|
"GET",
|
|
1562
3210
|
"POST",
|
|
@@ -1596,9 +3244,9 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
1596
3244
|
if (parseErrors > 0) summary.parseErrors = parseErrors;
|
|
1597
3245
|
for (const statement of sourceFile.statements) {
|
|
1598
3246
|
if (ts__default.default.isExpressionStatement(statement) && ts__default.default.isStringLiteral(statement.expression)) {
|
|
1599
|
-
const
|
|
1600
|
-
if (
|
|
1601
|
-
summary.directives.push(
|
|
3247
|
+
const text2 = statement.expression.text;
|
|
3248
|
+
if (text2 === "use client" || text2 === "use server") {
|
|
3249
|
+
summary.directives.push(text2);
|
|
1602
3250
|
}
|
|
1603
3251
|
continue;
|
|
1604
3252
|
}
|
|
@@ -1698,23 +3346,212 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
1698
3346
|
stack.push(children[index]);
|
|
1699
3347
|
}
|
|
1700
3348
|
}
|
|
1701
|
-
const
|
|
1702
|
-
summary.directives =
|
|
1703
|
-
summary.imports =
|
|
1704
|
-
summary.exports =
|
|
1705
|
-
summary.reExports =
|
|
1706
|
-
summary.functions =
|
|
1707
|
-
summary.classes =
|
|
1708
|
-
summary.interfaces =
|
|
1709
|
-
summary.types =
|
|
1710
|
-
summary.enums =
|
|
1711
|
-
summary.variables =
|
|
1712
|
-
summary.reactComponents =
|
|
1713
|
-
summary.hooks =
|
|
1714
|
-
summary.routeHandlers =
|
|
3349
|
+
const dedupeSorted2 = (values) => Array.from(new Set(values)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
3350
|
+
summary.directives = dedupeSorted2(summary.directives);
|
|
3351
|
+
summary.imports = dedupeSorted2(summary.imports);
|
|
3352
|
+
summary.exports = dedupeSorted2(summary.exports);
|
|
3353
|
+
summary.reExports = dedupeSorted2(summary.reExports);
|
|
3354
|
+
summary.functions = dedupeSorted2(summary.functions);
|
|
3355
|
+
summary.classes = dedupeSorted2(summary.classes);
|
|
3356
|
+
summary.interfaces = dedupeSorted2(summary.interfaces);
|
|
3357
|
+
summary.types = dedupeSorted2(summary.types);
|
|
3358
|
+
summary.enums = dedupeSorted2(summary.enums);
|
|
3359
|
+
summary.variables = dedupeSorted2(summary.variables);
|
|
3360
|
+
summary.reactComponents = dedupeSorted2(summary.reactComponents);
|
|
3361
|
+
summary.hooks = dedupeSorted2(summary.hooks);
|
|
3362
|
+
summary.routeHandlers = dedupeSorted2(summary.routeHandlers);
|
|
1715
3363
|
return summary;
|
|
1716
3364
|
}
|
|
1717
3365
|
|
|
3366
|
+
// src/ast/profiles/sfc.ts
|
|
3367
|
+
var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
3368
|
+
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
3369
|
+
function componentName(path12) {
|
|
3370
|
+
const base = path12.split("/").pop() ?? path12;
|
|
3371
|
+
return base.replace(/\.[^.]+$/, "");
|
|
3372
|
+
}
|
|
3373
|
+
var sfcProfile = {
|
|
3374
|
+
id: "sfc",
|
|
3375
|
+
extensions: [".vue", ".svelte", ".astro"],
|
|
3376
|
+
summarize(path12, content) {
|
|
3377
|
+
let script = "";
|
|
3378
|
+
if (path12.toLowerCase().endsWith(".astro")) {
|
|
3379
|
+
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
3380
|
+
if (frontmatter) script += `${frontmatter[1]}
|
|
3381
|
+
`;
|
|
3382
|
+
}
|
|
3383
|
+
for (const match of content.matchAll(SCRIPT_BLOCK)) {
|
|
3384
|
+
script += `${match[1]}
|
|
3385
|
+
`;
|
|
3386
|
+
}
|
|
3387
|
+
const name = componentName(path12);
|
|
3388
|
+
if (!script.trim()) {
|
|
3389
|
+
const summary2 = emptySummary();
|
|
3390
|
+
if (name) {
|
|
3391
|
+
summary2.exports.push(name);
|
|
3392
|
+
summary2.reactComponents.push(name);
|
|
3393
|
+
}
|
|
3394
|
+
return { summary: finalizeSummary(summary2), quality: "heuristic" };
|
|
3395
|
+
}
|
|
3396
|
+
const summary = summarizeTypeScriptSource(`${name}.ts`, script);
|
|
3397
|
+
summary.reactComponents.push(name);
|
|
3398
|
+
summary.exports.push(name);
|
|
3399
|
+
return { summary, quality: "ast" };
|
|
3400
|
+
}
|
|
3401
|
+
};
|
|
3402
|
+
|
|
3403
|
+
// src/ast/profiles/index.ts
|
|
3404
|
+
var LANGUAGE_PROFILES = [
|
|
3405
|
+
pythonProfile,
|
|
3406
|
+
goProfile,
|
|
3407
|
+
rustProfile,
|
|
3408
|
+
jvmProfile,
|
|
3409
|
+
dotnetProfile,
|
|
3410
|
+
phpProfile,
|
|
3411
|
+
rubyProfile,
|
|
3412
|
+
cfamilyProfile,
|
|
3413
|
+
sfcProfile
|
|
3414
|
+
];
|
|
3415
|
+
var BY_EXTENSION = /* @__PURE__ */ new Map();
|
|
3416
|
+
for (const profile of LANGUAGE_PROFILES) {
|
|
3417
|
+
for (const extension of profile.extensions) {
|
|
3418
|
+
BY_EXTENSION.set(extension.toLowerCase(), profile);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
function profileForPath(path12) {
|
|
3422
|
+
const lower = path12.toLowerCase();
|
|
3423
|
+
const dot = lower.lastIndexOf(".");
|
|
3424
|
+
if (dot === -1) return null;
|
|
3425
|
+
return BY_EXTENSION.get(lower.slice(dot)) ?? null;
|
|
3426
|
+
}
|
|
3427
|
+
function summarizeWithProfile(path12, content) {
|
|
3428
|
+
const profile = profileForPath(path12);
|
|
3429
|
+
return profile ? profile.summarize(path12, content) : null;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
// src/ast/structured.ts
|
|
3433
|
+
function stripJsonComments2(content) {
|
|
3434
|
+
const text2 = content.replace(/^\uFEFF/, "");
|
|
3435
|
+
let out = "";
|
|
3436
|
+
let inString = false;
|
|
3437
|
+
let inLineComment = false;
|
|
3438
|
+
let inBlockComment = false;
|
|
3439
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3440
|
+
const char = text2[i];
|
|
3441
|
+
const next = text2[i + 1];
|
|
3442
|
+
if (inLineComment) {
|
|
3443
|
+
if (char === "\n") {
|
|
3444
|
+
inLineComment = false;
|
|
3445
|
+
out += char;
|
|
3446
|
+
}
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
if (inBlockComment) {
|
|
3450
|
+
if (char === "*" && next === "/") {
|
|
3451
|
+
inBlockComment = false;
|
|
3452
|
+
i += 1;
|
|
3453
|
+
}
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
if (inString) {
|
|
3457
|
+
out += char;
|
|
3458
|
+
if (char === "\\") {
|
|
3459
|
+
if (next !== void 0) {
|
|
3460
|
+
out += next;
|
|
3461
|
+
i += 1;
|
|
3462
|
+
}
|
|
3463
|
+
} else if (char === '"') {
|
|
3464
|
+
inString = false;
|
|
3465
|
+
}
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
if (char === '"') {
|
|
3469
|
+
inString = true;
|
|
3470
|
+
out += char;
|
|
3471
|
+
continue;
|
|
3472
|
+
}
|
|
3473
|
+
if (char === "/" && next === "/") {
|
|
3474
|
+
inLineComment = true;
|
|
3475
|
+
i += 1;
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
if (char === "/" && next === "*") {
|
|
3479
|
+
inBlockComment = true;
|
|
3480
|
+
i += 1;
|
|
3481
|
+
continue;
|
|
3482
|
+
}
|
|
3483
|
+
out += char;
|
|
3484
|
+
}
|
|
3485
|
+
return out;
|
|
3486
|
+
}
|
|
3487
|
+
function summarizeJson(content) {
|
|
3488
|
+
try {
|
|
3489
|
+
const parsed = JSON.parse(stripJsonComments2(content));
|
|
3490
|
+
if (Array.isArray(parsed)) {
|
|
3491
|
+
const sample = parsed[0];
|
|
3492
|
+
const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
|
|
3493
|
+
return {
|
|
3494
|
+
shape: "array",
|
|
3495
|
+
itemCount: parsed.length,
|
|
3496
|
+
keys: [],
|
|
3497
|
+
sampleKeys
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
if (parsed && typeof parsed === "object") {
|
|
3501
|
+
const keys = Object.keys(parsed).sort();
|
|
3502
|
+
return {
|
|
3503
|
+
shape: "object",
|
|
3504
|
+
itemCount: keys.length,
|
|
3505
|
+
keys: keys.slice(0, 30),
|
|
3506
|
+
sampleKeys: []
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
return {
|
|
3510
|
+
shape: typeof parsed,
|
|
3511
|
+
itemCount: null,
|
|
3512
|
+
keys: [],
|
|
3513
|
+
sampleKeys: []
|
|
3514
|
+
};
|
|
3515
|
+
} catch {
|
|
3516
|
+
return {
|
|
3517
|
+
shape: "invalid-json",
|
|
3518
|
+
itemCount: null,
|
|
3519
|
+
keys: [],
|
|
3520
|
+
sampleKeys: []
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
function summarizeMarkdown(content) {
|
|
3525
|
+
const headings = [];
|
|
3526
|
+
const frontmatterKeys = [];
|
|
3527
|
+
const lines = content.replace(/^\uFEFF/, "").split("\n");
|
|
3528
|
+
let inFrontmatter = false;
|
|
3529
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3530
|
+
const line = lines[index];
|
|
3531
|
+
if (index === 0 && line.trim() === "---") {
|
|
3532
|
+
inFrontmatter = true;
|
|
3533
|
+
continue;
|
|
3534
|
+
}
|
|
3535
|
+
if (inFrontmatter) {
|
|
3536
|
+
if (line.trim() === "---") {
|
|
3537
|
+
inFrontmatter = false;
|
|
3538
|
+
continue;
|
|
3539
|
+
}
|
|
3540
|
+
const match = /^([a-zA-Z0-9_-]+):/.exec(line);
|
|
3541
|
+
if (match) frontmatterKeys.push(match[1]);
|
|
3542
|
+
continue;
|
|
3543
|
+
}
|
|
3544
|
+
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
|
|
3545
|
+
if (headingMatch) {
|
|
3546
|
+
headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
return {
|
|
3550
|
+
headings: headings.slice(0, 40),
|
|
3551
|
+
frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
|
|
3552
|
+
};
|
|
3553
|
+
}
|
|
3554
|
+
|
|
1718
3555
|
// src/generator/audit.ts
|
|
1719
3556
|
function generateAuditReport(manifest, secretFindings) {
|
|
1720
3557
|
const lines = [
|
|
@@ -1780,11 +3617,11 @@ function generateAuditReport(manifest, secretFindings) {
|
|
|
1780
3617
|
"| :--- | :--- | :--- |"
|
|
1781
3618
|
);
|
|
1782
3619
|
const totalFiles = manifest.counts.totalFiles || 1;
|
|
1783
|
-
for (const [
|
|
3620
|
+
for (const [layer2, count] of Object.entries(manifest.layers).sort(
|
|
1784
3621
|
(a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
1785
3622
|
)) {
|
|
1786
3623
|
const pct = (count / totalFiles * 100).toFixed(1);
|
|
1787
|
-
lines.push(`| \`${
|
|
3624
|
+
lines.push(`| \`${layer2}\` | ${count} | ${pct}% |`);
|
|
1788
3625
|
}
|
|
1789
3626
|
lines.push("");
|
|
1790
3627
|
lines.push(
|
|
@@ -1850,21 +3687,21 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
1850
3687
|
section.byteSize = Buffer.byteLength(section.text, "utf8");
|
|
1851
3688
|
continue;
|
|
1852
3689
|
}
|
|
1853
|
-
const
|
|
1854
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
3690
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
3691
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1855
3692
|
const clipped = truncateToBytes(rendered[oversized].markdown, target);
|
|
1856
|
-
rendered[oversized] = rehashPart(rendered[oversized], clipped.content +
|
|
3693
|
+
rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
|
|
1857
3694
|
return rendered;
|
|
1858
3695
|
}
|
|
1859
3696
|
return enforceBudget(renderGroups(groups, contentFingerprint), budget);
|
|
1860
3697
|
}
|
|
1861
3698
|
function enforceBudget(parts, budget) {
|
|
1862
|
-
const
|
|
1863
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
3699
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
3700
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1864
3701
|
return parts.map((part) => {
|
|
1865
3702
|
if (part.metadata.byteSize <= budget) return part;
|
|
1866
3703
|
const clipped = truncateToBytes(part.markdown, target);
|
|
1867
|
-
return rehashPart(part, clipped.content +
|
|
3704
|
+
return rehashPart(part, clipped.content + marker2);
|
|
1868
3705
|
});
|
|
1869
3706
|
}
|
|
1870
3707
|
function renumberParts(parts, contentFingerprint) {
|
|
@@ -1872,10 +3709,10 @@ function renumberParts(parts, contentFingerprint) {
|
|
|
1872
3709
|
if (total === 0) return [];
|
|
1873
3710
|
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
1874
3711
|
const nameFor = (position) => `part-${zeroPad(position)}.md`;
|
|
1875
|
-
const
|
|
3712
|
+
const marker2 = "## Contents\n";
|
|
1876
3713
|
return parts.map((part, index) => {
|
|
1877
|
-
const markerIndex = part.markdown.indexOf(
|
|
1878
|
-
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex +
|
|
3714
|
+
const markerIndex = part.markdown.indexOf(marker2);
|
|
3715
|
+
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
|
|
1879
3716
|
const included = part.metadata.includedFiles;
|
|
1880
3717
|
if (markerIndex === -1) {
|
|
1881
3718
|
throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
|
|
@@ -1933,9 +3770,9 @@ function topEntries(counts, limit) {
|
|
|
1933
3770
|
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(", ");
|
|
1934
3771
|
}
|
|
1935
3772
|
function prepareSection(file, sectionBudget) {
|
|
1936
|
-
let
|
|
1937
|
-
let byteSize = Buffer.byteLength(
|
|
1938
|
-
if (byteSize <= sectionBudget) return { file, text, byteSize };
|
|
3773
|
+
let text2 = renderFileSection(file);
|
|
3774
|
+
let byteSize = Buffer.byteLength(text2, "utf8");
|
|
3775
|
+
if (byteSize <= sectionBudget) return { file, text: text2, byteSize };
|
|
1939
3776
|
const shrinkBody = () => {
|
|
1940
3777
|
const content = file.content ?? "";
|
|
1941
3778
|
const overhead = byteSize - Buffer.byteLength(content, "utf8");
|
|
@@ -1944,24 +3781,24 @@ function prepareSection(file, sectionBudget) {
|
|
|
1944
3781
|
const truncated = truncateToBytes(content, allowed);
|
|
1945
3782
|
if (!truncated.isTruncated) return;
|
|
1946
3783
|
applyTruncatedContent(file, truncated.content);
|
|
1947
|
-
|
|
1948
|
-
byteSize = Buffer.byteLength(
|
|
3784
|
+
text2 = renderFileSection(file);
|
|
3785
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1949
3786
|
};
|
|
1950
3787
|
shrinkBody();
|
|
1951
3788
|
if (byteSize > sectionBudget && file.summary) {
|
|
1952
3789
|
file.summary = void 0;
|
|
1953
3790
|
file.summaryOmitted = true;
|
|
1954
|
-
|
|
1955
|
-
byteSize = Buffer.byteLength(
|
|
3791
|
+
text2 = renderFileSection(file);
|
|
3792
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1956
3793
|
shrinkBody();
|
|
1957
3794
|
}
|
|
1958
3795
|
if (byteSize > sectionBudget) {
|
|
1959
3796
|
const target = sectionBudget - Buffer.byteLength(SECTION_TRUNCATION_NOTICE, "utf8");
|
|
1960
|
-
const truncated = truncateToBytes(
|
|
1961
|
-
|
|
1962
|
-
byteSize = Buffer.byteLength(
|
|
3797
|
+
const truncated = truncateToBytes(text2, Math.max(0, target));
|
|
3798
|
+
text2 = truncated.content + SECTION_TRUNCATION_NOTICE;
|
|
3799
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1963
3800
|
}
|
|
1964
|
-
return { file, text, byteSize };
|
|
3801
|
+
return { file, text: text2, byteSize };
|
|
1965
3802
|
}
|
|
1966
3803
|
function applyTruncatedContent(file, content) {
|
|
1967
3804
|
file.content = content;
|
|
@@ -2083,6 +3920,7 @@ function renderFileSection(file) {
|
|
|
2083
3920
|
`- Source Lines: ${file.originalLines}`,
|
|
2084
3921
|
`- Included Lines: ${file.includedLines}`,
|
|
2085
3922
|
`- SHA-256: ${file.hashSha256}`,
|
|
3923
|
+
...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
|
|
2086
3924
|
...renderSummary(file.summary),
|
|
2087
3925
|
...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
|
|
2088
3926
|
"",
|
|
@@ -2330,7 +4168,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2330
4168
|
".so"
|
|
2331
4169
|
]);
|
|
2332
4170
|
function isBinaryExtension(filePath) {
|
|
2333
|
-
const ext =
|
|
4171
|
+
const ext = path10__namespace.extname(filePath).toLowerCase();
|
|
2334
4172
|
return BINARY_EXTENSIONS.has(ext);
|
|
2335
4173
|
}
|
|
2336
4174
|
function isBinaryBuffer(buffer) {
|
|
@@ -2344,9 +4182,9 @@ function isBinaryFile(absolutePath) {
|
|
|
2344
4182
|
if (isBinaryExtension(absolutePath)) return true;
|
|
2345
4183
|
let fd;
|
|
2346
4184
|
try {
|
|
2347
|
-
fd =
|
|
4185
|
+
fd = fs9__namespace.openSync(absolutePath, "r");
|
|
2348
4186
|
const buffer = Buffer.alloc(8192);
|
|
2349
|
-
const bytesRead =
|
|
4187
|
+
const bytesRead = fs9__namespace.readSync(fd, buffer, 0, 8192, 0);
|
|
2350
4188
|
if (bytesRead === 0) return false;
|
|
2351
4189
|
return isBinaryBuffer(buffer.subarray(0, bytesRead));
|
|
2352
4190
|
} catch {
|
|
@@ -2354,7 +4192,7 @@ function isBinaryFile(absolutePath) {
|
|
|
2354
4192
|
} finally {
|
|
2355
4193
|
if (fd !== void 0) {
|
|
2356
4194
|
try {
|
|
2357
|
-
|
|
4195
|
+
fs9__namespace.closeSync(fd);
|
|
2358
4196
|
} catch {
|
|
2359
4197
|
}
|
|
2360
4198
|
}
|
|
@@ -2376,8 +4214,8 @@ function sha256Hex(buffer) {
|
|
|
2376
4214
|
function validateSnapshot(configInput) {
|
|
2377
4215
|
assertUsableConfigInput(configInput);
|
|
2378
4216
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
|
|
2379
|
-
const manifestPath =
|
|
2380
|
-
if (!
|
|
4217
|
+
const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
|
|
4218
|
+
if (!fs9__namespace.existsSync(manifestPath)) {
|
|
2381
4219
|
return {
|
|
2382
4220
|
valid: false,
|
|
2383
4221
|
manifestPresent: false,
|
|
@@ -2388,7 +4226,7 @@ function validateSnapshot(configInput) {
|
|
|
2388
4226
|
}
|
|
2389
4227
|
let manifest;
|
|
2390
4228
|
try {
|
|
2391
|
-
manifest = JSON.parse(
|
|
4229
|
+
manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
2392
4230
|
} catch (err) {
|
|
2393
4231
|
return {
|
|
2394
4232
|
valid: false,
|
|
@@ -2411,7 +4249,7 @@ function validateSnapshot(configInput) {
|
|
|
2411
4249
|
}
|
|
2412
4250
|
const drift = emptyDrift();
|
|
2413
4251
|
const warnings = [];
|
|
2414
|
-
const snapshotDir =
|
|
4252
|
+
const snapshotDir = path10__namespace.resolve(config.cwd, config.outDir);
|
|
2415
4253
|
const manifestFileMap = /* @__PURE__ */ new Map();
|
|
2416
4254
|
for (const file of manifest.files) {
|
|
2417
4255
|
if (file && typeof file.relativePath === "string") {
|
|
@@ -2423,7 +4261,7 @@ function validateSnapshot(configInput) {
|
|
|
2423
4261
|
);
|
|
2424
4262
|
const collection = collectFiles(config.cwd, {
|
|
2425
4263
|
includePatterns: config.include,
|
|
2426
|
-
excludePatterns: config.excludePatterns,
|
|
4264
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2427
4265
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2428
4266
|
});
|
|
2429
4267
|
for (const diskFile of collection.files) {
|
|
@@ -2435,7 +4273,7 @@ function validateSnapshot(configInput) {
|
|
|
2435
4273
|
}
|
|
2436
4274
|
let diskHash;
|
|
2437
4275
|
try {
|
|
2438
|
-
diskHash = sha256Hex(
|
|
4276
|
+
diskHash = sha256Hex(fs9__namespace.readFileSync(path10__namespace.join(config.cwd, diskFile)));
|
|
2439
4277
|
} catch {
|
|
2440
4278
|
drift.missingOnDisk.push(diskFile);
|
|
2441
4279
|
continue;
|
|
@@ -2445,10 +4283,11 @@ function validateSnapshot(configInput) {
|
|
|
2445
4283
|
}
|
|
2446
4284
|
}
|
|
2447
4285
|
for (const snapshotFile of manifestFileMap.keys()) {
|
|
2448
|
-
if (!
|
|
4286
|
+
if (!fs9__namespace.existsSync(path10__namespace.join(config.cwd, snapshotFile))) {
|
|
2449
4287
|
drift.missingOnDisk.push(snapshotFile);
|
|
2450
4288
|
}
|
|
2451
4289
|
}
|
|
4290
|
+
drift.missingOnDisk = [...new Set(drift.missingOnDisk)];
|
|
2452
4291
|
const maxEffectiveBudget = Math.max(
|
|
2453
4292
|
config.maxPartBytes,
|
|
2454
4293
|
config.budgets.default,
|
|
@@ -2459,10 +4298,10 @@ function validateSnapshot(configInput) {
|
|
|
2459
4298
|
if (!part || typeof part.filename !== "string") continue;
|
|
2460
4299
|
partNames.add(part.filename);
|
|
2461
4300
|
if (!config.artifacts.parts) continue;
|
|
2462
|
-
const partPath =
|
|
4301
|
+
const partPath = path10__namespace.join(snapshotDir, "parts", part.filename);
|
|
2463
4302
|
let buffer;
|
|
2464
4303
|
try {
|
|
2465
|
-
buffer =
|
|
4304
|
+
buffer = fs9__namespace.readFileSync(partPath);
|
|
2466
4305
|
} catch {
|
|
2467
4306
|
drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
|
|
2468
4307
|
continue;
|
|
@@ -2549,6 +4388,11 @@ function validateSnapshot(configInput) {
|
|
|
2549
4388
|
}
|
|
2550
4389
|
}
|
|
2551
4390
|
}
|
|
4391
|
+
if (recorded?.preset !== void 0 && recorded.preset !== config.preset) {
|
|
4392
|
+
drift.configMismatches.push(
|
|
4393
|
+
`preset: manifest ${recorded.preset} vs current ${config.preset}`
|
|
4394
|
+
);
|
|
4395
|
+
}
|
|
2552
4396
|
if (recorded?.layersMode !== void 0 && recorded.layersMode !== config.layersMode) {
|
|
2553
4397
|
drift.configMismatches.push(
|
|
2554
4398
|
`layersMode: manifest ${recorded.layersMode} vs current ${config.layersMode}`
|
|
@@ -2566,10 +4410,10 @@ function validateSnapshot(configInput) {
|
|
|
2566
4410
|
...Object.keys(recordedByLayer),
|
|
2567
4411
|
...Object.keys(config.budgets.byLayer)
|
|
2568
4412
|
]);
|
|
2569
|
-
for (const
|
|
2570
|
-
if (recordedByLayer[
|
|
4413
|
+
for (const layer2 of Array.from(layerNames).sort()) {
|
|
4414
|
+
if (recordedByLayer[layer2] !== config.budgets.byLayer[layer2]) {
|
|
2571
4415
|
drift.configMismatches.push(
|
|
2572
|
-
`budgets.byLayer.${
|
|
4416
|
+
`budgets.byLayer.${layer2}: manifest ${recordedByLayer[layer2]} vs current ${config.budgets.byLayer[layer2]}`
|
|
2573
4417
|
);
|
|
2574
4418
|
}
|
|
2575
4419
|
}
|
|
@@ -2652,9 +4496,9 @@ function validateSnapshot(configInput) {
|
|
|
2652
4496
|
["full-codebase.md", manifest.artifacts?.fullMarkdown]
|
|
2653
4497
|
]) {
|
|
2654
4498
|
if (!artifact) continue;
|
|
2655
|
-
const artifactPath =
|
|
4499
|
+
const artifactPath = path10__namespace.join(snapshotDir, key);
|
|
2656
4500
|
try {
|
|
2657
|
-
const buffer =
|
|
4501
|
+
const buffer = fs9__namespace.readFileSync(artifactPath);
|
|
2658
4502
|
if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
|
|
2659
4503
|
drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
|
|
2660
4504
|
}
|
|
@@ -2703,12 +4547,12 @@ function validateSnapshot(configInput) {
|
|
|
2703
4547
|
|
|
2704
4548
|
// src/index.ts
|
|
2705
4549
|
var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
|
|
2706
|
-
var GENERATOR_VERSION = "2.
|
|
4550
|
+
var GENERATOR_VERSION = "2.1.1";
|
|
2707
4551
|
var SCHEMA_VERSION = "3.0.0";
|
|
2708
4552
|
function sha256Hex2(input) {
|
|
2709
4553
|
return crypto__namespace.createHash("sha256").update(input).digest("hex");
|
|
2710
4554
|
}
|
|
2711
|
-
var
|
|
4555
|
+
var TS_EXTENSIONS = [
|
|
2712
4556
|
".ts",
|
|
2713
4557
|
".mts",
|
|
2714
4558
|
".cts",
|
|
@@ -2716,24 +4560,21 @@ var SUMMARY_EXTENSIONS = [
|
|
|
2716
4560
|
".js",
|
|
2717
4561
|
".mjs",
|
|
2718
4562
|
".cjs",
|
|
2719
|
-
".jsx"
|
|
2720
|
-
".json",
|
|
2721
|
-
".jsonc",
|
|
2722
|
-
".md",
|
|
2723
|
-
".mdx"
|
|
4563
|
+
".jsx"
|
|
2724
4564
|
];
|
|
2725
4565
|
function summarizeFile(relativePath, redactedContent) {
|
|
2726
4566
|
const lower = relativePath.toLowerCase();
|
|
2727
|
-
if (!SUMMARY_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
2728
|
-
return void 0;
|
|
2729
|
-
}
|
|
2730
4567
|
if (lower.endsWith(".json") || lower.endsWith(".jsonc")) {
|
|
2731
|
-
return summarizeJson(redactedContent);
|
|
4568
|
+
return { summary: summarizeJson(redactedContent), quality: "structural" };
|
|
2732
4569
|
}
|
|
2733
4570
|
if (lower.endsWith(".md") || lower.endsWith(".mdx")) {
|
|
2734
|
-
return summarizeMarkdown(redactedContent);
|
|
4571
|
+
return { summary: summarizeMarkdown(redactedContent), quality: "structural" };
|
|
4572
|
+
}
|
|
4573
|
+
if (TS_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
4574
|
+
return { summary: summarizeTypeScriptSource(relativePath, redactedContent), quality: "ast" };
|
|
2735
4575
|
}
|
|
2736
|
-
|
|
4576
|
+
const profiled = summarizeWithProfile(relativePath, redactedContent);
|
|
4577
|
+
return profiled ? { summary: profiled.summary, quality: profiled.quality } : void 0;
|
|
2737
4578
|
}
|
|
2738
4579
|
function isSourceSummary(summary) {
|
|
2739
4580
|
return Boolean(summary && typeof summary === "object" && "imports" in summary);
|
|
@@ -2759,8 +4600,8 @@ var IMPORT_CANDIDATE_SUFFIXES = [
|
|
|
2759
4600
|
];
|
|
2760
4601
|
function resolveSpecifier(fromFile, specifier, knownFiles) {
|
|
2761
4602
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
2762
|
-
const baseDir =
|
|
2763
|
-
const joined =
|
|
4603
|
+
const baseDir = path10__namespace.posix.dirname(fromFile);
|
|
4604
|
+
const joined = path10__namespace.posix.normalize(path10__namespace.posix.join(baseDir, specifier));
|
|
2764
4605
|
const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
|
|
2765
4606
|
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
2766
4607
|
const withSuffix = `${joined}${suffix}`;
|
|
@@ -2852,11 +4693,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
2852
4693
|
return errors;
|
|
2853
4694
|
}
|
|
2854
4695
|
function resolveSafeOutputDirs(config) {
|
|
2855
|
-
const cwd =
|
|
2856
|
-
const outDir =
|
|
2857
|
-
const tmpDir =
|
|
2858
|
-
const root =
|
|
2859
|
-
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${
|
|
4696
|
+
const cwd = path10__namespace.resolve(config.cwd);
|
|
4697
|
+
const outDir = path10__namespace.resolve(cwd, config.outDir);
|
|
4698
|
+
const tmpDir = path10__namespace.resolve(cwd, config.tmpDir);
|
|
4699
|
+
const root = path10__namespace.parse(cwd).root;
|
|
4700
|
+
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path10__namespace.sep}`);
|
|
2860
4701
|
const reject = (reason) => {
|
|
2861
4702
|
throw new Error(
|
|
2862
4703
|
`Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
|
|
@@ -2875,26 +4716,66 @@ function resolveSafeOutputDirs(config) {
|
|
|
2875
4716
|
if (contains(tmpDir, outDir)) reject("outDir is inside tmpDir");
|
|
2876
4717
|
return { outDir, tmpDir };
|
|
2877
4718
|
}
|
|
4719
|
+
var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
|
|
4720
|
+
var OWNERSHIP_MARKER = ".codebase-snapshot";
|
|
4721
|
+
function writeOwnershipMarker(dir) {
|
|
4722
|
+
const marker2 = {
|
|
4723
|
+
generator: GENERATOR_NAME,
|
|
4724
|
+
version: GENERATOR_VERSION,
|
|
4725
|
+
schemaVersion: SCHEMA_VERSION
|
|
4726
|
+
};
|
|
4727
|
+
fs9__namespace.writeFileSync(path10__namespace.join(dir, OWNERSHIP_MARKER), `${JSON.stringify(marker2, null, 2)}
|
|
4728
|
+
`, "utf8");
|
|
4729
|
+
}
|
|
4730
|
+
function isOwnOutput(dir) {
|
|
4731
|
+
if (fs9__namespace.existsSync(path10__namespace.join(dir, OWNERSHIP_MARKER))) return true;
|
|
4732
|
+
const manifestPath = path10__namespace.join(dir, "manifest.json");
|
|
4733
|
+
if (!fs9__namespace.existsSync(manifestPath)) return false;
|
|
4734
|
+
try {
|
|
4735
|
+
const parsed = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
4736
|
+
const generator = parsed.generator;
|
|
4737
|
+
return typeof parsed.schemaVersion === "string" && Array.isArray(parsed.files) && typeof generator === "object" && generator !== null && generator.name === GENERATOR_NAME;
|
|
4738
|
+
} catch {
|
|
4739
|
+
return false;
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
function assertReplaceable(target, label) {
|
|
4743
|
+
if (!fs9__namespace.existsSync(target)) return;
|
|
4744
|
+
if (!fs9__namespace.statSync(target).isDirectory()) {
|
|
4745
|
+
throw new Error(
|
|
4746
|
+
`Refusing to write into ${label} ${target}: it exists but is not a directory.`
|
|
4747
|
+
);
|
|
4748
|
+
}
|
|
4749
|
+
const entries = fs9__namespace.readdirSync(target);
|
|
4750
|
+
const meaningful = entries.filter((name) => !PLACEHOLDER_ENTRIES.has(name));
|
|
4751
|
+
if (meaningful.length === 0) return;
|
|
4752
|
+
if (isOwnOutput(target)) return;
|
|
4753
|
+
const preview = meaningful.slice(0, 5).join(", ");
|
|
4754
|
+
const more = meaningful.length > 5 ? `, and ${meaningful.length - 5} more` : "";
|
|
4755
|
+
throw new Error(
|
|
4756
|
+
`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.`
|
|
4757
|
+
);
|
|
4758
|
+
}
|
|
2878
4759
|
function swapIntoPlace(tmpDir, outDir) {
|
|
2879
4760
|
const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
|
|
2880
|
-
const hadPrevious =
|
|
2881
|
-
if (hadPrevious)
|
|
4761
|
+
const hadPrevious = fs9__namespace.existsSync(outDir);
|
|
4762
|
+
if (hadPrevious) fs9__namespace.renameSync(outDir, backupDir);
|
|
2882
4763
|
try {
|
|
2883
4764
|
try {
|
|
2884
|
-
|
|
4765
|
+
fs9__namespace.renameSync(tmpDir, outDir);
|
|
2885
4766
|
} catch (err) {
|
|
2886
4767
|
if (err.code === "EXDEV") {
|
|
2887
|
-
|
|
2888
|
-
|
|
4768
|
+
fs9__namespace.cpSync(tmpDir, outDir, { recursive: true });
|
|
4769
|
+
fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
|
|
2889
4770
|
} else {
|
|
2890
4771
|
throw err;
|
|
2891
4772
|
}
|
|
2892
4773
|
}
|
|
2893
|
-
if (hadPrevious)
|
|
4774
|
+
if (hadPrevious) fs9__namespace.rmSync(backupDir, { recursive: true, force: true });
|
|
2894
4775
|
} catch (err) {
|
|
2895
|
-
if (hadPrevious &&
|
|
2896
|
-
if (
|
|
2897
|
-
|
|
4776
|
+
if (hadPrevious && fs9__namespace.existsSync(backupDir)) {
|
|
4777
|
+
if (fs9__namespace.existsSync(outDir)) fs9__namespace.rmSync(outDir, { recursive: true, force: true });
|
|
4778
|
+
fs9__namespace.renameSync(backupDir, outDir);
|
|
2898
4779
|
}
|
|
2899
4780
|
throw err;
|
|
2900
4781
|
}
|
|
@@ -2910,7 +4791,9 @@ function artifactWarnings(artifacts) {
|
|
|
2910
4791
|
);
|
|
2911
4792
|
}
|
|
2912
4793
|
if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
|
|
2913
|
-
messages.push(
|
|
4794
|
+
messages.push(
|
|
4795
|
+
"every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
|
|
4796
|
+
);
|
|
2914
4797
|
}
|
|
2915
4798
|
return messages;
|
|
2916
4799
|
}
|
|
@@ -2958,7 +4841,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2958
4841
|
const gitInfo = getGitInfo(config.cwd, config.branch);
|
|
2959
4842
|
const collection = collectFiles(config.cwd, {
|
|
2960
4843
|
includePatterns: config.include,
|
|
2961
|
-
excludePatterns: config.excludePatterns,
|
|
4844
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2962
4845
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2963
4846
|
});
|
|
2964
4847
|
const files = [];
|
|
@@ -2971,10 +4854,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2971
4854
|
let totalSizeBytes = 0;
|
|
2972
4855
|
let largeFileCount = 0;
|
|
2973
4856
|
for (const relativePath of collection.files) {
|
|
2974
|
-
const absolutePath =
|
|
4857
|
+
const absolutePath = path10__namespace.join(config.cwd, relativePath);
|
|
2975
4858
|
let stats;
|
|
2976
4859
|
try {
|
|
2977
|
-
stats =
|
|
4860
|
+
stats = fs9__namespace.statSync(absolutePath);
|
|
2978
4861
|
if (!stats.isFile()) {
|
|
2979
4862
|
skipped.push({ path: relativePath, reason: "not-file" });
|
|
2980
4863
|
continue;
|
|
@@ -2989,7 +4872,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2989
4872
|
}
|
|
2990
4873
|
let buffer;
|
|
2991
4874
|
try {
|
|
2992
|
-
buffer =
|
|
4875
|
+
buffer = fs9__namespace.readFileSync(absolutePath);
|
|
2993
4876
|
} catch {
|
|
2994
4877
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
2995
4878
|
continue;
|
|
@@ -2999,10 +4882,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2999
4882
|
const lineCount = countLines(rawContent);
|
|
3000
4883
|
totalLines += lineCount;
|
|
3001
4884
|
totalSizeBytes += stats.size;
|
|
3002
|
-
const
|
|
4885
|
+
const layer2 = classifyLayer(relativePath, config.layers);
|
|
3003
4886
|
const kind = classifyKind(relativePath, config.kinds);
|
|
3004
4887
|
const language = detectLanguage(relativePath);
|
|
3005
|
-
bump(layerCounts,
|
|
4888
|
+
bump(layerCounts, layer2);
|
|
3006
4889
|
bump(kindCounts, kind);
|
|
3007
4890
|
const { redacted, findings } = scanAndRedactSecrets(
|
|
3008
4891
|
rawContent,
|
|
@@ -3026,8 +4909,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3026
4909
|
includedLines = preview.includedLines;
|
|
3027
4910
|
}
|
|
3028
4911
|
let summary;
|
|
4912
|
+
let summaryQuality;
|
|
3029
4913
|
try {
|
|
3030
|
-
|
|
4914
|
+
const outcome = summarizeFile(relativePath, redacted);
|
|
4915
|
+
summary = outcome?.summary;
|
|
4916
|
+
summaryQuality = outcome?.quality;
|
|
3031
4917
|
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
3032
4918
|
warnings.push(
|
|
3033
4919
|
`Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
|
|
@@ -3044,7 +4930,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3044
4930
|
lines: lineCount,
|
|
3045
4931
|
hashSha256,
|
|
3046
4932
|
part: "",
|
|
3047
|
-
layer,
|
|
4933
|
+
layer: layer2,
|
|
3048
4934
|
kind,
|
|
3049
4935
|
language,
|
|
3050
4936
|
inclusionMode: isPreviewTruncated ? "preview" : "full",
|
|
@@ -3054,6 +4940,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3054
4940
|
includedLines,
|
|
3055
4941
|
includedBytes: Buffer.byteLength(finalContent, "utf8"),
|
|
3056
4942
|
summary,
|
|
4943
|
+
summaryQuality,
|
|
3057
4944
|
content: finalContent
|
|
3058
4945
|
});
|
|
3059
4946
|
}
|
|
@@ -3094,10 +4981,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3094
4981
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
3095
4982
|
artifacts: { ...config.artifacts },
|
|
3096
4983
|
include: [...config.include],
|
|
4984
|
+
preset: config.preset,
|
|
3097
4985
|
layersMode: config.layersMode,
|
|
3098
4986
|
budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
|
|
3099
4987
|
excludePatterns: [...config.excludePatterns],
|
|
3100
|
-
layers: config.layers.map((
|
|
4988
|
+
layers: config.layers.map((layer2) => layer2.name),
|
|
3101
4989
|
kinds: config.kinds.map((kind) => kind.kind)
|
|
3102
4990
|
},
|
|
3103
4991
|
counts: {
|
|
@@ -3184,45 +5072,48 @@ function generateSnapshot(configInput, options) {
|
|
|
3184
5072
|
return manifest;
|
|
3185
5073
|
}
|
|
3186
5074
|
const { outDir, tmpDir } = resolveSafeOutputDirs(config);
|
|
5075
|
+
assertReplaceable(tmpDir, "tmpDir");
|
|
5076
|
+
assertReplaceable(outDir, "outDir");
|
|
3187
5077
|
const { artifacts } = config;
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
const
|
|
3192
|
-
|
|
5078
|
+
fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
|
|
5079
|
+
fs9__namespace.mkdirSync(tmpDir, { recursive: true });
|
|
5080
|
+
writeOwnershipMarker(tmpDir);
|
|
5081
|
+
const partsDir = path10__namespace.join(tmpDir, "parts");
|
|
5082
|
+
const reportsDir = path10__namespace.join(tmpDir, "reports");
|
|
5083
|
+
if (artifacts.parts) fs9__namespace.mkdirSync(partsDir, { recursive: true });
|
|
3193
5084
|
if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
|
|
3194
|
-
|
|
5085
|
+
fs9__namespace.mkdirSync(reportsDir, { recursive: true });
|
|
3195
5086
|
}
|
|
3196
5087
|
if (artifacts.parts) {
|
|
3197
5088
|
for (const part of parts) {
|
|
3198
|
-
|
|
5089
|
+
fs9__namespace.writeFileSync(path10__namespace.join(partsDir, part.filename), part.markdown, "utf8");
|
|
3199
5090
|
}
|
|
3200
5091
|
}
|
|
3201
5092
|
if (artifacts.projectTree) {
|
|
3202
5093
|
const projectTree = generateProjectTree(files.map((file) => file.relativePath));
|
|
3203
|
-
|
|
5094
|
+
fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
|
|
3204
5095
|
}
|
|
3205
5096
|
if (artifacts.navigation) {
|
|
3206
|
-
|
|
5097
|
+
fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
|
|
3207
5098
|
}
|
|
3208
5099
|
if (artifacts.audit) {
|
|
3209
|
-
|
|
3210
|
-
|
|
5100
|
+
fs9__namespace.writeFileSync(
|
|
5101
|
+
path10__namespace.join(reportsDir, "audit.md"),
|
|
3211
5102
|
generateAuditReport(manifest, secretFindings),
|
|
3212
5103
|
"utf8"
|
|
3213
5104
|
);
|
|
3214
5105
|
}
|
|
3215
5106
|
if (artifacts.readme) {
|
|
3216
|
-
|
|
5107
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
|
|
3217
5108
|
}
|
|
3218
5109
|
if (artifacts.manifest) {
|
|
3219
|
-
|
|
5110
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3220
5111
|
}
|
|
3221
5112
|
if (monolithJson !== null) {
|
|
3222
|
-
|
|
5113
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
|
|
3223
5114
|
}
|
|
3224
5115
|
if (monolithMarkdown !== null) {
|
|
3225
|
-
|
|
5116
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
|
|
3226
5117
|
}
|
|
3227
5118
|
swapIntoPlace(tmpDir, outDir);
|
|
3228
5119
|
runAfterGenerate(config, manifest);
|
|
@@ -3262,6 +5153,12 @@ var CLI_OPTIONS = {
|
|
|
3262
5153
|
mode: { type: "string" },
|
|
3263
5154
|
"dry-run": { type: "boolean" },
|
|
3264
5155
|
watch: { type: "boolean" },
|
|
5156
|
+
preset: { type: "string" },
|
|
5157
|
+
include: { type: "string" },
|
|
5158
|
+
exclude: { type: "string" },
|
|
5159
|
+
yes: { type: "boolean" },
|
|
5160
|
+
minimal: { type: "boolean" },
|
|
5161
|
+
force: { type: "boolean" },
|
|
3265
5162
|
help: { type: "boolean", short: "h" },
|
|
3266
5163
|
version: { type: "boolean", short: "v" }
|
|
3267
5164
|
};
|
|
@@ -3299,6 +5196,14 @@ Options:
|
|
|
3299
5196
|
--format <text|json> CLI output format (default: text)
|
|
3300
5197
|
--dry-run Compute the snapshot without writing anything
|
|
3301
5198
|
--watch Re-generate whenever the repository changes
|
|
5199
|
+
|
|
5200
|
+
init only:
|
|
5201
|
+
--preset <name> Force a project preset instead of detecting one
|
|
5202
|
+
--include <globs> Comma-separated allow-list
|
|
5203
|
+
--exclude <globs> Comma-separated exclude patterns
|
|
5204
|
+
--minimal Write only the settings that differ from defaults
|
|
5205
|
+
--yes Accept every default without prompting
|
|
5206
|
+
--force Overwrite an existing config
|
|
3302
5207
|
-h, --help Display this help message
|
|
3303
5208
|
-v, --version Display version
|
|
3304
5209
|
|
|
@@ -3355,9 +5260,32 @@ function parseCliArgs(argv) {
|
|
|
3355
5260
|
strict: true
|
|
3356
5261
|
});
|
|
3357
5262
|
} catch (err) {
|
|
3358
|
-
|
|
5263
|
+
const message = err.message;
|
|
5264
|
+
const ambiguous = /^Option '(--[^']+)' argument is ambiguous/i.exec(message);
|
|
5265
|
+
if (ambiguous) {
|
|
5266
|
+
throw new UsageError(
|
|
5267
|
+
`${ambiguous[1]} received a value that looks like another flag (for example a negative number). Use the "--option=value" form, and note that sizes must be positive.`
|
|
5268
|
+
);
|
|
5269
|
+
}
|
|
5270
|
+
throw new UsageError(message);
|
|
3359
5271
|
}
|
|
3360
5272
|
const values = parsed.values;
|
|
5273
|
+
for (const [name, spec] of Object.entries(CLI_OPTIONS)) {
|
|
5274
|
+
if (spec.type !== "string") continue;
|
|
5275
|
+
const raw = values[name];
|
|
5276
|
+
if (raw !== void 0 && String(raw).trim() === "") {
|
|
5277
|
+
throw new UsageError(`--${name} requires a value (received an empty string).`);
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
if (values.cwd !== void 0) {
|
|
5281
|
+
const target = String(values.cwd);
|
|
5282
|
+
if (!fs9__namespace.existsSync(target)) {
|
|
5283
|
+
throw new UsageError(`--cwd directory does not exist: ${target}`);
|
|
5284
|
+
}
|
|
5285
|
+
if (!fs9__namespace.statSync(target).isDirectory()) {
|
|
5286
|
+
throw new UsageError(`--cwd is not a directory: ${target}`);
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
3361
5289
|
if (values.help) {
|
|
3362
5290
|
process.stdout.write(`${USAGE}
|
|
3363
5291
|
`);
|
|
@@ -3423,13 +5351,38 @@ function parseCliArgs(argv) {
|
|
|
3423
5351
|
} else if (rawSkip !== void 0) {
|
|
3424
5352
|
skipArtifacts = parseArtifactList(String(rawSkip), "--skip-artifacts");
|
|
3425
5353
|
}
|
|
5354
|
+
if (command !== "init") {
|
|
5355
|
+
const initOnly = [];
|
|
5356
|
+
if (values.preset !== void 0) initOnly.push("--preset");
|
|
5357
|
+
if (values.include !== void 0) initOnly.push("--include");
|
|
5358
|
+
if (values.exclude !== void 0) initOnly.push("--exclude");
|
|
5359
|
+
if (values.yes === true) initOnly.push("--yes");
|
|
5360
|
+
if (values.minimal === true) initOnly.push("--minimal");
|
|
5361
|
+
if (values.force === true) initOnly.push("--force");
|
|
5362
|
+
if (initOnly.length > 0) {
|
|
5363
|
+
throw new UsageError(
|
|
5364
|
+
`${initOnly.join(", ")} ${initOnly.length === 1 ? "is" : "are"} only valid with \`init\`. To scope \`${command}\`, set include/excludePatterns/preset in snapshot.config.ts (or run \`snap init\` to generate one).`
|
|
5365
|
+
);
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
if (command === "init" && values.preset !== void 0) {
|
|
5369
|
+
const requested = String(values.preset);
|
|
5370
|
+
if (!getPreset(requested)) throw new UsageError(unknownPresetMessage(requested));
|
|
5371
|
+
}
|
|
3426
5372
|
return {
|
|
3427
5373
|
command,
|
|
3428
5374
|
args,
|
|
3429
5375
|
format: rawFormat,
|
|
3430
5376
|
dryRun: values["dry-run"] === true,
|
|
3431
5377
|
watch: values.watch === true,
|
|
3432
|
-
|
|
5378
|
+
preset: values.preset === void 0 ? void 0 : String(values.preset),
|
|
5379
|
+
maxPartBytesRaw: values["max-part-bytes"] === void 0 ? void 0 : String(values["max-part-bytes"]),
|
|
5380
|
+
include: values.include === void 0 ? void 0 : String(values.include),
|
|
5381
|
+
exclude: values.exclude === void 0 ? void 0 : String(values.exclude),
|
|
5382
|
+
yes: values.yes === true,
|
|
5383
|
+
minimal: values.minimal === true,
|
|
5384
|
+
force: values.force === true,
|
|
5385
|
+
cwd: values.cwd === void 0 ? process.cwd() : path10__namespace.resolve(String(values.cwd)),
|
|
3433
5386
|
outDir: values["out-dir"] === void 0 ? void 0 : String(values["out-dir"]),
|
|
3434
5387
|
includeMonolith: values["include-monolith"] === true ? true : void 0,
|
|
3435
5388
|
includeSensitiveFiles: values["include-sensitive-files"] === true ? true : void 0,
|
|
@@ -3469,32 +5422,65 @@ function emit(format, payload, textRenderer) {
|
|
|
3469
5422
|
}
|
|
3470
5423
|
textRenderer();
|
|
3471
5424
|
}
|
|
3472
|
-
function
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
5425
|
+
function splitList(value) {
|
|
5426
|
+
if (value === void 0) return void 0;
|
|
5427
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
5428
|
+
}
|
|
5429
|
+
function describeManifestShapeProblem(manifest) {
|
|
5430
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
5431
|
+
return "not a JSON object";
|
|
5432
|
+
}
|
|
5433
|
+
const candidate = manifest;
|
|
5434
|
+
const counts = candidate.counts;
|
|
5435
|
+
if (!counts || typeof counts !== "object") return "missing `counts`";
|
|
5436
|
+
for (const key of ["totalFiles", "totalLines", "totalSizeBytes", "partsCount"]) {
|
|
5437
|
+
if (typeof counts[key] !== "number") return `missing numeric \`counts.${key}\``;
|
|
5438
|
+
}
|
|
5439
|
+
if (!candidate.layers || typeof candidate.layers !== "object") return "missing `layers`";
|
|
5440
|
+
if (!Array.isArray(candidate.files)) return "missing `files` array";
|
|
5441
|
+
return null;
|
|
5442
|
+
}
|
|
5443
|
+
async function runInitCommand(invocation) {
|
|
5444
|
+
try {
|
|
5445
|
+
const result = await runInit({
|
|
5446
|
+
cwd: invocation.cwd,
|
|
5447
|
+
preset: invocation.preset,
|
|
5448
|
+
maxPartBytes: invocation.maxPartBytesRaw ?? invocation.maxPartBytes,
|
|
5449
|
+
include: splitList(invocation.include),
|
|
5450
|
+
exclude: splitList(invocation.exclude),
|
|
5451
|
+
skipArtifacts: invocation.skipArtifacts,
|
|
5452
|
+
includeSensitiveFiles: invocation.includeSensitiveFiles,
|
|
5453
|
+
yes: invocation.yes,
|
|
5454
|
+
minimal: invocation.minimal,
|
|
5455
|
+
force: invocation.force,
|
|
5456
|
+
dryRun: invocation.dryRun
|
|
5457
|
+
});
|
|
5458
|
+
if (invocation.format === "json") {
|
|
5459
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
5460
|
+
`);
|
|
5461
|
+
return 0;
|
|
5462
|
+
}
|
|
5463
|
+
if (!result.written) {
|
|
5464
|
+
info(result.configText);
|
|
5465
|
+
return 0;
|
|
5466
|
+
}
|
|
5467
|
+
info(`\u2705 Created ${path10__namespace.basename(result.configPath)} (preset: ${result.preset})`);
|
|
5468
|
+
if (result.scripts.length > 0) info(` Added npm scripts: ${result.scripts.join(", ")}`);
|
|
5469
|
+
if (result.gitignoreEntries.length > 0) {
|
|
5470
|
+
info(` Added to .gitignore: ${result.gitignoreEntries.join(", ")}`);
|
|
5471
|
+
}
|
|
5472
|
+
for (const note of result.notes) info(` \u26A0 ${note}`);
|
|
5473
|
+
info("");
|
|
5474
|
+
info(" Next: npm run snapshot");
|
|
5475
|
+
return 0;
|
|
5476
|
+
} catch (err) {
|
|
5477
|
+
if (err instanceof PromptCancelled) {
|
|
5478
|
+
warn("\u274C Cancelled.");
|
|
5479
|
+
return 1;
|
|
5480
|
+
}
|
|
5481
|
+
warn(`\u274C ${err.message}`);
|
|
3477
5482
|
return 1;
|
|
3478
5483
|
}
|
|
3479
|
-
const importPath = JSON.stringify(GENERATOR_NAME);
|
|
3480
|
-
const starter = `import { defineConfig } from ${importPath};
|
|
3481
|
-
|
|
3482
|
-
export default defineConfig({
|
|
3483
|
-
branch: "main",
|
|
3484
|
-
outDir: "snapshot",
|
|
3485
|
-
maxPartBytes: 300 * 1024,
|
|
3486
|
-
largeFileBytes: 200 * 1024,
|
|
3487
|
-
excludePatterns: [],
|
|
3488
|
-
});
|
|
3489
|
-
`;
|
|
3490
|
-
fs8__namespace.writeFileSync(targetPath, starter, "utf8");
|
|
3491
|
-
info(`\u2705 Created ${targetName} in ${cwd}`);
|
|
3492
|
-
if (targetName.endsWith(".mjs")) {
|
|
3493
|
-
info(
|
|
3494
|
-
" (a .ts config needs Node >= 22.6 to import natively; this Node writes a .mjs instead)"
|
|
3495
|
-
);
|
|
3496
|
-
}
|
|
3497
|
-
return 0;
|
|
3498
5484
|
}
|
|
3499
5485
|
function renderValidateText(result) {
|
|
3500
5486
|
if (result.valid) {
|
|
@@ -3517,6 +5503,9 @@ function renderValidateText(result) {
|
|
|
3517
5503
|
for (const missing of result.drift.missingOnDisk.slice(0, 20)) {
|
|
3518
5504
|
warn(` - deleted: ${missing}`);
|
|
3519
5505
|
}
|
|
5506
|
+
for (const mismatch of result.drift.totalsMismatches.slice(0, 20)) {
|
|
5507
|
+
warn(` - counts: ${mismatch}`);
|
|
5508
|
+
}
|
|
3520
5509
|
}
|
|
3521
5510
|
for (const warning of result.warnings) warn(` \u26A0 ${warning}`);
|
|
3522
5511
|
}
|
|
@@ -3536,11 +5525,15 @@ ${USAGE}
|
|
|
3536
5525
|
throw err;
|
|
3537
5526
|
}
|
|
3538
5527
|
if (invocation.command === "init") {
|
|
3539
|
-
return
|
|
5528
|
+
return await runInitCommand(invocation);
|
|
3540
5529
|
}
|
|
3541
5530
|
const loaded = await loadConfigFileDetailed(invocation.cwd);
|
|
3542
5531
|
for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
|
|
3543
|
-
if (loaded.
|
|
5532
|
+
if (loaded.errors.length > 0) {
|
|
5533
|
+
warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
|
|
5534
|
+
return 1;
|
|
5535
|
+
}
|
|
5536
|
+
if (loaded.file) info(`\u2699 Using config ${path10__namespace.relative(process.cwd(), loaded.file) || loaded.file}`);
|
|
3544
5537
|
const resolved = resolveConfig(
|
|
3545
5538
|
loaded.config,
|
|
3546
5539
|
{
|
|
@@ -3564,18 +5557,23 @@ ${USAGE}
|
|
|
3564
5557
|
return result.valid ? 0 : 1;
|
|
3565
5558
|
}
|
|
3566
5559
|
if (invocation.command === "report") {
|
|
3567
|
-
const manifestPath =
|
|
3568
|
-
if (!
|
|
5560
|
+
const manifestPath = path10__namespace.resolve(resolved.cwd, resolved.outDir, "manifest.json");
|
|
5561
|
+
if (!fs9__namespace.existsSync(manifestPath)) {
|
|
3569
5562
|
warn(`\u274C Manifest not found at ${manifestPath}. Run generate first.`);
|
|
3570
5563
|
return 1;
|
|
3571
5564
|
}
|
|
3572
5565
|
let manifest;
|
|
3573
5566
|
try {
|
|
3574
|
-
manifest = JSON.parse(
|
|
5567
|
+
manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
3575
5568
|
} catch (err) {
|
|
3576
5569
|
warn(`\u274C Could not read ${manifestPath}: ${err.message}`);
|
|
3577
5570
|
return 1;
|
|
3578
5571
|
}
|
|
5572
|
+
const shapeError = describeManifestShapeProblem(manifest);
|
|
5573
|
+
if (shapeError) {
|
|
5574
|
+
warn(`\u274C ${manifestPath} is not a snapshot manifest: ${shapeError}`);
|
|
5575
|
+
return 1;
|
|
5576
|
+
}
|
|
3579
5577
|
const validation = validateSnapshot(resolved);
|
|
3580
5578
|
emit(invocation.format, { manifest, validation }, () => {
|
|
3581
5579
|
info(`
|
|
@@ -3592,8 +5590,8 @@ ${USAGE}
|
|
|
3592
5590
|
info(` - Integrity: ${validation.valid ? "\u2705 Clean (no drift)" : "\u274C Drift detected"}`);
|
|
3593
5591
|
info(` - Warnings: ${manifest.warnings?.length ?? 0}`);
|
|
3594
5592
|
info("\n Layers:");
|
|
3595
|
-
for (const [
|
|
3596
|
-
info(` - ${
|
|
5593
|
+
for (const [layer2, count] of Object.entries(manifest.layers)) {
|
|
5594
|
+
info(` - ${layer2.padEnd(16)}: ${count} files`);
|
|
3597
5595
|
}
|
|
3598
5596
|
});
|
|
3599
5597
|
return validation.valid ? 0 : 1;
|
|
@@ -3638,9 +5636,23 @@ ${USAGE}
|
|
|
3638
5636
|
info(
|
|
3639
5637
|
` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
|
|
3640
5638
|
);
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
5639
|
+
const { artifacts } = resolved;
|
|
5640
|
+
if (artifacts.parts) {
|
|
5641
|
+
info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
|
|
5642
|
+
}
|
|
5643
|
+
const reports = [
|
|
5644
|
+
artifacts.projectTree && "project-tree",
|
|
5645
|
+
artifacts.navigation && "navigation",
|
|
5646
|
+
artifacts.audit && "audit"
|
|
5647
|
+
].filter(Boolean);
|
|
5648
|
+
if (reports.length > 0) {
|
|
5649
|
+
info(` - Reports: ${resolved.outDir}/reports/ (${reports.join(", ")})`);
|
|
5650
|
+
}
|
|
5651
|
+
if (artifacts.manifest) info(` - Manifest: ${resolved.outDir}/manifest.json`);
|
|
5652
|
+
if (artifacts.monolith) info(` - Monolith: ${resolved.outDir}/full-codebase.{json,md}`);
|
|
5653
|
+
if (reports.length === 0 && !artifacts.parts && !artifacts.manifest && !artifacts.monolith) {
|
|
5654
|
+
info(` - Artifacts: all disabled (only the ownership marker was written)`);
|
|
5655
|
+
}
|
|
3644
5656
|
info(` - Fingerprint: ${manifest.fingerprint}`);
|
|
3645
5657
|
info(` - Integrity: ${manifest.validation.status}`);
|
|
3646
5658
|
if (manifest.warnings.length > 0) {
|
|
@@ -3657,15 +5669,23 @@ ${USAGE}
|
|
|
3657
5669
|
}
|
|
3658
5670
|
return manifest.validation.status === "passed" ? 0 : 1;
|
|
3659
5671
|
} catch (err) {
|
|
5672
|
+
const code = err.code;
|
|
5673
|
+
if (code === "EPERM" || code === "EACCES") {
|
|
5674
|
+
warn(
|
|
5675
|
+
`\u274C Cannot write the snapshot: permission denied under ${resolved.outDir}. Check the directory's permissions or choose another --out-dir.`
|
|
5676
|
+
);
|
|
5677
|
+
warn(` (${err.message})`);
|
|
5678
|
+
return 1;
|
|
5679
|
+
}
|
|
3660
5680
|
warn(`\u274C Failed to generate snapshot: ${err.message}`);
|
|
3661
5681
|
return 1;
|
|
3662
5682
|
}
|
|
3663
5683
|
}
|
|
3664
5684
|
async function runWatchLoop(invocation, resolved) {
|
|
3665
|
-
const outDir =
|
|
3666
|
-
const tmpDir =
|
|
3667
|
-
const outName =
|
|
3668
|
-
const tmpName =
|
|
5685
|
+
const outDir = path10__namespace.resolve(resolved.cwd, resolved.outDir);
|
|
5686
|
+
const tmpDir = path10__namespace.resolve(resolved.cwd, resolved.tmpDir);
|
|
5687
|
+
const outName = path10__namespace.basename(outDir).toLowerCase();
|
|
5688
|
+
const tmpName = path10__namespace.basename(tmpDir).toLowerCase();
|
|
3669
5689
|
const isGenerated = (candidate) => candidate.toLowerCase().split(/[\\/]/).some(
|
|
3670
5690
|
(segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`)
|
|
3671
5691
|
);
|
|
@@ -3689,19 +5709,19 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
3689
5709
|
info("\u{1F440} Watching for changes. Press Ctrl+C to stop.");
|
|
3690
5710
|
regenerate();
|
|
3691
5711
|
let timer = null;
|
|
3692
|
-
const watcher =
|
|
5712
|
+
const watcher = fs9__namespace.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
|
|
3693
5713
|
if (filename) {
|
|
3694
|
-
const changed =
|
|
5714
|
+
const changed = path10__namespace.resolve(resolved.cwd, filename.toString());
|
|
3695
5715
|
if (isGenerated(changed)) return;
|
|
3696
5716
|
}
|
|
3697
5717
|
if (timer) clearTimeout(timer);
|
|
3698
5718
|
timer = setTimeout(regenerate, 250);
|
|
3699
5719
|
});
|
|
3700
|
-
await new Promise((
|
|
5720
|
+
await new Promise((resolve6) => {
|
|
3701
5721
|
const stop = () => {
|
|
3702
5722
|
if (timer) clearTimeout(timer);
|
|
3703
5723
|
watcher.close();
|
|
3704
|
-
|
|
5724
|
+
resolve6();
|
|
3705
5725
|
};
|
|
3706
5726
|
process.once("SIGINT", stop);
|
|
3707
5727
|
process.once("SIGTERM", stop);
|