Skip to content

Commit 0ce4f69

Browse files
authored
fix(sync): resolve tool version from module dir (#33)
* fix(sync): resolve tool version from module dir * refactor(core): extract isrecord to shared utility * chore: cleanup
1 parent c8481ce commit 0ce4f69

4 files changed

Lines changed: 73 additions & 36 deletions

File tree

src/cache/lock.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFile, writeFile } from "node:fs/promises";
22
import path from "node:path";
3+
import { isRecord } from "#core/is-record";
34

45
export interface DocsCacheLockSource {
56
repo: string;
@@ -19,9 +20,6 @@ export interface DocsCacheLock {
1920

2021
export const DEFAULT_LOCK_FILENAME = "docs-lock.json";
2122

22-
const isRecord = (value: unknown): value is Record<string, unknown> =>
23-
typeof value === "object" && value !== null && !Array.isArray(value);
24-
2523
const assertString = (value: unknown, label: string): string => {
2624
if (typeof value !== "string" || value.length === 0) {
2725
throw new Error(`${label} must be a non-empty string.`);

src/commands/sync.ts

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createHash } from "node:crypto";
22
import { access, mkdir, readFile } from "node:fs/promises";
33
import path from "node:path";
4+
import { fileURLToPath } from "node:url";
45
import pc from "picocolors";
56
import type { DocsCacheLock, DocsCacheLockSource } from "#cache/lock";
67
import { readLock, resolveLockPath, writeLock } from "#cache/lock";
@@ -18,6 +19,7 @@ import {
1819
type DocsCacheResolvedSource,
1920
loadConfig,
2021
} from "#config";
22+
import { isRecord } from "#core/is-record";
2123
import { resolveCacheDir, resolveTargetDir } from "#core/paths";
2224
import { fetchSource } from "#git/fetch-source";
2325
import { resolveRemoteCommit } from "#git/resolve-remote";
@@ -187,35 +189,56 @@ export const getSyncPlan = async (
187189
};
188190
};
189191

190-
const loadToolVersion = async () => {
191-
const cwdPath = path.resolve(process.cwd(), "package.json");
192+
const TOOL_PACKAGE_NAME = "docs-cache";
193+
194+
const readToolVersionFromPackageFile = async (packagePath: string) => {
192195
try {
193-
const raw = await readFile(cwdPath, "utf8");
194-
const pkg = JSON.parse(raw.toString());
195-
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
196+
const raw = await readFile(packagePath, "utf8");
197+
const parsed: unknown = JSON.parse(raw);
198+
if (!isRecord(parsed)) {
199+
return null;
200+
}
201+
const pkgName = parsed.name;
202+
const pkgVersion = parsed.version;
203+
if (pkgName !== TOOL_PACKAGE_NAME) {
204+
return null;
205+
}
206+
if (typeof pkgVersion !== "string" || pkgVersion.length === 0) {
207+
return null;
208+
}
209+
return pkgVersion;
196210
} catch {
197-
// fallback to bundle-relative location
211+
return null;
198212
}
199-
try {
200-
const raw = await readFile(
201-
new URL("../package.json", import.meta.url),
202-
"utf8",
203-
);
204-
const pkg = JSON.parse(raw.toString());
205-
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
206-
} catch {
207-
// fallback to dist/chunks relative location
213+
};
214+
215+
const findToolVersionFrom = async (startDir: string) => {
216+
let currentDir = startDir;
217+
while (true) {
218+
const packagePath = path.join(currentDir, "package.json");
219+
const version = await readToolVersionFromPackageFile(packagePath);
220+
if (version) {
221+
return version;
222+
}
223+
const parentDir = path.dirname(currentDir);
224+
if (parentDir === currentDir) {
225+
return null;
226+
}
227+
currentDir = parentDir;
208228
}
209-
try {
210-
const raw = await readFile(
211-
new URL("../../package.json", import.meta.url),
212-
"utf8",
213-
);
214-
const pkg = JSON.parse(raw.toString());
215-
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
216-
} catch {
217-
return "0.0.0";
229+
};
230+
231+
const loadToolVersion = async () => {
232+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
233+
const moduleVersion = await findToolVersionFrom(moduleDir);
234+
if (moduleVersion) {
235+
return moduleVersion;
236+
}
237+
const cwdVersion = await findToolVersionFrom(process.cwd());
238+
if (cwdVersion) {
239+
return cwdVersion;
218240
}
241+
return "0.0.0";
219242
};
220243

221244
const buildLockSource = (

src/is-record.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const isRecord = (value: unknown): value is Record<string, unknown> =>
2+
typeof value === "object" && value !== null && !Array.isArray(value);

tests/sync-tool-version.test.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,37 @@ test("sync writes lock toolVersion from package.json", async () => {
3333
),
3434
"utf8",
3535
);
36+
await writeFile(
37+
path.join(tmpRoot, "package.json"),
38+
JSON.stringify({
39+
name: "not-docs-cache",
40+
version: "9.9.9",
41+
}),
42+
"utf8",
43+
);
3644

37-
await runSync({
38-
configPath,
39-
cacheDirOverride: cacheDir,
40-
json: true,
41-
lockOnly: true,
42-
offline: true,
43-
failOnMiss: false,
44-
});
45+
const originalCwd = process.cwd();
46+
try {
47+
process.chdir(tmpRoot);
48+
await runSync({
49+
configPath,
50+
cacheDirOverride: cacheDir,
51+
json: true,
52+
lockOnly: true,
53+
offline: true,
54+
failOnMiss: false,
55+
});
56+
} finally {
57+
process.chdir(originalCwd);
58+
}
4559

4660
const lockRaw = await readFile(
4761
path.join(tmpRoot, DEFAULT_LOCK_FILENAME),
4862
"utf8",
4963
);
5064
const lock = JSON.parse(lockRaw);
5165
const pkgRaw = await readFile(
52-
path.resolve(process.cwd(), "package.json"),
66+
new URL("../package.json", import.meta.url),
5367
"utf8",
5468
);
5569
const pkg = JSON.parse(pkgRaw);

0 commit comments

Comments
 (0)