Skip to content
Open
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/fix-local-version-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/intent': patch
---

Read local package.json version before falling back to npm registry in `intent stale`. This fixes version drift detection for packages not published to public registry.npmjs.org (e.g. GitHub Packages, Artifactory, private registries).
24 changes: 21 additions & 3 deletions packages/intent/src/staleness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,20 @@ function classifyVersionDrift(
}

// ---------------------------------------------------------------------------
// npm version fetching
// Version resolution
// ---------------------------------------------------------------------------

function readLocalVersion(packageDir: string): string | null {
try {
const pkgJson = JSON.parse(
readFileSync(join(packageDir, 'package.json'), 'utf8'),
) as Record<string, unknown>
return typeof pkgJson.version === 'string' ? pkgJson.version : null
} catch {
return null
}
}

async function fetchNpmVersion(packageName: string): Promise<string | null> {
try {
const res = await fetch(
Expand All @@ -50,6 +61,13 @@ async function fetchNpmVersion(packageName: string): Promise<string | null> {
}
}

async function fetchCurrentVersion(
packageDir: string,
packageName: string,
): Promise<string | null> {
return readLocalVersion(packageDir) ?? (await fetchNpmVersion(packageName))
}

// ---------------------------------------------------------------------------
// Sync state
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -141,8 +159,8 @@ export async function checkStaleness(
const skillVersion =
skillMetas.find((s) => s.libraryVersion)?.libraryVersion ?? null

// Fetch current npm version
const currentVersion = await fetchNpmVersion(library)
// Resolve current version: prefer local package.json, fall back to npm registry
const currentVersion = await fetchCurrentVersion(packageDir, library)

// Classify drift
const versionDrift =
Expand Down
43 changes: 43 additions & 0 deletions packages/intent/tests/staleness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,47 @@ describe('checkStaleness', () => {
expect(report.skills).toHaveLength(1)
expect(requireFirstSkill(report).needsReview).toBe(false)
})

it('reads version from local package.json when npm fetch fails', async () => {
writeFileSync(
join(tmpDir, 'package.json'),
JSON.stringify({ name: '@private/lib', version: '2.5.0' }),
)

writeSkill(tmpDir, 'core', {
name: 'core',
description: 'Core',
library_version: '2.0.0',
})

mockFetchNotOk()

const report = await checkStaleness(tmpDir, '@private/lib')
expect(report.currentVersion).toBe('2.5.0')
expect(report.versionDrift).toBe('minor')
const skill = requireFirstSkill(report)
expect(skill.needsReview).toBe(true)
expect(skill.reasons[0]).toContain('version drift')
})

it('prefers local package.json over npm registry', async () => {
writeFileSync(
join(tmpDir, 'package.json'),
JSON.stringify({ name: '@example/lib', version: '3.0.0' }),
)

writeSkill(tmpDir, 'core', {
name: 'core',
description: 'Core',
library_version: '2.0.0',
})

// npm returns an older published version
mockFetchVersion('2.5.0')

const report = await checkStaleness(tmpDir, '@example/lib')
// Local package.json should take precedence
expect(report.currentVersion).toBe('3.0.0')
expect(report.versionDrift).toBe('major')
})
})
Loading