fix: harden monorepo workspace detection and workflow generation#83
fix: harden monorepo workspace detection and workflow generation#83WolfieLeader wants to merge 12 commits intoTanStack:mainfrom
Conversation
🦋 Changeset detectedLatest commit: 6f1a802 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughCentralizes monorepo/workspace detection and pattern resolution (pnpm, package.json workspaces, Deno), exposes workspace helpers, and updates template variable derivation so setup-github-actions, validate, stale, and workflow templates behave correctly when run from repo roots or package directories. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CLI as "setup-github-actions / CLI"
participant Finder as "findWorkspaceRoot"
participant Parser as "readWorkspacePatterns"
participant Resolver as "resolveWorkspacePackages"
participant Vars as "detectVars / TemplateVars"
participant Template as "Template Rendering / Workflows"
User->>CLI: run setup-github-actions (cwd)
CLI->>Finder: findWorkspaceRoot(cwd)
Finder-->>CLI: workspaceRoot | may be root or cwd
CLI->>Parser: readWorkspacePatterns(workspaceRoot)
Parser-->>CLI: workspace patterns (pnpm/npm/deno)
CLI->>Resolver: resolveWorkspacePackages(workspaceRoot, patterns)
Resolver-->>CLI: package dirs (resolved globs)
CLI->>Vars: detectVars(cwd, MonorepoContext(packages, patterns))
Vars-->>CLI: SRC_PATH, WATCH_PATHS, WORKSPACE_SKILL_GLOBS
CLI->>Template: render templates with vars
Template-->>User: monorepo-aware workflows written to target location
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
commit: |
|
Let's go all green🫡 |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
.changeset/fix-monorepo-workspace-detection.md (1)
1-5: Consider documenting the API signature changes in the changeset.The changeset describes the monorepo detection fixes well, but doesn't mention that
scanForIntentsandscanLibrarychanged from async to sync. If these are part of the public API, callers usingawaitwill still work (awaiting a non-Promise returns the value), but this behavioral change might be worth documenting for clarity.If these functions are internal-only, the current changeset is sufficient.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.changeset/fix-monorepo-workspace-detection.md around lines 1 - 5, Update the changeset to document that the functions scanForIntents and scanLibrary changed from async to synchronous signatures; explicitly state the new return types and note potential caller impact (e.g., awaiting a non-Promise is benign but callers should remove unnecessary async/await), and reference the exported symbols scanForIntents and scanLibrary so consumers can find and adapt their usage.packages/intent/src/utils.ts (1)
186-191: Consider edge cases in URL normalization.The function handles common cases well, but consider these edge cases:
- URLs ending with
.git.gitwould become.gitafter one replacement- SSH URLs like
git@github.com:org/repo.gitare not normalized toorg/repo- Non-GitHub URLs (GitLab, Bitbucket) retain their full path after
https://removalIf these edge cases are acceptable for the current use case (deriving repo identifiers), the implementation is fine. Otherwise, consider more robust URL parsing.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/intent/src/utils.ts` around lines 186 - 191, normalizeRepoUrl currently only strips a single leading git+ prefix, a single trailing ".git", and only matches "https://github.com/"; to handle the edge cases update normalizeRepoUrl to (1) remove repeated ".git" suffixes (e.g. trim all trailing ".git" occurrences), (2) convert SSH style URLs like "git@github.com:org/repo.git" into "org/repo" by detecting the "git@" pattern and replacing the "user@host:" prefix with host path, and (3) optionally generalize host stripping to remove "https?://<host>/" for non-GitHub hosts (or keep existing behavior if only GitHub is desired); update the function normalizeRepoUrl accordingly and add unit tests for inputs like "git+https://github.com/org/repo.git.git", "git@github.com:org/repo.git", and a non-GitHub https URL to validate the chosen behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.changeset/fix-monorepo-workspace-detection.md:
- Around line 1-5: Update the changeset to document that the functions
scanForIntents and scanLibrary changed from async to synchronous signatures;
explicitly state the new return types and note potential caller impact (e.g.,
awaiting a non-Promise is benign but callers should remove unnecessary
async/await), and reference the exported symbols scanForIntents and scanLibrary
so consumers can find and adapt their usage.
In `@packages/intent/src/utils.ts`:
- Around line 186-191: normalizeRepoUrl currently only strips a single leading
git+ prefix, a single trailing ".git", and only matches "https://github.com/";
to handle the edge cases update normalizeRepoUrl to (1) remove repeated ".git"
suffixes (e.g. trim all trailing ".git" occurrences), (2) convert SSH style URLs
like "git@github.com:org/repo.git" into "org/repo" by detecting the "git@"
pattern and replacing the "user@host:" prefix with host path, and (3) optionally
generalize host stripping to remove "https?://<host>/" for non-GitHub hosts (or
keep existing behavior if only GitHub is desired); update the function
normalizeRepoUrl accordingly and add unit tests for inputs like
"git+https://github.com/org/repo.git.git", "git@github.com:org/repo.git", and a
non-GitHub https URL to validate the chosen behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 639760bb-837c-4af4-9248-1033735b3f73
📒 Files selected for processing (14)
.changeset/fix-monorepo-workspace-detection.mdpackages/intent/meta/templates/workflows/validate-skills.ymlpackages/intent/src/cli.tspackages/intent/src/intent-library.tspackages/intent/src/library-scanner.tspackages/intent/src/scanner.tspackages/intent/src/setup.tspackages/intent/src/staleness.tspackages/intent/src/utils.tspackages/intent/tests/cli.test.tspackages/intent/tests/library-scanner.test.tspackages/intent/tests/scanner.test.tspackages/intent/tests/setup.test.tspackages/intent/tests/staleness.test.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/intent/tests/setup.test.ts (1)
255-282: Harden test teardown to avoid leaked temp dirs/mocks on assertion failures.Several tests clean up only at the end of the test body. If an assertion throws early, teardown is skipped, which can pollute subsequent tests:
- Lines 255–282:
rmSync(monoRoot, ...)at end, unguarded- Lines 343–384:
rmSync(monoRoot, ...)at end, unguarded- Lines 451–524:
logSpy.mockRestore()at end, unguarded- Lines 526–570:
logSpy.mockRestore()at end, unguardedWrap each test body in a
try/finallyblock to ensure cleanup always executes:♻️ Suggested pattern (try/finally guarded teardown)
it('treats workspace roots without package skills as monorepos', () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}) + try { - writeFileSync( - join(metaDir, 'templates', 'workflows', 'validate-skills.yml'), - 'for dir in {{WORKSPACE_SKILL_GLOBS}}; do\n echo "$dir"\ndone\n', - ) - // ... test setup/assertions ... - logSpy.mockRestore() + writeFileSync( + join(metaDir, 'templates', 'workflows', 'validate-skills.yml'), + 'for dir in {{WORKSPACE_SKILL_GLOBS}}; do\n echo "$dir"\ndone\n', + ) + // ... test setup/assertions ... + } finally { + logSpy.mockRestore() + } })it('skips !skills/_artifacts in pnpm monorepo packages (pnpm-workspace.yaml)', () => { const monoRoot = mkdtempSync(join(tmpdir(), 'pnpm-mono-')) + try { - const pkgDir = join(monoRoot, 'packages', 'my-lib') - mkdirSync(pkgDir, { recursive: true }) - // ... test setup/assertions ... - rmSync(monoRoot, { recursive: true, force: true }) + const pkgDir = join(monoRoot, 'packages', 'my-lib') + mkdirSync(pkgDir, { recursive: true }) + // ... test setup/assertions ... + } finally { + rmSync(monoRoot, { recursive: true, force: true }) + } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/intent/tests/setup.test.ts` around lines 255 - 282, The tests leak temp dirs and spies when assertions throw because teardown calls (rmSync on monoRoot and logSpy.mockRestore()) are at the end of the test bodies; wrap the relevant test bodies that create monoRoot (where mkdtempSync is used) and those that set logSpy in a try/finally and move rmSync(monoRoot, { recursive: true, force: true }) and logSpy.mockRestore() into the finally block so cleanup always runs; specifically update the test cases that reference monoRoot/mkdtempSync and the tests that reference logSpy to use try { ... assertions ... } finally { rmSync(...)/logSpy.mockRestore() } to guarantee teardown even on failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/intent/tests/setup.test.ts`:
- Around line 255-282: The tests leak temp dirs and spies when assertions throw
because teardown calls (rmSync on monoRoot and logSpy.mockRestore()) are at the
end of the test bodies; wrap the relevant test bodies that create monoRoot
(where mkdtempSync is used) and those that set logSpy in a try/finally and move
rmSync(monoRoot, { recursive: true, force: true }) and logSpy.mockRestore() into
the finally block so cleanup always runs; specifically update the test cases
that reference monoRoot/mkdtempSync and the tests that reference logSpy to use
try { ... assertions ... } finally { rmSync(...)/logSpy.mockRestore() } to
guarantee teardown even on failures.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 932e2c26-2f99-466c-8782-fae8c2e371ff
📒 Files selected for processing (1)
packages/intent/tests/setup.test.ts
Resolve conflicts with PR TanStack#85 (cac CLI migration): - Accept upstream's cac-based CLI structure - Keep our enhanced workspace detection (pnpm, deno, nested patterns) - Port pnpm-aware monorepo detection to commands/validate.ts - Integrate upstream's isGenericWorkspaceName/deriveWorkspacePackageName
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
packages/intent/tests/setup.test.ts (2)
229-283: Add a workspace-rootrunEditPackageJsonmonorepo testThese cases validate package-dir behavior, but not running
runEditPackageJsondirectly at the workspace root. A root-level case would guard against false!skills/_artifactsinsertion for monorepos.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/intent/tests/setup.test.ts` around lines 229 - 283, Add a new test that invokes runEditPackageJson at the monorepo workspace root (not a package subdir) to ensure the root package.json doesn't get a '!skills/_artifacts' exclusion; specifically simulate a monorepo root (create package.json with workspaces or pnpm-workspace.yaml and a packages/* subdir), call runEditPackageJson(monoRoot), assert result.added contains 'files: "skills"' and does not contain any string matching '!skills/_artifacts', and assert the written package.json (JSON.parse(readFileSync(...))) has no '!skills/_artifacts' in its files array; reuse the existing patterns from the tests that call runEditPackageJson(pkgDir) so the new test mirrors both npm and pnpm workspace scenarios.
54-116: Add regression coverage for negated workspace patternsCurrent workspace tests cover positive globs but not exclusion globs (
!pattern). A test like['packages/*', '!packages/private/*']would catch incorrect package inclusion early.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/intent/tests/setup.test.ts` around lines 54 - 116, Add a regression test to validate that readWorkspacePatterns/resolveWorkspacePackages/findPackagesWithSkills handle negated workspace globs (e.g., ['packages/*','!packages/private/*']) by creating packages/packages/a and packages/private/b, ensure the negated package (packages/private/b) is excluded from resolveWorkspacePackages(root, patterns) and findPackagesWithSkills(root), and assert only packages/packages/a is returned; reference the existing tests that call readWorkspacePatterns, resolveWorkspacePackages, and findPackagesWithSkills to mirror setup (writePkg/writeFileSync/mkdirSync) and verify the exclusion behavior.packages/intent/src/setup.ts (1)
320-320: Avoid hardcoded monorepo docs glob in template varsLine 320 hardcodes
packages/*/docs/**for monorepos. This diverges from workspace-pattern-driven behavior (e.g., nestedexamples/react/*) and can leak incorrect values into templates that still use{{DOCS_PATH}}.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/intent/src/setup.ts` at line 320, The hardcoded monorepo docs glob assigned to docsPath when isMonorepo is true should be replaced by deriving docs globs from the workspace/package patterns instead of "packages/*/docs/**"; update the logic that sets docsPath (the constant docsPath) to map the existing workspace/package patterns (the same source used for resolving packages) into corresponding docs globs (e.g., replace trailing "/*" or package placeholder with "/docs/**" per pattern) so nested patterns like "examples/react/*" produce "examples/react/docs/**", and ensure the resulting string is used for the template variable {{DOCS_PATH}}.
🤖 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/commands/validate.ts`:
- Around line 221-223: The monorepo detection misses cases where packageRoot
itself is the workspace root because findWorkspaceRoot is called on
join(packageRoot, '..'); update the check in validate.ts so isMonorepo calls
findWorkspaceRoot with packageRoot (or checks both packageRoot and its parent)
before computing warnings; reference the findWorkspaceRoot import, the
packageRoot variable, the isMonorepo binding, and the subsequent
collectPackagingWarnings(packageRoot, isMonorepo) call when making the change.
In `@packages/intent/src/setup.ts`:
- Around line 121-123: normalizeWorkspacePattern currently leaves leading '!'
untouched but the workspace resolution logic treats negated patterns as literal
paths so excluded workspaces are never removed; update the resolution flow to
detect negated patterns by checking for a leading '!' (you can keep
normalizeWorkspacePattern as-is or adjust it to return both the normalized
pattern and a negation flag), strip the '!' before performing glob/match
operations, collect matches from negated patterns into an excluded set and
subtract that set from the included matches, and ensure the code path that
iterates/filters packages uses this included-minus-excluded result (focus
changes around normalizeWorkspacePattern and the workspace pattern resolution
block that computes included/excluded package lists).
- Line 444: The monorepo detection currently calls findWorkspaceRoot(join(root,
'..')) which misses when root is itself a workspace root; update the check in
the isMonorepo computation to probe the current directory (root) as well as its
parent (e.g., call findWorkspaceRoot(root) and/or fallback to
findWorkspaceRoot(join(root, '..'))), so that isMonorepo uses the result of
findWorkspaceRoot(root) !== null (or an OR of both) to correctly detect
workspace roots when runEditPackageJson runs at the workspace root.
---
Nitpick comments:
In `@packages/intent/src/setup.ts`:
- Line 320: The hardcoded monorepo docs glob assigned to docsPath when
isMonorepo is true should be replaced by deriving docs globs from the
workspace/package patterns instead of "packages/*/docs/**"; update the logic
that sets docsPath (the constant docsPath) to map the existing workspace/package
patterns (the same source used for resolving packages) into corresponding docs
globs (e.g., replace trailing "/*" or package placeholder with "/docs/**" per
pattern) so nested patterns like "examples/react/*" produce
"examples/react/docs/**", and ensure the resulting string is used for the
template variable {{DOCS_PATH}}.
In `@packages/intent/tests/setup.test.ts`:
- Around line 229-283: Add a new test that invokes runEditPackageJson at the
monorepo workspace root (not a package subdir) to ensure the root package.json
doesn't get a '!skills/_artifacts' exclusion; specifically simulate a monorepo
root (create package.json with workspaces or pnpm-workspace.yaml and a
packages/* subdir), call runEditPackageJson(monoRoot), assert result.added
contains 'files: "skills"' and does not contain any string matching
'!skills/_artifacts', and assert the written package.json
(JSON.parse(readFileSync(...))) has no '!skills/_artifacts' in its files array;
reuse the existing patterns from the tests that call runEditPackageJson(pkgDir)
so the new test mirrors both npm and pnpm workspace scenarios.
- Around line 54-116: Add a regression test to validate that
readWorkspacePatterns/resolveWorkspacePackages/findPackagesWithSkills handle
negated workspace globs (e.g., ['packages/*','!packages/private/*']) by creating
packages/packages/a and packages/private/b, ensure the negated package
(packages/private/b) is excluded from resolveWorkspacePackages(root, patterns)
and findPackagesWithSkills(root), and assert only packages/packages/a is
returned; reference the existing tests that call readWorkspacePatterns,
resolveWorkspacePackages, and findPackagesWithSkills to mirror setup
(writePkg/writeFileSync/mkdirSync) and verify the exclusion behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b33fa37d-ae4a-4f9c-996f-bcdadd2f69dc
📒 Files selected for processing (4)
packages/intent/src/commands/validate.tspackages/intent/src/setup.tspackages/intent/tests/cli.test.tspackages/intent/tests/setup.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/intent/tests/cli.test.ts
Negated patterns like !packages/internal (supported by pnpm, npm, yarn) were silently ignored during resolution and produced invalid paths with ! prefix in generated workflow YAML. - resolveWorkspacePackages now splits include/exclude, resolves both, and subtracts excluded dirs from the result - buildFallbackWorkspacePaths skips negated patterns - Added test for negated pattern exclusion
🎯 Changes
Fixes #71. Also fixes #86, #87, #88.
This PR fixes the monorepo regressions reported against
@tanstack/intentand tightens workspace handling across setup, validation, and generated CI workflows.setup-github-actionsso monorepo roots are detected from actual workspace config, not just current skill-bearing packages.validate packages/<pkg>/skillsso packaging warnings are evaluated against the target package instead of the workspace root.!skills/_artifactswarnings for pnpm workspace packages.packages/*.Additional fixes (post-PR #85)
This PR also addresses issues introduced or not covered by #85:
validateandedit-package-jsonnow detect pnpm monorepos viafindWorkspaceRootinstead of only checkingpackage.jsonworkspacesreadWorkspacePatternssupportsdeno.json/deno.jsoncworkspace configs with JSONC comment strippingresolveWorkspacePackagesuses recursive glob resolution for nested patterns likeapps/*/packages/*✅ Checklist
pnpm run test:pr.🚀 Release Impact
Summary by CodeRabbit
Bug Fixes
New Features
Tests