Skip to content

feat(gemini): A whimsical Node.js utility to unearth and report forgotten files ('digital dust bunnies') in your file system.#5133

Open
polsala wants to merge 1 commit into
mainfrom
ai/gemini-20260707-2122
Open

feat(gemini): A whimsical Node.js utility to unearth and report forgotten files ('digital dust bunnies') in your file system.#5133
polsala wants to merge 1 commit into
mainfrom
ai/gemini-20260707-2122

Conversation

@polsala

@polsala polsala commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Implementation Summary

  • Utility: nightly-digital-dust-bunny
  • Provider: gemini
  • Location: node-utils/nightly-nightly-digital-dust-bunny-4
  • Files Created: 4
  • Description: A whimsical Node.js utility to unearth and report forgotten files ('digital dust bunnies') in your file system.

Rationale

  • Automated proposal from the Gemini generator delivering a fresh community utility.
  • This utility was generated using the gemini AI provider.

Why safe to merge

  • Utility is isolated to node-utils/nightly-nightly-digital-dust-bunny-4.
  • README + tests ship together (see folder contents).
  • No secrets or credentials touched.
  • All changes are additive and self-contained.

Test Plan

  • Follow the instructions in the generated README at node-utils/nightly-nightly-digital-dust-bunny-4/README.md
  • Run tests located in node-utils/nightly-nightly-digital-dust-bunny-4/tests/

Links

  • Generated docs and examples committed alongside this change.

Mock Justification

  • Not applicable; generator did not introduce new mocks.

…tten files ('digital dust bunnies') in your file system.
@polsala

polsala commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

🤖 Review by GROQ Agent

✅ What's solid

  • Clear purpose & scope – the utility is nicely isolated in its own folder and does not touch any external services or credentials.
  • Self‑contained CLInode src/index.js <dir> [days] works out‑of‑the‑box and the README walks a user through installation and execution.
  • Graceful error handling – unreadable directories are caught and reported with a warning instead of crashing the whole run.
  • Human‑readable output – the report includes path, modification date, size (via formatBytes) and a whimsical message, which matches the “whimsical” branding.
  • Test coverage – the test suite exercises the core recursion, age filtering, permission‑error handling, and the CLI entry‑point, giving confidence that the main flows work as intended.

🧪 Tests

Area Feedback
Unit coverage The tests cover the happy path, empty directories, old vs. recent files, and permission errors. Consider adding a case for symlink loops to ensure the recursion does not get stuck.
Mocking strategy fs.readdirSync and fs.statSync are mocked directly, which is fine. However the current tests rely on assert from Node while the rest of the suite uses Jest’s expect. Mixing the two works but can be confusing. Switching fully to Jest assertions (expect(result).toHaveLength(1), etc.) would make the test style consistent.
Date handling The test that overrides global.Date correctly restores the original Date after the test suite finishes. Adding an explicit afterAll(() => { global.Date = RealDate; }) makes the intent clearer and protects future tests.
Process exit process.exit is mocked and verified, which is good. Ensure the mock is restored in an afterEach block (currently done) and that the mock does not leak into other test files.
Edge‑case validation Add a test for non‑numeric days_old argument (e.g., node src/index.js . abc). The current implementation will coerce NaN and treat it as 0, which could lead to surprising results.
Performance test (optional) For very large directory trees, the synchronous recursion could become a bottleneck. A quick benchmark test (e.g., using a generated deep tree) would highlight any need for an async version.

Suggested test snippet for symlink handling

test('should skip symbolic links to avoid infinite recursion', () => {
  const mockNow = new Date('2024-01-01T00:00:00Z');
  const oldDate = new Date(mockNow.getTime() - 100 * 24 * 60 * 60 * 1000); // 100d

  // Directory contains a symlink that points to its parent
  fs.readdirSync.mockReturnValue([
    { name: 'link-to-parent', isDirectory: () => false, isFile: () => false, isSymbolicLink: () => true },
    { name: 'old.txt', isDirectory: () => false, isFile: () => true, isSymbolicLink: () => false },
  ]);

  // lstat to detect symlink, stat for regular file
  fs.lstatSync = jest.fn(p => {
    if (p.endsWith('link-to-parent')) {
      return { isSymbolicLink: () => true };
    }
    return { isSymbolicLink: () => false };
  });
  fs.statSync.mockReturnValue({ mtime: oldDate, size: 123 });

  const result = findOldFiles('/test/symlink', 90);
  expect(result).toHaveLength(1);
  expect(result[0].path).toMatch(/old\.txt$/);
});

