Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vast-bags-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/intent': patch
---

Fix intent stale so monorepo package paths resolve to the targeted workspace package instead of scanning the whole workspace.
23 changes: 21 additions & 2 deletions packages/intent/src/cli-support.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join, relative } from 'node:path'
import { dirname, join, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { fail } from './cli-error.js'
import { resolveProjectContext } from './core/project-context.js'
import type { ScanResult, StalenessReport } from './types.js'

export function printWarnings(warnings: Array<string>): void {
Expand Down Expand Up @@ -47,10 +48,28 @@ export async function resolveStaleTargets(
targetDir?: string,
): Promise<{ reports: Array<StalenessReport> }> {
const resolvedRoot = targetDir
? join(process.cwd(), targetDir)
? resolve(process.cwd(), targetDir)
: process.cwd()
const context = resolveProjectContext({
cwd: process.cwd(),
targetPath: targetDir,
})
const { checkStaleness } = await import('./staleness.js')

if (
context.packageRoot &&
(context.targetSkillsDir !== null || resolvedRoot !== context.workspaceRoot)
) {
return {
reports: [
await checkStaleness(
context.packageRoot,
readPackageName(context.packageRoot),
),
],
}
}

if (existsSync(join(resolvedRoot, 'skills'))) {
return {
reports: [
Expand Down
205 changes: 205 additions & 0 deletions packages/intent/tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,43 @@ describe('cli commands', () => {
expect(output).toContain('Template variables applied:')
})

it('copies github workflow templates to the workspace root', async () => {
const root = mkdtempSync(join(realTmpdir, 'intent-cli-setup-gha-mono-'))
tempDirs.push(root)

writeJson(join(root, 'package.json'), {
private: true,
workspaces: ['packages/*'],
})
writeJson(join(root, 'packages', 'router', 'package.json'), {
name: '@tanstack/router',
version: '1.0.0',
intent: { version: 1, repo: 'TanStack/router', docs: 'docs/' },
})
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
name: 'routing',
description: 'Routing skill',
})

process.chdir(join(root, 'packages', 'router'))

const exitCode = await main(['setup-github-actions'])
const rootWorkflowsDir = join(root, '.github', 'workflows')
const packageWorkflowsDir = join(
root,
'packages',
'router',
'.github',
'workflows',
)
const output = logSpy.mock.calls.flat().join('\n')

expect(exitCode).toBe(0)
expect(existsSync(rootWorkflowsDir)).toBe(true)
expect(existsSync(packageWorkflowsDir)).toBe(false)
expect(output).toContain('Mode: monorepo')
})

it('lists installed intent packages as json', async () => {
const root = mkdtempSync(join(realTmpdir, 'intent-cli-list-'))
tempDirs.push(root)
Expand Down Expand Up @@ -484,6 +521,174 @@ describe('cli commands', () => {

fetchSpy.mockRestore()
})

it('checks only the targeted workspace package for staleness', async () => {
const root = mkdtempSync(join(realTmpdir, 'intent-cli-stale-target-'))
tempDirs.push(root)

writeJson(join(root, 'package.json'), {
private: true,
workspaces: ['packages/*'],
})
writeJson(join(root, 'packages', 'router', 'package.json'), {
name: '@tanstack/router',
})
writeJson(join(root, 'packages', 'query', 'package.json'), {
name: '@tanstack/query',
})
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
name: 'routing',
description: 'Routing skill',
library_version: '1.0.0',
})
writeSkillMd(join(root, 'packages', 'query', 'skills', 'cache'), {
name: 'cache',
description: 'Caching skill',
library_version: '1.0.0',
})

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
} as Response)

process.chdir(root)

const exitCode = await main(['stale', 'packages/router/skills', '--json'])
const output = logSpy.mock.calls.at(-1)?.[0]
const reports = JSON.parse(String(output)) as Array<{ library: string }>

expect(exitCode).toBe(0)
expect(reports).toHaveLength(1)
expect(reports[0]!.library).toBe('@tanstack/router')
expect(fetchSpy).toHaveBeenCalledTimes(1)

fetchSpy.mockRestore()
})

it('checks only the targeted workspace package when path omits /skills suffix', async () => {
const root = mkdtempSync(
join(realTmpdir, 'intent-cli-stale-target-nosuffix-'),
)
tempDirs.push(root)

writeJson(join(root, 'package.json'), {
private: true,
workspaces: ['packages/*'],
})
writeJson(join(root, 'packages', 'router', 'package.json'), {
name: '@tanstack/router',
})
writeJson(join(root, 'packages', 'query', 'package.json'), {
name: '@tanstack/query',
})
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
name: 'routing',
description: 'Routing skill',
library_version: '1.0.0',
})
writeSkillMd(join(root, 'packages', 'query', 'skills', 'cache'), {
name: 'cache',
description: 'Caching skill',
library_version: '1.0.0',
})

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
} as Response)

