@ankurhalder/codebase-snapshot 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { O as OutputFormat } from './types-S-5eRuRc.cjs';
1
+ import { O as OutputFormat } from './types-CJqkePM4.cjs';
2
2
 
3
3
  type Command = "generate" | "validate" | "report" | "init" | "explain" | "diff";
4
4
  /** A user-input problem: printed without a stack trace, exits 2. */
@@ -11,6 +11,14 @@ interface ParsedInvocation {
11
11
  format: OutputFormat;
12
12
  dryRun: boolean;
13
13
  watch: boolean;
14
+ preset?: string;
15
+ /** The size exactly as typed, so the generated config can keep `120k`. */
16
+ maxPartBytesRaw?: string;
17
+ include?: string;
18
+ exclude?: string;
19
+ yes: boolean;
20
+ minimal: boolean;
21
+ force: boolean;
14
22
  cwd: string;
15
23
  outDir?: string;
16
24
  includeMonolith?: boolean;
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { O as OutputFormat } from './types-S-5eRuRc.js';
1
+ import { O as OutputFormat } from './types-CJqkePM4.js';
2
2
 
3
3
  type Command = "generate" | "validate" | "report" | "init" | "explain" | "diff";
4
4
  /** A user-input problem: printed without a stack trace, exits 2. */
@@ -11,6 +11,14 @@ interface ParsedInvocation {
11
11
  format: OutputFormat;
12
12
  dryRun: boolean;
13
13
  watch: boolean;
14
+ preset?: string;
15
+ /** The size exactly as typed, so the generated config can keep `120k`. */
16
+ maxPartBytesRaw?: string;
17
+ include?: string;
18
+ exclude?: string;
19
+ yes: boolean;
20
+ minimal: boolean;
21
+ force: boolean;
14
22
  cwd: string;
15
23
  outDir?: string;
16
24
  includeMonolith?: boolean;
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
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';
1
+ import { parseByteSize, GENERATOR_VERSION, ARTIFACT_NAMES, getPreset, unknownPresetMessage, MIN_MAX_PART_BYTES, loadConfigFileDetailed, resolveConfig, validateSnapshot, explainPath, compareManifests, loadManifest, renderDiffText, generateSnapshot, GENERATOR_NAME, runInit, PromptCancelled, renderExplainText } from './chunk-SUOJL5EX.js';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { parseArgs } from 'util';
@@ -35,6 +35,12 @@ var CLI_OPTIONS = {
35
35
  mode: { type: "string" },
36
36
  "dry-run": { type: "boolean" },
37
37
  watch: { type: "boolean" },
38
+ preset: { type: "string" },
39
+ include: { type: "string" },
40
+ exclude: { type: "string" },
41
+ yes: { type: "boolean" },
42
+ minimal: { type: "boolean" },
43
+ force: { type: "boolean" },
38
44
  help: { type: "boolean", short: "h" },
39
45
  version: { type: "boolean", short: "v" }
40
46
  };
@@ -72,6 +78,14 @@ Options:
72
78
  --format <text|json> CLI output format (default: text)
73
79
  --dry-run Compute the snapshot without writing anything
74
80
  --watch Re-generate whenever the repository changes
81
+
82
+ init only:
83
+ --preset <name> Force a project preset instead of detecting one
84
+ --include <globs> Comma-separated allow-list
85
+ --exclude <globs> Comma-separated exclude patterns
86
+ --minimal Write only the settings that differ from defaults
87
+ --yes Accept every default without prompting
88
+ --force Overwrite an existing config
75
89
  -h, --help Display this help message
76
90
  -v, --version Display version
77
91
 
@@ -128,9 +142,32 @@ function parseCliArgs(argv) {
128
142
  strict: true
129
143
  });
130
144
  } catch (err) {
131
- throw new UsageError(err.message);
145
+ const message = err.message;
146
+ const ambiguous = /^Option '(--[^']+)' argument is ambiguous/i.exec(message);
147
+ if (ambiguous) {
148
+ throw new UsageError(
149
+ `${ambiguous[1]} received a value that looks like another flag (for example a negative number). Use the "--option=value" form, and note that sizes must be positive.`
150
+ );
151
+ }
152
+ throw new UsageError(message);
132
153
  }
133
154
  const values = parsed.values;