🔒 Security

  • Symlink traversal – The current implementation uses fs.statSync, which follows symlinks. An attacker could create a symlink that points outside the target directory, causing the utility to scan arbitrary parts of the filesystem.
    Action: Use fs.lstatSync to detect symlinks and either skip them or explicitly limit traversal to the original root path.

  • Path validation – The CLI accepts any string as the directory argument. If a user accidentally passes a relative path like ../.., the tool will climb up the tree. While this is not a security flaw per‑se, it may violate the “scan only the intended folder” expectation.
    Action: Resolve the input with path.resolve and optionally enforce that the resolved path is a sub‑directory of the current working directory (or a whitelist).

  • Permission error handling – Directory read errors are logged with console.warn, which is appropriate. File‑stat errors are silently ignored. Consider logging a warning for unreadable files as well, so users know why some files are omitted.

  • Dependency surface – Only jest is listed as a dev dependency, which is safe. No runtime dependencies are pulled in, reducing the attack surface.

  • Node version – The README recommends Node 14+. If the project ever runs on older versions, the use of optional chaining (?.) or newer fs APIs could break. Pinning the engine in package.json ("engines": { "node": ">=14" }) would make the requirement explicit.


🧩 Docs / Developer Experience

  • README completeness – The guide covers installation, usage, and testing. Adding a “Running as a global CLI” section (e.g., npm link or a bin entry in package.json) would help users invoke the tool without node src/index.js.

    // package.json
    "bin": {
      "dust-bunny": "./src/index.js"
    }
  • CLI help flag – The script currently prints a usage line only on error. Providing a --help flag that displays the usage and options would improve UX.

    if (process.argv.includes('--help') || process.argv.includes('-h')) {
      console.log(`Usage: node src/index.js <directory_path> [days_old]
    <directory_path>  Path to scan (required)
    [days_old]        Minimum age in days (default: 90)`);
      process.exit(0);
    }
  • Argument parsing – Right now days_old is taken as a raw string and passed to Number. Adding explicit validation (e.g., const days = parseInt(arg, 10); if (isNaN(days) || days < 0) …) prevents accidental misuse.

  • Output formatting – The report prints raw Date objects (stats.mtime). Converting to an ISO string or a locale‑aware format improves readability. The existing test expects 2023-12-17, which is fine, but the implementation should use toISOString().split('T')[0] for consistency.

  • License & contribution – The MIT license is declared, but adding a CONTRIBUTING.md (even a short note) encourages community contributions and clarifies the review process.


🧱 Mocks / Fakes

  • Mock reset strategy – The test suite calls fs.readdirSync.mockReset() and fs.statSync.mockReset() before each main test, which is good. However, the suite also adds a custom fs.lstatSync mock in the symlink test suggestion. Remember to restore any added mocks after the test (jest.restoreAllMocks() or explicit mockRestore).

  • Avoid over‑mocking – The current tests mock the entire fs module. For more realistic behavior, consider using memfs (an in‑memory filesystem) to create a temporary directory structure that the real fs APIs can operate on. This reduces the need to manually stub every readdirSync/statSync call and can catch bugs related to path handling.

    const { vol } = require('memfs');
    beforeEach(() => {
      vol.fromJSON({
        '/proj/old.txt': 'old content',
        '/proj/sub/recent.txt': 'new content',
      });
      jest.spyOn(fs, 'readdirSync').mockImplementation(vol.readdirSync.bind(vol));
      jest.spyOn(fs, 'statSync').mockImplementation(vol.statSync.bind(vol));
    });
  • Consistent mock libraries – As mentioned, stick to either Jest’s built‑in matchers (expect) or Node’s assert. Mixing both works but can be confusing for future contributors.

  • Test isolation – The global Date mock is overridden only in the “main” test block. Ensure that any other test that relies on the real clock (e.g., future tests added later) restores the original Date to avoid flaky behavior.


TL;DR Action items

  1. Add symlink detection (lstatSync) and optionally skip them to prevent directory traversal outside the target.
  2. Validate CLI arguments (numeric days_old, presence of required path) and expose a --help flag.
  3. Consider a bin entry in package.json for a smoother global install experience.
  4. Standardize test assertions to Jest’s expect and optionally use memfs for more realistic filesystem mocks.
  5. Document edge‑case handling (permission errors, symlinks) in the README and add a brief contribution guide.

These tweaks will tighten security, improve developer ergonomics, and make the utility more robust for real‑world usage while preserving its whimsical charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant