@ankurhalder/codebase-snapshot 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/bin.js +2330 -367
- package/dist/{chunk-RGDVLQTT.js → chunk-QEIOXPMU.js} +2242 -366
- package/dist/cli.cjs +2346 -390
- package/dist/cli.d.cts +9 -1
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +108 -27
- package/dist/index.cjs +2255 -365
- package/dist/index.d.cts +183 -6
- package/dist/index.d.ts +183 -6
- package/dist/index.js +1 -1
- package/dist/{types-S-5eRuRc.d.cts → types-CJqkePM4.d.cts} +20 -1
- package/dist/{types-S-5eRuRc.d.ts → types-CJqkePM4.d.ts} +20 -1
- package/docs/configuration.md +97 -1
- package/docs/install.md +136 -0
- package/package.json +3 -2
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,10 @@ function compareManifests(before, after) {
|
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
function loadManifest(target) {
|
|
82
|
-
const manifestPath =
|
|
84
|
+
const manifestPath = fs9__namespace.statSync(target).isDirectory() ? path10__namespace.join(target, "manifest.json") : target;
|
|
83
85
|
let raw;
|
|
84
86
|
try {
|
|
85
|
-
raw =
|
|
87
|
+
raw = fs9__namespace.readFileSync(manifestPath, "utf8");
|
|
86
88
|
} catch {
|
|
87
89
|
throw new Error(`Could not read a manifest at ${manifestPath}.`);
|
|
88
90
|
}
|
|
@@ -117,6 +119,138 @@ function renderDiffText(diff) {
|
|
|
117
119
|
section("changed:", diff.changed);
|
|
118
120
|
return lines.join("\n");
|
|
119
121
|
}
|
|
122
|
+
var MANIFEST_FILES = [
|
|
123
|
+
"package.json",
|
|
124
|
+
"pyproject.toml",
|
|
125
|
+
"requirements.txt",
|
|
126
|
+
"setup.py",
|
|
127
|
+
"go.mod",
|
|
128
|
+
"Cargo.toml",
|
|
129
|
+
"pom.xml",
|
|
130
|
+
"build.gradle",
|
|
131
|
+
"build.gradle.kts",
|
|
132
|
+
"composer.json",
|
|
133
|
+
"Gemfile"
|
|
134
|
+
];
|
|
135
|
+
var MANIFEST_MAX_BYTES = 1e5;
|
|
136
|
+
function readManifest(cwd, name) {
|
|
137
|
+
const full = path10__namespace.join(cwd, name);
|
|
138
|
+
try {
|
|
139
|
+
const stats = fs9__namespace.statSync(full);
|
|
140
|
+
if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
|
|
141
|
+
return fs9__namespace.readFileSync(full, "utf8");
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function collectDependencies(manifests) {
|
|
147
|
+
const names = /* @__PURE__ */ new Set();
|
|
148
|
+
const raw = manifests["package.json"];
|
|
149
|
+
if (!raw) return names;
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(raw);
|
|
152
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
153
|
+
const block = parsed[section];
|
|
154
|
+
if (block && typeof block === "object") {
|
|
155
|
+
for (const name of Object.keys(block)) names.add(name);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
return names;
|
|
161
|
+
}
|
|
162
|
+
function buildPresetContext(cwd) {
|
|
163
|
+
const files = /* @__PURE__ */ new Set();
|
|
164
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
165
|
+
try {
|
|
166
|
+
for (const entry of fs9__namespace.readdirSync(cwd, { withFileTypes: true })) {
|
|
167
|
+
if (entry.isDirectory()) dirs.add(entry.name);
|
|
168
|
+
else if (entry.isFile()) files.add(entry.name);
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
const manifests = {};
|
|
173
|
+
for (const name of MANIFEST_FILES) {
|
|
174
|
+
const content = readManifest(cwd, name);
|
|
175
|
+
if (content !== null) manifests[name] = content;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
cwd,
|
|
179
|
+
files,
|
|
180
|
+
dirs,
|
|
181
|
+
dependencies: collectDependencies(manifests),
|
|
182
|
+
manifests
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function hasFile(context, name) {
|
|
186
|
+
return context.files.has(name);
|
|
187
|
+
}
|
|
188
|
+
function hasFileStartingWith(context, prefix) {
|
|
189
|
+
for (const file of context.files) {
|
|
190
|
+
if (file.startsWith(prefix)) return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
function manifestContains(context, manifest, needle) {
|
|
195
|
+
const content = context.manifests[manifest];
|
|
196
|
+
if (!content) return false;
|
|
197
|
+
return content.toLowerCase().includes(needle.toLowerCase());
|
|
198
|
+
}
|
|
199
|
+
function hasDependency(context, ...names) {
|
|
200
|
+
return names.some((name) => context.dependencies.has(name));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/presets/helpers.ts
|
|
204
|
+
function layer(name, ...prefixes) {
|
|
205
|
+
return {
|
|
206
|
+
name,
|
|
207
|
+
match: (filePath) => prefixes.some((prefix) => filePath.startsWith(prefix))
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function kindOf(name, ...needles) {
|
|
211
|
+
return {
|
|
212
|
+
kind: name,
|
|
213
|
+
match: (filePath) => needles.some((needle) => filePath.includes(needle))
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function suffixKind(name, ...suffixes) {
|
|
217
|
+
return {
|
|
218
|
+
kind: name,
|
|
219
|
+
match: (filePath) => suffixes.some((suffix) => filePath.endsWith(suffix))
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function commonLayers() {
|
|
223
|
+
return [
|
|
224
|
+
layer("scripts", "scripts/", "tools/"),
|
|
225
|
+
layer("docs", "docs/", "documentation/"),
|
|
226
|
+
{
|
|
227
|
+
name: "config",
|
|
228
|
+
match: (filePath) => filePath.startsWith(".github/") || filePath.startsWith(".husky/") || filePath.startsWith(".vscode/") || filePath.startsWith(".") || filePath.includes("config")
|
|
229
|
+
},
|
|
230
|
+
{ name: "root", match: (filePath) => !filePath.includes("/") }
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
function commonKinds() {
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
kind: "test",
|
|
237
|
+
match: (filePath) => {
|
|
238
|
+
const lower = filePath.toLowerCase();
|
|
239
|
+
return lower.includes(".test.") || lower.includes(".spec.") || lower.startsWith("test/") || lower.startsWith("tests/") || lower.includes("_test.") || lower.endsWith("_test.go") || lower.includes("__tests__");
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
suffixKind("doc", ".md", ".mdx", ".rst", ".txt"),
|
|
243
|
+
suffixKind("data", ".json", ".yaml", ".yml", ".csv", ".toml"),
|
|
244
|
+
{
|
|
245
|
+
kind: "script",
|
|
246
|
+
match: (filePath) => filePath.startsWith("scripts/") || /\.(sh|ps1|bat)$/i.test(filePath)
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
kind: "config",
|
|
250
|
+
match: (filePath) => filePath.startsWith(".") || filePath.includes("config") || filePath === "package.json" || filePath === "tsconfig.json"
|
|
251
|
+
}
|
|
252
|
+
];
|
|
253
|
+
}
|
|
120
254
|
|
|
121
255
|
// src/presets/nextjs.ts
|
|
122
256
|
var NEXTJS_LAYERS = [
|
|
@@ -173,6 +307,47 @@ var NEXTJS_KINDS = [
|
|
|
173
307
|
}
|
|
174
308
|
];
|
|
175
309
|
|
|
310
|
+
// src/presets/node.ts
|
|
311
|
+
var NODE_LAYERS = [
|
|
312
|
+
{ name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
|
|
313
|
+
{ name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
|
|
314
|
+
{ name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
|
|
315
|
+
{ name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
|
|
316
|
+
{ name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
|
|
317
|
+
{ name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
|
|
318
|
+
{ name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
|
|
319
|
+
{ name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
|
|
320
|
+
{ name: "scripts", match: (p) => p.startsWith("scripts/") },
|
|
321
|
+
{ name: "docs", match: (p) => p.startsWith("docs/") },
|
|
322
|
+
{
|
|
323
|
+
name: "config",
|
|
324
|
+
match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
|
|
325
|
+
},
|
|
326
|
+
{ name: "root", match: (p) => !p.includes("/") }
|
|
327
|
+
];
|
|
328
|
+
var NODE_KINDS = [
|
|
329
|
+
{ kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
|
|
330
|
+
{ kind: "route", match: (p) => p.includes("/routes/") || p.includes("route.") || p.includes("/api/") },
|
|
331
|
+
{ kind: "controller", match: (p) => p.includes("controller") },
|
|
332
|
+
{ kind: "service", match: (p) => p.includes("service") },
|
|
333
|
+
{ kind: "provider", match: (p) => p.includes("provider") },
|
|
334
|
+
{ kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
|
|
335
|
+
{ kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
|
|
336
|
+
{ kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
|
|
337
|
+
{ kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
|
|
338
|
+
{ kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
|
|
339
|
+
{ kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
|
|
340
|
+
{ kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
|
|
341
|
+
{ kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
|
|
342
|
+
{
|
|
343
|
+
kind: "config",
|
|
344
|
+
match: (p) => {
|
|
345
|
+
const base = p.split("/").pop() || "";
|
|
346
|
+
return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
|
|
176
351
|
// src/presets/vite.ts
|
|
177
352
|
var VITE_LAYERS = [
|
|
178
353
|
{ name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
|
|
@@ -220,44 +395,419 @@ var VITE_KINDS = [
|
|
|
220
395
|
}
|
|
221
396
|
];
|
|
222
397
|
|
|
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/") },
|
|
398
|
+
// src/presets/js-presets.ts
|
|
399
|
+
var NEXT_LIKE_EXCLUDES = [".svelte-kit", ".astro", ".vite", ".output"];
|
|
400
|
+
var JS_PRESETS = [
|
|
235
401
|
{
|
|
236
|
-
name: "
|
|
237
|
-
|
|
402
|
+
name: "nextjs",
|
|
403
|
+
priority: 100,
|
|
404
|
+
detect: (context) => hasFileStartingWith(context, "next.config") || hasDependency(context, "next"),
|
|
405
|
+
layers: NEXTJS_LAYERS,
|
|
406
|
+
kinds: NEXTJS_KINDS,
|
|
407
|
+
exclude: NEXT_LIKE_EXCLUDES,
|
|
408
|
+
recommendedBudget: "300k"
|
|
238
409
|
},
|
|
239
|
-
{
|
|
410
|
+
{
|
|
411
|
+
name: "nuxt",
|
|
412
|
+
priority: 98,
|
|
413
|
+
detect: (context) => hasFileStartingWith(context, "nuxt.config") || hasDependency(context, "nuxt", "nuxt3"),
|
|
414
|
+
layers: [
|
|
415
|
+
layer("pages", "pages/", "src/pages/"),
|
|
416
|
+
layer("components", "components/", "src/components/"),
|
|
417
|
+
layer("composables", "composables/", "src/composables/"),
|
|
418
|
+
layer("server", "server/", "src/server/", "api/"),
|
|
419
|
+
layer("stores", "stores/", "src/stores/"),
|
|
420
|
+
layer("layouts", "layouts/", "src/layouts/"),
|
|
421
|
+
...commonLayers()
|
|
422
|
+
],
|
|
423
|
+
kinds: [
|
|
424
|
+
kindOf("api-route", "server/api/", "server/routes/"),
|
|
425
|
+
suffixKind("component", ".vue"),
|
|
426
|
+
kindOf("composable", "composables/"),
|
|
427
|
+
kindOf("store", "stores/", "store/"),
|
|
428
|
+
kindOf("layout", "layouts/"),
|
|
429
|
+
...commonKinds()
|
|
430
|
+
],
|
|
431
|
+
exclude: [".nuxt", ".output"],
|
|
432
|
+
recommendedBudget: "200k"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "sveltekit",
|
|
436
|
+
priority: 96,
|
|
437
|
+
detect: (context) => hasFileStartingWith(context, "svelte.config") || hasDependency(context, "@sveltejs/kit", "svelte"),
|
|
438
|
+
layers: [
|
|
439
|
+
layer("routes", "src/routes/", "routes/"),
|
|
440
|
+
layer("lib", "src/lib/", "lib/"),
|
|
441
|
+
layer("components", "src/lib/components/", "src/components/"),
|
|
442
|
+
layer("stores", "src/lib/stores/", "src/stores/"),
|
|
443
|
+
...commonLayers()
|
|
444
|
+
],
|
|
445
|
+
kinds: [
|
|
446
|
+
kindOf("route", "+page.", "+server.", "+layout."),
|
|
447
|
+
suffixKind("component", ".svelte"),
|
|
448
|
+
kindOf("store", "stores/"),
|
|
449
|
+
kindOf("params", "params/"),
|
|
450
|
+
...commonKinds()
|
|
451
|
+
],
|
|
452
|
+
exclude: [".svelte-kit", ".vercel"],
|
|
453
|
+
recommendedBudget: "200k"
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: "remix",
|
|
457
|
+
priority: 94,
|
|
458
|
+
detect: (context) => hasDependency(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
|
|
459
|
+
layers: [
|
|
460
|
+
layer("routes", "app/routes/", "routes/"),
|
|
461
|
+
layer("components", "app/components/"),
|
|
462
|
+
layer("lib", "app/lib/", "app/utils/"),
|
|
463
|
+
...commonLayers()
|
|
464
|
+
],
|
|
465
|
+
kinds: [kindOf("route", "routes/"), ...commonKinds()],
|
|
466
|
+
exclude: [".cache", "public/build"],
|
|
467
|
+
recommendedBudget: "200k"
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: "astro",
|
|
471
|
+
priority: 92,
|
|
472
|
+
detect: (context) => hasFileStartingWith(context, "astro.config") || hasDependency(context, "astro"),
|
|
473
|
+
layers: [
|
|
474
|
+
layer("pages", "src/pages/"),
|
|
475
|
+
layer("layouts", "src/layouts/"),
|
|
476
|
+
layer("components", "src/components/"),
|
|
477
|
+
layer("content", "src/content/"),
|
|
478
|
+
...commonLayers()
|
|
479
|
+
],
|
|
480
|
+
kinds: [suffixKind("page", ".astro"), kindOf("layout", "layouts/"), ...commonKinds()],
|
|
481
|
+
exclude: [".astro", "dist"],
|
|
482
|
+
recommendedBudget: "150k"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "angular",
|
|
486
|
+
priority: 90,
|
|
487
|
+
detect: (context) => hasFile(context, "angular.json") || hasDependency(context, "@angular/core"),
|
|
488
|
+
layers: [
|
|
489
|
+
layer("app", "src/app/"),
|
|
490
|
+
layer("features", "src/app/features/"),
|
|
491
|
+
layer("shared", "src/app/shared/"),
|
|
492
|
+
layer("core", "src/app/core/"),
|
|
493
|
+
layer("assets", "src/assets/"),
|
|
494
|
+
...commonLayers()
|
|
495
|
+
],
|
|
496
|
+
kinds: [
|
|
497
|
+
suffixKind("component", ".component.ts"),
|
|
498
|
+
suffixKind("service", ".service.ts"),
|
|
499
|
+
suffixKind("module", ".module.ts"),
|
|
500
|
+
suffixKind("guard", ".guard.ts"),
|
|
501
|
+
suffixKind("pipe", ".pipe.ts"),
|
|
502
|
+
suffixKind("directive", ".directive.ts"),
|
|
503
|
+
...commonKinds()
|
|
504
|
+
],
|
|
505
|
+
exclude: [".angular", "dist"],
|
|
506
|
+
recommendedBudget: "200k"
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: "solid",
|
|
510
|
+
priority: 88,
|
|
511
|
+
detect: (context) => hasDependency(context, "solid-js", "solid-start"),
|
|
512
|
+
layers: [
|
|
513
|
+
layer("pages", "src/routes/", "src/pages/"),
|
|
514
|
+
layer("components", "src/components/"),
|
|
515
|
+
...commonLayers()
|
|
516
|
+
],
|
|
517
|
+
kinds: [kindOf("route", "routes/"), ...commonKinds()],
|
|
518
|
+
exclude: [".solid", "dist"],
|
|
519
|
+
recommendedBudget: "120k"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: "vue-vite",
|
|
523
|
+
priority: 86,
|
|
524
|
+
detect: (context) => hasDependency(context, "vue") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
|
|
525
|
+
layers: [
|
|
526
|
+
layer("pages", "src/pages/", "src/views/"),
|
|
527
|
+
layer("components", "src/components/"),
|
|
528
|
+
layer("composables", "src/composables/", "src/hooks/"),
|
|
529
|
+
layer("stores", "src/stores/", "src/store/"),
|
|
530
|
+
layer("services", "src/services/", "src/api/"),
|
|
531
|
+
layer("shared", "src/shared/", "src/common/"),
|
|
532
|
+
layer("styles", "src/styles/", "src/assets/"),
|
|
533
|
+
...commonLayers()
|
|
534
|
+
],
|
|
535
|
+
kinds: [
|
|
536
|
+
suffixKind("component", ".vue"),
|
|
537
|
+
kindOf("composable", "composables/", "use"),
|
|
538
|
+
kindOf("store", "stores/", "store/"),
|
|
539
|
+
kindOf("service", "services/", "api/"),
|
|
540
|
+
suffixKind("style", ".css", ".scss", ".sass", ".less"),
|
|
541
|
+
...commonKinds()
|
|
542
|
+
],
|
|
543
|
+
exclude: [".vite", "dist"],
|
|
544
|
+
recommendedBudget: "120k"
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
name: "react-vite",
|
|
548
|
+
priority: 84,
|
|
549
|
+
detect: (context) => hasDependency(context, "react") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
|
|
550
|
+
layers: VITE_LAYERS,
|
|
551
|
+
kinds: VITE_KINDS,
|
|
552
|
+
exclude: [".vite", "dist"],
|
|
553
|
+
recommendedBudget: "120k"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "nest",
|
|
557
|
+
priority: 80,
|
|
558
|
+
detect: (context) => hasDependency(context, "@nestjs/core"),
|
|
559
|
+
layers: [
|
|
560
|
+
layer("modules", "src/modules/", "modules/"),
|
|
561
|
+
layer("controllers", "src/controllers/", "controllers/"),
|
|
562
|
+
layer("providers", "src/providers/", "providers/"),
|
|
563
|
+
layer("core", "src/core/"),
|
|
564
|
+
layer("security", "src/security/", "src/auth/"),
|
|
565
|
+
layer("db", "src/db/", "src/database/", "prisma/"),
|
|
566
|
+
...commonLayers()
|
|
567
|
+
],
|
|
568
|
+
kinds: [
|
|
569
|
+
suffixKind("controller", ".controller.ts"),
|
|
570
|
+
suffixKind("service", ".service.ts"),
|
|
571
|
+
suffixKind("module", ".module.ts"),
|
|
572
|
+
suffixKind("guard", ".guard.ts"),
|
|
573
|
+
suffixKind("dto", ".dto.ts"),
|
|
574
|
+
suffixKind("entity", ".entity.ts"),
|
|
575
|
+
kindOf("migration", "migrations/"),
|
|
576
|
+
...commonKinds()
|
|
577
|
+
],
|
|
578
|
+
exclude: ["dist", "node_modules"],
|
|
579
|
+
recommendedBudget: "200k"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: "node-express",
|
|
583
|
+
priority: 78,
|
|
584
|
+
detect: (context) => hasDependency(context, "express", "fastify", "koa", "@hapi/hapi"),
|
|
585
|
+
layers: NODE_LAYERS,
|
|
586
|
+
kinds: NODE_KINDS,
|
|
587
|
+
exclude: ["dist"],
|
|
588
|
+
recommendedBudget: "200k"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "node-lib",
|
|
592
|
+
// A bare package.json is often just tooling around a non-JS project, so it
|
|
593
|
+
// ranks below every definitive language manifest.
|
|
594
|
+
priority: 45,
|
|
595
|
+
detect: (context) => hasFile(context, "package.json"),
|
|
596
|
+
layers: [
|
|
597
|
+
layer("src", "src/"),
|
|
598
|
+
layer("lib", "lib/"),
|
|
599
|
+
layer("test", "test/", "tests/", "__tests__/"),
|
|
600
|
+
...commonLayers()
|
|
601
|
+
],
|
|
602
|
+
kinds: [suffixKind("type", ".d.ts"), ...commonKinds()],
|
|
603
|
+
exclude: ["dist"],
|
|
604
|
+
recommendedBudget: "120k"
|
|
605
|
+
}
|
|
240
606
|
];
|
|
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") },
|
|
607
|
+
|
|
608
|
+
// src/presets/lang-presets.ts
|
|
609
|
+
var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
|
|
610
|
+
var DIR = (name) => `${name}/`;
|
|
611
|
+
var LANGUAGE_PRESETS = [
|
|
255
612
|
{
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
613
|
+
name: "python-django",
|
|
614
|
+
priority: 56,
|
|
615
|
+
detect: (context) => hasFile(context, "manage.py") || manifestContains(context, "pyproject.toml", "django") || manifestContains(context, "requirements.txt", "django"),
|
|
616
|
+
layers: [
|
|
617
|
+
layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
|
|
618
|
+
layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
|
|
619
|
+
layer("serializers", DIR("serializers")),
|
|
620
|
+
layer("urls", "urls.py"),
|
|
621
|
+
layer("migrations", DIR("migrations")),
|
|
622
|
+
layer("settings", "settings.py", "settings/"),
|
|
623
|
+
layer("templates", DIR("templates")),
|
|
624
|
+
...commonLayers()
|
|
625
|
+
],
|
|
626
|
+
kinds: [
|
|
627
|
+
suffixKind("model", "models.py"),
|
|
628
|
+
suffixKind("view", "views.py"),
|
|
629
|
+
suffixKind("serializer", "serializers.py"),
|
|
630
|
+
suffixKind("urlconf", "urls.py"),
|
|
631
|
+
kindOf("migration", "migrations/"),
|
|
632
|
+
suffixKind("settings", "settings.py"),
|
|
633
|
+
...commonKinds()
|
|
634
|
+
],
|
|
635
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs", "staticfiles", "media"],
|
|
636
|
+
recommendedBudget: "150k"
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "python-fastapi",
|
|
640
|
+
priority: 54,
|
|
641
|
+
detect: (context) => manifestContains(context, "requirements.txt", "fastapi") || manifestContains(context, "requirements.txt", "flask") || manifestContains(context, "pyproject.toml", "fastapi") || manifestContains(context, "pyproject.toml", "flask"),
|
|
642
|
+
layers: [
|
|
643
|
+
layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
|
|
644
|
+
layer("models", DIR("models"), DIR("db")),
|
|
645
|
+
layer("schemas", DIR("schemas")),
|
|
646
|
+
layer("services", DIR("services")),
|
|
647
|
+
layer("core", DIR("core")),
|
|
648
|
+
layer("tests", DIR("tests")),
|
|
649
|
+
...commonLayers()
|
|
650
|
+
],
|
|
651
|
+
kinds: [
|
|
652
|
+
kindOf("router", "routers/", "routes/", "api/"),
|
|
653
|
+
kindOf("model", "models/"),
|
|
654
|
+
kindOf("schema", "schemas/"),
|
|
655
|
+
kindOf("service", "services/"),
|
|
656
|
+
...commonKinds()
|
|
657
|
+
],
|
|
658
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
|
|
659
|
+
recommendedBudget: "150k"
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: "python-lib",
|
|
663
|
+
// Below node-lib: a Node repo with a stray .py file is still a Node repo.
|
|
664
|
+
priority: 30,
|
|
665
|
+
detect: (context) => hasFile(context, "pyproject.toml") || hasFile(context, "setup.py") || hasFile(context, "requirements.txt") || anyFileEndingWith(context, ".py"),
|
|
666
|
+
layers: [
|
|
667
|
+
layer("package", DIR("src"), DIR("lib")),
|
|
668
|
+
layer("tests", DIR("tests"), DIR("test")),
|
|
669
|
+
...commonLayers()
|
|
670
|
+
],
|
|
671
|
+
kinds: [suffixKind("module", ".py"), ...commonKinds()],
|
|
672
|
+
exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
|
|
673
|
+
recommendedBudget: "120k"
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: "go",
|
|
677
|
+
priority: 50,
|
|
678
|
+
detect: (context) => hasFile(context, "go.mod"),
|
|
679
|
+
layers: [
|
|
680
|
+
layer("cmd", "cmd/"),
|
|
681
|
+
layer("internal", "internal/"),
|
|
682
|
+
layer("pkg", "pkg/"),
|
|
683
|
+
layer("api", "api/", "proto/"),
|
|
684
|
+
layer("migrations", "migrations/"),
|
|
685
|
+
...commonLayers()
|
|
686
|
+
],
|
|
687
|
+
kinds: [
|
|
688
|
+
kindOf("main", "cmd/"),
|
|
689
|
+
kindOf("handler", "handler", "server", "http"),
|
|
690
|
+
kindOf("service", "service"),
|
|
691
|
+
kindOf("repository", "repository", "store", "repo"),
|
|
692
|
+
kindOf("model", "model", "entity"),
|
|
693
|
+
suffixKind("test", "_test.go"),
|
|
694
|
+
...commonKinds()
|
|
695
|
+
],
|
|
696
|
+
exclude: ["vendor", "testdata"],
|
|
697
|
+
recommendedBudget: "200k"
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: "rust",
|
|
701
|
+
priority: 48,
|
|
702
|
+
detect: (context) => hasFile(context, "Cargo.toml"),
|
|
703
|
+
layers: [
|
|
704
|
+
layer("src", "src/"),
|
|
705
|
+
layer("crates", "crates/"),
|
|
706
|
+
layer("bins", "src/bin/"),
|
|
707
|
+
layer("tests", "tests/"),
|
|
708
|
+
layer("benches", "benches/"),
|
|
709
|
+
...commonLayers()
|
|
710
|
+
],
|
|
711
|
+
kinds: [suffixKind("module", ".rs"), suffixKind("test", "_test.rs"), ...commonKinds()],
|
|
712
|
+
exclude: ["target"],
|
|
713
|
+
recommendedBudget: "200k"
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
name: "java-spring",
|
|
717
|
+
priority: 46,
|
|
718
|
+
detect: (context) => hasFile(context, "pom.xml") || hasFile(context, "build.gradle") || hasFile(context, "build.gradle.kts"),
|
|
719
|
+
layers: [
|
|
720
|
+
layer("api", "src/main/java/"),
|
|
721
|
+
layer("test", "src/test/java/"),
|
|
722
|
+
layer("resources", "src/main/resources/"),
|
|
723
|
+
layer("migrations", "src/main/resources/db/"),
|
|
724
|
+
...commonLayers()
|
|
725
|
+
],
|
|
726
|
+
kinds: [
|
|
727
|
+
kindOf("controller", "controller"),
|
|
728
|
+
kindOf("service", "service"),
|
|
729
|
+
kindOf("repository", "repository", "dao"),
|
|
730
|
+
kindOf("entity", "entity", "model"),
|
|
731
|
+
kindOf("dto", "dto", "request", "response"),
|
|
732
|
+
kindOf("migration", "migration", "db/migration"),
|
|
733
|
+
...commonKinds()
|
|
734
|
+
],
|
|
735
|
+
exclude: [".gradle", "out", "target"],
|
|
736
|
+
recommendedBudget: "200k"
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
name: "dotnet",
|
|
740
|
+
priority: 44,
|
|
741
|
+
detect: (context) => anyFileEndingWith(context, ".csproj") || anyFileEndingWith(context, ".sln") || hasFile(context, "global.json"),
|
|
742
|
+
layers: [
|
|
743
|
+
layer("web", "src/", "Web/"),
|
|
744
|
+
layer("domain", "Domain/", "Core/"),
|
|
745
|
+
layer("infrastructure", "Infrastructure/", "Data/"),
|
|
746
|
+
layer("tests", "Tests/", "test/"),
|
|
747
|
+
...commonLayers()
|
|
748
|
+
],
|
|
749
|
+
kinds: [
|
|
750
|
+
kindOf("controller", "Controller"),
|
|
751
|
+
kindOf("service", "Service"),
|
|
752
|
+
kindOf("repository", "Repository"),
|
|
753
|
+
kindOf("model", "Model", "Entity"),
|
|
754
|
+
kindOf("dto", "Dto", "Request", "Response"),
|
|
755
|
+
suffixKind("test", "Tests.cs"),
|
|
756
|
+
...commonKinds()
|
|
757
|
+
],
|
|
758
|
+
exclude: ["bin", "obj", ".vs"],
|
|
759
|
+
recommendedBudget: "200k"
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
name: "php-laravel",
|
|
763
|
+
priority: 42,
|
|
764
|
+
detect: (context) => hasFile(context, "artisan") || manifestContains(context, "composer.json", "laravel") || hasFile(context, "composer.json"),
|
|
765
|
+
layers: [
|
|
766
|
+
layer("app", "app/"),
|
|
767
|
+
layer("routes", "routes/"),
|
|
768
|
+
layer("http", "app/Http/"),
|
|
769
|
+
layer("models", "app/Models/"),
|
|
770
|
+
layer("providers", "app/Providers/"),
|
|
771
|
+
layer("database", "database/"),
|
|
772
|
+
layer("tests", "tests/"),
|
|
773
|
+
...commonLayers()
|
|
774
|
+
],
|
|
775
|
+
kinds: [
|
|
776
|
+
kindOf("controller", "Http/Controllers/"),
|
|
777
|
+
kindOf("model", "Models/"),
|
|
778
|
+
kindOf("migration", "migrations/"),
|
|
779
|
+
kindOf("middleware", "Middleware/"),
|
|
780
|
+
kindOf("request", "Requests/"),
|
|
781
|
+
kindOf("provider", "Providers/"),
|
|
782
|
+
...commonKinds()
|
|
783
|
+
],
|
|
784
|
+
exclude: ["vendor", "storage", "bootstrap/cache", "public/build"],
|
|
785
|
+
recommendedBudget: "150k"
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
name: "ruby-rails",
|
|
789
|
+
priority: 40,
|
|
790
|
+
detect: (context) => hasFile(context, "Gemfile") || hasFile(context, "config.ru") || hasFile(context, "Rakefile"),
|
|
791
|
+
layers: [
|
|
792
|
+
layer("app", "app/"),
|
|
793
|
+
layer("controllers", "app/controllers/"),
|
|
794
|
+
layer("models", "app/models/"),
|
|
795
|
+
layer("config", "config/"),
|
|
796
|
+
layer("db", "db/"),
|
|
797
|
+
layer("spec", "spec/", "test/"),
|
|
798
|
+
...commonLayers()
|
|
799
|
+
],
|
|
800
|
+
kinds: [
|
|
801
|
+
kindOf("controller", "controllers/"),
|
|
802
|
+
kindOf("model", "models/"),
|
|
803
|
+
kindOf("migration", "migrate/"),
|
|
804
|
+
kindOf("job", "jobs/"),
|
|
805
|
+
kindOf("mailer", "mailers/"),
|
|
806
|
+
suffixKind("test", "_spec.rb", "_test.rb"),
|
|
807
|
+
...commonKinds()
|
|
808
|
+
],
|
|
809
|
+
exclude: [".bundle", "log", "tmp", "vendor/bundle", "public/assets"],
|
|
810
|
+
recommendedBudget: "150k"
|
|
261
811
|
}
|
|
262
812
|
];
|
|
263
813
|
|
|
@@ -303,50 +853,110 @@ var DEFAULT_KINDS = [
|
|
|
303
853
|
}
|
|
304
854
|
];
|
|
305
855
|
|
|
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
|
-
|
|
856
|
+
// src/presets/shape-presets.ts
|
|
857
|
+
var SHAPE_PRESETS = [
|
|
858
|
+
{
|
|
859
|
+
name: "monorepo",
|
|
860
|
+
priority: 70,
|
|
861
|
+
detect: (context) => hasFile(context, "pnpm-workspace.yaml") || hasFile(context, "nx.json") || hasFile(context, "turbo.json") || hasFile(context, "lerna.json") || manifestContains(context, "package.json", '"workspaces"'),
|
|
862
|
+
layers: [
|
|
863
|
+
layer("apps", "apps/"),
|
|
864
|
+
layer("packages", "packages/"),
|
|
865
|
+
layer("tooling", "tooling/", "tools/", "scripts/"),
|
|
866
|
+
...commonLayers()
|
|
867
|
+
],
|
|
868
|
+
kinds: commonKinds(),
|
|
869
|
+
exclude: [".turbo", ".nx"],
|
|
870
|
+
recommendedBudget: "200k"
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
name: "infra",
|
|
874
|
+
priority: 24,
|
|
875
|
+
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"),
|
|
876
|
+
layers: [
|
|
877
|
+
layer("modules", "modules/"),
|
|
878
|
+
layer("environments", "env/", "environments/"),
|
|
879
|
+
layer("manifests", "k8s/", "helm/", "charts/", "deploy/"),
|
|
880
|
+
...commonLayers()
|
|
881
|
+
],
|
|
882
|
+
kinds: [
|
|
883
|
+
{ kind: "terraform", match: (filePath) => filePath.endsWith(".tf") },
|
|
884
|
+
{ kind: "kubernetes", match: (filePath) => /\.(ya?ml)$/i.test(filePath) && /k8s|helm|charts|deploy/.test(filePath) },
|
|
885
|
+
...commonKinds()
|
|
886
|
+
],
|
|
887
|
+
exclude: [".terraform", ".terragrunt-cache"],
|
|
888
|
+
recommendedBudget: "100k"
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: "docs",
|
|
892
|
+
priority: 22,
|
|
893
|
+
detect: (context) => {
|
|
894
|
+
const markdown = Array.from(context.files).filter((file) => /\.mdx?$/i.test(file)).length;
|
|
895
|
+
return markdown >= 3 && !hasFile(context, "package.json");
|
|
896
|
+
},
|
|
897
|
+
layers: [
|
|
898
|
+
layer("content", "content/", "docs/"),
|
|
899
|
+
layer("assets", "assets/", "static/", "public/"),
|
|
900
|
+
...commonLayers()
|
|
901
|
+
],
|
|
902
|
+
kinds: commonKinds(),
|
|
903
|
+
exclude: [".docusaurus", ".vitepress/dist", "site"],
|
|
904
|
+
recommendedBudget: "100k"
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
name: "generic",
|
|
908
|
+
priority: 0,
|
|
909
|
+
detect: () => true,
|
|
910
|
+
layers: DEFAULT_LAYERS,
|
|
911
|
+
kinds: DEFAULT_KINDS,
|
|
912
|
+
exclude: [],
|
|
913
|
+
recommendedBudget: "120k"
|
|
329
914
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
915
|
+
];
|
|
916
|
+
|
|
917
|
+
// src/presets/registry.ts
|
|
918
|
+
function compare(a, b) {
|
|
919
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
920
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
921
|
+
}
|
|
922
|
+
var PRESETS = [
|
|
923
|
+
...JS_PRESETS,
|
|
924
|
+
...LANGUAGE_PRESETS,
|
|
925
|
+
...SHAPE_PRESETS
|
|
926
|
+
].sort(compare);
|
|
927
|
+
var BY_NAME = new Map(PRESETS.map((preset) => [preset.name, preset]));
|
|
928
|
+
function listPresetNames() {
|
|
929
|
+
return PRESETS.map((preset) => preset.name);
|
|
930
|
+
}
|
|
931
|
+
function getPreset(name) {
|
|
932
|
+
return BY_NAME.get(name);
|
|
933
|
+
}
|
|
934
|
+
function unknownPresetMessage(name) {
|
|
935
|
+
return `Unknown preset "${name}". Expected one of: ${listPresetNames().join(", ")}.`;
|
|
936
|
+
}
|
|
937
|
+
function detectPresetFromContext(context, forced) {
|
|
938
|
+
if (forced !== void 0) {
|
|
939
|
+
const preset = BY_NAME.get(forced);
|
|
940
|
+
if (!preset) throw new Error(unknownPresetMessage(forced));
|
|
941
|
+
return toDetected(preset);
|
|
336
942
|
}
|
|
337
|
-
|
|
338
|
-
return
|
|
339
|
-
presetName: "node",
|
|
340
|
-
layers: NODE_LAYERS,
|
|
341
|
-
kinds: NODE_KINDS
|
|
342
|
-
};
|
|
943
|
+
for (const preset of PRESETS) {
|
|
944
|
+
if (preset.detect(context)) return toDetected(preset);
|
|
343
945
|
}
|
|
946
|
+
return toDetected(PRESETS[PRESETS.length - 1]);
|
|
947
|
+
}
|
|
948
|
+
function toDetected(preset) {
|
|
344
949
|
return {
|
|
345
|
-
presetName:
|
|
346
|
-
layers:
|
|
347
|
-
kinds:
|
|
950
|
+
presetName: preset.name,
|
|
951
|
+
layers: preset.layers,
|
|
952
|
+
kinds: preset.kinds,
|
|
953
|
+
exclude: preset.exclude ? [...preset.exclude] : [],
|
|
954
|
+
recommendedBudget: preset.recommendedBudget ?? null
|
|
348
955
|
};
|
|
349
956
|
}
|
|
957
|
+
function detectPreset(cwd, forced) {
|
|
958
|
+
return detectPresetFromContext(buildPresetContext(cwd), forced);
|
|
959
|
+
}
|
|
350
960
|
function getGitInfo(cwd, fallbackBranch = "main") {
|
|
351
961
|
try {
|
|
352
962
|
const branch = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
|
|
@@ -481,10 +1091,10 @@ function matchesExclude(relativePath, pattern) {
|
|
|
481
1091
|
return posixPath === cleaned || posixPath.startsWith(`${cleaned}/`) || posixPath.includes(`/${cleaned}/`) || posixPath.endsWith(`/${cleaned}`);
|
|
482
1092
|
}
|
|
483
1093
|
function readGitignoreMatcher(dir) {
|
|
484
|
-
const gitignorePath =
|
|
1094
|
+
const gitignorePath = path10__namespace.join(dir, ".gitignore");
|
|
485
1095
|
let raw;
|
|
486
1096
|
try {
|
|
487
|
-
raw =
|
|
1097
|
+
raw = fs9__namespace.readFileSync(gitignorePath, "utf8");
|
|
488
1098
|
} catch {
|
|
489
1099
|
return () => false;
|
|
490
1100
|
}
|
|
@@ -587,7 +1197,7 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
587
1197
|
const results = { files: [], skipped: [] };
|
|
588
1198
|
let realDir;
|
|
589
1199
|
try {
|
|
590
|
-
realDir =
|
|
1200
|
+
realDir = fs9__namespace.realpathSync(dir);
|
|
591
1201
|
} catch {
|
|
592
1202
|
realDir = dir;
|
|
593
1203
|
}
|
|
@@ -596,21 +1206,21 @@ function walkDirectory(dir, baseDir, visited) {
|
|
|
596
1206
|
const ignoredByGitignore = dir === baseDir ? readGitignoreMatcher(baseDir) : () => false;
|
|
597
1207
|
let entries;
|
|
598
1208
|
try {
|
|
599
|
-
entries =
|
|
1209
|
+
entries = fs9__namespace.readdirSync(dir, { withFileTypes: true });
|
|
600
1210
|
} catch {
|
|
601
1211
|
results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
|
|
602
1212
|
return results;
|
|
603
1213
|
}
|
|
604
1214
|
for (const entry of entries) {
|
|
605
1215
|
if (DEFAULT_IGNORED_DIRS.includes(entry.name)) continue;
|
|
606
|
-
const fullPath =
|
|
607
|
-
const relPath =
|
|
1216
|
+
const fullPath = path10__namespace.join(dir, entry.name);
|
|
1217
|
+
const relPath = path10__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
608
1218
|
if (ignoredByGitignore(relPath)) continue;
|
|
609
1219
|
let isDirectory = entry.isDirectory();
|
|
610
1220
|
let isFile = entry.isFile();
|
|
611
1221
|
if (entry.isSymbolicLink()) {
|
|
612
1222
|
try {
|
|
613
|
-
const stats =
|
|
1223
|
+
const stats = fs9__namespace.statSync(fullPath);
|
|
614
1224
|
isDirectory = stats.isDirectory();
|
|
615
1225
|
isFile = stats.isFile();
|
|
616
1226
|
if (!isDirectory && !isFile) {
|
|
@@ -875,6 +1485,8 @@ function normalizeResolvedConfig(config) {
|
|
|
875
1485
|
config.includeMonolith === true
|
|
876
1486
|
),
|
|
877
1487
|
include: resolveInclude(config.include),
|
|
1488
|
+
preset: typeof config.preset === "string" ? config.preset : "generic",
|
|
1489
|
+
presetExclude: Array.isArray(config.presetExclude) ? config.presetExclude : [],
|
|
878
1490
|
layersMode: config.layersMode === "replace" ? "replace" : "merge",
|
|
879
1491
|
budgets: config.budgets && typeof config.budgets === "object" ? config.budgets : { default: config.maxPartBytes, byLayer: {} },
|
|
880
1492
|
hooks: resolveHooks(config.hooks),
|
|
@@ -922,18 +1534,22 @@ function parseJsonish(raw, file) {
|
|
|
922
1534
|
}
|
|
923
1535
|
async function loadConfigFileDetailed(cwd) {
|
|
924
1536
|
const warnings = [];
|
|
1537
|
+
const errors = [];
|
|
925
1538
|
for (const filename of CONFIG_FILENAMES) {
|
|
926
|
-
const fullPath =
|
|
927
|
-
if (!
|
|
1539
|
+
const fullPath = path10__namespace.join(cwd, filename);
|
|
1540
|
+
if (!fs9__namespace.existsSync(fullPath)) continue;
|
|
928
1541
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
929
1542
|
try {
|
|
930
1543
|
return {
|
|
931
|
-
config: parseJsonish(
|
|
1544
|
+
config: parseJsonish(fs9__namespace.readFileSync(fullPath, "utf8"), fullPath),
|
|
932
1545
|
file: fullPath,
|
|
933
|
-
warnings
|
|
1546
|
+
warnings,
|
|
1547
|
+
errors
|
|
934
1548
|
};
|
|
935
1549
|
} catch (err) {
|
|
936
|
-
|
|
1550
|
+
const message = err.message;
|
|
1551
|
+
warnings.push(message);
|
|
1552
|
+
errors.push(message);
|
|
937
1553
|
continue;
|
|
938
1554
|
}
|
|
939
1555
|
}
|
|
@@ -947,28 +1563,34 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
947
1563
|
const imported = await import(url.pathToFileURL(fullPath).href);
|
|
948
1564
|
const config = imported.default ?? imported;
|
|
949
1565
|
if (!config || typeof config !== "object") {
|
|
950
|
-
|
|
1566
|
+
const message = `${fullPath} does not export a configuration object.`;
|
|
1567
|
+
warnings.push(message);
|
|
1568
|
+
errors.push(message);
|
|
951
1569
|
continue;
|
|
952
1570
|
}
|
|
953
|
-
return { config, file: fullPath, warnings };
|
|
1571
|
+
return { config, file: fullPath, warnings, errors };
|
|
954
1572
|
} catch (err) {
|
|
955
|
-
|
|
1573
|
+
const message = `Could not load ${fullPath}: ${err.message}`;
|
|
1574
|
+
warnings.push(message);
|
|
1575
|
+
errors.push(message);
|
|
956
1576
|
continue;
|
|
957
1577
|
}
|
|
958
1578
|
}
|
|
959
|
-
const pkgJsonPath =
|
|
960
|
-
if (
|
|
1579
|
+
const pkgJsonPath = path10__namespace.join(cwd, "package.json");
|
|
1580
|
+
if (fs9__namespace.existsSync(pkgJsonPath)) {
|
|
961
1581
|
try {
|
|
962
|
-
const parsed = parseJsonish(
|
|
1582
|
+
const parsed = parseJsonish(fs9__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
|
|
963
1583
|
const snapshotKey = parsed.snapshot;
|
|
964
1584
|
if (snapshotKey && typeof snapshotKey === "object") {
|
|
965
|
-
return { config: snapshotKey, file: pkgJsonPath, warnings };
|
|
1585
|
+
return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
|
|
966
1586
|
}
|
|
967
1587
|
} catch (err) {
|
|
968
|
-
|
|
1588
|
+
const message = err.message;
|
|
1589
|
+
warnings.push(message);
|
|
1590
|
+
errors.push(message);
|
|
969
1591
|
}
|
|
970
1592
|
}
|
|
971
|
-
return { config: null, file: null, warnings };
|
|
1593
|
+
return { config: null, file: null, warnings, errors };
|
|
972
1594
|
}
|
|
973
1595
|
var MIN_MAX_PART_BYTES = 4096;
|
|
974
1596
|
var ARTIFACT_NAMES = [
|
|
@@ -1063,7 +1685,9 @@ function boundedBytes(value, fallback, minimum, label) {
|
|
|
1063
1685
|
`${label} must be a byte count such as 120000, "120k" or "1.5mb" (received ${describeBytes(value)}).`
|
|
1064
1686
|
);
|
|
1065
1687
|
}
|
|
1066
|
-
if (parsed <= 0)
|
|
1688
|
+
if (parsed <= 0) {
|
|
1689
|
+
throw new Error(`${label} must be a positive byte count (received ${describeBytes(value)}).`);
|
|
1690
|
+
}
|
|
1067
1691
|
if (parsed < minimum) {
|
|
1068
1692
|
throw new Error(`${label} must be at least ${minimum} bytes (received ${describeBytes(value)}).`);
|
|
1069
1693
|
}
|
|
@@ -1103,12 +1727,12 @@ function resolveBudgets(budgets, fallback) {
|
|
|
1103
1727
|
if (typeof budgets.byLayer !== "object" || budgets.byLayer === null) {
|
|
1104
1728
|
throw new Error("`budgets.byLayer` must be an object of layer name to byte size.");
|
|
1105
1729
|
}
|
|
1106
|
-
for (const [
|
|
1107
|
-
byLayer[
|
|
1730
|
+
for (const [layer2, value] of Object.entries(budgets.byLayer)) {
|
|
1731
|
+
byLayer[layer2] = boundedBytes(
|
|
1108
1732
|
value,
|
|
1109
1733
|
fallback,
|
|
1110
1734
|
MIN_MAX_PART_BYTES,
|
|
1111
|
-
`budgets.byLayer["${
|
|
1735
|
+
`budgets.byLayer["${layer2}"]`
|
|
1112
1736
|
);
|
|
1113
1737
|
}
|
|
1114
1738
|
}
|
|
@@ -1129,18 +1753,22 @@ function resolveHooks(hooks) {
|
|
|
1129
1753
|
}
|
|
1130
1754
|
function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
|
|
1131
1755
|
const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
|
|
1132
|
-
const resolved =
|
|
1133
|
-
if (!
|
|
1756
|
+
const resolved = path10__namespace.resolve(candidate);
|
|
1757
|
+
if (!fs9__namespace.existsSync(resolved)) {
|
|
1134
1758
|
throw new Error(`Working directory does not exist: ${resolved}`);
|
|
1135
1759
|
}
|
|
1136
|
-
if (!
|
|
1760
|
+
if (!fs9__namespace.statSync(resolved).isDirectory()) {
|
|
1137
1761
|
throw new Error(`Working directory is not a directory: ${resolved}`);
|
|
1138
1762
|
}
|
|
1139
1763
|
return resolved;
|
|
1140
1764
|
}
|
|
1141
1765
|
function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
1142
1766
|
const cwd = resolveCandidateCwd(userConfig ?? null, cliOptions, baseDir);
|
|
1143
|
-
const
|
|
1767
|
+
const forcedPreset = userConfig?.preset;
|
|
1768
|
+
if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
|
|
1769
|
+
throw new Error(unknownPresetMessage(forcedPreset));
|
|
1770
|
+
}
|
|
1771
|
+
const preset = detectPreset(cwd, forcedPreset);
|
|
1144
1772
|
const layersMode = resolveLayersMode(userConfig?.layersMode);
|
|
1145
1773
|
const layers = userConfig?.layers && userConfig.layers.length > 0 ? layersMode === "replace" ? userConfig.layers : [...userConfig.layers, ...preset.layers] : preset.layers;
|
|
1146
1774
|
const kinds = userConfig?.kinds && userConfig.kinds.length > 0 ? layersMode === "replace" ? userConfig.kinds : [...userConfig.kinds, ...preset.kinds] : preset.kinds;
|
|
@@ -1176,6 +1804,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
1176
1804
|
),
|
|
1177
1805
|
includeSensitiveFiles: cliOptions?.includeSensitiveFiles ?? userConfig?.includeSensitiveFiles ?? false,
|
|
1178
1806
|
include: resolveInclude(userConfig?.include),
|
|
1807
|
+
preset: preset.presetName,
|
|
1808
|
+
presetExclude: [...preset.exclude],
|
|
1179
1809
|
artifacts,
|
|
1180
1810
|
layersMode,
|
|
1181
1811
|
budgets,
|
|
@@ -1210,8 +1840,8 @@ function matchRule(filePath, matcher) {
|
|
|
1210
1840
|
return false;
|
|
1211
1841
|
}
|
|
1212
1842
|
function classifyLayer(filePath, layers) {
|
|
1213
|
-
for (const
|
|
1214
|
-
if (matchRule(filePath,
|
|
1843
|
+
for (const layer2 of layers) {
|
|
1844
|
+
if (matchRule(filePath, layer2.match)) return layer2.name;
|
|
1215
1845
|
}
|
|
1216
1846
|
return "other";
|
|
1217
1847
|
}
|
|
@@ -1315,10 +1945,10 @@ var LANGUAGE_BY_BASENAME = {
|
|
|
1315
1945
|
};
|
|
1316
1946
|
function detectLanguage(filePath) {
|
|
1317
1947
|
const normalized = filePath.replace(/\\/g, "/");
|
|
1318
|
-
const base =
|
|
1948
|
+
const base = path10__namespace.posix.basename(normalized);
|
|
1319
1949
|
if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
|
|
1320
1950
|
if (base === ".env" || base.startsWith(".env.")) return "dotenv";
|
|
1321
|
-
const ext =
|
|
1951
|
+
const ext = path10__namespace.posix.extname(base).toLowerCase();
|
|
1322
1952
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
1323
1953
|
}
|
|
1324
1954
|
|
|
@@ -1333,35 +1963,35 @@ function describeMatcher(matcher) {
|
|
|
1333
1963
|
return String(matcher);
|
|
1334
1964
|
}
|
|
1335
1965
|
function normalizeTarget(cwd, target) {
|
|
1336
|
-
const absolute =
|
|
1337
|
-
const relative4 =
|
|
1338
|
-
if (relative4.startsWith("..") ||
|
|
1339
|
-
return relative4.split(
|
|
1966
|
+
const absolute = path10__namespace.resolve(cwd, target);
|
|
1967
|
+
const relative4 = path10__namespace.relative(cwd, absolute);
|
|
1968
|
+
if (relative4.startsWith("..") || path10__namespace.isAbsolute(relative4)) return target;
|
|
1969
|
+
return relative4.split(path10__namespace.sep).join("/");
|
|
1340
1970
|
}
|
|
1341
1971
|
function findRuleMatch(rules, relativePath) {
|
|
1342
1972
|
return rules.find(
|
|
1343
1973
|
(rule) => matchRule(relativePath, rule.match)
|
|
1344
1974
|
);
|
|
1345
1975
|
}
|
|
1346
|
-
function
|
|
1347
|
-
const manifestPath =
|
|
1976
|
+
function readManifest2(config) {
|
|
1977
|
+
const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
|
|
1348
1978
|
try {
|
|
1349
|
-
return JSON.parse(
|
|
1979
|
+
return JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
1350
1980
|
} catch {
|
|
1351
1981
|
return null;
|
|
1352
1982
|
}
|
|
1353
1983
|
}
|
|
1354
1984
|
function explainPath(config, target) {
|
|
1355
1985
|
const relative4 = normalizeTarget(config.cwd, target);
|
|
1356
|
-
const absolute =
|
|
1986
|
+
const absolute = path10__namespace.join(config.cwd, relative4);
|
|
1357
1987
|
const result = {
|
|
1358
1988
|
path: relative4,
|
|
1359
|
-
exists:
|
|
1989
|
+
exists: fs9__namespace.existsSync(absolute),
|
|
1360
1990
|
included: false
|
|
1361
1991
|
};
|
|
1362
1992
|
const collection = collectFiles(config.cwd, {
|
|
1363
1993
|
includePatterns: config.include,
|
|
1364
|
-
excludePatterns: config.excludePatterns,
|
|
1994
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
1365
1995
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
1366
1996
|
});
|
|
1367
1997
|
if (!collection.files.includes(relative4)) {
|
|
@@ -1386,16 +2016,16 @@ function explainPath(config, target) {
|
|
|
1386
2016
|
if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
|
|
1387
2017
|
const kindRule = findRuleMatch(config.kinds, relative4);
|
|
1388
2018
|
if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
|
|
1389
|
-
if (
|
|
1390
|
-
const buffer =
|
|
1391
|
-
const
|
|
2019
|
+
if (fs9__namespace.existsSync(absolute) && fs9__namespace.statSync(absolute).isFile()) {
|
|
2020
|
+
const buffer = fs9__namespace.readFileSync(absolute);
|
|
2021
|
+
const text2 = buffer.toString("utf8");
|
|
1392
2022
|
result.bytes = buffer.length;
|
|
1393
|
-
result.lines = countLines(
|
|
1394
|
-
const { findings } = scanAndRedactSecrets(
|
|
2023
|
+
result.lines = countLines(text2);
|
|
2024
|
+
const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative4);
|
|
1395
2025
|
result.secretCount = findings.length;
|
|
1396
2026
|
result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
|
|
1397
2027
|
}
|
|
1398
|
-
const manifest =
|
|
2028
|
+
const manifest = readManifest2(config);
|
|
1399
2029
|
const entry = manifest?.files.find((file) => file.relativePath === relative4);
|
|
1400
2030
|
result.part = entry?.part ?? null;
|
|
1401
2031
|
result.inclusionMode = entry?.inclusionMode;
|
|
@@ -1435,128 +2065,1133 @@ function renderExplainText(result) {
|
|
|
1435
2065
|
return lines.join("\n");
|
|
1436
2066
|
}
|
|
1437
2067
|
|
|
1438
|
-
// src/
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
i += 1;
|
|
1459
|
-
}
|
|
1460
|
-
continue;
|
|
1461
|
-
}
|
|
1462
|
-
if (inString) {
|
|
1463
|
-
out += char;
|
|
1464
|
-
if (char === "\\") {
|
|
1465
|
-
if (next !== void 0) {
|
|
1466
|
-
out += next;
|
|
1467
|
-
i += 1;
|
|
1468
|
-
}
|
|
1469
|
-
} else if (char === '"') {
|
|
1470
|
-
inString = false;
|
|
1471
|
-
}
|
|
1472
|
-
continue;
|
|
1473
|
-
}
|
|
1474
|
-
if (char === '"') {
|
|
1475
|
-
inString = true;
|
|
1476
|
-
out += char;
|
|
1477
|
-
continue;
|
|
1478
|
-
}
|
|
1479
|
-
if (char === "/" && next === "/") {
|
|
1480
|
-
inLineComment = true;
|
|
1481
|
-
i += 1;
|
|
1482
|
-
continue;
|
|
1483
|
-
}
|
|
1484
|
-
if (char === "/" && next === "*") {
|
|
1485
|
-
inBlockComment = true;
|
|
1486
|
-
i += 1;
|
|
1487
|
-
continue;
|
|
1488
|
-
}
|
|
1489
|
-
out += char;
|
|
1490
|
-
}
|
|
1491
|
-
return out;
|
|
2068
|
+
// src/wizard/config-template.ts
|
|
2069
|
+
var DEFAULT_ANSWERS = {
|
|
2070
|
+
preset: "generic",
|
|
2071
|
+
maxPartBytes: "300k",
|
|
2072
|
+
largeFileBytes: "200k",
|
|
2073
|
+
artifacts: {
|
|
2074
|
+
readme: true,
|
|
2075
|
+
manifest: true,
|
|
2076
|
+
parts: true,
|
|
2077
|
+
navigation: true,
|
|
2078
|
+
projectTree: true,
|
|
2079
|
+
audit: true,
|
|
2080
|
+
monolith: false
|
|
2081
|
+
},
|
|
2082
|
+
includeSensitiveFiles: false,
|
|
2083
|
+
outDir: "snapshot"
|
|
2084
|
+
};
|
|
2085
|
+
function list(values) {
|
|
2086
|
+
if (values.length === 0) return "[]";
|
|
2087
|
+
return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
1492
2088
|
}
|
|
1493
|
-
function
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
2089
|
+
function size(value) {
|
|
2090
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2091
|
+
}
|
|
2092
|
+
var PRESET_NAMES = [
|
|
2093
|
+
"nextjs \xB7 react-vite \xB7 vue-vite \xB7 nuxt \xB7 sveltekit \xB7 angular \xB7 astro",
|
|
2094
|
+
"remix \xB7 solid \xB7 nest \xB7 node-express \xB7 node-lib",
|
|
2095
|
+
"python-django \xB7 python-fastapi \xB7 python-lib \xB7 go \xB7 rust",
|
|
2096
|
+
"java-spring \xB7 dotnet \xB7 php-laravel \xB7 ruby-rails",
|
|
2097
|
+
"monorepo \xB7 docs \xB7 infra \xB7 generic"
|
|
2098
|
+
];
|
|
2099
|
+
function renderConfig(answers) {
|
|
2100
|
+
const { artifacts } = answers;
|
|
2101
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
2102
|
+
|
|
2103
|
+
export default defineConfig({
|
|
2104
|
+
// \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
|
|
2105
|
+
// Which project shape to use. Detected from the repo when omitted.
|
|
2106
|
+
// ${PRESET_NAMES.join("\n // ")}
|
|
2107
|
+
preset: ${JSON.stringify(answers.preset)},
|
|
2108
|
+
|
|
2109
|
+
// Recorded in the manifest when git cannot report a branch.
|
|
2110
|
+
branch: "main",
|
|
2111
|
+
|
|
2112
|
+
// \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
|
|
2113
|
+
// Where the snapshot is written.
|
|
2114
|
+
outDir: ${JSON.stringify(answers.outDir)},
|
|
2115
|
+
// Scratch directory; the new snapshot is renamed into place atomically.
|
|
2116
|
+
tmpDir: "snapshot.tmp",
|
|
2117
|
+
|
|
2118
|
+
// \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
|
|
2119
|
+
// Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
|
|
2120
|
+
maxPartBytes: ${size(answers.maxPartBytes)},
|
|
2121
|
+
// Files above this size get a head/tail preview instead of the full body.
|
|
2122
|
+
largeFileBytes: ${size(answers.largeFileBytes)},
|
|
2123
|
+
previewHeadLines: 80,
|
|
2124
|
+
previewTailLines: 40,
|
|
2125
|
+
// Different ceilings per layer. Unlisted layers use \`default\`.
|
|
2126
|
+
budgets: {
|
|
2127
|
+
default: ${size(answers.maxPartBytes)},
|
|
2128
|
+
byLayer: {},
|
|
2129
|
+
},
|
|
2130
|
+
|
|
2131
|
+
// \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
|
|
2132
|
+
// Allow-list. Empty means "everything that is not ignored".
|
|
2133
|
+
// Example: ["src/**", "packages/*/src/**"]
|
|
2134
|
+
include: ${list(answers.include)},
|
|
2135
|
+
// Globs or path segments to drop. Example: ["**/*.test.ts", "docs"]
|
|
2136
|
+
excludePatterns: ${list(answers.excludePatterns)},
|
|
2137
|
+
|
|
2138
|
+
// \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
|
|
2139
|
+
// "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
|
|
2140
|
+
layersMode: "merge",
|
|
2141
|
+
// [{ name: "billing", match: "src/features/billing/**" }]
|
|
2142
|
+
layers: [],
|
|
2143
|
+
// [{ kind: "route", match: "src/app/api/**" }]
|
|
2144
|
+
kinds: [],
|
|
2145
|
+
|
|
2146
|
+
// \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
|
|
2147
|
+
// .env, *.pem, .npmrc, .ssh/ and friends are skipped unless this is true.
|
|
2148
|
+
includeSensitiveFiles: ${answers.includeSensitiveFiles},
|
|
2149
|
+
secrets: {
|
|
2150
|
+
// [{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ }]
|
|
2151
|
+
customPatterns: [],
|
|
2152
|
+
// Set true to drop the 21 built-in rules.
|
|
2153
|
+
disableDefaultPatterns: false,
|
|
2154
|
+
},
|
|
2155
|
+
|
|
2156
|
+
// \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
|
|
2157
|
+
artifacts: {
|
|
2158
|
+
readme: ${artifacts.readme}, // README.md
|
|
2159
|
+
manifest: ${artifacts.manifest}, // manifest.json \u2014 required by validate and report
|
|
2160
|
+
parts: ${artifacts.parts}, // parts/part-XXX.md
|
|
2161
|
+
navigation: ${artifacts.navigation}, // reports/navigation.md
|
|
2162
|
+
projectTree: ${artifacts.projectTree}, // reports/project-tree.md
|
|
2163
|
+
audit: ${artifacts.audit}, // reports/audit.md
|
|
2164
|
+
monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
|
|
2165
|
+
},
|
|
2166
|
+
// Skip the monolith above this size. null = no limit.
|
|
2167
|
+
maxMonolithBytes: null,
|
|
2168
|
+
|
|
2169
|
+
// \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
|
|
2170
|
+
// afterGenerate: (manifest) => { /* write your own artifact */ }
|
|
2171
|
+
hooks: {},
|
|
2172
|
+
});
|
|
2173
|
+
`;
|
|
2174
|
+
}
|
|
2175
|
+
function renderMinimalConfig(answers) {
|
|
2176
|
+
const lines = [];
|
|
2177
|
+
if (answers.preset !== DEFAULT_ANSWERS.preset) lines.push(` preset: ${JSON.stringify(answers.preset)},`);
|
|
2178
|
+
if (answers.maxPartBytes !== DEFAULT_ANSWERS.maxPartBytes) {
|
|
2179
|
+
lines.push(` maxPartBytes: ${size(answers.maxPartBytes)},`);
|
|
2180
|
+
}
|
|
2181
|
+
if (answers.largeFileBytes !== DEFAULT_ANSWERS.largeFileBytes) {
|
|
2182
|
+
lines.push(` largeFileBytes: ${size(answers.largeFileBytes)},`);
|
|
2183
|
+
}
|
|
2184
|
+
if (answers.include.length > 0) lines.push(` include: ${list(answers.include)},`);
|
|
2185
|
+
if (answers.excludePatterns.length > 0) {
|
|
2186
|
+
lines.push(` excludePatterns: ${list(answers.excludePatterns)},`);
|
|
2187
|
+
}
|
|
2188
|
+
if (answers.includeSensitiveFiles) lines.push(" includeSensitiveFiles: true,");
|
|
2189
|
+
if (answers.outDir !== DEFAULT_ANSWERS.outDir) lines.push(` outDir: ${JSON.stringify(answers.outDir)},`);
|
|
2190
|
+
const changed = Object.keys(answers.artifacts).filter(
|
|
2191
|
+
(name) => answers.artifacts[name] !== DEFAULT_ANSWERS.artifacts[name]
|
|
2192
|
+
);
|
|
2193
|
+
if (changed.length > 0) {
|
|
2194
|
+
const entries = changed.map((name) => ` ${name}: ${answers.artifacts[name]}`).join(",\n");
|
|
2195
|
+
lines.push(` artifacts: {
|
|
2196
|
+
${entries},
|
|
2197
|
+
},`);
|
|
2198
|
+
}
|
|
2199
|
+
if (lines.length === 0) {
|
|
2200
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
2201
|
+
|
|
2202
|
+
export default defineConfig({});
|
|
2203
|
+
`;
|
|
2204
|
+
}
|
|
2205
|
+
return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
|
|
2206
|
+
|
|
2207
|
+
export default defineConfig({
|
|
2208
|
+
${lines.join("\n")}
|
|
2209
|
+
});
|
|
2210
|
+
`;
|
|
2211
|
+
}
|
|
2212
|
+
var PromptCancelled = class extends Error {
|
|
2213
|
+
constructor() {
|
|
2214
|
+
super("Prompt cancelled");
|
|
2215
|
+
this.name = "PromptCancelled";
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
function defaultIO() {
|
|
2219
|
+
return { input: process.stdin, output: process.stdout };
|
|
2220
|
+
}
|
|
2221
|
+
function isInteractive(io) {
|
|
2222
|
+
return Boolean(io.input.isTTY && io.output.isTTY);
|
|
2223
|
+
}
|
|
2224
|
+
function moveSelection(current, delta, count) {
|
|
2225
|
+
if (count <= 0) return 0;
|
|
2226
|
+
return ((current + delta) % count + count) % count;
|
|
2227
|
+
}
|
|
2228
|
+
function toggleIndex(selected, index) {
|
|
2229
|
+
const next = new Set(selected);
|
|
2230
|
+
if (next.has(index)) next.delete(index);
|
|
2231
|
+
else next.add(index);
|
|
2232
|
+
return next;
|
|
2233
|
+
}
|
|
2234
|
+
function marker(active) {
|
|
2235
|
+
return active ? "\u276F" : " ";
|
|
2236
|
+
}
|
|
2237
|
+
function choiceLine(choice, active) {
|
|
2238
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
2239
|
+
return ` ${marker(active)} ${choice.label}${hint}`;
|
|
2240
|
+
}
|
|
2241
|
+
function renderSelect(message, choices, index) {
|
|
2242
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
2243
|
+
choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
|
|
2244
|
+
lines.push("", " \u2191\u2193 move \xB7 enter select");
|
|
2245
|
+
return lines.join("\n");
|
|
2246
|
+
}
|
|
2247
|
+
function renderMultiSelect(message, choices, index, selected) {
|
|
2248
|
+
const lines = [`\u25C6 ${message}`, ""];
|
|
2249
|
+
choices.forEach((choice, position) => {
|
|
2250
|
+
const box = selected.has(position) ? "\u25FC" : "\u25FB";
|
|
2251
|
+
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
2252
|
+
lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
|
|
2253
|
+
});
|
|
2254
|
+
lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
|
|
2255
|
+
return lines.join("\n");
|
|
2256
|
+
}
|
|
2257
|
+
function renderConfirm(message, value) {
|
|
2258
|
+
return [
|
|
2259
|
+
`\u25C6 ${message}`,
|
|
2260
|
+
"",
|
|
2261
|
+
` ${marker(value)} Yes`,
|
|
2262
|
+
` ${marker(!value)} No`,
|
|
2263
|
+
"",
|
|
2264
|
+
" \u2190\u2192 or y/n \xB7 enter confirm"
|
|
2265
|
+
].join("\n");
|
|
2266
|
+
}
|
|
2267
|
+
function renderText(message, value, error) {
|
|
2268
|
+
const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
|
|
2269
|
+
if (error) lines.push("", ` \u2716 ${error}`);
|
|
2270
|
+
return lines.join("\n");
|
|
2271
|
+
}
|
|
2272
|
+
function keyReader(input) {
|
|
2273
|
+
const queue = [];
|
|
2274
|
+
const waiters = [];
|
|
2275
|
+
const wasRaw = input.isRaw;
|
|
2276
|
+
const onKey = (_text, key) => {
|
|
2277
|
+
const waiter = waiters.shift();
|
|
2278
|
+
if (waiter) waiter(key);
|
|
2279
|
+
else queue.push(key);
|
|
2280
|
+
};
|
|
2281
|
+
readline__namespace.emitKeypressEvents(input);
|
|
2282
|
+
if (input.isTTY) input.setRawMode(true);
|
|
2283
|
+
input.resume();
|
|
2284
|
+
input.on("keypress", onKey);
|
|
2285
|
+
return {
|
|
2286
|
+
next() {
|
|
2287
|
+
const queued = queue.shift();
|
|
2288
|
+
if (queued) return Promise.resolve(queued);
|
|
2289
|
+
return new Promise((resolve6) => waiters.push(resolve6));
|
|
2290
|
+
},
|
|
2291
|
+
close() {
|
|
2292
|
+
input.off("keypress", onKey);
|
|
2293
|
+
if (input.isTTY) input.setRawMode(wasRaw ?? false);
|
|
2294
|
+
input.pause();
|
|
1505
2295
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
var Frame = class {
|
|
2299
|
+
constructor(output) {
|
|
2300
|
+
this.output = output;
|
|
2301
|
+
}
|
|
2302
|
+
output;
|
|
2303
|
+
drawn = 0;
|
|
2304
|
+
draw(text2) {
|
|
2305
|
+
this.erase();
|
|
2306
|
+
this.output.write(`${text2}
|
|
2307
|
+
`);
|
|
2308
|
+
this.drawn = text2.split("\n").length;
|
|
2309
|
+
}
|
|
2310
|
+
erase() {
|
|
2311
|
+
if (this.drawn > 0) this.output.write(`\x1B[${this.drawn}A\x1B[0J`);
|
|
2312
|
+
this.drawn = 0;
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
function isCancel(key) {
|
|
2316
|
+
return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
|
|
2317
|
+
}
|
|
2318
|
+
async function select(io, message, choices, options = {}) {
|
|
2319
|
+
if (choices.length === 0) throw new Error("select() requires at least one choice.");
|
|
2320
|
+
const fallbackIndex = Math.max(
|
|
2321
|
+
0,
|
|
2322
|
+
choices.findIndex((choice) => choice.value === options.defaultValue)
|
|
2323
|
+
);
|
|
2324
|
+
if (!isInteractive(io)) {
|
|
2325
|
+
io.output.write(`${renderSelect(message, choices, fallbackIndex)}
|
|
2326
|
+
`);
|
|
2327
|
+
return choices[fallbackIndex].value;
|
|
2328
|
+
}
|
|
2329
|
+
const frame = new Frame(io.output);
|
|
2330
|
+
const reader = keyReader(io.input);
|
|
2331
|
+
let index = fallbackIndex;
|
|
2332
|
+
try {
|
|
2333
|
+
for (; ; ) {
|
|
2334
|
+
frame.draw(renderSelect(message, choices, index));
|
|
2335
|
+
const key = await reader.next();
|
|
2336
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2337
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
2338
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
2339
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
2340
|
+
frame.erase();
|
|
2341
|
+
return choices[index].value;
|
|
2342
|
+
} else if (key.name === "j") index = moveSelection(index, 1, choices.length);
|
|
2343
|
+
else if (key.name === "k") index = moveSelection(index, -1, choices.length);
|
|
1514
2344
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
2345
|
+
} finally {
|
|
2346
|
+
reader.close();
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
async function multiSelect(io, message, choices, options = {}) {
|
|
2350
|
+
const selected = /* @__PURE__ */ new Set();
|
|
2351
|
+
choices.forEach((choice, position) => {
|
|
2352
|
+
if (options.defaults?.includes(choice.value)) selected.add(position);
|
|
2353
|
+
});
|
|
2354
|
+
const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
|
|
2355
|
+
if (!isInteractive(io)) {
|
|
2356
|
+
io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
|
|
2357
|
+
`);
|
|
2358
|
+
return result();
|
|
2359
|
+
}
|
|
2360
|
+
const frame = new Frame(io.output);
|
|
2361
|
+
const reader = keyReader(io.input);
|
|
2362
|
+
let index = 0;
|
|
2363
|
+
try {
|
|
2364
|
+
for (; ; ) {
|
|
2365
|
+
frame.draw(renderMultiSelect(message, choices, index, selected));
|
|
2366
|
+
const key = await reader.next();
|
|
2367
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2368
|
+
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
2369
|
+
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
2370
|
+
else if (key.name === "space") {
|
|
2371
|
+
const next = toggleIndex(selected, index);
|
|
2372
|
+
selected.clear();
|
|
2373
|
+
for (const position of next) selected.add(position);
|
|
2374
|
+
} else if (key.name === "a") {
|
|
2375
|
+
if (selected.size === choices.length) selected.clear();
|
|
2376
|
+
else choices.forEach((_, position) => selected.add(position));
|
|
2377
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
2378
|
+
frame.erase();
|
|
2379
|
+
return result();
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
} finally {
|
|
2383
|
+
reader.close();
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
async function confirm(io, message, options = {}) {
|
|
2387
|
+
let value = options.defaultValue ?? true;
|
|
2388
|
+
if (!isInteractive(io)) {
|
|
2389
|
+
io.output.write(`${renderConfirm(message, value)}
|
|
2390
|
+
`);
|
|
2391
|
+
return value;
|
|
2392
|
+
}
|
|
2393
|
+
const frame = new Frame(io.output);
|
|
2394
|
+
const reader = keyReader(io.input);
|
|
2395
|
+
try {
|
|
2396
|
+
for (; ; ) {
|
|
2397
|
+
frame.draw(renderConfirm(message, value));
|
|
2398
|
+
const key = await reader.next();
|
|
2399
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2400
|
+
if (key.name === "up" || key.name === "left") value = true;
|
|
2401
|
+
else if (key.name === "down" || key.name === "right") value = false;
|
|
2402
|
+
else if (key.name === "y") value = true;
|
|
2403
|
+
else if (key.name === "n") value = false;
|
|
2404
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
2405
|
+
frame.erase();
|
|
2406
|
+
return value;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
} finally {
|
|
2410
|
+
reader.close();
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
async function text(io, message, options = {}) {
|
|
2414
|
+
const fallback = options.defaultValue ?? "";
|
|
2415
|
+
if (!isInteractive(io)) {
|
|
2416
|
+
io.output.write(`${renderText(message, fallback)}
|
|
2417
|
+
`);
|
|
2418
|
+
return fallback;
|
|
2419
|
+
}
|
|
2420
|
+
const frame = new Frame(io.output);
|
|
2421
|
+
const reader = keyReader(io.input);
|
|
2422
|
+
let value = fallback;
|
|
2423
|
+
let error;
|
|
2424
|
+
try {
|
|
2425
|
+
for (; ; ) {
|
|
2426
|
+
frame.draw(renderText(message, value, error));
|
|
2427
|
+
const key = await reader.next();
|
|
2428
|
+
if (isCancel(key)) throw new PromptCancelled();
|
|
2429
|
+
error = void 0;
|
|
2430
|
+
if (key.name === "return" || key.name === "enter") {
|
|
2431
|
+
const problem = options.validate ? options.validate(value) : null;
|
|
2432
|
+
if (problem) {
|
|
2433
|
+
error = problem;
|
|
2434
|
+
continue;
|
|
2435
|
+
}
|
|
2436
|
+
frame.erase();
|
|
2437
|
+
return value;
|
|
2438
|
+
}
|
|
2439
|
+
if (key.name === "backspace") {
|
|
2440
|
+
value = value.slice(0, -1);
|
|
2441
|
+
continue;
|
|
2442
|
+
}
|
|
2443
|
+
if (key.name === "escape") {
|
|
2444
|
+
value = "";
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
if (_isPrintable(key)) value += _keyText(key);
|
|
2448
|
+
}
|
|
2449
|
+
} finally {
|
|
2450
|
+
reader.close();
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
function _isPrintable(key) {
|
|
2454
|
+
return typeof key.sequence === "string" && key.sequence.length === 1 && key.sequence >= " ";
|
|
2455
|
+
}
|
|
2456
|
+
function _keyText(key) {
|
|
2457
|
+
return key.sequence ?? "";
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// src/wizard/init.ts
|
|
2461
|
+
var CONFIG_CANDIDATES = [
|
|
2462
|
+
"snapshot.config.ts",
|
|
2463
|
+
"snapshot.config.js",
|
|
2464
|
+
"snapshot.config.mjs",
|
|
2465
|
+
"snapshot.config.cjs",
|
|
2466
|
+
"snapshot.config.json",
|
|
2467
|
+
".snapshotrc.json",
|
|
2468
|
+
".snapshotrc"
|
|
2469
|
+
];
|
|
2470
|
+
var SIZE_CHOICES = [
|
|
2471
|
+
{ value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
|
|
2472
|
+
{ value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
|
|
2473
|
+
{ value: "300k", label: "Large", hint: "300 KB \xB7 ~75k tokens \xB7 the default" },
|
|
2474
|
+
{ value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
|
|
2475
|
+
];
|
|
2476
|
+
function existingConfig(cwd) {
|
|
2477
|
+
for (const name of CONFIG_CANDIDATES) {
|
|
2478
|
+
if (fs9__namespace.existsSync(path10__namespace.join(cwd, name))) return name;
|
|
2479
|
+
}
|
|
2480
|
+
return null;
|
|
2481
|
+
}
|
|
2482
|
+
function artifactAnswers(options) {
|
|
2483
|
+
const artifacts = { ...DEFAULT_ANSWERS.artifacts };
|
|
2484
|
+
for (const name of options.skipArtifacts ?? []) {
|
|
2485
|
+
if (ARTIFACT_NAMES.includes(name)) {
|
|
2486
|
+
artifacts[name] = false;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
if (options.onlyArtifacts && options.onlyArtifacts.length > 0) {
|
|
2490
|
+
const keep = new Set(options.onlyArtifacts);
|
|
2491
|
+
for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
|
|
2492
|
+
}
|
|
2493
|
+
return artifacts;
|
|
2494
|
+
}
|
|
2495
|
+
function topLevelDirs(cwd) {
|
|
2496
|
+
try {
|
|
2497
|
+
return fs9__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
|
|
1521
2498
|
} catch {
|
|
2499
|
+
return [];
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
function addScripts(cwd, notes) {
|
|
2503
|
+
const packagePath = path10__namespace.join(cwd, "package.json");
|
|
2504
|
+
if (!fs9__namespace.existsSync(packagePath)) {
|
|
2505
|
+
notes.push("No package.json found, so npm scripts were not added.");
|
|
2506
|
+
return [];
|
|
2507
|
+
}
|
|
2508
|
+
let parsed;
|
|
2509
|
+
try {
|
|
2510
|
+
parsed = JSON.parse(fs9__namespace.readFileSync(packagePath, "utf8"));
|
|
2511
|
+
} catch {
|
|
2512
|
+
notes.push("Could not parse package.json; npm scripts were not added.");
|
|
2513
|
+
return [];
|
|
2514
|
+
}
|
|
2515
|
+
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
2516
|
+
const added = [];
|
|
2517
|
+
if (!scripts.snapshot) {
|
|
2518
|
+
scripts.snapshot = "codebase-snapshot generate";
|
|
2519
|
+
added.push("snapshot");
|
|
2520
|
+
}
|
|
2521
|
+
if (!scripts["snapshot:validate"]) {
|
|
2522
|
+
scripts["snapshot:validate"] = "codebase-snapshot validate";
|
|
2523
|
+
added.push("snapshot:validate");
|
|
2524
|
+
}
|
|
2525
|
+
if (added.length === 0) return [];
|
|
2526
|
+
parsed.scripts = scripts;
|
|
2527
|
+
fs9__namespace.writeFileSync(packagePath, `${JSON.stringify(parsed, null, 2)}
|
|
2528
|
+
`, "utf8");
|
|
2529
|
+
return added;
|
|
2530
|
+
}
|
|
2531
|
+
function addGitignore(cwd) {
|
|
2532
|
+
const ignorePath = path10__namespace.join(cwd, ".gitignore");
|
|
2533
|
+
const existing = fs9__namespace.existsSync(ignorePath) ? fs9__namespace.readFileSync(ignorePath, "utf8") : "";
|
|
2534
|
+
const wanted = ["snapshot/", "snapshot.tmp/"];
|
|
2535
|
+
const added = wanted.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
2536
|
+
if (added.length === 0) return [];
|
|
2537
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
2538
|
+
fs9__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
|
|
2539
|
+
`, "utf8");
|
|
2540
|
+
return added;
|
|
2541
|
+
}
|
|
2542
|
+
function resolvePresetChoice(answers, presetName) {
|
|
2543
|
+
return {
|
|
2544
|
+
value: presetName,
|
|
2545
|
+
label: presetName,
|
|
2546
|
+
hint: presetName === answers.preset ? "detected" : void 0
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
async function runInit(options) {
|
|
2550
|
+
const io = options.io ?? defaultIO();
|
|
2551
|
+
const detected = detectPreset(options.cwd, options.preset);
|
|
2552
|
+
const notes = [];
|
|
2553
|
+
const answers = {
|
|
2554
|
+
preset: detected.presetName,
|
|
2555
|
+
maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
|
|
2556
|
+
largeFileBytes: DEFAULT_ANSWERS.largeFileBytes,
|
|
2557
|
+
include: options.include ?? [],
|
|
2558
|
+
excludePatterns: options.exclude ?? [],
|
|
2559
|
+
artifacts: artifactAnswers(options),
|
|
2560
|
+
includeSensitiveFiles: options.includeSensitiveFiles ?? DEFAULT_ANSWERS.includeSensitiveFiles,
|
|
2561
|
+
outDir: options.outDir ?? DEFAULT_ANSWERS.outDir
|
|
2562
|
+
};
|
|
2563
|
+
const interactive = isInteractive(io) && options.yes !== true;
|
|
2564
|
+
if (interactive) {
|
|
2565
|
+
io.output.write("\n @ankurhalder/codebase-snapshot\n");
|
|
2566
|
+
io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
|
|
2567
|
+
io.output.write(` Detected ${detected.presetName}
|
|
2568
|
+
`);
|
|
2569
|
+
io.output.write(` Directory ${options.cwd}
|
|
2570
|
+
|
|
2571
|
+
`);
|
|
2572
|
+
const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
|
|
2573
|
+
defaultValue: true
|
|
2574
|
+
});
|
|
2575
|
+
if (!useDetected) {
|
|
2576
|
+
answers.preset = await select(
|
|
2577
|
+
io,
|
|
2578
|
+
"What kind of project is this?",
|
|
2579
|
+
listPresetNames().map((name) => resolvePresetChoice(answers, name))
|
|
2580
|
+
);
|
|
2581
|
+
const chosen = detectPreset(options.cwd, answers.preset);
|
|
2582
|
+
answers.preset = chosen.presetName;
|
|
2583
|
+
}
|
|
2584
|
+
const scope = await select(
|
|
2585
|
+
io,
|
|
2586
|
+
"What should go into the snapshot?",
|
|
2587
|
+
[
|
|
2588
|
+
{ value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
|
|
2589
|
+
{ value: "folders", label: "Only specific folders\u2026" },
|
|
2590
|
+
{ value: "no-tests", label: "Everything, but skip tests" },
|
|
2591
|
+
{ value: "custom", label: "Let me write glob patterns" }
|
|
2592
|
+
],
|
|
2593
|
+
{ defaultValue: "all" }
|
|
2594
|
+
);
|
|
2595
|
+
if (scope === "folders") {
|
|
2596
|
+
const dirs = topLevelDirs(options.cwd).slice(0, 40);
|
|
2597
|
+
if (dirs.length === 0) {
|
|
2598
|
+
notes.push("No top-level directories found; the snapshot will include everything.");
|
|
2599
|
+
} else {
|
|
2600
|
+
const chosen = await multiSelect(
|
|
2601
|
+
io,
|
|
2602
|
+
"Select folders",
|
|
2603
|
+
dirs.map((name) => ({ value: name, label: `${name}/` })),
|
|
2604
|
+
{ defaults: dirs }
|
|
2605
|
+
);
|
|
2606
|
+
answers.include = chosen.map((name) => `${name}/**`);
|
|
2607
|
+
}
|
|
2608
|
+
} else if (scope === "no-tests") {
|
|
2609
|
+
answers.excludePatterns = [
|
|
2610
|
+
...answers.excludePatterns,
|
|
2611
|
+
"**/*.test.*",
|
|
2612
|
+
"**/*.spec.*",
|
|
2613
|
+
"tests",
|
|
2614
|
+
"test"
|
|
2615
|
+
];
|
|
2616
|
+
} else if (scope === "custom") {
|
|
2617
|
+
const raw = await text(io, "Glob patterns to include (comma separated)", {
|
|
2618
|
+
defaultValue: ""
|
|
2619
|
+
});
|
|
2620
|
+
answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
2621
|
+
}
|
|
2622
|
+
const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
|
|
2623
|
+
defaultValue: "120k"
|
|
2624
|
+
});
|
|
2625
|
+
if (sizeChoice === "custom") {
|
|
2626
|
+
const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
|
|
2627
|
+
defaultValue: String(answers.maxPartBytes),
|
|
2628
|
+
validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
|
|
2629
|
+
});
|
|
2630
|
+
answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
|
|
2631
|
+
} else {
|
|
2632
|
+
answers.maxPartBytes = sizeChoice;
|
|
2633
|
+
}
|
|
2634
|
+
const artifactChoices = [
|
|
2635
|
+
{ value: "readme", label: "README.md", hint: "orientation page" },
|
|
2636
|
+
{ value: "manifest", label: "manifest.json", hint: "required by validate and report" },
|
|
2637
|
+
{ value: "parts", label: "parts/", hint: "the code payload" },
|
|
2638
|
+
{ value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
|
|
2639
|
+
{ value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
|
|
2640
|
+
{ value: "audit", label: "reports/audit.md", hint: "security and health findings" },
|
|
2641
|
+
{ value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
|
|
2642
|
+
];
|
|
2643
|
+
const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
|
|
2644
|
+
const selectedArtifacts = await multiSelect(
|
|
2645
|
+
io,
|
|
2646
|
+
"Which artifacts should be generated?",
|
|
2647
|
+
artifactChoices,
|
|
2648
|
+
{ defaults: presetEnabled }
|
|
2649
|
+
);
|
|
2650
|
+
for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
|
|
2651
|
+
const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
|
|
2652
|
+
answers.includeSensitiveFiles = await confirm(
|
|
2653
|
+
io,
|
|
2654
|
+
"Include credential files (.env, *.pem, .npmrc, .ssh/)?",
|
|
2655
|
+
{ defaultValue: false }
|
|
2656
|
+
);
|
|
2657
|
+
io.output.write("\n Review\n\n");
|
|
2658
|
+
io.output.write(` project ${answers.preset}
|
|
2659
|
+
`);
|
|
2660
|
+
io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
|
|
2661
|
+
`);
|
|
2662
|
+
io.output.write(` part size ${String(answers.maxPartBytes)}
|
|
2663
|
+
`);
|
|
2664
|
+
io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
|
|
2665
|
+
`);
|
|
2666
|
+
io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
|
|
2667
|
+
|
|
2668
|
+
`);
|
|
2669
|
+
io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
|
|
2670
|
+
io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
|
|
2671
|
+
const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
|
|
2672
|
+
if (!proceed) throw new PromptCancelled();
|
|
2673
|
+
}
|
|
2674
|
+
const configText = options.minimal ? renderMinimalConfig(answers) : renderConfig(answers);
|
|
2675
|
+
const configName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
2676
|
+
const configPath = path10__namespace.join(options.cwd, configName);
|
|
2677
|
+
if (options.dryRun) {
|
|
1522
2678
|
return {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
2679
|
+
configPath,
|
|
2680
|
+
configText,
|
|
2681
|
+
written: false,
|
|
2682
|
+
preset: answers.preset,
|
|
2683
|
+
scripts: [],
|
|
2684
|
+
gitignoreEntries: [],
|
|
2685
|
+
notes
|
|
1527
2686
|
};
|
|
1528
2687
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2688
|
+
const present = existingConfig(options.cwd);
|
|
2689
|
+
if (present && present !== configName && !options.force) {
|
|
2690
|
+
throw new Error(
|
|
2691
|
+
`A configuration already exists (${present}). Re-run with --force to replace it.`
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
if (fs9__namespace.existsSync(configPath) && !options.force) {
|
|
2695
|
+
throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
|
|
2696
|
+
}
|
|
2697
|
+
fs9__namespace.writeFileSync(configPath, configText, "utf8");
|
|
2698
|
+
const scripts = addScripts(options.cwd, notes);
|
|
2699
|
+
const gitignoreEntries = addGitignore(options.cwd);
|
|
2700
|
+
return {
|
|
2701
|
+
configPath,
|
|
2702
|
+
configText,
|
|
2703
|
+
written: true,
|
|
2704
|
+
preset: answers.preset,
|
|
2705
|
+
scripts,
|
|
2706
|
+
gitignoreEntries,
|
|
2707
|
+
notes
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
// src/ast/profiles/shared.ts
|
|
2712
|
+
var MAX_SUMMARY_CHARS = 1e6;
|
|
2713
|
+
function emptySummary() {
|
|
2714
|
+
return {
|
|
2715
|
+
directives: [],
|
|
2716
|
+
imports: [],
|
|
2717
|
+
exports: [],
|
|
2718
|
+
reExports: [],
|
|
2719
|
+
functions: [],
|
|
2720
|
+
classes: [],
|
|
2721
|
+
interfaces: [],
|
|
2722
|
+
types: [],
|
|
2723
|
+
enums: [],
|
|
2724
|
+
variables: [],
|
|
2725
|
+
reactComponents: [],
|
|
2726
|
+
hooks: [],
|
|
2727
|
+
routeHandlers: []
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
function dedupeSorted(values) {
|
|
2731
|
+
return Array.from(new Set(values.filter(Boolean))).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2732
|
+
}
|
|
2733
|
+
function finalizeSummary(summary) {
|
|
2734
|
+
return {
|
|
2735
|
+
directives: dedupeSorted(summary.directives),
|
|
2736
|
+
imports: dedupeSorted(summary.imports),
|
|
2737
|
+
exports: dedupeSorted(summary.exports),
|
|
2738
|
+
reExports: dedupeSorted(summary.reExports),
|
|
2739
|
+
functions: dedupeSorted(summary.functions),
|
|
2740
|
+
classes: dedupeSorted(summary.classes),
|
|
2741
|
+
interfaces: dedupeSorted(summary.interfaces),
|
|
2742
|
+
types: dedupeSorted(summary.types),
|
|
2743
|
+
enums: dedupeSorted(summary.enums),
|
|
2744
|
+
variables: dedupeSorted(summary.variables),
|
|
2745
|
+
reactComponents: dedupeSorted(summary.reactComponents),
|
|
2746
|
+
hooks: dedupeSorted(summary.hooks),
|
|
2747
|
+
routeHandlers: dedupeSorted(summary.routeHandlers)
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
function eachLine(content, visit) {
|
|
2751
|
+
const text2 = content.length > MAX_SUMMARY_CHARS ? content.slice(0, MAX_SUMMARY_CHARS) : content;
|
|
2752
|
+
for (const line of text2.split(/\r?\n/)) visit(line);
|
|
2753
|
+
}
|
|
2754
|
+
function isTopLevel(line) {
|
|
2755
|
+
return !/^[\s]/.test(line);
|
|
2756
|
+
}
|
|
2757
|
+
function isPublicName(name) {
|
|
2758
|
+
return name.length > 0 && !name.startsWith("_") && /^[A-Za-z]/.test(name);
|
|
2759
|
+
}
|
|
2760
|
+
function isCapitalizedName(name) {
|
|
2761
|
+
return /^[A-Z]/.test(name);
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// src/ast/profiles/cfamily.ts
|
|
2765
|
+
var INCLUDE = /^#\s*include\s*[<"]([^>"]+)[>"]/;
|
|
2766
|
+
var DEFINE = /^#\s*define\s+([A-Za-z_]\w*)/;
|
|
2767
|
+
var USING = /^using\s+(?:namespace\s+)?([\w:]+)\s*;/;
|
|
2768
|
+
var TYPE_DECL = /^(?:typedef\s+)?(?:struct|union|class|enum)\s+([A-Za-z_]\w*)/;
|
|
2769
|
+
var TYPEDEF = /^typedef\b[^;{}]*?\b([A-Za-z_]\w*)\s*;/;
|
|
2770
|
+
var FUNCTION = /^[A-Za-z_][\w \t*&:<>,]*?\b([A-Za-z_]\w*)\s*\([^;]*\)\s*\{?\s*$/;
|
|
2771
|
+
var NOT_A_FUNCTION = /^(?:if|for|while|switch|return|else|do|case|sizeof|catch|throw)\b/;
|
|
2772
|
+
var cfamilyProfile = {
|
|
2773
|
+
id: "cfamily",
|
|
2774
|
+
extensions: [".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hxx", ".m", ".mm"],
|
|
2775
|
+
summarize(_path, content) {
|
|
2776
|
+
const summary = emptySummary();
|
|
2777
|
+
eachLine(content, (line) => {
|
|
2778
|
+
const trimmed = line.trim();
|
|
2779
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
const included = INCLUDE.exec(trimmed);
|
|
2783
|
+
if (included) {
|
|
2784
|
+
summary.imports.push(included[1]);
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
const used = USING.exec(trimmed);
|
|
2788
|
+
if (used) {
|
|
2789
|
+
summary.imports.push(used[1]);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
const macro = DEFINE.exec(trimmed);
|
|
2793
|
+
if (macro) {
|
|
2794
|
+
summary.variables.push(macro[1]);
|
|
2795
|
+
return;
|
|
2796
|
+
}
|
|
2797
|
+
if (!isTopLevel(line)) return;
|
|
2798
|
+
const declared = TYPE_DECL.exec(line);
|
|
2799
|
+
if (declared) {
|
|
2800
|
+
const kind = /^typedef\s+enum/.test(line) || /^enum\b/.test(trimmed) ? "enum" : "type";
|
|
2801
|
+
if (kind === "enum") summary.enums.push(declared[1]);
|
|
2802
|
+
else summary.classes.push(declared[1]);
|
|
2803
|
+
summary.exports.push(declared[1]);
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
const aliased = TYPEDEF.exec(line);
|
|
2807
|
+
if (aliased) {
|
|
2808
|
+
summary.types.push(aliased[1]);
|
|
2809
|
+
summary.exports.push(aliased[1]);
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (NOT_A_FUNCTION.test(trimmed)) return;
|
|
2813
|
+
const fn = FUNCTION.exec(line);
|
|
2814
|
+
if (fn) {
|
|
2815
|
+
summary.functions.push(fn[1]);
|
|
2816
|
+
summary.exports.push(fn[1]);
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
// src/ast/profiles/dotnet.ts
|
|
2824
|
+
var USING2 = /^using\s+([\w.]+)\s*;/;
|
|
2825
|
+
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*)/;
|
|
2826
|
+
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*[<(]/;
|
|
2827
|
+
var dotnetProfile = {
|
|
2828
|
+
id: "dotnet",
|
|
2829
|
+
extensions: [".cs"],
|
|
2830
|
+
summarize(_path, content) {
|
|
2831
|
+
const summary = emptySummary();
|
|
2832
|
+
eachLine(content, (line) => {
|
|
2833
|
+
const trimmed = line.trim();
|
|
2834
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
const used = USING2.exec(trimmed);
|
|
2838
|
+
if (used) {
|
|
2839
|
+
summary.imports.push(used[1]);
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
const declaration = TYPE_DECL2.exec(line);
|
|
2843
|
+
if (declaration) {
|
|
2844
|
+
const kind = declaration[1];
|
|
2845
|
+
const name = declaration[2];
|
|
2846
|
+
if (kind === "interface") summary.interfaces.push(name);
|
|
2847
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
2848
|
+
else summary.classes.push(name);
|
|
2849
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(name);
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
const method = METHOD.exec(line);
|
|
2853
|
+
if (method) {
|
|
2854
|
+
summary.functions.push(method[1]);
|
|
2855
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
// src/ast/profiles/go.ts
|
|
2863
|
+
var FUNC = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/;
|
|
2864
|
+
var TYPE = /^type\s+([A-Za-z_]\w*)\s+(.*)$/;
|
|
2865
|
+
var VAR_CONST = /^(?:var|const)\s+([A-Za-z_]\w*)/;
|
|
2866
|
+
var IMPORT_SINGLE = /^import\s+(?:[\w.]+\s+)?"([^"]+)"/;
|
|
2867
|
+
var IMPORT_OPEN = /^import\s*\(/;
|
|
2868
|
+
var IMPORT_ENTRY = /^\s*(?:[\w.]+\s+)?"([^"]+)"/;
|
|
2869
|
+
var goProfile = {
|
|
2870
|
+
id: "go",
|
|
2871
|
+
extensions: [".go"],
|
|
2872
|
+
summarize(_path, content) {
|
|
2873
|
+
const summary = emptySummary();
|
|
2874
|
+
let inImportBlock = false;
|
|
2875
|
+
eachLine(content, (line) => {
|
|
2876
|
+
const trimmed = line.trim();
|
|
2877
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
2878
|
+
if (inImportBlock) {
|
|
2879
|
+
if (trimmed.startsWith(")")) {
|
|
2880
|
+
inImportBlock = false;
|
|
2881
|
+
return;
|
|
2882
|
+
}
|
|
2883
|
+
const entry = IMPORT_ENTRY.exec(line);
|
|
2884
|
+
if (entry) summary.imports.push(entry[1]);
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
if (IMPORT_OPEN.test(trimmed)) {
|
|
2888
|
+
inImportBlock = true;
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
const single = IMPORT_SINGLE.exec(trimmed);
|
|
2892
|
+
if (single) {
|
|
2893
|
+
summary.imports.push(single[1]);
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
if (!isTopLevel(line)) return;
|
|
2897
|
+
const method = FUNC.exec(line);
|
|
2898
|
+
if (method) {
|
|
2899
|
+
const name = method[1];
|
|
2900
|
+
summary.functions.push(name);
|
|
2901
|
+
if (isCapitalizedName(name)) summary.exports.push(name);
|
|
2902
|
+
return;
|
|
2903
|
+
}
|
|
2904
|
+
const declared = TYPE.exec(line);
|
|
2905
|
+
if (declared) {
|
|
2906
|
+
const name = declared[1];
|
|
2907
|
+
const body = declared[2].trim();
|
|
2908
|
+
if (body.startsWith("struct")) summary.classes.push(name);
|
|
2909
|
+
else if (body.startsWith("interface")) summary.interfaces.push(name);
|
|
2910
|
+
else summary.types.push(name);
|
|
2911
|
+
if (isCapitalizedName(name)) summary.exports.push(name);
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
const value = VAR_CONST.exec(line);
|
|
2915
|
+
if (value) {
|
|
2916
|
+
summary.variables.push(value[1]);
|
|
2917
|
+
if (isCapitalizedName(value[1])) summary.exports.push(value[1]);
|
|
2918
|
+
}
|
|
2919
|
+
});
|
|
2920
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
|
|
2924
|
+
// src/ast/profiles/jvm.ts
|
|
2925
|
+
var IMPORT = /^import\s+(?:static\s+)?([\w.]+?)(?:\.\*)?;?\s*$/;
|
|
2926
|
+
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*)/;
|
|
2927
|
+
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*)/;
|
|
2928
|
+
var SCALA_DEF = /^\s*def\s+([A-Za-z_]\w*)/;
|
|
2929
|
+
var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?[\w<>[\],.?]+\s+([A-Za-z_]\w*)\s*\(/;
|
|
2930
|
+
var jvmProfile = {
|
|
2931
|
+
id: "jvm",
|
|
2932
|
+
extensions: [".java", ".kt", ".kts", ".scala"],
|
|
2933
|
+
summarize(path12, content) {
|
|
2934
|
+
const summary = emptySummary();
|
|
2935
|
+
const lower = path12.toLowerCase();
|
|
2936
|
+
const isJava = lower.endsWith(".java");
|
|
2937
|
+
const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
|
|
2938
|
+
eachLine(content, (line) => {
|
|
2939
|
+
const trimmed = line.trim();
|
|
2940
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
const imported = IMPORT.exec(trimmed);
|
|
2944
|
+
if (imported) {
|
|
2945
|
+
summary.imports.push(imported[1]);
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
const declaration = TYPE_DECL3.exec(line);
|
|
2949
|
+
if (declaration) {
|
|
2950
|
+
const kind = declaration[1];
|
|
2951
|
+
const name = declaration[2];
|
|
2952
|
+
if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
|
|
2953
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
2954
|
+
else summary.classes.push(name);
|
|
2955
|
+
const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
|
|
2956
|
+
if (isJava ? /^\s*public\b/.test(line) : !hidden) summary.exports.push(name);
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const kotlinFunction = KOTLIN_FUN.exec(line);
|
|
2960
|
+
if (kotlinFunction && isKotlin) {
|
|
2961
|
+
summary.functions.push(kotlinFunction[1]);
|
|
2962
|
+
const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
|
|
2963
|
+
if (!hidden) summary.exports.push(kotlinFunction[1]);
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
const scalaFunction = SCALA_DEF.exec(line);
|
|
2967
|
+
if (scalaFunction && !isJava && !isKotlin) {
|
|
2968
|
+
summary.functions.push(scalaFunction[1]);
|
|
2969
|
+
const hidden = /^\s*(?:private|protected)\b/.test(line);
|
|
2970
|
+
if (!hidden) summary.exports.push(scalaFunction[1]);
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
if (isJava) {
|
|
2974
|
+
const method = JAVA_METHOD.exec(line);
|
|
2975
|
+
if (method) {
|
|
2976
|
+
summary.functions.push(method[1]);
|
|
2977
|
+
if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2981
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
// src/ast/profiles/php.ts
|
|
2986
|
+
var USE = /^use\s+([\w\\]+)\s*(?:as\s+\w+)?;/;
|
|
2987
|
+
var DECLARE = /^(?:abstract\s+|final\s+|readonly\s+)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/;
|
|
2988
|
+
var FUNCTION2 = /^(?:public\s+|protected\s+|private\s+|static\s+|final\s+|abstract\s+)*function\s+([A-Za-z_]\w*)/;
|
|
2989
|
+
var phpProfile = {
|
|
2990
|
+
id: "php",
|
|
2991
|
+
extensions: [".php"],
|
|
2992
|
+
summarize(_path, content) {
|
|
2993
|
+
const summary = emptySummary();
|
|
2994
|
+
eachLine(content, (line) => {
|
|
2995
|
+
const trimmed = line.trim();
|
|
2996
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*")) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
const used = USE.exec(trimmed);
|
|
3000
|
+
if (used) {
|
|
3001
|
+
summary.imports.push(used[1].replace(/\\\\/g, "\\"));
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
const declaration = DECLARE.exec(trimmed);
|
|
3005
|
+
if (declaration) {
|
|
3006
|
+
const kind = declaration[1];
|
|
3007
|
+
const name = declaration[2];
|
|
3008
|
+
if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
|
|
3009
|
+
else if (kind === "enum") summary.enums.push(name);
|
|
3010
|
+
else summary.classes.push(name);
|
|
3011
|
+
summary.exports.push(name);
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
const fn = FUNCTION2.exec(trimmed);
|
|
3015
|
+
if (fn) {
|
|
3016
|
+
summary.functions.push(fn[1]);
|
|
3017
|
+
if (!/^private\b/.test(trimmed)) summary.exports.push(fn[1]);
|
|
3018
|
+
}
|
|
3019
|
+
});
|
|
3020
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
3021
|
+
}
|
|
3022
|
+
};
|
|
3023
|
+
|
|
3024
|
+
// src/ast/profiles/python.ts
|
|
3025
|
+
var IMPORT2 = /^import\s+(.+)$/;
|
|
3026
|
+
var FROM_IMPORT = /^from\s+([\w.]+)\s+import\s+(.+)$/;
|
|
3027
|
+
var CLASS = /^class\s+([A-Za-z_]\w*)/;
|
|
3028
|
+
var DEF = /^(?:async\s+)?def\s+([A-Za-z_]\w*)/;
|
|
3029
|
+
var CONSTANT = /^([A-Z_][A-Z0-9_]*)\s*(?::[^=]+)?=/;
|
|
3030
|
+
var ALL_DUNDER = /^__all__\s*=\s*\[([^\]]*)\]/;
|
|
3031
|
+
var ROUTE_DECORATOR = /^\s*@[\w.]*\b(?:get|post|put|patch|delete|head|options|route)\s*\(/i;
|
|
3032
|
+
var pythonProfile = {
|
|
3033
|
+
id: "python",
|
|
3034
|
+
extensions: [".py", ".pyi"],
|
|
3035
|
+
summarize(_path, content) {
|
|
3036
|
+
const summary = emptySummary();
|
|
3037
|
+
let routePending = false;
|
|
3038
|
+
eachLine(content, (line) => {
|
|
3039
|
+
const trimmed = line.trim();
|
|
3040
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
3041
|
+
if (ROUTE_DECORATOR.test(line)) {
|
|
3042
|
+
routePending = true;
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
const from = FROM_IMPORT.exec(trimmed);
|
|
3046
|
+
if (from) {
|
|
3047
|
+
summary.imports.push(from[1]);
|
|
3048
|
+
routePending = false;
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
const imported = IMPORT2.exec(trimmed);
|
|
3052
|
+
if (imported) {
|
|
3053
|
+
for (const part of imported[1].split(",")) {
|
|
3054
|
+
const name = part.trim().split(/\s+as\s+/)[0].trim();
|
|
3055
|
+
if (name) summary.imports.push(name);
|
|
3056
|
+
}
|
|
3057
|
+
routePending = false;
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
if (isTopLevel(line)) {
|
|
3061
|
+
const klass = CLASS.exec(line);
|
|
3062
|
+
if (klass) {
|
|
3063
|
+
summary.classes.push(klass[1]);
|
|
3064
|
+
if (isPublicName(klass[1])) summary.exports.push(klass[1]);
|
|
3065
|
+
routePending = false;
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
const definition = DEF.exec(line);
|
|
3069
|
+
if (definition) {
|
|
3070
|
+
const name = definition[1];
|
|
3071
|
+
summary.functions.push(name);
|
|
3072
|
+
if (routePending) {
|
|
3073
|
+
summary.routeHandlers.push(name);
|
|
3074
|
+
routePending = false;
|
|
3075
|
+
}
|
|
3076
|
+
if (isPublicName(name)) summary.exports.push(name);
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
const all = ALL_DUNDER.exec(trimmed);
|
|
3080
|
+
if (all) {
|
|
3081
|
+
for (const entry of all[1].split(",")) {
|
|
3082
|
+
const name = entry.trim().replace(/['"]/g, "").trim();
|
|
3083
|
+
if (name) summary.exports.push(name);
|
|
3084
|
+
}
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
const constant = CONSTANT.exec(line);
|
|
3088
|
+
if (constant) summary.variables.push(constant[1]);
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
3092
|
+
}
|
|
3093
|
+
};
|
|
3094
|
+
|
|
3095
|
+
// src/ast/profiles/ruby.ts
|
|
3096
|
+
var REQUIRE = /^require(?:_relative)?\s+["']([^"']+)["']/;
|
|
3097
|
+
var CLASS_OR_MODULE = /^(?:class|module)\s+([A-Z][\w:]*)/;
|
|
3098
|
+
var METHOD2 = /^def\s+(?:self\.)?([A-Za-z_]\w*[?!=]?)/;
|
|
3099
|
+
var rubyProfile = {
|
|
3100
|
+
id: "ruby",
|
|
3101
|
+
extensions: [".rb", ".rake", ".gemspec"],
|
|
3102
|
+
summarize(_path, content) {
|
|
3103
|
+
const summary = emptySummary();
|
|
3104
|
+
eachLine(content, (line) => {
|
|
3105
|
+
const trimmed = line.trim();
|
|
3106
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
3107
|
+
const required = REQUIRE.exec(trimmed);
|
|
3108
|
+
if (required) {
|
|
3109
|
+
summary.imports.push(required[1]);
|
|
3110
|
+
return;
|
|
1545
3111
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
3112
|
+
if (!isTopLevel(line)) return;
|
|
3113
|
+
const declaration = CLASS_OR_MODULE.exec(line);
|
|
3114
|
+
if (declaration) {
|
|
3115
|
+
summary.classes.push(declaration[1]);
|
|
3116
|
+
summary.exports.push(declaration[1]);
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
3119
|
+
const method = METHOD2.exec(line);
|
|
3120
|
+
if (method) {
|
|
3121
|
+
summary.functions.push(method[1]);
|
|
3122
|
+
summary.exports.push(method[1]);
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
3125
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
1554
3126
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
3127
|
+
};
|
|
3128
|
+
|
|
3129
|
+
// src/ast/profiles/rust.ts
|
|
3130
|
+
var USE2 = /^use\s+([^;]+);/;
|
|
3131
|
+
var FN = /^(pub(?:\([^)]*\))?\s+)?(?:const\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+([A-Za-z_]\w*)/;
|
|
3132
|
+
var STRUCT = /^(pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)/;
|
|
3133
|
+
var TRAIT = /^(pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?trait\s+([A-Za-z_]\w*)/;
|
|
3134
|
+
var ENUM = /^(pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)/;
|
|
3135
|
+
var TYPE2 = /^(pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)/;
|
|
3136
|
+
var MOD = /^(pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)/;
|
|
3137
|
+
var IMPL = /^impl(?:<[^>]*>)?\s+(?:[\w:<>]+\s+for\s+)?([A-Za-z_]\w*)/;
|
|
3138
|
+
var rustProfile = {
|
|
3139
|
+
id: "rust",
|
|
3140
|
+
extensions: [".rs"],
|
|
3141
|
+
summarize(_path, content) {
|
|
3142
|
+
const summary = emptySummary();
|
|
3143
|
+
eachLine(content, (line) => {
|
|
3144
|
+
const trimmed = line.trim();
|
|
3145
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
3146
|
+
const used = USE2.exec(trimmed);
|
|
3147
|
+
if (used) {
|
|
3148
|
+
summary.imports.push(used[1].trim());
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
if (!isTopLevel(line)) return;
|
|
3152
|
+
const isPublic = /^pub\b/.test(trimmed);
|
|
3153
|
+
const fn = FN.exec(line);
|
|
3154
|
+
if (fn) {
|
|
3155
|
+
summary.functions.push(fn[2]);
|
|
3156
|
+
if (isPublic) summary.exports.push(fn[2]);
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const struct = STRUCT.exec(line);
|
|
3160
|
+
if (struct) {
|
|
3161
|
+
summary.classes.push(struct[2]);
|
|
3162
|
+
if (isPublic) summary.exports.push(struct[2]);
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
const trait = TRAIT.exec(line);
|
|
3166
|
+
if (trait) {
|
|
3167
|
+
summary.interfaces.push(trait[2]);
|
|
3168
|
+
if (isPublic) summary.exports.push(trait[2]);
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
const enumeration = ENUM.exec(line);
|
|
3172
|
+
if (enumeration) {
|
|
3173
|
+
summary.enums.push(enumeration[2]);
|
|
3174
|
+
if (isPublic) summary.exports.push(enumeration[2]);
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
const alias = TYPE2.exec(line);
|
|
3178
|
+
if (alias) {
|
|
3179
|
+
summary.types.push(alias[2]);
|
|
3180
|
+
if (isPublic) summary.exports.push(alias[2]);
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
const module = MOD.exec(line);
|
|
3184
|
+
if (module) {
|
|
3185
|
+
summary.variables.push(module[2]);
|
|
3186
|
+
if (isPublic) summary.exports.push(module[2]);
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
const implementation = IMPL.exec(line);
|
|
3190
|
+
if (implementation) summary.classes.push(implementation[1]);
|
|
3191
|
+
});
|
|
3192
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
3193
|
+
}
|
|
3194
|
+
};
|
|
1560
3195
|
var ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
1561
3196
|
"GET",
|
|
1562
3197
|
"POST",
|
|
@@ -1596,9 +3231,9 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
1596
3231
|
if (parseErrors > 0) summary.parseErrors = parseErrors;
|
|
1597
3232
|
for (const statement of sourceFile.statements) {
|
|
1598
3233
|
if (ts__default.default.isExpressionStatement(statement) && ts__default.default.isStringLiteral(statement.expression)) {
|
|
1599
|
-
const
|
|
1600
|
-
if (
|
|
1601
|
-
summary.directives.push(
|
|
3234
|
+
const text2 = statement.expression.text;
|
|
3235
|
+
if (text2 === "use client" || text2 === "use server") {
|
|
3236
|
+
summary.directives.push(text2);
|
|
1602
3237
|
}
|
|
1603
3238
|
continue;
|
|
1604
3239
|
}
|
|
@@ -1698,23 +3333,212 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
1698
3333
|
stack.push(children[index]);
|
|
1699
3334
|
}
|
|
1700
3335
|
}
|
|
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 =
|
|
3336
|
+
const dedupeSorted2 = (values) => Array.from(new Set(values)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
3337
|
+
summary.directives = dedupeSorted2(summary.directives);
|
|
3338
|
+
summary.imports = dedupeSorted2(summary.imports);
|
|
3339
|
+
summary.exports = dedupeSorted2(summary.exports);
|
|
3340
|
+
summary.reExports = dedupeSorted2(summary.reExports);
|
|
3341
|
+
summary.functions = dedupeSorted2(summary.functions);
|
|
3342
|
+
summary.classes = dedupeSorted2(summary.classes);
|
|
3343
|
+
summary.interfaces = dedupeSorted2(summary.interfaces);
|
|
3344
|
+
summary.types = dedupeSorted2(summary.types);
|
|
3345
|
+
summary.enums = dedupeSorted2(summary.enums);
|
|
3346
|
+
summary.variables = dedupeSorted2(summary.variables);
|
|
3347
|
+
summary.reactComponents = dedupeSorted2(summary.reactComponents);
|
|
3348
|
+
summary.hooks = dedupeSorted2(summary.hooks);
|
|
3349
|
+
summary.routeHandlers = dedupeSorted2(summary.routeHandlers);
|
|
1715
3350
|
return summary;
|
|
1716
3351
|
}
|
|
1717
3352
|
|
|
3353
|
+
// src/ast/profiles/sfc.ts
|
|
3354
|
+
var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
3355
|
+
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
3356
|
+
function componentName(path12) {
|
|
3357
|
+
const base = path12.split("/").pop() ?? path12;
|
|
3358
|
+
return base.replace(/\.[^.]+$/, "");
|
|
3359
|
+
}
|
|
3360
|
+
var sfcProfile = {
|
|
3361
|
+
id: "sfc",
|
|
3362
|
+
extensions: [".vue", ".svelte", ".astro"],
|
|
3363
|
+
summarize(path12, content) {
|
|
3364
|
+
let script = "";
|
|
3365
|
+
if (path12.toLowerCase().endsWith(".astro")) {
|
|
3366
|
+
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
3367
|
+
if (frontmatter) script += `${frontmatter[1]}
|
|
3368
|
+
`;
|
|
3369
|
+
}
|
|
3370
|
+
for (const match of content.matchAll(SCRIPT_BLOCK)) {
|
|
3371
|
+
script += `${match[1]}
|
|
3372
|
+
`;
|
|
3373
|
+
}
|
|
3374
|
+
const name = componentName(path12);
|
|
3375
|
+
if (!script.trim()) {
|
|
3376
|
+
const summary2 = emptySummary();
|
|
3377
|
+
if (name) {
|
|
3378
|
+
summary2.exports.push(name);
|
|
3379
|
+
summary2.reactComponents.push(name);
|
|
3380
|
+
}
|
|
3381
|
+
return { summary: finalizeSummary(summary2), quality: "heuristic" };
|
|
3382
|
+
}
|
|
3383
|
+
const summary = summarizeTypeScriptSource(`${name}.ts`, script);
|
|
3384
|
+
summary.reactComponents.push(name);
|
|
3385
|
+
summary.exports.push(name);
|
|
3386
|
+
return { summary, quality: "ast" };
|
|
3387
|
+
}
|
|
3388
|
+
};
|
|
3389
|
+
|
|
3390
|
+
// src/ast/profiles/index.ts
|
|
3391
|
+
var LANGUAGE_PROFILES = [
|
|
3392
|
+
pythonProfile,
|
|
3393
|
+
goProfile,
|
|
3394
|
+
rustProfile,
|
|
3395
|
+
jvmProfile,
|
|
3396
|
+
dotnetProfile,
|
|
3397
|
+
phpProfile,
|
|
3398
|
+
rubyProfile,
|
|
3399
|
+
cfamilyProfile,
|
|
3400
|
+
sfcProfile
|
|
3401
|
+
];
|
|
3402
|
+
var BY_EXTENSION = /* @__PURE__ */ new Map();
|
|
3403
|
+
for (const profile of LANGUAGE_PROFILES) {
|
|
3404
|
+
for (const extension of profile.extensions) {
|
|
3405
|
+
BY_EXTENSION.set(extension.toLowerCase(), profile);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
function profileForPath(path12) {
|
|
3409
|
+
const lower = path12.toLowerCase();
|
|
3410
|
+
const dot = lower.lastIndexOf(".");
|
|
3411
|
+
if (dot === -1) return null;
|
|
3412
|
+
return BY_EXTENSION.get(lower.slice(dot)) ?? null;
|
|
3413
|
+
}
|
|
3414
|
+
function summarizeWithProfile(path12, content) {
|
|
3415
|
+
const profile = profileForPath(path12);
|
|
3416
|
+
return profile ? profile.summarize(path12, content) : null;
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
// src/ast/structured.ts
|
|
3420
|
+
function stripJsonComments2(content) {
|
|
3421
|
+
const text2 = content.replace(/^\uFEFF/, "");
|
|
3422
|
+
let out = "";
|
|
3423
|
+
let inString = false;
|
|
3424
|
+
let inLineComment = false;
|
|
3425
|
+
let inBlockComment = false;
|
|
3426
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3427
|
+
const char = text2[i];
|
|
3428
|
+
const next = text2[i + 1];
|
|
3429
|
+
if (inLineComment) {
|
|
3430
|
+
if (char === "\n") {
|
|
3431
|
+
inLineComment = false;
|
|
3432
|
+
out += char;
|
|
3433
|
+
}
|
|
3434
|
+
continue;
|
|
3435
|
+
}
|
|
3436
|
+
if (inBlockComment) {
|
|
3437
|
+
if (char === "*" && next === "/") {
|
|
3438
|
+
inBlockComment = false;
|
|
3439
|
+
i += 1;
|
|
3440
|
+
}
|
|
3441
|
+
continue;
|
|
3442
|
+
}
|
|
3443
|
+
if (inString) {
|
|
3444
|
+
out += char;
|
|
3445
|
+
if (char === "\\") {
|
|
3446
|
+
if (next !== void 0) {
|
|
3447
|
+
out += next;
|
|
3448
|
+
i += 1;
|
|
3449
|
+
}
|
|
3450
|
+
} else if (char === '"') {
|
|
3451
|
+
inString = false;
|
|
3452
|
+
}
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
if (char === '"') {
|
|
3456
|
+
inString = true;
|
|
3457
|
+
out += char;
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
if (char === "/" && next === "/") {
|
|
3461
|
+
inLineComment = true;
|
|
3462
|
+
i += 1;
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
if (char === "/" && next === "*") {
|
|
3466
|
+
inBlockComment = true;
|
|
3467
|
+
i += 1;
|
|
3468
|
+
continue;
|
|
3469
|
+
}
|
|
3470
|
+
out += char;
|
|
3471
|
+
}
|
|
3472
|
+
return out;
|
|
3473
|
+
}
|
|
3474
|
+
function summarizeJson(content) {
|
|
3475
|
+
try {
|
|
3476
|
+
const parsed = JSON.parse(stripJsonComments2(content));
|
|
3477
|
+
if (Array.isArray(parsed)) {
|
|
3478
|
+
const sample = parsed[0];
|
|
3479
|
+
const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
|
|
3480
|
+
return {
|
|
3481
|
+
shape: "array",
|
|
3482
|
+
itemCount: parsed.length,
|
|
3483
|
+
keys: [],
|
|
3484
|
+
sampleKeys
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3487
|
+
if (parsed && typeof parsed === "object") {
|
|
3488
|
+
const keys = Object.keys(parsed).sort();
|
|
3489
|
+
return {
|
|
3490
|
+
shape: "object",
|
|
3491
|
+
itemCount: keys.length,
|
|
3492
|
+
keys: keys.slice(0, 30),
|
|
3493
|
+
sampleKeys: []
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
return {
|
|
3497
|
+
shape: typeof parsed,
|
|
3498
|
+
itemCount: null,
|
|
3499
|
+
keys: [],
|
|
3500
|
+
sampleKeys: []
|
|
3501
|
+
};
|
|
3502
|
+
} catch {
|
|
3503
|
+
return {
|
|
3504
|
+
shape: "invalid-json",
|
|
3505
|
+
itemCount: null,
|
|
3506
|
+
keys: [],
|
|
3507
|
+
sampleKeys: []
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
function summarizeMarkdown(content) {
|
|
3512
|
+
const headings = [];
|
|
3513
|
+
const frontmatterKeys = [];
|
|
3514
|
+
const lines = content.replace(/^\uFEFF/, "").split("\n");
|
|
3515
|
+
let inFrontmatter = false;
|
|
3516
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3517
|
+
const line = lines[index];
|
|
3518
|
+
if (index === 0 && line.trim() === "---") {
|
|
3519
|
+
inFrontmatter = true;
|
|
3520
|
+
continue;
|
|
3521
|
+
}
|
|
3522
|
+
if (inFrontmatter) {
|
|
3523
|
+
if (line.trim() === "---") {
|
|
3524
|
+
inFrontmatter = false;
|
|
3525
|
+
continue;
|
|
3526
|
+
}
|
|
3527
|
+
const match = /^([a-zA-Z0-9_-]+):/.exec(line);
|
|
3528
|
+
if (match) frontmatterKeys.push(match[1]);
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
|
|
3532
|
+
if (headingMatch) {
|
|
3533
|
+
headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
return {
|
|
3537
|
+
headings: headings.slice(0, 40),
|
|
3538
|
+
frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
|
|
1718
3542
|
// src/generator/audit.ts
|
|
1719
3543
|
function generateAuditReport(manifest, secretFindings) {
|
|
1720
3544
|
const lines = [
|
|
@@ -1780,11 +3604,11 @@ function generateAuditReport(manifest, secretFindings) {
|
|
|
1780
3604
|
"| :--- | :--- | :--- |"
|
|
1781
3605
|
);
|
|
1782
3606
|
const totalFiles = manifest.counts.totalFiles || 1;
|
|
1783
|
-
for (const [
|
|
3607
|
+
for (const [layer2, count] of Object.entries(manifest.layers).sort(
|
|
1784
3608
|
(a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
1785
3609
|
)) {
|
|
1786
3610
|
const pct = (count / totalFiles * 100).toFixed(1);
|
|
1787
|
-
lines.push(`| \`${
|
|
3611
|
+
lines.push(`| \`${layer2}\` | ${count} | ${pct}% |`);
|
|
1788
3612
|
}
|
|
1789
3613
|
lines.push("");
|
|
1790
3614
|
lines.push(
|
|
@@ -1850,21 +3674,21 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
1850
3674
|
section.byteSize = Buffer.byteLength(section.text, "utf8");
|
|
1851
3675
|
continue;
|
|
1852
3676
|
}
|
|
1853
|
-
const
|
|
1854
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
3677
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
3678
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1855
3679
|
const clipped = truncateToBytes(rendered[oversized].markdown, target);
|
|
1856
|
-
rendered[oversized] = rehashPart(rendered[oversized], clipped.content +
|
|
3680
|
+
rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
|
|
1857
3681
|
return rendered;
|
|
1858
3682
|
}
|
|
1859
3683
|
return enforceBudget(renderGroups(groups, contentFingerprint), budget);
|
|
1860
3684
|
}
|
|
1861
3685
|
function enforceBudget(parts, budget) {
|
|
1862
|
-
const
|
|
1863
|
-
const target = Math.max(0, budget - Buffer.byteLength(
|
|
3686
|
+
const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
|
|
3687
|
+
const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
|
|
1864
3688
|
return parts.map((part) => {
|
|
1865
3689
|
if (part.metadata.byteSize <= budget) return part;
|
|
1866
3690
|
const clipped = truncateToBytes(part.markdown, target);
|
|
1867
|
-
return rehashPart(part, clipped.content +
|
|
3691
|
+
return rehashPart(part, clipped.content + marker2);
|
|
1868
3692
|
});
|
|
1869
3693
|
}
|
|
1870
3694
|
function renumberParts(parts, contentFingerprint) {
|
|
@@ -1872,10 +3696,10 @@ function renumberParts(parts, contentFingerprint) {
|
|
|
1872
3696
|
if (total === 0) return [];
|
|
1873
3697
|
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
1874
3698
|
const nameFor = (position) => `part-${zeroPad(position)}.md`;
|
|
1875
|
-
const
|
|
3699
|
+
const marker2 = "## Contents\n";
|
|
1876
3700
|
return parts.map((part, index) => {
|
|
1877
|
-
const markerIndex = part.markdown.indexOf(
|
|
1878
|
-
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex +
|
|
3701
|
+
const markerIndex = part.markdown.indexOf(marker2);
|
|
3702
|
+
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
|
|
1879
3703
|
const included = part.metadata.includedFiles;
|
|
1880
3704
|
if (markerIndex === -1) {
|
|
1881
3705
|
throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
|
|
@@ -1933,9 +3757,9 @@ function topEntries(counts, limit) {
|
|
|
1933
3757
|
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
3758
|
}
|
|
1935
3759
|
function prepareSection(file, sectionBudget) {
|
|
1936
|
-
let
|
|
1937
|
-
let byteSize = Buffer.byteLength(
|
|
1938
|
-
if (byteSize <= sectionBudget) return { file, text, byteSize };
|
|
3760
|
+
let text2 = renderFileSection(file);
|
|
3761
|
+
let byteSize = Buffer.byteLength(text2, "utf8");
|
|
3762
|
+
if (byteSize <= sectionBudget) return { file, text: text2, byteSize };
|
|
1939
3763
|
const shrinkBody = () => {
|
|
1940
3764
|
const content = file.content ?? "";
|
|
1941
3765
|
const overhead = byteSize - Buffer.byteLength(content, "utf8");
|
|
@@ -1944,24 +3768,24 @@ function prepareSection(file, sectionBudget) {
|
|
|
1944
3768
|
const truncated = truncateToBytes(content, allowed);
|
|
1945
3769
|
if (!truncated.isTruncated) return;
|
|
1946
3770
|
applyTruncatedContent(file, truncated.content);
|
|
1947
|
-
|
|
1948
|
-
byteSize = Buffer.byteLength(
|
|
3771
|
+
text2 = renderFileSection(file);
|
|
3772
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1949
3773
|
};
|
|
1950
3774
|
shrinkBody();
|
|
1951
3775
|
if (byteSize > sectionBudget && file.summary) {
|
|
1952
3776
|
file.summary = void 0;
|
|
1953
3777
|
file.summaryOmitted = true;
|
|
1954
|
-
|
|
1955
|
-
byteSize = Buffer.byteLength(
|
|
3778
|
+
text2 = renderFileSection(file);
|
|
3779
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1956
3780
|
shrinkBody();
|
|
1957
3781
|
}
|
|
1958
3782
|
if (byteSize > sectionBudget) {
|
|
1959
3783
|
const target = sectionBudget - Buffer.byteLength(SECTION_TRUNCATION_NOTICE, "utf8");
|
|
1960
|
-
const truncated = truncateToBytes(
|
|
1961
|
-
|
|
1962
|
-
byteSize = Buffer.byteLength(
|
|
3784
|
+
const truncated = truncateToBytes(text2, Math.max(0, target));
|
|
3785
|
+
text2 = truncated.content + SECTION_TRUNCATION_NOTICE;
|
|
3786
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
1963
3787
|
}
|
|
1964
|
-
return { file, text, byteSize };
|
|
3788
|
+
return { file, text: text2, byteSize };
|
|
1965
3789
|
}
|
|
1966
3790
|
function applyTruncatedContent(file, content) {
|
|
1967
3791
|
file.content = content;
|
|
@@ -2083,6 +3907,7 @@ function renderFileSection(file) {
|
|
|
2083
3907
|
`- Source Lines: ${file.originalLines}`,
|
|
2084
3908
|
`- Included Lines: ${file.includedLines}`,
|
|
2085
3909
|
`- SHA-256: ${file.hashSha256}`,
|
|
3910
|
+
...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
|
|
2086
3911
|
...renderSummary(file.summary),
|
|
2087
3912
|
...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
|
|
2088
3913
|
"",
|
|
@@ -2330,7 +4155,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2330
4155
|
".so"
|
|
2331
4156
|
]);
|
|
2332
4157
|
function isBinaryExtension(filePath) {
|
|
2333
|
-
const ext =
|
|
4158
|
+
const ext = path10__namespace.extname(filePath).toLowerCase();
|
|
2334
4159
|
return BINARY_EXTENSIONS.has(ext);
|
|
2335
4160
|
}
|
|
2336
4161
|
function isBinaryBuffer(buffer) {
|
|
@@ -2344,9 +4169,9 @@ function isBinaryFile(absolutePath) {
|
|
|
2344
4169
|
if (isBinaryExtension(absolutePath)) return true;
|
|
2345
4170
|
let fd;
|
|
2346
4171
|
try {
|
|
2347
|
-
fd =
|
|
4172
|
+
fd = fs9__namespace.openSync(absolutePath, "r");
|
|
2348
4173
|
const buffer = Buffer.alloc(8192);
|
|
2349
|
-
const bytesRead =
|
|
4174
|
+
const bytesRead = fs9__namespace.readSync(fd, buffer, 0, 8192, 0);
|
|
2350
4175
|
if (bytesRead === 0) return false;
|
|
2351
4176
|
return isBinaryBuffer(buffer.subarray(0, bytesRead));
|
|
2352
4177
|
} catch {
|
|
@@ -2354,7 +4179,7 @@ function isBinaryFile(absolutePath) {
|
|
|
2354
4179
|
} finally {
|
|
2355
4180
|
if (fd !== void 0) {
|
|
2356
4181
|
try {
|
|
2357
|
-
|
|
4182
|
+
fs9__namespace.closeSync(fd);
|
|
2358
4183
|
} catch {
|
|
2359
4184
|
}
|
|
2360
4185
|
}
|
|
@@ -2376,8 +4201,8 @@ function sha256Hex(buffer) {
|
|
|
2376
4201
|
function validateSnapshot(configInput) {
|
|
2377
4202
|
assertUsableConfigInput(configInput);
|
|
2378
4203
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
|
|
2379
|
-
const manifestPath =
|
|
2380
|
-
if (!
|
|
4204
|
+
const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
|
|
4205
|
+
if (!fs9__namespace.existsSync(manifestPath)) {
|
|
2381
4206
|
return {
|
|
2382
4207
|
valid: false,
|
|
2383
4208
|
manifestPresent: false,
|
|
@@ -2388,7 +4213,7 @@ function validateSnapshot(configInput) {
|
|
|
2388
4213
|
}
|
|
2389
4214
|
let manifest;
|
|
2390
4215
|
try {
|
|
2391
|
-
manifest = JSON.parse(
|
|
4216
|
+
manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
2392
4217
|
} catch (err) {
|
|
2393
4218
|
return {
|
|
2394
4219
|
valid: false,
|
|
@@ -2411,7 +4236,7 @@ function validateSnapshot(configInput) {
|
|
|
2411
4236
|
}
|
|
2412
4237
|
const drift = emptyDrift();
|
|
2413
4238
|
const warnings = [];
|
|
2414
|
-
const snapshotDir =
|
|
4239
|
+
const snapshotDir = path10__namespace.resolve(config.cwd, config.outDir);
|
|
2415
4240
|
const manifestFileMap = /* @__PURE__ */ new Map();
|
|
2416
4241
|
for (const file of manifest.files) {
|
|
2417
4242
|
if (file && typeof file.relativePath === "string") {
|
|
@@ -2423,7 +4248,7 @@ function validateSnapshot(configInput) {
|
|
|
2423
4248
|
);
|
|
2424
4249
|
const collection = collectFiles(config.cwd, {
|
|
2425
4250
|
includePatterns: config.include,
|
|
2426
|
-
excludePatterns: config.excludePatterns,
|
|
4251
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2427
4252
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2428
4253
|
});
|
|
2429
4254
|
for (const diskFile of collection.files) {
|
|
@@ -2435,7 +4260,7 @@ function validateSnapshot(configInput) {
|
|
|
2435
4260
|
}
|
|
2436
4261
|
let diskHash;
|
|
2437
4262
|
try {
|
|
2438
|
-
diskHash = sha256Hex(
|
|
4263
|
+
diskHash = sha256Hex(fs9__namespace.readFileSync(path10__namespace.join(config.cwd, diskFile)));
|
|
2439
4264
|
} catch {
|
|
2440
4265
|
drift.missingOnDisk.push(diskFile);
|
|
2441
4266
|
continue;
|
|
@@ -2445,7 +4270,7 @@ function validateSnapshot(configInput) {
|
|
|
2445
4270
|
}
|
|
2446
4271
|
}
|
|
2447
4272
|
for (const snapshotFile of manifestFileMap.keys()) {
|
|
2448
|
-
if (!
|
|
4273
|
+
if (!fs9__namespace.existsSync(path10__namespace.join(config.cwd, snapshotFile))) {
|
|
2449
4274
|
drift.missingOnDisk.push(snapshotFile);
|
|
2450
4275
|
}
|
|
2451
4276
|
}
|
|
@@ -2459,10 +4284,10 @@ function validateSnapshot(configInput) {
|
|
|
2459
4284
|
if (!part || typeof part.filename !== "string") continue;
|
|
2460
4285
|
partNames.add(part.filename);
|
|
2461
4286
|
if (!config.artifacts.parts) continue;
|
|
2462
|
-
const partPath =
|
|
4287
|
+
const partPath = path10__namespace.join(snapshotDir, "parts", part.filename);
|
|
2463
4288
|
let buffer;
|
|
2464
4289
|
try {
|
|
2465
|
-
buffer =
|
|
4290
|
+
buffer = fs9__namespace.readFileSync(partPath);
|
|
2466
4291
|
} catch {
|
|
2467
4292
|
drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
|
|
2468
4293
|
continue;
|
|
@@ -2549,6 +4374,11 @@ function validateSnapshot(configInput) {
|
|
|
2549
4374
|
}
|
|
2550
4375
|
}
|
|
2551
4376
|
}
|
|
4377
|
+
if (recorded?.preset !== void 0 && recorded.preset !== config.preset) {
|
|
4378
|
+
drift.configMismatches.push(
|
|
4379
|
+
`preset: manifest ${recorded.preset} vs current ${config.preset}`
|
|
4380
|
+
);
|
|
4381
|
+
}
|
|
2552
4382
|
if (recorded?.layersMode !== void 0 && recorded.layersMode !== config.layersMode) {
|
|
2553
4383
|
drift.configMismatches.push(
|
|
2554
4384
|
`layersMode: manifest ${recorded.layersMode} vs current ${config.layersMode}`
|
|
@@ -2566,10 +4396,10 @@ function validateSnapshot(configInput) {
|
|
|
2566
4396
|
...Object.keys(recordedByLayer),
|
|
2567
4397
|
...Object.keys(config.budgets.byLayer)
|
|
2568
4398
|
]);
|
|
2569
|
-
for (const
|
|
2570
|
-
if (recordedByLayer[
|
|
4399
|
+
for (const layer2 of Array.from(layerNames).sort()) {
|
|
4400
|
+
if (recordedByLayer[layer2] !== config.budgets.byLayer[layer2]) {
|
|
2571
4401
|
drift.configMismatches.push(
|
|
2572
|
-
`budgets.byLayer.${
|
|
4402
|
+
`budgets.byLayer.${layer2}: manifest ${recordedByLayer[layer2]} vs current ${config.budgets.byLayer[layer2]}`
|
|
2573
4403
|
);
|
|
2574
4404
|
}
|
|
2575
4405
|
}
|
|
@@ -2652,9 +4482,9 @@ function validateSnapshot(configInput) {
|
|
|
2652
4482
|
["full-codebase.md", manifest.artifacts?.fullMarkdown]
|
|
2653
4483
|
]) {
|
|
2654
4484
|
if (!artifact) continue;
|
|
2655
|
-
const artifactPath =
|
|
4485
|
+
const artifactPath = path10__namespace.join(snapshotDir, key);
|
|
2656
4486
|
try {
|
|
2657
|
-
const buffer =
|
|
4487
|
+
const buffer = fs9__namespace.readFileSync(artifactPath);
|
|
2658
4488
|
if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
|
|
2659
4489
|
drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
|
|
2660
4490
|
}
|
|
@@ -2703,12 +4533,12 @@ function validateSnapshot(configInput) {
|
|
|
2703
4533
|
|
|
2704
4534
|
// src/index.ts
|
|
2705
4535
|
var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
|
|
2706
|
-
var GENERATOR_VERSION = "2.
|
|
4536
|
+
var GENERATOR_VERSION = "2.1.0";
|
|
2707
4537
|
var SCHEMA_VERSION = "3.0.0";
|
|
2708
4538
|
function sha256Hex2(input) {
|
|
2709
4539
|
return crypto__namespace.createHash("sha256").update(input).digest("hex");
|
|
2710
4540
|
}
|
|
2711
|
-
var
|
|
4541
|
+
var TS_EXTENSIONS = [
|
|
2712
4542
|
".ts",
|
|
2713
4543
|
".mts",
|
|
2714
4544
|
".cts",
|
|
@@ -2716,24 +4546,21 @@ var SUMMARY_EXTENSIONS = [
|
|
|
2716
4546
|
".js",
|
|
2717
4547
|
".mjs",
|
|
2718
4548
|
".cjs",
|
|
2719
|
-
".jsx"
|
|
2720
|
-
".json",
|
|
2721
|
-
".jsonc",
|
|
2722
|
-
".md",
|
|
2723
|
-
".mdx"
|
|
4549
|
+
".jsx"
|
|
2724
4550
|
];
|
|
2725
4551
|
function summarizeFile(relativePath, redactedContent) {
|
|
2726
4552
|
const lower = relativePath.toLowerCase();
|
|
2727
|
-
if (!SUMMARY_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
2728
|
-
return void 0;
|
|
2729
|
-
}
|
|
2730
4553
|
if (lower.endsWith(".json") || lower.endsWith(".jsonc")) {
|
|
2731
|
-
return summarizeJson(redactedContent);
|
|
4554
|
+
return { summary: summarizeJson(redactedContent), quality: "structural" };
|
|
2732
4555
|
}
|
|
2733
4556
|
if (lower.endsWith(".md") || lower.endsWith(".mdx")) {
|
|
2734
|
-
return summarizeMarkdown(redactedContent);
|
|
4557
|
+
return { summary: summarizeMarkdown(redactedContent), quality: "structural" };
|
|
2735
4558
|
}
|
|
2736
|
-
|
|
4559
|
+
if (TS_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
|
|
4560
|
+
return { summary: summarizeTypeScriptSource(relativePath, redactedContent), quality: "ast" };
|
|
4561
|
+
}
|
|
4562
|
+
const profiled = summarizeWithProfile(relativePath, redactedContent);
|
|
4563
|
+
return profiled ? { summary: profiled.summary, quality: profiled.quality } : void 0;
|
|
2737
4564
|
}
|
|
2738
4565
|
function isSourceSummary(summary) {
|
|
2739
4566
|
return Boolean(summary && typeof summary === "object" && "imports" in summary);
|
|
@@ -2759,8 +4586,8 @@ var IMPORT_CANDIDATE_SUFFIXES = [
|
|
|
2759
4586
|
];
|
|
2760
4587
|
function resolveSpecifier(fromFile, specifier, knownFiles) {
|
|
2761
4588
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
2762
|
-
const baseDir =
|
|
2763
|
-
const joined =
|
|
4589
|
+
const baseDir = path10__namespace.posix.dirname(fromFile);
|
|
4590
|
+
const joined = path10__namespace.posix.normalize(path10__namespace.posix.join(baseDir, specifier));
|
|
2764
4591
|
const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
|
|
2765
4592
|
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
2766
4593
|
const withSuffix = `${joined}${suffix}`;
|
|
@@ -2852,11 +4679,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
2852
4679
|
return errors;
|
|
2853
4680
|
}
|
|
2854
4681
|
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}${
|
|
4682
|
+
const cwd = path10__namespace.resolve(config.cwd);
|
|
4683
|
+
const outDir = path10__namespace.resolve(cwd, config.outDir);
|
|
4684
|
+
const tmpDir = path10__namespace.resolve(cwd, config.tmpDir);
|
|
4685
|
+
const root = path10__namespace.parse(cwd).root;
|
|
4686
|
+
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path10__namespace.sep}`);
|
|
2860
4687
|
const reject = (reason) => {
|
|
2861
4688
|
throw new Error(
|
|
2862
4689
|
`Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
|
|
@@ -2875,26 +4702,66 @@ function resolveSafeOutputDirs(config) {
|
|
|
2875
4702
|
if (contains(tmpDir, outDir)) reject("outDir is inside tmpDir");
|
|
2876
4703
|
return { outDir, tmpDir };
|
|
2877
4704
|
}
|
|
4705
|
+
var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
|
|
4706
|
+
var OWNERSHIP_MARKER = ".codebase-snapshot";
|
|
4707
|
+
function writeOwnershipMarker(dir) {
|
|
4708
|
+
const marker2 = {
|
|
4709
|
+
generator: GENERATOR_NAME,
|
|
4710
|
+
version: GENERATOR_VERSION,
|
|
4711
|
+
schemaVersion: SCHEMA_VERSION
|
|
4712
|
+
};
|
|
4713
|
+
fs9__namespace.writeFileSync(path10__namespace.join(dir, OWNERSHIP_MARKER), `${JSON.stringify(marker2, null, 2)}
|
|
4714
|
+
`, "utf8");
|
|
4715
|
+
}
|
|
4716
|
+
function isOwnOutput(dir) {
|
|
4717
|
+
if (fs9__namespace.existsSync(path10__namespace.join(dir, OWNERSHIP_MARKER))) return true;
|
|
4718
|
+
const manifestPath = path10__namespace.join(dir, "manifest.json");
|
|
4719
|
+
if (!fs9__namespace.existsSync(manifestPath)) return false;
|
|
4720
|
+
try {
|
|
4721
|
+
const parsed = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
4722
|
+
const generator = parsed.generator;
|
|
4723
|
+
return typeof parsed.schemaVersion === "string" && Array.isArray(parsed.files) && typeof generator === "object" && generator !== null && generator.name === GENERATOR_NAME;
|
|
4724
|
+
} catch {
|
|
4725
|
+
return false;
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
function assertReplaceable(target, label) {
|
|
4729
|
+
if (!fs9__namespace.existsSync(target)) return;
|
|
4730
|
+
if (!fs9__namespace.statSync(target).isDirectory()) {
|
|
4731
|
+
throw new Error(
|
|
4732
|
+
`Refusing to write into ${label} ${target}: it exists but is not a directory.`
|
|
4733
|
+
);
|
|
4734
|
+
}
|
|
4735
|
+
const entries = fs9__namespace.readdirSync(target);
|
|
4736
|
+
const meaningful = entries.filter((name) => !PLACEHOLDER_ENTRIES.has(name));
|
|
4737
|
+
if (meaningful.length === 0) return;
|
|
4738
|
+
if (isOwnOutput(target)) return;
|
|
4739
|
+
const preview = meaningful.slice(0, 5).join(", ");
|
|
4740
|
+
const more = meaningful.length > 5 ? `, and ${meaningful.length - 5} more` : "";
|
|
4741
|
+
throw new Error(
|
|
4742
|
+
`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.`
|
|
4743
|
+
);
|
|
4744
|
+
}
|
|
2878
4745
|
function swapIntoPlace(tmpDir, outDir) {
|
|
2879
4746
|
const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
|
|
2880
|
-
const hadPrevious =
|
|
2881
|
-
if (hadPrevious)
|
|
4747
|
+
const hadPrevious = fs9__namespace.existsSync(outDir);
|
|
4748
|
+
if (hadPrevious) fs9__namespace.renameSync(outDir, backupDir);
|
|
2882
4749
|
try {
|
|
2883
4750
|
try {
|
|
2884
|
-
|
|
4751
|
+
fs9__namespace.renameSync(tmpDir, outDir);
|
|
2885
4752
|
} catch (err) {
|
|
2886
4753
|
if (err.code === "EXDEV") {
|
|
2887
|
-
|
|
2888
|
-
|
|
4754
|
+
fs9__namespace.cpSync(tmpDir, outDir, { recursive: true });
|
|
4755
|
+
fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
|
|
2889
4756
|
} else {
|
|
2890
4757
|
throw err;
|
|
2891
4758
|
}
|
|
2892
4759
|
}
|
|
2893
|
-
if (hadPrevious)
|
|
4760
|
+
if (hadPrevious) fs9__namespace.rmSync(backupDir, { recursive: true, force: true });
|
|
2894
4761
|
} catch (err) {
|
|
2895
|
-
if (hadPrevious &&
|
|
2896
|
-
if (
|
|
2897
|
-
|
|
4762
|
+
if (hadPrevious && fs9__namespace.existsSync(backupDir)) {
|
|
4763
|
+
if (fs9__namespace.existsSync(outDir)) fs9__namespace.rmSync(outDir, { recursive: true, force: true });
|
|
4764
|
+
fs9__namespace.renameSync(backupDir, outDir);
|
|
2898
4765
|
}
|
|
2899
4766
|
throw err;
|
|
2900
4767
|
}
|
|
@@ -2958,7 +4825,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2958
4825
|
const gitInfo = getGitInfo(config.cwd, config.branch);
|
|
2959
4826
|
const collection = collectFiles(config.cwd, {
|
|
2960
4827
|
includePatterns: config.include,
|
|
2961
|
-
excludePatterns: config.excludePatterns,
|
|
4828
|
+
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
2962
4829
|
includeSensitiveFiles: config.includeSensitiveFiles
|
|
2963
4830
|
});
|
|
2964
4831
|
const files = [];
|
|
@@ -2971,10 +4838,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2971
4838
|
let totalSizeBytes = 0;
|
|
2972
4839
|
let largeFileCount = 0;
|
|
2973
4840
|
for (const relativePath of collection.files) {
|
|
2974
|
-
const absolutePath =
|
|
4841
|
+
const absolutePath = path10__namespace.join(config.cwd, relativePath);
|
|
2975
4842
|
let stats;
|
|
2976
4843
|
try {
|
|
2977
|
-
stats =
|
|
4844
|
+
stats = fs9__namespace.statSync(absolutePath);
|
|
2978
4845
|
if (!stats.isFile()) {
|
|
2979
4846
|
skipped.push({ path: relativePath, reason: "not-file" });
|
|
2980
4847
|
continue;
|
|
@@ -2989,7 +4856,7 @@ function generateSnapshot(configInput, options) {
|
|
|
2989
4856
|
}
|
|
2990
4857
|
let buffer;
|
|
2991
4858
|
try {
|
|
2992
|
-
buffer =
|
|
4859
|
+
buffer = fs9__namespace.readFileSync(absolutePath);
|
|
2993
4860
|
} catch {
|
|
2994
4861
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
2995
4862
|
continue;
|
|
@@ -2999,10 +4866,10 @@ function generateSnapshot(configInput, options) {
|
|
|
2999
4866
|
const lineCount = countLines(rawContent);
|
|
3000
4867
|
totalLines += lineCount;
|
|
3001
4868
|
totalSizeBytes += stats.size;
|
|
3002
|
-
const
|
|
4869
|
+
const layer2 = classifyLayer(relativePath, config.layers);
|
|
3003
4870
|
const kind = classifyKind(relativePath, config.kinds);
|
|
3004
4871
|
const language = detectLanguage(relativePath);
|
|
3005
|
-
bump(layerCounts,
|
|
4872
|
+
bump(layerCounts, layer2);
|
|
3006
4873
|
bump(kindCounts, kind);
|
|
3007
4874
|
const { redacted, findings } = scanAndRedactSecrets(
|
|
3008
4875
|
rawContent,
|
|
@@ -3026,8 +4893,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3026
4893
|
includedLines = preview.includedLines;
|
|
3027
4894
|
}
|
|
3028
4895
|
let summary;
|
|
4896
|
+
let summaryQuality;
|
|
3029
4897
|
try {
|
|
3030
|
-
|
|
4898
|
+
const outcome = summarizeFile(relativePath, redacted);
|
|
4899
|
+
summary = outcome?.summary;
|
|
4900
|
+
summaryQuality = outcome?.quality;
|
|
3031
4901
|
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
3032
4902
|
warnings.push(
|
|
3033
4903
|
`Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
|
|
@@ -3044,7 +4914,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3044
4914
|
lines: lineCount,
|
|
3045
4915
|
hashSha256,
|
|
3046
4916
|
part: "",
|
|
3047
|
-
layer,
|
|
4917
|
+
layer: layer2,
|
|
3048
4918
|
kind,
|
|
3049
4919
|
language,
|
|
3050
4920
|
inclusionMode: isPreviewTruncated ? "preview" : "full",
|
|
@@ -3054,6 +4924,7 @@ function generateSnapshot(configInput, options) {
|
|
|
3054
4924
|
includedLines,
|
|
3055
4925
|
includedBytes: Buffer.byteLength(finalContent, "utf8"),
|
|
3056
4926
|
summary,
|
|
4927
|
+
summaryQuality,
|
|
3057
4928
|
content: finalContent
|
|
3058
4929
|
});
|
|
3059
4930
|
}
|
|
@@ -3094,10 +4965,11 @@ function generateSnapshot(configInput, options) {
|
|
|
3094
4965
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
3095
4966
|
artifacts: { ...config.artifacts },
|
|
3096
4967
|
include: [...config.include],
|
|
4968
|
+
preset: config.preset,
|
|
3097
4969
|
layersMode: config.layersMode,
|
|
3098
4970
|
budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
|
|
3099
4971
|
excludePatterns: [...config.excludePatterns],
|
|
3100
|
-
layers: config.layers.map((
|
|
4972
|
+
layers: config.layers.map((layer2) => layer2.name),
|
|
3101
4973
|
kinds: config.kinds.map((kind) => kind.kind)
|
|
3102
4974
|
},
|
|
3103
4975
|
counts: {
|
|
@@ -3184,45 +5056,48 @@ function generateSnapshot(configInput, options) {
|
|
|
3184
5056
|
return manifest;
|
|
3185
5057
|
}
|
|
3186
5058
|
const { outDir, tmpDir } = resolveSafeOutputDirs(config);
|
|
5059
|
+
assertReplaceable(tmpDir, "tmpDir");
|
|
5060
|
+
assertReplaceable(outDir, "outDir");
|
|
3187
5061
|
const { artifacts } = config;
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
const
|
|
3192
|
-
|
|
5062
|
+
fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
|
|
5063
|
+
fs9__namespace.mkdirSync(tmpDir, { recursive: true });
|
|
5064
|
+
writeOwnershipMarker(tmpDir);
|
|
5065
|
+
const partsDir = path10__namespace.join(tmpDir, "parts");
|
|
5066
|
+
const reportsDir = path10__namespace.join(tmpDir, "reports");
|
|
5067
|
+
if (artifacts.parts) fs9__namespace.mkdirSync(partsDir, { recursive: true });
|
|
3193
5068
|
if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
|
|
3194
|
-
|
|
5069
|
+
fs9__namespace.mkdirSync(reportsDir, { recursive: true });
|
|
3195
5070
|
}
|
|
3196
5071
|
if (artifacts.parts) {
|
|
3197
5072
|
for (const part of parts) {
|
|
3198
|
-
|
|
5073
|
+
fs9__namespace.writeFileSync(path10__namespace.join(partsDir, part.filename), part.markdown, "utf8");
|
|
3199
5074
|
}
|
|
3200
5075
|
}
|
|
3201
5076
|
if (artifacts.projectTree) {
|
|
3202
5077
|
const projectTree = generateProjectTree(files.map((file) => file.relativePath));
|
|
3203
|
-
|
|
5078
|
+
fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
|
|
3204
5079
|
}
|
|
3205
5080
|
if (artifacts.navigation) {
|
|
3206
|
-
|
|
5081
|
+
fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
|
|
3207
5082
|
}
|
|
3208
5083
|
if (artifacts.audit) {
|
|
3209
|
-
|
|
3210
|
-
|
|
5084
|
+
fs9__namespace.writeFileSync(
|
|
5085
|
+
path10__namespace.join(reportsDir, "audit.md"),
|
|
3211
5086
|
generateAuditReport(manifest, secretFindings),
|
|
3212
5087
|
"utf8"
|
|
3213
5088
|
);
|
|
3214
5089
|
}
|
|
3215
5090
|
if (artifacts.readme) {
|
|
3216
|
-
|
|
5091
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
|
|
3217
5092
|
}
|
|
3218
5093
|
if (artifacts.manifest) {
|
|
3219
|
-
|
|
5094
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3220
5095
|
}
|
|
3221
5096
|
if (monolithJson !== null) {
|
|
3222
|
-
|
|
5097
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
|
|
3223
5098
|
}
|
|
3224
5099
|
if (monolithMarkdown !== null) {
|
|
3225
|
-
|
|
5100
|
+
fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
|
|
3226
5101
|
}
|
|
3227
5102
|
swapIntoPlace(tmpDir, outDir);
|
|
3228
5103
|
runAfterGenerate(config, manifest);
|
|
@@ -3262,6 +5137,12 @@ var CLI_OPTIONS = {
|
|
|
3262
5137
|
mode: { type: "string" },
|
|
3263
5138
|
"dry-run": { type: "boolean" },
|
|
3264
5139
|
watch: { type: "boolean" },
|
|
5140
|
+
preset: { type: "string" },
|
|
5141
|
+
include: { type: "string" },
|
|
5142
|
+
exclude: { type: "string" },
|
|
5143
|
+
yes: { type: "boolean" },
|
|
5144
|
+
minimal: { type: "boolean" },
|
|
5145
|
+
force: { type: "boolean" },
|
|
3265
5146
|
help: { type: "boolean", short: "h" },
|
|
3266
5147
|
version: { type: "boolean", short: "v" }
|
|
3267
5148
|
};
|
|
@@ -3299,6 +5180,14 @@ Options:
|
|
|
3299
5180
|
--format <text|json> CLI output format (default: text)
|
|
3300
5181
|
--dry-run Compute the snapshot without writing anything
|
|
3301
5182
|
--watch Re-generate whenever the repository changes
|
|
5183
|
+
|
|
5184
|
+
init only:
|
|
5185
|
+
--preset <name> Force a project preset instead of detecting one
|
|
5186
|
+
--include <globs> Comma-separated allow-list
|
|
5187
|
+
--exclude <globs> Comma-separated exclude patterns
|
|
5188
|
+
--minimal Write only the settings that differ from defaults
|
|
5189
|
+
--yes Accept every default without prompting
|
|
5190
|
+
--force Overwrite an existing config
|
|
3302
5191
|
-h, --help Display this help message
|
|
3303
5192
|
-v, --version Display version
|
|
3304
5193
|
|
|
@@ -3423,13 +5312,38 @@ function parseCliArgs(argv) {
|
|
|
3423
5312
|
} else if (rawSkip !== void 0) {
|
|
3424
5313
|
skipArtifacts = parseArtifactList(String(rawSkip), "--skip-artifacts");
|
|
3425
5314
|
}
|
|
5315
|
+
if (command !== "init") {
|
|
5316
|
+
const initOnly = [];
|
|
5317
|
+
if (values.preset !== void 0) initOnly.push("--preset");
|
|
5318
|
+
if (values.include !== void 0) initOnly.push("--include");
|
|
5319
|
+
if (values.exclude !== void 0) initOnly.push("--exclude");
|
|
5320
|
+
if (values.yes === true) initOnly.push("--yes");
|
|
5321
|
+
if (values.minimal === true) initOnly.push("--minimal");
|
|
5322
|
+
if (values.force === true) initOnly.push("--force");
|
|
5323
|
+
if (initOnly.length > 0) {
|
|
5324
|
+
throw new UsageError(
|
|
5325
|
+
`${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).`
|
|
5326
|
+
);
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5329
|
+
if (command === "init" && values.preset !== void 0) {
|
|
5330
|
+
const requested = String(values.preset);
|
|
5331
|
+
if (!getPreset(requested)) throw new UsageError(unknownPresetMessage(requested));
|
|
5332
|
+
}
|
|
3426
5333
|
return {
|
|
3427
5334
|
command,
|
|
3428
5335
|
args,
|
|
3429
5336
|
format: rawFormat,
|
|
3430
5337
|
dryRun: values["dry-run"] === true,
|
|
3431
5338
|
watch: values.watch === true,
|
|
3432
|
-
|
|
5339
|
+
preset: values.preset === void 0 ? void 0 : String(values.preset),
|
|
5340
|
+
maxPartBytesRaw: values["max-part-bytes"] === void 0 ? void 0 : String(values["max-part-bytes"]),
|
|
5341
|
+
include: values.include === void 0 ? void 0 : String(values.include),
|
|
5342
|
+
exclude: values.exclude === void 0 ? void 0 : String(values.exclude),
|
|
5343
|
+
yes: values.yes === true,
|
|
5344
|
+
minimal: values.minimal === true,
|
|
5345
|
+
force: values.force === true,
|
|
5346
|
+
cwd: values.cwd === void 0 ? process.cwd() : path10__namespace.resolve(String(values.cwd)),
|
|
3433
5347
|
outDir: values["out-dir"] === void 0 ? void 0 : String(values["out-dir"]),
|
|
3434
5348
|
includeMonolith: values["include-monolith"] === true ? true : void 0,
|
|
3435
5349
|
includeSensitiveFiles: values["include-sensitive-files"] === true ? true : void 0,
|
|
@@ -3469,32 +5383,65 @@ function emit(format, payload, textRenderer) {
|
|
|
3469
5383
|
}
|
|
3470
5384
|
textRenderer();
|
|
3471
5385
|
}
|
|
3472
|
-
function
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
5386
|
+
function splitList(value) {
|
|
5387
|
+
if (value === void 0) return void 0;
|
|
5388
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
5389
|
+
}
|
|
5390
|
+
function describeManifestShapeProblem(manifest) {
|
|
5391
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
5392
|
+
return "not a JSON object";
|
|
5393
|
+
}
|
|
5394
|
+
const candidate = manifest;
|
|
5395
|
+
const counts = candidate.counts;
|
|
5396
|
+
if (!counts || typeof counts !== "object") return "missing `counts`";
|
|
5397
|
+
for (const key of ["totalFiles", "totalLines", "totalSizeBytes", "partsCount"]) {
|
|
5398
|
+
if (typeof counts[key] !== "number") return `missing numeric \`counts.${key}\``;
|
|
5399
|
+
}
|
|
5400
|
+
if (!candidate.layers || typeof candidate.layers !== "object") return "missing `layers`";
|
|
5401
|
+
if (!Array.isArray(candidate.files)) return "missing `files` array";
|
|
5402
|
+
return null;
|
|
5403
|
+
}
|
|
5404
|
+
async function runInitCommand(invocation) {
|
|
5405
|
+
try {
|
|
5406
|
+
const result = await runInit({
|
|
5407
|
+
cwd: invocation.cwd,
|
|
5408
|
+
preset: invocation.preset,
|
|
5409
|
+
maxPartBytes: invocation.maxPartBytesRaw ?? invocation.maxPartBytes,
|
|
5410
|
+
include: splitList(invocation.include),
|
|
5411
|
+
exclude: splitList(invocation.exclude),
|
|
5412
|
+
skipArtifacts: invocation.skipArtifacts,
|
|
5413
|
+
includeSensitiveFiles: invocation.includeSensitiveFiles,
|
|
5414
|
+
yes: invocation.yes,
|
|
5415
|
+
minimal: invocation.minimal,
|
|
5416
|
+
force: invocation.force,
|
|
5417
|
+
dryRun: invocation.dryRun
|
|
5418
|
+
});
|
|
5419
|
+
if (invocation.format === "json") {
|
|
5420
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
5421
|
+
`);
|
|
5422
|
+
return 0;
|
|
5423
|
+
}
|
|
5424
|
+
if (!result.written) {
|
|
5425
|
+
info(result.configText);
|
|
5426
|
+
return 0;
|
|
5427
|
+
}
|
|
5428
|
+
info(`\u2705 Created ${path10__namespace.basename(result.configPath)} (preset: ${result.preset})`);
|
|
5429
|
+
if (result.scripts.length > 0) info(` Added npm scripts: ${result.scripts.join(", ")}`);
|
|
5430
|
+
if (result.gitignoreEntries.length > 0) {
|
|
5431
|
+
info(` Added to .gitignore: ${result.gitignoreEntries.join(", ")}`);
|
|
5432
|
+
}
|
|
5433
|
+
for (const note of result.notes) info(` \u26A0 ${note}`);
|
|
5434
|
+
info("");
|
|
5435
|
+
info(" Next: npm run snapshot");
|
|
5436
|
+
return 0;
|
|
5437
|
+
} catch (err) {
|
|
5438
|
+
if (err instanceof PromptCancelled) {
|
|
5439
|
+
warn("\u274C Cancelled.");
|
|
5440
|
+
return 1;
|
|
5441
|
+
}
|
|
5442
|
+
warn(`\u274C ${err.message}`);
|
|
3477
5443
|
return 1;
|
|
3478
5444
|
}
|
|
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
5445
|
}
|
|
3499
5446
|
function renderValidateText(result) {
|
|
3500
5447
|
if (result.valid) {
|
|
@@ -3536,11 +5483,15 @@ ${USAGE}
|
|
|
3536
5483
|
throw err;
|
|
3537
5484
|
}
|
|
3538
5485
|
if (invocation.command === "init") {
|
|
3539
|
-
return
|
|
5486
|
+
return await runInitCommand(invocation);
|
|
3540
5487
|
}
|
|
3541
5488
|
const loaded = await loadConfigFileDetailed(invocation.cwd);
|
|
3542
5489
|
for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
|
|
3543
|
-
if (loaded.
|
|
5490
|
+
if (loaded.errors.length > 0) {
|
|
5491
|
+
warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
|
|
5492
|
+
return 1;
|
|
5493
|
+
}
|
|
5494
|
+
if (loaded.file) info(`\u2699 Using config ${path10__namespace.relative(process.cwd(), loaded.file) || loaded.file}`);
|
|
3544
5495
|
const resolved = resolveConfig(
|
|
3545
5496
|
loaded.config,
|
|
3546
5497
|
{
|
|
@@ -3564,18 +5515,23 @@ ${USAGE}
|
|
|
3564
5515
|
return result.valid ? 0 : 1;
|
|
3565
5516
|
}
|
|
3566
5517
|
if (invocation.command === "report") {
|
|
3567
|
-
const manifestPath =
|
|
3568
|
-
if (!
|
|
5518
|
+
const manifestPath = path10__namespace.resolve(resolved.cwd, resolved.outDir, "manifest.json");
|
|
5519
|
+
if (!fs9__namespace.existsSync(manifestPath)) {
|
|
3569
5520
|
warn(`\u274C Manifest not found at ${manifestPath}. Run generate first.`);
|
|
3570
5521
|
return 1;
|
|
3571
5522
|
}
|
|
3572
5523
|
let manifest;
|
|
3573
5524
|
try {
|
|
3574
|
-
manifest = JSON.parse(
|
|
5525
|
+
manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
|
|
3575
5526
|
} catch (err) {
|
|
3576
5527
|
warn(`\u274C Could not read ${manifestPath}: ${err.message}`);
|
|
3577
5528
|
return 1;
|
|
3578
5529
|
}
|
|
5530
|
+
const shapeError = describeManifestShapeProblem(manifest);
|
|
5531
|
+
if (shapeError) {
|
|
5532
|
+
warn(`\u274C ${manifestPath} is not a snapshot manifest: ${shapeError}`);
|
|
5533
|
+
return 1;
|
|
5534
|
+
}
|
|
3579
5535
|
const validation = validateSnapshot(resolved);
|
|
3580
5536
|
emit(invocation.format, { manifest, validation }, () => {
|
|
3581
5537
|
info(`
|
|
@@ -3592,8 +5548,8 @@ ${USAGE}
|
|
|
3592
5548
|
info(` - Integrity: ${validation.valid ? "\u2705 Clean (no drift)" : "\u274C Drift detected"}`);
|
|
3593
5549
|
info(` - Warnings: ${manifest.warnings?.length ?? 0}`);
|
|
3594
5550
|
info("\n Layers:");
|
|
3595
|
-
for (const [
|
|
3596
|
-
info(` - ${
|
|
5551
|
+
for (const [layer2, count] of Object.entries(manifest.layers)) {
|
|
5552
|
+
info(` - ${layer2.padEnd(16)}: ${count} files`);
|
|
3597
5553
|
}
|
|
3598
5554
|
});
|
|
3599
5555
|
return validation.valid ? 0 : 1;
|
|
@@ -3662,10 +5618,10 @@ ${USAGE}
|
|
|
3662
5618
|
}
|
|
3663
5619
|
}
|
|
3664
5620
|
async function runWatchLoop(invocation, resolved) {
|
|
3665
|
-
const outDir =
|
|
3666
|
-
const tmpDir =
|
|
3667
|
-
const outName =
|
|
3668
|
-
const tmpName =
|
|
5621
|
+
const outDir = path10__namespace.resolve(resolved.cwd, resolved.outDir);
|
|
5622
|
+
const tmpDir = path10__namespace.resolve(resolved.cwd, resolved.tmpDir);
|
|
5623
|
+
const outName = path10__namespace.basename(outDir).toLowerCase();
|
|
5624
|
+
const tmpName = path10__namespace.basename(tmpDir).toLowerCase();
|
|
3669
5625
|
const isGenerated = (candidate) => candidate.toLowerCase().split(/[\\/]/).some(
|
|
3670
5626
|
(segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`)
|
|
3671
5627
|
);
|
|
@@ -3689,19 +5645,19 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
3689
5645
|
info("\u{1F440} Watching for changes. Press Ctrl+C to stop.");
|
|
3690
5646
|
regenerate();
|
|
3691
5647
|
let timer = null;
|
|
3692
|
-
const watcher =
|
|
5648
|
+
const watcher = fs9__namespace.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
|
|
3693
5649
|
if (filename) {
|
|
3694
|
-
const changed =
|
|
5650
|
+
const changed = path10__namespace.resolve(resolved.cwd, filename.toString());
|
|
3695
5651
|
if (isGenerated(changed)) return;
|
|
3696
5652
|
}
|
|
3697
5653
|
if (timer) clearTimeout(timer);
|
|
3698
5654
|
timer = setTimeout(regenerate, 250);
|
|
3699
5655
|
});
|
|
3700
|
-
await new Promise((
|
|
5656
|
+
await new Promise((resolve6) => {
|
|
3701
5657
|
const stop = () => {
|
|
3702
5658
|
if (timer) clearTimeout(timer);
|
|
3703
5659
|
watcher.close();
|
|
3704
|
-
|
|
5660
|
+
resolve6();
|
|
3705
5661
|
};
|
|
3706
5662
|
process.once("SIGINT", stop);
|
|
3707
5663
|
process.once("SIGTERM", stop);
|