diff --git a/src/cache/lock.ts b/src/cache/lock.ts index c42c69b..e734dee 100644 --- a/src/cache/lock.ts +++ b/src/cache/lock.ts @@ -1,5 +1,6 @@ import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; +import { isRecord } from "#core/is-record"; export interface DocsCacheLockSource { repo: string; @@ -19,9 +20,6 @@ export interface DocsCacheLock { export const DEFAULT_LOCK_FILENAME = "docs-lock.json"; -const isRecord = (value: unknown): value is Record => - typeof value === "object" && value !== null && !Array.isArray(value); - const assertString = (value: unknown, label: string): string => { if (typeof value !== "string" || value.length === 0) { throw new Error(`${label} must be a non-empty string.`); diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 064d02d..27bf2a3 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -1,6 +1,7 @@ import { createHash } from "node:crypto"; import { access, mkdir, readFile } from "node:fs/promises"; import path from "node:path"; +import { fileURLToPath } from "node:url"; import pc from "picocolors"; import type { DocsCacheLock, DocsCacheLockSource } from "#cache/lock"; import { readLock, resolveLockPath, writeLock } from "#cache/lock"; @@ -18,6 +19,7 @@ import { type DocsCacheResolvedSource, loadConfig, } from "#config"; +import { isRecord } from "#core/is-record"; import { resolveCacheDir, resolveTargetDir } from "#core/paths"; import { fetchSource } from "#git/fetch-source"; import { resolveRemoteCommit } from "#git/resolve-remote"; @@ -187,35 +189,56 @@ export const getSyncPlan = async ( }; }; -const loadToolVersion = async () => { - const cwdPath = path.resolve(process.cwd(), "package.json"); +const TOOL_PACKAGE_NAME = "docs-cache"; + +const readToolVersionFromPackageFile = async (packagePath: string) => { try { - const raw = await readFile(cwdPath, "utf8"); - const pkg = JSON.parse(raw.toString()); - return typeof pkg.version === "string" ? pkg.version : "0.0.0"; + const raw = await readFile(packagePath, "utf8"); + const parsed: unknown = JSON.parse(raw); + if (!isRecord(parsed)) { + return null; + } + const pkgName = parsed.name; + const pkgVersion = parsed.version; + if (pkgName !== TOOL_PACKAGE_NAME) { + return null; + } + if (typeof pkgVersion !== "string" || pkgVersion.length === 0) { + return null; + } + return pkgVersion; } catch { - // fallback to bundle-relative location + return null; } - try { - const raw = await readFile( - new URL("../package.json", import.meta.url), - "utf8", - ); - const pkg = JSON.parse(raw.toString()); - return typeof pkg.version === "string" ? pkg.version : "0.0.0"; - } catch { - // fallback to dist/chunks relative location +}; + +const findToolVersionFrom = async (startDir: string) => { + let currentDir = startDir; + while (true) { + const packagePath = path.join(currentDir, "package.json"); + const version = await readToolVersionFromPackageFile(packagePath); + if (version) { + return version; + } + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir) { + return null; + } + currentDir = parentDir; } - try { - const raw = await readFile( - new URL("../../package.json", import.meta.url), - "utf8", - ); - const pkg = JSON.parse(raw.toString()); - return typeof pkg.version === "string" ? pkg.version : "0.0.0"; - } catch { - return "0.0.0"; +}; + +const loadToolVersion = async () => { + const moduleDir = path.dirname(fileURLToPath(import.meta.url)); + const moduleVersion = await findToolVersionFrom(moduleDir); + if (moduleVersion) { + return moduleVersion; + } + const cwdVersion = await findToolVersionFrom(process.cwd()); + if (cwdVersion) { + return cwdVersion; } + return "0.0.0"; }; const buildLockSource = ( diff --git a/src/is-record.ts b/src/is-record.ts new file mode 100644 index 0000000..c632f81 --- /dev/null +++ b/src/is-record.ts @@ -0,0 +1,2 @@ +export const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null && !Array.isArray(value); diff --git a/tests/sync-tool-version.test.js b/tests/sync-tool-version.test.js index 3ab6ff0..e6d4856 100644 --- a/tests/sync-tool-version.test.js +++ b/tests/sync-tool-version.test.js @@ -33,15 +33,29 @@ test("sync writes lock toolVersion from package.json", async () => { ), "utf8", ); + await writeFile( + path.join(tmpRoot, "package.json"), + JSON.stringify({ + name: "not-docs-cache", + version: "9.9.9", + }), + "utf8", + ); - await runSync({ - configPath, - cacheDirOverride: cacheDir, - json: true, - lockOnly: true, - offline: true, - failOnMiss: false, - }); + const originalCwd = process.cwd(); + try { + process.chdir(tmpRoot); + await runSync({ + configPath, + cacheDirOverride: cacheDir, + json: true, + lockOnly: true, + offline: true, + failOnMiss: false, + }); + } finally { + process.chdir(originalCwd); + } const lockRaw = await readFile( path.join(tmpRoot, DEFAULT_LOCK_FILENAME), @@ -49,7 +63,7 @@ test("sync writes lock toolVersion from package.json", async () => { ); const lock = JSON.parse(lockRaw); const pkgRaw = await readFile( - path.resolve(process.cwd(), "package.json"), + new URL("../package.json", import.meta.url), "utf8", ); const pkg = JSON.parse(pkgRaw);