Skip to content

feat(config): add unwrapSingleRootDir#15

Merged
fbosch merged 10 commits into
masterfrom
feature/unwrapSingleRootDir
Feb 2, 2026
Merged

feat(config): add unwrapSingleRootDir#15
fbosch merged 10 commits into
masterfrom
feature/unwrapSingleRootDir

Conversation

@fbosch
Copy link
Copy Markdown
Owner

@fbosch fbosch commented Feb 2, 2026

Summary

Implements unwrapSingleRootDir feature to flatten single-root directories.

Changes

  • Remove depth config option, use hardcoded default value
  • Add unwrapSingleRootDir boolean option to unwrap nested root directories recursively
  • Implement path normalization logic in materialize to apply unwrapping
  • Update rules hash computation to track all relevant materialization parameters
  • Enhance fetch/materialize output tracking with cache status

Breaking Changes

Removed depth configuration option; uses hardcoded depth value

Summary by CodeRabbit

  • New Features

    • Added unwrapSingleRootDir option to unwrap single-root outputs across sync, materialize, and target steps.
    • Progress messages now show "Restoring from cache" vs "Downloading repo".
  • Bug Fixes

    • Stricter cache/manifest validation and rules hashing for better integrity; duplicate source ID checks.
  • Removed Features

    • Removed legacy depth configuration (now rejected by schema).
  • Documentation

    • Updated docs and schema for the new/removed options.
  • Tests

    • Added/adjusted tests for unwrap behavior and cache scenarios.
  • Chores

    • CI precheck and pre-commit workflow enhanced (typecheck added).

Copilot AI review requested due to automatic review settings February 2, 2026 17:50
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 2, 2026

Warning

Rate limit exceeded

@fbosch has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Removes the depth option and adds unwrapSingleRootDir to config schemas, runtime types, and defaults. Propagates unwrap behavior through fetch, sync, materialize, and target application; fetch now returns structured FetchResult with cache metadata. Hash computation and manifest validation include the new option. Various tests and CI updated.

Changes

Cohort / File(s) Summary
Docs & Schema
README.md, docs.config.schema.json, src/config-schema.ts
Removed depth from defaults and sources[]; added unwrapSingleRootDir: boolean to defaults and per-source schema.
Config Types & Resolution
src/config.ts
Removed depth from defaults/sources/resolved types and DEFAULT_CONFIG; added unwrapSingleRootDir?: boolean; validate/resolve flows updated and duplicate source ID check added.
Git Fetch Layer
src/git/fetch-source.ts, tests/fetch-source-file-protocol.test.js
Replaced per-call depth with DEFAULT_GIT_DEPTH; removed depth from FetchParams; added FetchResult (repoDir, cleanup, fromCache) and updated fetchSource returns; shim test path adjusted.
Materialization
src/materialize.ts, tests/sync-materialize.test.js
Added unwrapSingleRootDir?: boolean to params; implemented unwrap detection (resolveUnwrapPrefix) and applied prefix trimming to emitted paths and target layout; parameter normalization and error handling refactored.
Sync Pipeline & Hashing
src/sync.ts
Reworked computeRulesHash to accept a normalized DocsCacheResolvedSource and include unwrapSingleRootDir in hash; propagate unwrap flag through fetch/materialize/applyTarget flows; progress messages and manifest reuse guarded by rules hash.
Target Application
src/targets.ts
Added unwrapSingleRootDir param and readdir dep; added resolveSourceDir to detect single non-meta subdirectory and use it for symlink/copy operations; error handling adjusted to use errno helper.
Errors & Manifest
src/errors.ts, src/manifest.ts, src/verify.ts
New errno helper (isErrnoException, getErrnoCode) used across modules; ManifestEntry exported and manifest parsing hardened with parseManifestEntry; streams properly destroyed.
CLI Changes
src/cli/index.ts, src/cli/parse-args.ts, src/cli/types.ts
Introduced CliCommand type and parsed payload; runCommand now accepts parsed command object and raw args; parseArgs returns parsed field; new union type exported.
Tests & Validation
tests/edge-cases-validation.test.js, tests/edge-cases.test.js, tests/integration-real-repos.test.js, tests/sync-materialize.test.js
Depth-related tests updated to expect schema rejection; integration tests now assert via docs.lock; new tests for unwrapSingleRootDir behavior and re-materialization on toggle added.
CI & Hooks
.github/workflows/ci.yml, package.json
Added precheck job and chained build dependency in workflow; pre-commit hook now runs typecheck after lint-staged.
Misc
src/manifest.ts, src/verify.ts (additional edits)
Manifest streaming/type checks added; verify uses errno helper for safer error-code handling.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Sync
    participant Fetch
    participant Cache
    participant Materialize
    participant Targets

    Sync->>Fetch: request source (git/archive) with source config
    Fetch-->>Sync: FetchResult { repoDir, cleanup, fromCache }
    Sync->>Cache: check manifest & rulesSha256 (includes unwrapSingleRootDir)
    alt cache valid (rules match)
        Sync->>Materialize: materialize from cache (unwrapSingleRootDir passed)
    else cache miss or rules differ
        Sync->>Materialize: download/build cache layout (unwrapSingleRootDir passed)
    end
    Materialize->>Targets: applyTargetDir (uses resolved sourceDir after unwrap)
    Targets-->>Materialize: confirm symlink/copy applied
    Materialize->>Cache: write manifest & toc
    Sync-->>Cache: persist docs.lock / stats
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐇 I nudged a root and set it free,
I unwrapped a nest so files could see,
Fetch tells me cached or freshly grown,
Manifests sing in bytes and tone,
Hops of joy — the trees align!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(config): add unwrapSingleRootDir' accurately summarizes the main feature addition in the PR, which introduces a new configuration option for unwrapping single-root directories.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/unwrapSingleRootDir

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@fbosch
Copy link
Copy Markdown
Owner Author

fbosch commented Feb 2, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 2, 2026

✅ Actions performed

Full review triggered.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Feb 2, 2026

Open in StackBlitz

npx https://pkg.pr.new/fbosch/docs-cache@15

commit: 5d68eb4

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements a new unwrapSingleRootDir feature that automatically flattens single-root directory structures during materialization, and removes the depth configuration option as a breaking change.

Changes:

  • Removed depth config option and hardcoded git clone depth to 1
  • Added unwrapSingleRootDir boolean option for recursive directory unwrapping during materialization
  • Enhanced sync output to show cache status ("Restoring from cache" vs "Downloading repo")

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/config.ts Removed depth from source and defaults interfaces, added unwrapSingleRootDir to source interfaces
src/config-schema.ts Updated Zod schemas to remove depth and add unwrapSingleRootDir
src/sync.ts Updated rules hash computation to include all materialization-affecting parameters including unwrapSingleRootDir
src/materialize.ts Implemented recursive unwrap prefix resolution and path normalization during materialization
src/targets.ts Added single-level unwrap logic for target directory application
src/git/fetch-source.ts Hardcoded git depth to 1, added fromCache flag to track whether git archive or cached clone was used
docs.config.schema.json Removed depth from JSON schema, added unwrapSingleRootDir
README.md Updated documentation to remove depth and document unwrapSingleRootDir
tests/sync-materialize.test.js Added tests for unwrap functionality and re-materialization trigger
tests/edge-cases.test.js Updated to verify depth is rejected as unknown field
tests/edge-cases-validation.test.js Removed tests for depth validation, added rejection test
tests/integration-real-repos.test.js Refactored to verify lock file instead of checking returned plan object

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/materialize.ts Outdated
Comment thread README.md
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/git/fetch-source.ts (1)

252-264: ⚠️ Potential issue | 🟠 Major

The shallow fetch strategy for commit refs is problematic but the suggested fix won't work reliably.

When isCommitRef is true, the code fetches only the default branch at --depth 1. This fails if the commit is on a different branch or no longer in the default branch history. However, pushing params.resolvedCommit (the commit SHA) directly to git fetch is not reliably supported across standard Git servers—it requires server-side config (uploadpack.allowReachableSHA1InWant) that defaults to false.

There's also an inconsistency: cloneRepo fetches all branches for commit refs (no --single-branch restriction), but cloneOrUpdateRepo restricts the cache update to the default branch only. The fallback error handling masks the issue by falling back to a full re-clone when checkout fails, defeating the purpose of caching.

Instead of the suggested fix, either fetch all branches when isCommitRef is true, or remove the --depth limit for commit refs to ensure the commit is available.

🧹 Nitpick comments (4)
.github/workflows/ci.yml (1)

11-51: Consider reducing redundant CI work in the matrix job.

The precheck job runs lint, typecheck, test, build, and size on Node 22. The build job then repeats all these steps across the full matrix (up to 9 combinations on PRs). This results in significant redundancy.

Consider having the matrix job focus only on cross-platform/cross-version validation (test + build), while keeping lint, typecheck, audit, and size checks only in precheck:

♻️ Suggested optimization for build job steps
       - name: Install dependencies
         run: pnpm install --frozen-lockfile

-      - name: Audit dependencies
-        run: pnpm audit --audit-level=high
-
-      - name: Lint
-        run: pnpm lint
-
-      - name: Typecheck
-        run: pnpm typecheck
-
       - name: Test
         run: pnpm test

       - name: Build
         run: pnpm build
-
-      - name: Size limit
-        run: pnpm size
src/errors.ts (1)

3-9: Type guard accepts number code, but getErrnoCode ignores it.

isErrnoException allows code to be string | number | undefined, but NodeJS.ErrnoException.code is typed as string | undefined. The numeric check on line 8 is unnecessary since Node.js errno codes are always strings (e.g., "ENOENT").

This doesn't cause bugs since getErrnoCode correctly filters for strings only, but removing the numeric check would align the guard with the actual Node.js type:

♻️ Optional simplification
 export const isErrnoException = (error: unknown): error is ErrnoException =>
 	typeof error === "object" &&
 	error !== null &&
 	"code" in error &&
-	(typeof (error as ErrnoException).code === "string" ||
-		typeof (error as ErrnoException).code === "number" ||
-		(error as ErrnoException).code === undefined);
+	(typeof (error as ErrnoException).code === "string" ||
+		(error as ErrnoException).code === undefined);
src/cli/parse-args.ts (1)

20-27: Consider consolidating the redundant fields.

ParsedArgs now contains both individual fields (command, options, positionals) and a parsed object with equivalent data (parsed.command, parsed.options, parsed.args). This duplication could lead to maintenance overhead and potential inconsistencies.

If the CliCommand interface is the preferred consumer contract, consider removing the redundant individual fields over time:

♻️ Potential future simplification
 export type ParsedArgs = {
-	command: Command | null;
-	options: CliOptions;
-	positionals: string[];
 	rawArgs: string[];
 	help: boolean;
 	parsed: CliCommand;
 };
src/sync.ts (1)

6-12: Consider moving DocsCacheResolvedSource into src/types/.

It’s now shared across modules; relocating it would align with the shared-type guideline and keep imports consistent.

As per coding guidelines: "Place shared types in src/types/ and import them via import type".

@fbosch fbosch merged commit 2f4029a into master Feb 2, 2026
1 of 7 checks passed
@fbosch fbosch deleted the feature/unwrapSingleRootDir branch February 2, 2026 20:04
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.

2 participants