Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
73 changes: 49 additions & 24 deletions src/commands/sync.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -70,6 +71,9 @@ const normalizePatterns = (patterns?: string[]) => {
return Array.from(new Set(normalized)).sort();
};

const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isRecord is duplicated here (same helper already exists in src/cache/lock.ts). To avoid drift and keep validation helpers consistent, consider reusing a shared exported helper (e.g., export isRecord from a common util module) instead of redefining it in sync.ts.

Copilot uses AI. Check for mistakes.

const RULES_HASH_BLACKLIST = [
"id",
"repo",
Expand Down Expand Up @@ -187,35 +191,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.toString());
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 = (
Expand Down
2 changes: 1 addition & 1 deletion tests/sync-tool-version.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,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);
Expand Down
Loading