@ankurhalder/codebase-snapshot 0.0.0-stage → 2.0.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/dist/cli.d.cts ADDED
@@ -0,0 +1,40 @@
1
+ import { O as OutputFormat } from './types-S-5eRuRc.cjs';
2
+
3
+ type Command = "generate" | "validate" | "report" | "init" | "explain" | "diff";
4
+ /** A user-input problem: printed without a stack trace, exits 2. */
5
+ declare class UsageError extends Error {
6
+ }
7
+ interface ParsedInvocation {
8
+ command: Command;
9
+ /** Positional arguments that follow the command name. */
10
+ args: string[];
11
+ format: OutputFormat;
12
+ dryRun: boolean;
13
+ watch: boolean;
14
+ cwd: string;
15
+ outDir?: string;
16
+ includeMonolith?: boolean;
17
+ includeSensitiveFiles?: boolean;
18
+ maxPartBytes?: number;
19
+ largeFileBytes?: number;
20
+ maxMonolithBytes?: number | null;
21
+ skipArtifacts?: string[];
22
+ }
23
+ declare function parsePositiveInteger(raw: string | undefined, optionName: string, minimum: number): number | undefined;
24
+ /**
25
+ * Parses a byte-budget option, accepting human-readable units (`120k`, `1.5mb`)
26
+ * as well as raw byte counts.
27
+ */
28
+ declare function parseByteOption(raw: string | undefined, optionName: string, minimum: number): number | undefined;
29
+ /**
30
+ * Parses argv.
31
+ *
32
+ * Uses `node:util.parseArgs` in strict mode, so `--option=value` works, unknown
33
+ * flags are rejected instead of silently ignored, and a typo can no longer fall
34
+ * through to the writable `generate` command.
35
+ */
36
+ declare function parseCliArgs(argv: string[]): ParsedInvocation;
37
+ /** Runs the CLI and returns the process exit code. */
38
+ declare function main(argv?: string[]): Promise<number>;
39
+
40
+ export { UsageError, main, parseByteOption, parseCliArgs, parsePositiveInteger };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { O as OutputFormat } from './types-S-5eRuRc.js';
2
+
3
+ type Command = "generate" | "validate" | "report" | "init" | "explain" | "diff";
4
+ /** A user-input problem: printed without a stack trace, exits 2. */
5
+ declare class UsageError extends Error {
6
+ }
7
+ interface ParsedInvocation {
8
+ command: Command;
9
+ /** Positional arguments that follow the command name. */
10
+ args: string[];
11
+ format: OutputFormat;
12
+ dryRun: boolean;
13
+ watch: boolean;
14
+ cwd: string;
15
+ outDir?: string;
16
+ includeMonolith?: boolean;
17
+ includeSensitiveFiles?: boolean;
18
+ maxPartBytes?: number;
19
+ largeFileBytes?: number;
20
+ maxMonolithBytes?: number | null;
21
+ skipArtifacts?: string[];
22
+ }
23
+ declare function parsePositiveInteger(raw: string | undefined, optionName: string, minimum: number): number | undefined;
24
+ /**
25
+ * Parses a byte-budget option, accepting human-readable units (`120k`, `1.5mb`)
26
+ * as well as raw byte counts.
27
+ */
28
+ declare function parseByteOption(raw: string | undefined, optionName: string, minimum: number): number | undefined;
29
+ /**
30
+ * Parses argv.
31
+ *
32
+ * Uses `node:util.parseArgs` in strict mode, so `--option=value` works, unknown
33
+ * flags are rejected instead of silently ignored, and a typo can no longer fall
34
+ * through to the writable `generate` command.
35
+ */
36
+ declare function parseCliArgs(argv: string[]): ParsedInvocation;
37
+ /** Runs the CLI and returns the process exit code. */
38
+ declare function main(argv?: string[]): Promise<number>;
39
+
40
+ export { UsageError, main, parseByteOption, parseCliArgs, parsePositiveInteger };
package/dist/cli.js ADDED
@@ -0,0 +1,487 @@
1
+ import { parseByteSize, GENERATOR_VERSION, ARTIFACT_NAMES, MIN_MAX_PART_BYTES, loadConfigFileDetailed, resolveConfig, validateSnapshot, explainPath, compareManifests, loadManifest, renderDiffText, generateSnapshot, GENERATOR_NAME, supportsTypeScriptImports, renderExplainText } from './chunk-RGDVLQTT.js';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { parseArgs } from 'util';
5
+
6
+ var VERSION = GENERATOR_VERSION;
7
+ var COMMAND_ALIASES = /* @__PURE__ */ new Map([
8
+ ["generate", "generate"],
9
+ ["validate", "validate"],
10
+ ["check", "validate"],
11
+ ["report", "report"],
12
+ ["init", "init"],
13
+ ["explain", "explain"],
14
+ ["diff", "diff"]
15
+ ]);
16
+ var COMMAND_ARITY = {
17
+ generate: 0,
18
+ validate: 0,
19
+ report: 0,
20
+ init: 0,
21
+ explain: 1,
22
+ diff: 2
23
+ };
24
+ var CLI_OPTIONS = {
25
+ cwd: { type: "string" },
26
+ "out-dir": { type: "string" },
27
+ "max-part-bytes": { type: "string" },
28
+ "large-file-bytes": { type: "string" },
29
+ "max-monolith-bytes": { type: "string" },
30
+ "include-monolith": { type: "boolean" },
31
+ "include-sensitive-files": { type: "boolean" },
32
+ "skip-artifacts": { type: "string" },
33
+ "only-artifacts": { type: "string" },
34
+ format: { type: "string" },
35
+ mode: { type: "string" },
36
+ "dry-run": { type: "boolean" },
37
+ watch: { type: "boolean" },
38
+ help: { type: "boolean", short: "h" },
39
+ version: { type: "boolean", short: "v" }
40
+ };
41
+ var UsageError = class extends Error {
42
+ };
43
+ var USAGE = `
44
+ ${GENERATOR_NAME} v${VERSION}
45
+ Production-grade LLM codebase snapshot generator, auditor, and AST partitioner
46
+
47
+ Usage:
48
+ codebase-snapshot [command] [options]
49
+ snap [command] [options]
50
+
51
+ Commands:
52
+ generate (default) Generate a complete partitioned codebase snapshot
53
+ validate, check Validate snapshot integrity and check for drift
54
+ init Generate a starter snapshot.config file
55
+ report Print codebase health, layer metrics, and secret audit
56
+ explain <path> Show how one path would be treated: layer, kind, part, skips
57
+ diff <a> <b> Compare two snapshots (directories or manifest.json files)
58
+
59
+ Options:
60
+ --cwd <path> Target repository directory (default: current directory)
61
+ --out-dir <path> Snapshot output directory (default: snapshot)
62
+ --include-monolith Also generate full-codebase.json and full-codebase.md
63
+ --max-monolith-bytes <n> Skip monolith artefacts larger than n bytes
64
+ --max-part-bytes <n> Maximum byte budget per markdown part (default: 307200).
65
+ Accepts units: 120k, 1.5mb
66
+ --large-file-bytes <n> Threshold for head/tail truncation (default: 204800)
67
+ --include-sensitive-files Emit .env / *.pem / .npmrc / credential files
68
+ --skip-artifacts <names> Comma-separated artifacts to omit
69
+ (readme, manifest, parts, navigation,
70
+ projectTree, audit, monolith)
71
+ --only-artifacts <names> Write *only* these artifacts
72
+ --format <text|json> CLI output format (default: text)
73
+ --dry-run Compute the snapshot without writing anything
74
+ --watch Re-generate whenever the repository changes
75
+ -h, --help Display this help message
76
+ -v, --version Display version
77
+
78
+ All options also accept the --option=value form.
79
+ Human-readable progress is written to stderr; payloads (--format json) go to stdout.
80
+ `.trim();
81
+ function parsePositiveInteger(raw, optionName, minimum) {
82
+ if (raw === void 0) return void 0;
83
+ const trimmed = raw.trim();
84
+ if (!/^\d+$/.test(trimmed)) {
85
+ throw new UsageError(`--${optionName} expects a whole number, received "${raw}".`);
86
+ }
87
+ const value = Number(trimmed);
88
+ if (!Number.isSafeInteger(value) || value < minimum) {
89
+ throw new UsageError(`--${optionName} must be at least ${minimum}, received "${raw}".`);
90
+ }
91
+ return value;
92
+ }
93
+ function parseByteOption(raw, optionName, minimum) {
94
+ if (raw === void 0) return void 0;
95
+ const value = parseByteSize(raw);
96
+ if (value === void 0 || !Number.isSafeInteger(value)) {
97
+ throw new UsageError(
98
+ `--${optionName} expects a byte count such as 120000 or "120k", received "${raw}".`
99
+ );
100
+ }
101
+ if (value < minimum) {
102
+ throw new UsageError(`--${optionName} must be at least ${minimum}, received "${raw}".`);
103
+ }
104
+ return value;
105
+ }
106
+ function parseArtifactList(raw, flag) {
107
+ const names = raw.split(",").map((name) => name.trim()).filter(Boolean);
108
+ if (names.length === 0) {
109
+ throw new UsageError(
110
+ `${flag} expects at least one artifact name (${ARTIFACT_NAMES.join(", ")}).`
111
+ );
112
+ }
113
+ const unknown = names.filter((name) => !ARTIFACT_NAMES.includes(name));
114
+ if (unknown.length > 0) {
115
+ throw new UsageError(
116
+ `Unknown artifact(s): ${unknown.join(", ")}. Expected one of: ${ARTIFACT_NAMES.join(", ")}.`
117
+ );
118
+ }
119
+ return names;
120
+ }
121
+ function parseCliArgs(argv) {
122
+ let parsed;
123
+ try {
124
+ parsed = parseArgs({
125
+ args: argv,
126
+ options: CLI_OPTIONS,
127
+ allowPositionals: true,
128
+ strict: true
129
+ });
130
+ } catch (err) {
131
+ throw new UsageError(err.message);
132
+ }
133
+ const values = parsed.values;
134
+ if (values.help) {
135
+ process.stdout.write(`${USAGE}
136
+ `);
137
+ throw new HelpRequested();
138
+ }
139
+ if (values.version) {
140
+ process.stdout.write(`v${VERSION}
141
+ `);
142
+ throw new HelpRequested();
143
+ }
144
+ const positionals = parsed.positionals;
145
+ let command = "generate";
146
+ let args = [];
147
+ const explicitMode = values.mode ? String(values.mode) : void 0;
148
+ const firstPositional = positionals[0];
149
+ if (firstPositional !== void 0) {
150
+ const resolved = COMMAND_ALIASES.get(firstPositional);
151
+ if (!resolved) {
152
+ throw new UsageError(
153
+ `Unknown command "${firstPositional}". Expected one of: ${Array.from(COMMAND_ALIASES.keys()).join(", ")}.`
154
+ );
155
+ }
156
+ command = resolved;
157
+ args = positionals.slice(1);
158
+ } else if (explicitMode !== void 0) {
159
+ const resolved = COMMAND_ALIASES.get(explicitMode);
160
+ if (!resolved) {
161
+ throw new UsageError(
162
+ `Unknown --mode "${explicitMode}". Expected one of: ${Array.from(COMMAND_ALIASES.keys()).join(", ")}.`
163
+ );
164
+ }
165
+ command = resolved;
166
+ }
167
+ const expectedArgs = COMMAND_ARITY[command];
168
+ if (args.length !== expectedArgs) {
169
+ if (command === "explain") {
170
+ throw new UsageError("explain expects exactly one path.");
171
+ }
172
+ if (command === "diff") {
173
+ throw new UsageError(
174
+ "diff expects exactly two arguments: two snapshot directories or manifest.json files."
175
+ );
176
+ }
177
+ throw new UsageError(
178
+ `Unexpected argument(s): ${args.join(" ")}. ${command} takes no positional arguments.`
179
+ );
180
+ }
181
+ const rawFormat = values.format === void 0 ? "text" : String(values.format);
182
+ if (rawFormat !== "text" && rawFormat !== "json") {
183
+ throw new UsageError(`--format must be "text" or "json", received "${rawFormat}".`);
184
+ }
185
+ const rawSkip = values["skip-artifacts"];
186
+ const rawOnly = values["only-artifacts"];
187
+ if (rawSkip !== void 0 && rawOnly !== void 0) {
188
+ throw new UsageError("--skip-artifacts and --only-artifacts cannot be combined.");
189
+ }
190
+ let skipArtifacts;
191
+ if (rawOnly !== void 0) {
192
+ const keep = parseArtifactList(String(rawOnly), "--only-artifacts");
193
+ skipArtifacts = ARTIFACT_NAMES.filter(
194
+ (name) => !keep.includes(name)
195
+ );
196
+ } else if (rawSkip !== void 0) {
197
+ skipArtifacts = parseArtifactList(String(rawSkip), "--skip-artifacts");
198
+ }
199
+ return {
200
+ command,
201
+ args,
202
+ format: rawFormat,
203
+ dryRun: values["dry-run"] === true,
204
+ watch: values.watch === true,
205
+ cwd: values.cwd === void 0 ? process.cwd() : path.resolve(String(values.cwd)),
206
+ outDir: values["out-dir"] === void 0 ? void 0 : String(values["out-dir"]),
207
+ includeMonolith: values["include-monolith"] === true ? true : void 0,
208
+ includeSensitiveFiles: values["include-sensitive-files"] === true ? true : void 0,
209
+ maxPartBytes: parseByteOption(
210
+ values["max-part-bytes"],
211
+ "max-part-bytes",
212
+ MIN_MAX_PART_BYTES
213
+ ),
214
+ largeFileBytes: parseByteOption(
215
+ values["large-file-bytes"],
216
+ "large-file-bytes",
217
+ 1
218
+ ),
219
+ maxMonolithBytes: parseByteOption(
220
+ values["max-monolith-bytes"],
221
+ "max-monolith-bytes",
222
+ 1
223
+ ),
224
+ skipArtifacts
225
+ };
226
+ }
227
+ var HelpRequested = class extends Error {
228
+ };
229
+ function info(message) {
230
+ process.stderr.write(`${message}
231
+ `);
232
+ }
233
+ function warn(message) {
234
+ process.stderr.write(`${message}
235
+ `);
236
+ }
237
+ function emit(format, payload, textRenderer) {
238
+ if (format === "json") {
239
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}
240
+ `);
241
+ return;
242
+ }
243
+ textRenderer();
244
+ }
245
+ function runInit(cwd) {
246
+ const targetName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
247
+ const targetPath = path.join(cwd, targetName);
248
+ if (fs.existsSync(targetPath)) {
249
+ warn(`\u274C ${targetName} already exists in ${cwd}.`);
250
+ return 1;
251
+ }
252
+ const importPath = JSON.stringify(GENERATOR_NAME);
253
+ const starter = `import { defineConfig } from ${importPath};
254
+
255
+ export default defineConfig({
256
+ branch: "main",
257
+ outDir: "snapshot",
258
+ maxPartBytes: 300 * 1024,
259
+ largeFileBytes: 200 * 1024,
260
+ excludePatterns: [],
261
+ });
262
+ `;
263
+ fs.writeFileSync(targetPath, starter, "utf8");
264
+ info(`\u2705 Created ${targetName} in ${cwd}`);
265
+ if (targetName.endsWith(".mjs")) {
266
+ info(
267
+ " (a .ts config needs Node >= 22.6 to import natively; this Node writes a .mjs instead)"
268
+ );
269
+ }
270
+ return 0;
271
+ }
272
+ function renderValidateText(result) {
273
+ if (result.valid) {
274
+ info("\u2705 Snapshot is valid and up to date with disk.");
275
+ } else {
276
+ warn("\u274C Snapshot validation failed:");
277
+ for (const error of result.errors) warn(` - ${error}`);
278
+ for (const drift of result.drift.hashMismatches.slice(0, 20)) {
279
+ warn(` - modified: ${drift.path}`);
280
+ }
281
+ for (const issue of result.drift.partIssues.slice(0, 20)) {
282
+ warn(` - part: ${issue.part} (${issue.issue})`);
283
+ }
284
+ for (const mismatch of result.drift.configMismatches.slice(0, 20)) {
285
+ warn(` - config: ${mismatch}`);
286
+ }
287
+ for (const missing of result.drift.missingInSnapshot.slice(0, 20)) {
288
+ warn(` - new file: ${missing}`);
289
+ }
290
+ for (const missing of result.drift.missingOnDisk.slice(0, 20)) {
291
+ warn(` - deleted: ${missing}`);
292
+ }
293
+ }
294
+ for (const warning of result.warnings) warn(` \u26A0 ${warning}`);
295
+ }
296
+ async function main(argv = process.argv.slice(2)) {
297
+ let invocation;
298
+ try {
299
+ invocation = parseCliArgs(argv);
300
+ } catch (err) {
301
+ if (err instanceof HelpRequested) return 0;
302
+ if (err instanceof UsageError) {
303
+ process.stderr.write(`\u274C ${err.message}
304
+
305
+ ${USAGE}
306
+ `);
307
+ return 2;
308
+ }
309
+ throw err;
310
+ }
311
+ if (invocation.command === "init") {
312
+ return runInit(invocation.cwd);
313
+ }
314
+ const loaded = await loadConfigFileDetailed(invocation.cwd);
315
+ for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
316
+ if (loaded.file) info(`\u2699 Using config ${path.relative(process.cwd(), loaded.file) || loaded.file}`);
317
+ const resolved = resolveConfig(
318
+ loaded.config,
319
+ {
320
+ mode: invocation.command,
321
+ format: invocation.format,
322
+ dryRun: invocation.dryRun,
323
+ cwd: invocation.cwd,
324
+ outDir: invocation.outDir,
325
+ includeMonolith: invocation.includeMonolith,
326
+ includeSensitiveFiles: invocation.includeSensitiveFiles,
327
+ maxPartBytes: invocation.maxPartBytes,
328
+ largeFileBytes: invocation.largeFileBytes,
329
+ maxMonolithBytes: invocation.maxMonolithBytes,
330
+ skipArtifacts: invocation.skipArtifacts
331
+ },
332
+ invocation.cwd
333
+ );
334
+ if (invocation.command === "validate") {
335
+ const result = validateSnapshot(resolved);
336
+ emit(invocation.format, result, () => renderValidateText(result));
337
+ return result.valid ? 0 : 1;
338
+ }
339
+ if (invocation.command === "report") {
340
+ const manifestPath = path.join(resolved.cwd, resolved.outDir, "manifest.json");
341
+ if (!fs.existsSync(manifestPath)) {
342
+ warn(`\u274C Manifest not found at ${manifestPath}. Run generate first.`);
343
+ return 1;
344
+ }
345
+ let manifest;
346
+ try {
347
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
348
+ } catch (err) {
349
+ warn(`\u274C Could not read ${manifestPath}: ${err.message}`);
350
+ return 1;
351
+ }
352
+ const validation = validateSnapshot(resolved);
353
+ emit(invocation.format, { manifest, validation }, () => {
354
+ info(`
355
+ \u{1F4CA} Codebase Snapshot Report (${resolved.cwd})`);
356
+ info(` - Generated at: ${manifest.generatedAt}`);
357
+ info(` - Schema: ${manifest.schemaVersion}`);
358
+ info(` - Fingerprint: ${manifest.fingerprint}`);
359
+ info(` - Files included: ${manifest.counts.totalFiles}`);
360
+ info(` - Total lines: ${manifest.counts.totalLines.toLocaleString()}`);
361
+ info(
362
+ ` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
363
+ );
364
+ info(` - Total parts: ${manifest.counts.partsCount}`);
365
+ info(` - Integrity: ${validation.valid ? "\u2705 Clean (no drift)" : "\u274C Drift detected"}`);
366
+ info(` - Warnings: ${manifest.warnings?.length ?? 0}`);
367
+ info("\n Layers:");
368
+ for (const [layer, count] of Object.entries(manifest.layers)) {
369
+ info(` - ${layer.padEnd(16)}: ${count} files`);
370
+ }
371
+ });
372
+ return validation.valid ? 0 : 1;
373
+ }
374
+ if (invocation.command === "explain") {
375
+ const result = explainPath(resolved, invocation.args[0]);
376
+ emit(invocation.format, result, () => info(renderExplainText(result)));
377
+ return result.included ? 0 : 1;
378
+ }
379
+ if (invocation.command === "diff") {
380
+ try {
381
+ const [beforePath, afterPath] = invocation.args;
382
+ const diff = compareManifests(loadManifest(beforePath), loadManifest(afterPath));
383
+ emit(invocation.format, diff, () => info(renderDiffText(diff)));
384
+ return 0;
385
+ } catch (err) {
386
+ warn(`\u274C ${err.message}`);
387
+ return 1;
388
+ }
389
+ }
390
+ const started = performance.now();
391
+ info(`\u{1F4F8} ${invocation.dryRun ? "Planning snapshot (dry run)" : "Generating snapshot"} for: ${resolved.cwd}`);
392
+ info(
393
+ ` Branch: ${resolved.branch} | Part budget: ${(resolved.maxPartBytes / 1024).toFixed(0)} KB`
394
+ );
395
+ try {
396
+ const manifest = generateSnapshot(resolved, { dryRun: invocation.dryRun });
397
+ const elapsed = ((performance.now() - started) / 1e3).toFixed(2);
398
+ if (invocation.dryRun) {
399
+ info("\n\u{1F9EA} Dry run \u2014 nothing was written:");
400
+ info(` - Would track: ${manifest.counts.totalFiles} files`);
401
+ info(` - Would write: ${manifest.counts.partsCount} part(s) under ${resolved.outDir}/parts/`);
402
+ info(` - Skipped: ${manifest.counts.skippedCount} file(s)`);
403
+ info(` - Warnings: ${manifest.warnings.length}`);
404
+ info(` - Integrity: ${manifest.validation.status}`);
405
+ for (const error of manifest.validation.errors.slice(0, 10)) info(` - ${error}`);
406
+ } else {
407
+ info(`
408
+ \u2705 Snapshot generated in ${elapsed}s:`);
409
+ info(` - Tracked files: ${manifest.counts.totalFiles}`);
410
+ info(` - Total lines: ${manifest.counts.totalLines.toLocaleString()}`);
411
+ info(
412
+ ` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
413
+ );
414
+ info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
415
+ info(` - Reports: ${resolved.outDir}/reports/ (project-tree, navigation, audit)`);
416
+ info(` - Manifest: ${resolved.outDir}/manifest.json`);
417
+ info(` - Fingerprint: ${manifest.fingerprint}`);
418
+ info(` - Integrity: ${manifest.validation.status}`);
419
+ if (manifest.warnings.length > 0) {
420
+ info(` - Warnings: ${manifest.warnings.length}`);
421
+ for (const warning of manifest.warnings.slice(0, 10)) info(` - ${warning}`);
422
+ }
423
+ }
424
+ if (invocation.format === "json") {
425
+ process.stdout.write(`${JSON.stringify(manifest, null, 2)}
426
+ `);
427
+ }
428
+ if (invocation.watch) {
429
+ return await runWatchLoop(invocation, resolved);
430
+ }
431
+ return manifest.validation.status === "passed" ? 0 : 1;
432
+ } catch (err) {
433
+ warn(`\u274C Failed to generate snapshot: ${err.message}`);
434
+ return 1;
435
+ }
436
+ }
437
+ async function runWatchLoop(invocation, resolved) {
438
+ const outDir = path.resolve(resolved.cwd, resolved.outDir);
439
+ const tmpDir = path.resolve(resolved.cwd, resolved.tmpDir);
440
+ const outName = path.basename(outDir).toLowerCase();
441
+ const tmpName = path.basename(tmpDir).toLowerCase();
442
+ const isGenerated = (candidate) => candidate.toLowerCase().split(/[\\/]/).some(
443
+ (segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`)
444
+ );
445
+ let running = false;
446
+ const regenerate = () => {
447
+ if (running) return;
448
+ running = true;
449
+ const started = performance.now();
450
+ try {
451
+ const manifest = generateSnapshot(resolved, { dryRun: invocation.dryRun });
452
+ const elapsed = ((performance.now() - started) / 1e3).toFixed(2);
453
+ info(
454
+ `\u267B\uFE0F ${(/* @__PURE__ */ new Date()).toISOString()} ${manifest.counts.totalFiles} file(s), ${manifest.counts.partsCount} part(s), ${manifest.validation.status} (${elapsed}s)`
455
+ );
456
+ } catch (err) {
457
+ warn(`\u274C ${err.message}`);
458
+ } finally {
459
+ running = false;
460
+ }
461
+ };
462
+ info("\u{1F440} Watching for changes. Press Ctrl+C to stop.");
463
+ regenerate();
464
+ let timer = null;
465
+ const watcher = fs.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
466
+ if (filename) {
467
+ const changed = path.resolve(resolved.cwd, filename.toString());
468
+ if (isGenerated(changed)) return;
469
+ }
470
+ if (timer) clearTimeout(timer);
471
+ timer = setTimeout(regenerate, 250);
472
+ });
473
+ await new Promise((resolve2) => {
474
+ const stop = () => {
475
+ if (timer) clearTimeout(timer);
476
+ watcher.close();
477
+ resolve2();
478
+ };
479
+ process.once("SIGINT", stop);
480
+ process.once("SIGTERM", stop);
481
+ });
482
+ return 0;
483
+ }
484
+
485
+ export { UsageError, main, parseByteOption, parseCliArgs, parsePositiveInteger };
486
+ //# sourceMappingURL=cli.js.map
487
+ //# sourceMappingURL=cli.js.map