155
+ for (const [name, spec] of Object.entries(CLI_OPTIONS)) {
156
+ if (spec.type !== "string") continue;
157
+ const raw = values[name];
158
+ if (raw !== void 0 && String(raw).trim() === "") {
159
+ throw new UsageError(`--${name} requires a value (received an empty string).`);
160
+ }
161
+ }
162
+ if (values.cwd !== void 0) {
163
+ const target = String(values.cwd);
164
+ if (!fs.existsSync(target)) {
165
+ throw new UsageError(`--cwd directory does not exist: ${target}`);
166
+ }
167
+ if (!fs.statSync(target).isDirectory()) {
168
+ throw new UsageError(`--cwd is not a directory: ${target}`);
169
+ }
170
+ }
134
171
  if (values.help) {
135
172
  process.stdout.write(`${USAGE}
136
173
  `);
@@ -196,12 +233,37 @@ function parseCliArgs(argv) {
196
233
  } else if (rawSkip !== void 0) {
197
234
  skipArtifacts = parseArtifactList(String(rawSkip), "--skip-artifacts");
198
235
  }
236
+ if (command !== "init") {
237
+ const initOnly = [];
238
+ if (values.preset !== void 0) initOnly.push("--preset");
239
+ if (values.include !== void 0) initOnly.push("--include");
240
+ if (values.exclude !== void 0) initOnly.push("--exclude");
241
+ if (values.yes === true) initOnly.push("--yes");
242
+ if (values.minimal === true) initOnly.push("--minimal");
243
+ if (values.force === true) initOnly.push("--force");
244
+ if (initOnly.length > 0) {
245
+ throw new UsageError(
246
+ `${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).`
247
+ );
248
+ }
249
+ }
250
+ if (command === "init" && values.preset !== void 0) {
251
+ const requested = String(values.preset);
252
+ if (!getPreset(requested)) throw new UsageError(unknownPresetMessage(requested));
253
+ }
199
254
  return {
200
255
  command,
201
256
  args,
202
257
  format: rawFormat,
203
258
  dryRun: values["dry-run"] === true,
204
259
  watch: values.watch === true,
260
+ preset: values.preset === void 0 ? void 0 : String(values.preset),
261
+ maxPartBytesRaw: values["max-part-bytes"] === void 0 ? void 0 : String(values["max-part-bytes"]),
262
+ include: values.include === void 0 ? void 0 : String(values.include),
263
+ exclude: values.exclude === void 0 ? void 0 : String(values.exclude),
264
+ yes: values.yes === true,
265
+ minimal: values.minimal === true,
266
+ force: values.force === true,
205
267
  cwd: values.cwd === void 0 ? process.cwd() : path.resolve(String(values.cwd)),
206
268
  outDir: values["out-dir"] === void 0 ? void 0 : String(values["out-dir"]),
207
269
  includeMonolith: values["include-monolith"] === true ? true : void 0,
@@ -242,32 +304,65 @@ function emit(format, payload, textRenderer) {
242
304
  }
243
305
  textRenderer();
244
306
  }
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;
307
+ function splitList(value) {
308
+ if (value === void 0) return void 0;
309
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
310
+ }
311
+ function describeManifestShapeProblem(manifest) {
312
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
313
+ return "not a JSON object";
251
314
  }
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
- );
315
+ const candidate = manifest;
316
+ const counts = candidate.counts;
317
+ if (!counts || typeof counts !== "object") return "missing `counts`";
318
+ for (const key of ["totalFiles", "totalLines", "totalSizeBytes", "partsCount"]) {
319
+ if (typeof counts[key] !== "number") return `missing numeric \`counts.${key}\``;
320
+ }
321
+ if (!candidate.layers || typeof candidate.layers !== "object") return "missing `layers`";
322
+ if (!Array.isArray(candidate.files)) return "missing `files` array";
323
+ return null;
324
+ }
325
+ async function runInitCommand(invocation) {
326
+ try {
327
+ const result = await runInit({
328
+ cwd: invocation.cwd,
329
+ preset: invocation.preset,
330
+ maxPartBytes: invocation.maxPartBytesRaw ?? invocation.maxPartBytes,
331
+ include: splitList(invocation.include),
332
+ exclude: splitList(invocation.exclude),
333
+ skipArtifacts: invocation.skipArtifacts,
334
+ includeSensitiveFiles: invocation.includeSensitiveFiles,
335
+ yes: invocation.yes,
336
+ minimal: invocation.minimal,
337
+ force: invocation.force,
338
+ dryRun: invocation.dryRun
339
+ });
340
+ if (invocation.format === "json") {
341
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
342
+ `);
343
+ return 0;
344
+ }
345
+ if (!result.written) {
346
+ info(result.configText);
347
+ return 0;
348
+ }
349
+ info(`\u2705 Created ${path.basename(result.configPath)} (preset: ${result.preset})`);
350
+ if (result.scripts.length > 0) info(` Added npm scripts: ${result.scripts.join(", ")}`);
351
+ if (result.gitignoreEntries.length > 0) {
352
+ info(` Added to .gitignore: ${result.gitignoreEntries.join(", ")}`);
353
+ }
354
+ for (const note of result.notes) info(` \u26A0 ${note}`);
355
+ info("");
356
+ info(" Next: npm run snapshot");
357
+ return 0;
358
+ } catch (err) {
359
+ if (err instanceof PromptCancelled) {
360
+ warn("\u274C Cancelled.");
361
+ return 1;
362
+ }
363
+ warn(`\u274C ${err.message}`);
364
+ return 1;
269
365
  }
270
- return 0;
271
366
  }
272
367
  function renderValidateText(result) {
273
368
  if (result.valid) {
@@ -290,6 +385,9 @@ function renderValidateText(result) {
290
385
  for (const missing of result.drift.missingOnDisk.slice(0, 20)) {
291
386
  warn(` - deleted: ${missing}`);
292
387
  }
388
+ for (const mismatch of result.drift.totalsMismatches.slice(0, 20)) {
389
+ warn(` - counts: ${mismatch}`);
390
+ }
293
391
  }
294
392
  for (const warning of result.warnings) warn(` \u26A0 ${warning}`);
295
393
  }
@@ -309,10 +407,14 @@ ${USAGE}
309
407
  throw err;
310
408
  }
311
409
  if (invocation.command === "init") {
312
- return runInit(invocation.cwd);
410
+ return await runInitCommand(invocation);
313
411
  }
314
412
  const loaded = await loadConfigFileDetailed(invocation.cwd);
315
413
  for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
414
+ if (loaded.errors.length > 0) {
415
+ warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
416
+ return 1;
417
+ }
316
418
  if (loaded.file) info(`\u2699 Using config ${path.relative(process.cwd(), loaded.file) || loaded.file}`);
317
419
  const resolved = resolveConfig(
318
420
  loaded.config,
@@ -337,7 +439,7 @@ ${USAGE}
337
439
  return result.valid ? 0 : 1;
338
440
  }
339
441
  if (invocation.command === "report") {
340
- const manifestPath = path.join(resolved.cwd, resolved.outDir, "manifest.json");
442
+ const manifestPath = path.resolve(resolved.cwd, resolved.outDir, "manifest.json");
341
443
  if (!fs.existsSync(manifestPath)) {
342
444
  warn(`\u274C Manifest not found at ${manifestPath}. Run generate first.`);
343
445
  return 1;
@@ -349,6 +451,11 @@ ${USAGE}
349
451
  warn(`\u274C Could not read ${manifestPath}: ${err.message}`);
350
452
  return 1;
351
453
  }
454
+ const shapeError = describeManifestShapeProblem(manifest);
455
+ if (shapeError) {
456
+ warn(`\u274C ${manifestPath} is not a snapshot manifest: ${shapeError}`);
457
+ return 1;
458
+ }
352
459
  const validation = validateSnapshot(resolved);
353
460
  emit(invocation.format, { manifest, validation }, () => {
354
461
  info(`
@@ -411,9 +518,23 @@ ${USAGE}
411
518
  info(
412
519
  ` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
413
520
  );
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`);
521
+ const { artifacts } = resolved;
522
+ if (artifacts.parts) {
523
+ info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
524
+ }
525
+ const reports = [
526
+ artifacts.projectTree && "project-tree",
527
+ artifacts.navigation && "navigation",
528
+ artifacts.audit && "audit"
529
+ ].filter(Boolean);
530
+ if (reports.length > 0) {
531
+ info(` - Reports: ${resolved.outDir}/reports/ (${reports.join(", ")})`);
532
+ }
533
+ if (artifacts.manifest) info(` - Manifest: ${resolved.outDir}/manifest.json`);
534
+ if (artifacts.monolith) info(` - Monolith: ${resolved.outDir}/full-codebase.{json,md}`);
535
+ if (reports.length === 0 && !artifacts.parts && !artifacts.manifest && !artifacts.monolith) {
536
+ info(` - Artifacts: all disabled (only the ownership marker was written)`);
537
+ }
417
538
  info(` - Fingerprint: ${manifest.fingerprint}`);
418
539
  info(` - Integrity: ${manifest.validation.status}`);
419
540
  if (manifest.warnings.length > 0) {
@@ -430,6 +551,14 @@ ${USAGE}
430
551
  }
431
552
  return manifest.validation.status === "passed" ? 0 : 1;
432
553
  } catch (err) {
554
+ const code = err.code;
555
+ if (code === "EPERM" || code === "EACCES") {
556
+ warn(
557
+ `\u274C Cannot write the snapshot: permission denied under ${resolved.outDir}. Check the directory's permissions or choose another --out-dir.`
558
+ );
559
+ warn(` (${err.message})`);
560
+ return 1;
561
+ }
433
562
  warn(`\u274C Failed to generate snapshot: ${err.message}`);
434
563
  return 1;
435
564
  }