process.chdir(root)

const exitCode = await main(['stale', 'packages/router', '--json'])
const output = logSpy.mock.calls.at(-1)?.[0]
const reports = JSON.parse(String(output)) as Array<{ library: string }>

expect(exitCode).toBe(0)
expect(reports).toHaveLength(1)
expect(reports[0]!.library).toBe('@tanstack/router')
expect(fetchSpy).toHaveBeenCalledTimes(1)

fetchSpy.mockRestore()
})

it('checks the current workspace package for staleness from package cwd', async () => {
const root = mkdtempSync(join(realTmpdir, 'intent-cli-stale-package-cwd-'))
tempDirs.push(root)

writeJson(join(root, 'package.json'), {
private: true,
workspaces: ['packages/*'],
})
writeJson(join(root, 'packages', 'router', 'package.json'), {
name: '@tanstack/router',
})
writeJson(join(root, 'packages', 'query', 'package.json'), {
name: '@tanstack/query',
})
writeSkillMd(join(root, 'packages', 'router', 'skills', 'routing'), {
name: 'routing',
description: 'Routing skill',
library_version: '1.0.0',
})
writeSkillMd(join(root, 'packages', 'query', 'skills', 'cache'), {
name: 'cache',
description: 'Caching skill',
library_version: '1.0.0',
})

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
} as Response)

process.chdir(join(root, 'packages', 'router'))

const exitCode = await main(['stale', '--json'])
const output = logSpy.mock.calls.at(-1)?.[0]
const reports = JSON.parse(String(output)) as Array<{ library: string }>

expect(exitCode).toBe(0)
expect(reports).toHaveLength(1)
expect(reports[0]!.library).toBe('@tanstack/router')
expect(fetchSpy).toHaveBeenCalledTimes(1)

fetchSpy.mockRestore()
})

it('handles absolute targetDir path correctly', async () => {
const root = mkdtempSync(join(realTmpdir, 'intent-cli-stale-abs-'))
tempDirs.push(root)

writeJson(join(root, 'package.json'), {
name: '@tanstack/router',
version: '1.0.0',
})
writeSkillMd(join(root, 'skills', 'routing'), {
name: 'routing',
description: 'Routing skill',
library_version: '1.0.0',
})

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
} as Response)

const elsewhere = mkdtempSync(join(realTmpdir, 'intent-cli-stale-abs-cwd-'))
tempDirs.push(elsewhere)
process.chdir(elsewhere)

const exitCode = await main(['stale', root, '--json'])
const output = logSpy.mock.calls.at(-1)?.[0]
const reports = JSON.parse(String(output)) as Array<{ library: string }>

expect(exitCode).toBe(0)
expect(reports).toHaveLength(1)
expect(reports[0]!.library).toBe('@tanstack/router')

fetchSpy.mockRestore()
})
})

describe('package metadata', () => {
Expand Down
Loading