Skip to content

Commit a48b8b8

Browse files
fix: resolve stale targets from workspace package context (#102)
1 parent 2f29aa2 commit a48b8b8

File tree

3 files changed

+231
-2
lines changed

3 files changed

+231
-2
lines changed

.changeset/vast-bags-switch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/intent': patch
3+
---
4+
5+
Fix intent stale so monorepo package paths resolve to the targeted workspace package instead of scanning the whole workspace.

packages/intent/src/cli-support.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { existsSync, readFileSync } from 'node:fs'
2-
import { dirname, join, relative } from 'node:path'
2+
import { dirname, join, relative, resolve } from 'node:path'
33
import { fileURLToPath } from 'node:url'
44
import { fail } from './cli-error.js'
5+
import { resolveProjectContext } from './core/project-context.js'
56
import type { ScanResult, StalenessReport } from './types.js'
67

78
export function printWarnings(warnings: Array<string>): void {
@@ -47,10 +48,28 @@ export async function resolveStaleTargets(
4748
targetDir?: string,
4849
): Promise<{ reports: Array<StalenessReport> }> {
4950
const resolvedRoot = targetDir
50-
? join(process.cwd(), targetDir)
51+
? resolve(process.cwd(), targetDir)
5152
: process.cwd()
53+
const context = resolveProjectContext({
54+
cwd: process.cwd(),
55+
targetPath: targetDir,
56+
})
5257
const { checkStaleness } = await import('./staleness.js')
5358

59+
if (
60+
context.packageRoot &&
61+
(context.targetSkillsDir !== null || resolvedRoot !== context.workspaceRoot)
62+
) {
63+
return {
64+
reports: [
65+
await checkStaleness(
66+
context.packageRoot,
67+
readPackageName(context.packageRoot),
68+
),
69+
],
70+
}
71+
}
72+
5473
if (existsSync(join(resolvedRoot, 'skills'))) {
5574
return {
5675
reports: [

packages/intent/tests/cli.test.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,43 @@ describe('cli commands', () => {
229229
expect(output).toContain('Template variables applied:')
230230
})
231231

232+
it('copies github workflow templates to the workspace root', async () => {
233+
const root = mkdtempSync(join(realTmpdir, 'intent-cli-setup-gha-mono-'))
234+
tempDirs.push(root)
235+
236+
writeJson(join(root, 'package.json'), {
237+
private: true,
238+
workspaces: ['packages/*'],
239+
})
240+
writeJson(join(root, 'packages', 'router', 'package.json'), {
241+
name: '@tanstack/router',
242+
version: '1.0.0',
243+
intent: { version: 1, repo: 'TanStack/router', docs: 'docs/' },
244+
})
245+
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
246+
name: 'routing',
247+
description: 'Routing skill',
248+
})
249+
250+
process.chdir(join(root, 'packages', 'router'))
251+
252+
const exitCode = await main(['setup-github-actions'])
253+
const rootWorkflowsDir = join(root, '.github', 'workflows')
254+
const packageWorkflowsDir = join(
255+
root,
256+
'packages',
257+
'router',
258+
'.github',
259+
'workflows',
260+
)
261+
const output = logSpy.mock.calls.flat().join('\n')
262+
263+
expect(exitCode).toBe(0)
264+
expect(existsSync(rootWorkflowsDir)).toBe(true)
265+
expect(existsSync(packageWorkflowsDir)).toBe(false)
266+
expect(output).toContain('Mode: monorepo')
267+
})
268+
232269
it('lists installed intent packages as json', async () => {
233270
const root = mkdtempSync(join(realTmpdir, 'intent-cli-list-'))
234271
tempDirs.push(root)
@@ -484,6 +521,174 @@ describe('cli commands', () => {
484521

485522
fetchSpy.mockRestore()
486523
})
524+
525+
it('checks only the targeted workspace package for staleness', async () => {
526+
const root = mkdtempSync(join(realTmpdir, 'intent-cli-stale-target-'))
527+
tempDirs.push(root)
528+
529+
writeJson(join(root, 'package.json'), {
530+
private: true,
531+
workspaces: ['packages/*'],
532+
})
533+
writeJson(join(root, 'packages', 'router', 'package.json'), {
534+
name: '@tanstack/router',
535+
})
536+
writeJson(join(root, 'packages', 'query', 'package.json'), {
537+
name: '@tanstack/query',
538+
})
539+
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
540+
name: 'routing',
541+
description: 'Routing skill',
542+
library_version: '1.0.0',
543+
})
544+
writeSkillMd(join(root, 'packages', 'query', 'skills', 'cache'), {
545+
name: 'cache',
546+
description: 'Caching skill',
547+
library_version: '1.0.0',
548+
})
549+
550+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
551+
ok: true,
552+
json: async () => ({ version: '1.0.0' }),
553+
} as Response)
554+
555+
process.chdir(root)
556+
557+
const exitCode = await main(['stale', 'packages/router/skills', '--json'])
558+
const output = logSpy.mock.calls.at(-1)?.[0]
559+
const reports = JSON.parse(String(output)) as Array<{ library: string }>
560+
561+
expect(exitCode).toBe(0)
562+
expect(reports).toHaveLength(1)
563+
expect(reports[0]!.library).toBe('@tanstack/router')
564+
expect(fetchSpy).toHaveBeenCalledTimes(1)
565+
566+
fetchSpy.mockRestore()
567+
})
568+
569+
it('checks only the targeted workspace package when path omits /skills suffix', async () => {
570+
const root = mkdtempSync(
571+
join(realTmpdir, 'intent-cli-stale-target-nosuffix-'),
572+
)
573+
tempDirs.push(root)
574+
575+
writeJson(join(root, 'package.json'), {
576+
private: true,
577+
workspaces: ['packages/*'],
578+
})
579+
writeJson(join(root, 'packages', 'router', 'package.json'), {
580+
name: '@tanstack/router',
581+
})
582+
writeJson(join(root, 'packages', 'query', 'package.json'), {
583+
name: '@tanstack/query',
584+
})
585+
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
586+
name: 'routing',
587+
description: 'Routing skill',
588+
library_version: '1.0.0',
589+
})
590+
writeSkillMd(join(root, 'packages', 'query', 'skills', 'cache'), {
591+
name: 'cache',
592+
description: 'Caching skill',
593+
library_version: '1.0.0',
594+
})
595+
596+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
597+
ok: true,
598+
json: async () => ({ version: '1.0.0' }),
599+
} as Response)
600+
601+
process.chdir(root)
602+
603+
const exitCode = await main(['stale', 'packages/router', '--json'])
604+
const output = logSpy.mock.calls.at(-1)?.[0]
605+
const reports = JSON.parse(String(output)) as Array<{ library: string }>
606+
607+
expect(exitCode).toBe(0)
608+
expect(reports).toHaveLength(1)
609+
expect(reports[0]!.library).toBe('@tanstack/router')
610+
expect(fetchSpy).toHaveBeenCalledTimes(1)
611+
612+
fetchSpy.mockRestore()
613+
})
614+
615+
it('checks the current workspace package for staleness from package cwd', async () => {
616+
const root = mkdtempSync(join(realTmpdir, 'intent-cli-stale-package-cwd-'))
617+
tempDirs.push(root)
618+
619+
writeJson(join(root, 'package.json'), {
620+
private: true,
621+
workspaces: ['packages/*'],
622+
})
623+
writeJson(join(root, 'packages', 'router', 'package.json'), {
624+
name: '@tanstack/router',
625+
})
626+
writeJson(join(root, 'packages', 'query', 'package.json'), {
627+
name: '@tanstack/query',
628+
})
629+
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
630+
name: 'routing',
631+
description: 'Routing skill',
632+
library_version: '1.0.0',
633+
})
634+
writeSkillMd(join(root, 'packages', 'query', 'skills', 'cache'), {
635+
name: 'cache',
636+
description: 'Caching skill',
637+
library_version: '1.0.0',
638+
})
639+
640+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
641+
ok: true,
642+
json: async () => ({ version: '1.0.0' }),
643+
} as Response)
644+
645+
process.chdir(join(root, 'packages', 'router'))
646+
647+
const exitCode = await main(['stale', '--json'])
648+
const output = logSpy.mock.calls.at(-1)?.[0]
649+
const reports = JSON.parse(String(output)) as Array<{ library: string }>
650+
651+
expect(exitCode).toBe(0)
652+
expect(reports).toHaveLength(1)
653+
expect(reports[0]!.library).toBe('@tanstack/router')
654+
expect(fetchSpy).toHaveBeenCalledTimes(1)
655+
656+
fetchSpy.mockRestore()
657+
})
658+
659+
it('handles absolute targetDir path correctly', async () => {
660+
const root = mkdtempSync(join(realTmpdir, 'intent-cli-stale-abs-'))
661+
tempDirs.push(root)
662+
663+
writeJson(join(root, 'package.json'), {
664+
name: '@tanstack/router',
665+
version: '1.0.0',
666+
})
667+
writeSkillMd(join(root, 'skills', 'routing'), {
668+
name: 'routing',
669+
description: 'Routing skill',
670+
library_version: '1.0.0',
671+
})
672+
673+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
674+
ok: true,
675+
json: async () => ({ version: '1.0.0' }),
676+
} as Response)
677+
678+
const elsewhere = mkdtempSync(join(realTmpdir, 'intent-cli-stale-abs-cwd-'))
679+
tempDirs.push(elsewhere)
680+
process.chdir(elsewhere)
681+
682+
const exitCode = await main(['stale', root, '--json'])
683+
const output = logSpy.mock.calls.at(-1)?.[0]
684+
const reports = JSON.parse(String(output)) as Array<{ library: string }>
685+
686+
expect(exitCode).toBe(0)
687+
expect(reports).toHaveLength(1)
688+
expect(reports[0]!.library).toBe('@tanstack/router')
689+
690+
fetchSpy.mockRestore()
691+
})
487692
})
488693

489694
describe('package metadata', () => {

0 commit comments

Comments
 (0)