Skip to content

Refactor workspace pattern discovery and cache workspace info#125

Merged
LadyBluenotes merged 5 commits intomainfrom
update-workspace-patterns
May 3, 2026
Merged

Refactor workspace pattern discovery and cache workspace info#125
LadyBluenotes merged 5 commits intomainfrom
update-workspace-patterns

Conversation

@LadyBluenotes
Copy link
Copy Markdown
Member

@LadyBluenotes LadyBluenotes commented May 3, 2026

Refactors workspace pattern discovery and reduces repeated workspace filesystem work in CLI paths.

  • Replace custom JSONC comment/trailing-comma stripping with jsonc-parser
  • Refactor workspace config parsing into source-driven readers while preserving priority:
    pnpm-workspace.yamlpackage.jsondeno.jsondeno.jsonc
  • Add coverage for package object-form workspaces, direct pnpm workspace reads, fallback behavior, invalid workspace entries, Deno object-form workspace members, and Deno-only workspace packages
  • Resolve Deno workspace members with deno.json / deno.jsonc manifests as workspace packages
  • Cache parsed workspace patterns, workspace root lookups, resolved package dirs, and full workspace info per process
  • Add getWorkspaceInfo(root) and use it in stale target resolution so workspace package lists are resolved once

Notes

The JSONC parser change is primarily for correctness and maintainability. The CLI performance improvement comes from avoiding repeated workspace config reads, root discovery, and package resolution during a command, not from faster JSONC parsing.

Summary by CodeRabbit

  • Chores

    • Updated YAML dependency to version 2.8.3
    • Added jsonc-parser dependency
  • New Features

    • Enhanced workspace detection to support Deno configuration files alongside Node.js packages
    • Improved workspace pattern parsing with better error validation and reporting
  • Tests

    • Expanded test coverage for various workspace configuration formats and fallback scenarios

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 3, 2026

View your CI Pipeline Execution ↗ for commit db75840

Command Status Duration Result
nx run-many --targets=build --exclude=examples/** ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-03 06:07:14 UTC

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 3, 2026

View your CI Pipeline Execution ↗ for commit 47e671a

Command Status Duration Result
nx run-many --targets=build --exclude=examples/** ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-03 05:33:32 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 3, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@tanstack/intent@125

commit: 34a12ea

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Warning

Rate limit exceeded

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

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6012380f-711f-4de1-8e23-1969bc966115

📥 Commits

Reviewing files that changed from the base of the PR and between 34a12ea and db75840.

📒 Files selected for processing (1)
  • .changeset/perky-vans-yell.md
📝 Walkthrough

Walkthrough

This PR updates the YAML dependency to 2.8.3 across root and intent packages, adds JSONC parsing support, and refactors workspace pattern detection to support both npm and Deno workspace configurations with improved caching and a new getWorkspaceInfo API.

Changes

Workspace Pattern Detection & API Refactoring

Layer / File(s) Summary
Dependencies & JSONC Integration
package.json, packages/intent/package.json
YAML is updated from ^2.7.0 to 2.8.3; jsonc-parser (^3.3.1) is added to intent package dependencies.
Workspace Config Parsing
packages/intent/src/workspace-patterns.ts
New typed helpers parseWorkspacePatternList and parseWorkspacePatternField validate and normalize workspace configuration from array or nested object forms.
File Readers & Error Handling
packages/intent/src/workspace-patterns.ts
readYamlFile, readJsonFile, readJsoncFile replace inline parsing; JSONC parse errors are aggregated via jsonc-parser and consolidated into a single SyntaxError.
Workspace Detection & Manifest Support
packages/intent/src/workspace-patterns.ts
hasWorkspaceManifest now recognizes directories with package.json, deno.json, or deno.jsonc; pattern sources updated for pnpm YAML, npm object-form, and Deno object-form workspace configurations.
Caching & New Workspace Info API
packages/intent/src/workspace-patterns.ts
readWorkspacePatterns and new readWorkspacePackageDirs are memoized; exported getWorkspaceInfo(root) returns a typed WorkspaceInfo object with root, patterns, packageDirs, and packageDirsWithSkills; findWorkspaceRoot caches visited directories.
Simplified Workspace Resolution
packages/intent/src/workspace-patterns.ts
findPackagesWithSkills and findWorkspacePackages now delegate to the cached getWorkspaceInfo / readWorkspacePackageDirs results.
CLI Integration
packages/intent/src/cli-support.ts
resolveStaleTargets refactored to use getWorkspaceInfo for workspace discovery, staleness checks, and coverage signal construction; report library path now derived from workspaceInfo.root.
Test Coverage
packages/intent/tests/workspace-patterns.test.ts
New assertions for package.json object-form and pnpm YAML workspace patterns; Deno object-form member tests; fallback behavior when higher-priority configs lack workspace fields; error warning for non-string workspace entries; getWorkspaceInfo assertions for packageDirs and packageDirsWithSkills.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 With JSONC parsing and Deno in sight,
The workspace detection shines ever so bright!
Patterns now memoized, caching in place,
Both npm and Deno embrace—what a race!
The CLI steps forward with getWorkspaceInfo,
A refactored tale of abstraction—terrific! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the main changes: refactoring workspace pattern discovery and adding caching for workspace info, which aligns with the core objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description comprehensively covers all major changes, including JSONC parser replacement, workspace config parsing refactoring, new test coverage, and caching improvements.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch update-workspace-patterns

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
Review rate limit: 0/1 reviews remaining, refill in 26 minutes and 6 seconds.

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

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 3, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing update-workspace-patterns (db75840) with main (3165b16)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (08c22ce) during the generation of this report, so 3165b16 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@LadyBluenotes LadyBluenotes changed the title refactor workspace pattern parsing Refactor workspace pattern discovery and cache workspace info May 3, 2026
Copy link
Copy Markdown
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/intent/src/workspace-patterns.ts`:
- Around line 168-176: The cache currently returns and stores mutable
arrays/objects by reference causing external mutations to corrupt the cache;
update readWorkspacePatterns (and any other functions that return cached
collections) to store immutable copies in workspacePatternsCache.set (e.g.,
store a shallow copy of the array/object) and to return a fresh copy to callers
(e.g., return [...cached] or Object.assign({}, cached)) instead of the raw
cached reference; locate uses of workspacePatternsCache.get/set and
readWorkspacePatternsUncached to apply the same copy-on-read and copy-on-write
protection.

In `@packages/intent/tests/workspace-patterns.test.ts`:
- Around line 192-215: The test that sets consoleErrorSpy in the "it('warns and
falls back when package.json workspaces contains non-string entries')" block
must always restore the mock; wrap the test body (everything after creating
consoleErrorSpy and before assertions) in a try/finally and call
consoleErrorSpy.mockRestore() inside the finally so the mock is restored even if
an assertion (like expect(readWorkspacePatterns(root)) or
expect(consoleErrorSpy).toHaveBeenCalledWith(...)) throws; reference the
existing consoleErrorSpy variable and the test block name to locate where to add
the try/finally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfab35ac-4120-4174-9fa3-587913289a55

📥 Commits

Reviewing files that changed from the base of the PR and between 08c22ce and 34a12ea.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • package.json
  • packages/intent/package.json
  • packages/intent/src/cli-support.ts
  • packages/intent/src/workspace-patterns.ts
  • packages/intent/tests/workspace-patterns.test.ts

Comment on lines 168 to +176
export function readWorkspacePatterns(root: string): Array<string> | null {
const pnpmWs = join(root, 'pnpm-workspace.yaml')
if (existsSync(pnpmWs)) {
try {
const config = parseYaml(readFileSync(pnpmWs, 'utf8')) as Record<
string,
unknown
>
const patterns = parseWorkspacePatterns(config.packages)
if (patterns) {
return patterns
}
} catch (err: unknown) {
const verb = err instanceof SyntaxError ? 'parse' : 'read'
console.error(
`Warning: failed to ${verb} ${pnpmWs}: ${err instanceof Error ? err.message : err}`,
)
}
if (workspacePatternsCache.has(root)) {
return workspacePatternsCache.get(root) ?? null
}

const pkgPath = join(root, 'package.json')
if (existsSync(pkgPath)) {
try {
const pkg = readJsonFile(pkgPath) as Record<string, unknown>
const workspaces = pkg.workspaces as Record<string, unknown> | undefined
const patterns =
parseWorkspacePatterns(workspaces) ??
parseWorkspacePatterns(workspaces?.packages)
if (patterns) {
return patterns
}
} catch (err: unknown) {
const verb = err instanceof SyntaxError ? 'parse' : 'read'
console.error(
`Warning: failed to ${verb} ${pkgPath}: ${err instanceof Error ? err.message : err}`,
)
}
}
const patterns = readWorkspacePatternsUncached(root)
workspacePatternsCache.set(root, patterns)
return patterns
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not expose mutable cached references.

Lines [170], [201], and [217] return cached arrays/objects by reference. A downstream mutation (e.g., push, splice) will mutate cache contents and corrupt subsequent calls.

Suggested hardening
+function cloneWorkspaceInfo(info: WorkspaceInfo): WorkspaceInfo {
+  return {
+    root: info.root,
+    patterns: [...info.patterns],
+    packageDirs: [...info.packageDirs],
+    packageDirsWithSkills: [...info.packageDirsWithSkills],
+  }
+}
+
 export function readWorkspacePatterns(root: string): Array<string> | null {
   if (workspacePatternsCache.has(root)) {
-    return workspacePatternsCache.get(root) ?? null
+    const cached = workspacePatternsCache.get(root)
+    return cached ? [...cached] : null
   }
 
   const patterns = readWorkspacePatternsUncached(root)
-  workspacePatternsCache.set(root, patterns)
-  return patterns
+  workspacePatternsCache.set(root, patterns ? [...patterns] : null)
+  return patterns ? [...patterns] : null
 }
 
 function readWorkspacePackageDirs(root: string): Array<string> | null {
   if (workspacePackageDirsCache.has(root)) {
-    return workspacePackageDirsCache.get(root) ?? null
+    const cached = workspacePackageDirsCache.get(root)
+    return cached ? [...cached] : null
   }
@@
-  workspacePackageDirsCache.set(root, packageDirs)
-  return packageDirs
+  workspacePackageDirsCache.set(root, [...packageDirs])
+  return [...packageDirs]
 }
 
 export function getWorkspaceInfo(root: string): WorkspaceInfo | null {
   if (workspaceInfoCache.has(root)) {
-    return workspaceInfoCache.get(root) ?? null
+    const cached = workspaceInfoCache.get(root)
+    return cached ? cloneWorkspaceInfo(cached) : null
   }
@@
-  const info = {
+  const info: WorkspaceInfo = {
     root,
     patterns,
     packageDirs,
     packageDirsWithSkills,
   }
 
-  workspaceInfoCache.set(root, info)
-  return info
+  workspaceInfoCache.set(root, cloneWorkspaceInfo(info))
+  return cloneWorkspaceInfo(info)
 }

Also applies to: 199-213, 215-240

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/intent/src/workspace-patterns.ts` around lines 168 - 176, The cache
currently returns and stores mutable arrays/objects by reference causing
external mutations to corrupt the cache; update readWorkspacePatterns (and any
other functions that return cached collections) to store immutable copies in
workspacePatternsCache.set (e.g., store a shallow copy of the array/object) and
to return a fresh copy to callers (e.g., return [...cached] or Object.assign({},
cached)) instead of the raw cached reference; locate uses of
workspacePatternsCache.get/set and readWorkspacePatternsUncached to apply the
same copy-on-read and copy-on-write protection.

Comment on lines +192 to +215
it('warns and falls back when package.json workspaces contains non-string entries', () => {
const root = createRoot()
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined)

writeFileSync(
join(root, 'package.json'),
JSON.stringify({ workspaces: [123] }),
)
writeFileSync(
join(root, 'deno.json'),
JSON.stringify({ workspace: ['packages/*'] }),
)

expect(readWorkspacePatterns(root)).toEqual(['packages/*'])
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
`Warning: failed to read ${join(root, 'package.json')}`,
),
)

consoleErrorSpy.mockRestore()
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guarantee mock restoration with try/finally.

Line [214] restoration won’t run if an earlier assertion fails. Wrap the test body so console.error is always restored.

Suggested fix
   it('warns and falls back when package.json workspaces contains non-string entries', () => {
     const root = createRoot()
     const consoleErrorSpy = vi
       .spyOn(console, 'error')
       .mockImplementation(() => undefined)
-
-    writeFileSync(
-      join(root, 'package.json'),
-      JSON.stringify({ workspaces: [123] }),
-    )
-    writeFileSync(
-      join(root, 'deno.json'),
-      JSON.stringify({ workspace: ['packages/*'] }),
-    )
-
-    expect(readWorkspacePatterns(root)).toEqual(['packages/*'])
-    expect(consoleErrorSpy).toHaveBeenCalledWith(
-      expect.stringContaining(
-        `Warning: failed to read ${join(root, 'package.json')}`,
-      ),
-    )
-
-    consoleErrorSpy.mockRestore()
+    try {
+      writeFileSync(
+        join(root, 'package.json'),
+        JSON.stringify({ workspaces: [123] }),
+      )
+      writeFileSync(
+        join(root, 'deno.json'),
+        JSON.stringify({ workspace: ['packages/*'] }),
+      )
+
+      expect(readWorkspacePatterns(root)).toEqual(['packages/*'])
+      expect(consoleErrorSpy).toHaveBeenCalledWith(
+        expect.stringContaining(
+          `Warning: failed to read ${join(root, 'package.json')}`,
+        ),
+      )
+    } finally {
+      consoleErrorSpy.mockRestore()
+    }
   })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('warns and falls back when package.json workspaces contains non-string entries', () => {
const root = createRoot()
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined)
writeFileSync(
join(root, 'package.json'),
JSON.stringify({ workspaces: [123] }),
)
writeFileSync(
join(root, 'deno.json'),
JSON.stringify({ workspace: ['packages/*'] }),
)
expect(readWorkspacePatterns(root)).toEqual(['packages/*'])
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
`Warning: failed to read ${join(root, 'package.json')}`,
),
)
consoleErrorSpy.mockRestore()
})
it('warns and falls back when package.json workspaces contains non-string entries', () => {
const root = createRoot()
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined)
try {
writeFileSync(
join(root, 'package.json'),
JSON.stringify({ workspaces: [123] }),
)
writeFileSync(
join(root, 'deno.json'),
JSON.stringify({ workspace: ['packages/*'] }),
)
expect(readWorkspacePatterns(root)).toEqual(['packages/*'])
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
`Warning: failed to read ${join(root, 'package.json')}`,
),
)
} finally {
consoleErrorSpy.mockRestore()
}
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/intent/tests/workspace-patterns.test.ts` around lines 192 - 215, The
test that sets consoleErrorSpy in the "it('warns and falls back when
package.json workspaces contains non-string entries')" block must always restore
the mock; wrap the test body (everything after creating consoleErrorSpy and
before assertions) in a try/finally and call consoleErrorSpy.mockRestore()
inside the finally so the mock is restored even if an assertion (like
expect(readWorkspacePatterns(root)) or
expect(consoleErrorSpy).toHaveBeenCalledWith(...)) throws; reference the
existing consoleErrorSpy variable and the test block name to locate where to add
the try/finally.

@LadyBluenotes LadyBluenotes merged commit 3165b16 into main May 3, 2026
5 checks passed
@LadyBluenotes LadyBluenotes deleted the update-workspace-patterns branch May 3, 2026 06:07
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