diff --git a/packages/cli/src/filesystem/manifest.ts b/packages/cli/src/filesystem/manifest.ts index f78347bb..99698819 100644 --- a/packages/cli/src/filesystem/manifest.ts +++ b/packages/cli/src/filesystem/manifest.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { CLIError } from "../util/cli-error"; import { buildInvocation } from "../util/invocation"; +import { isRecord } from "../util/is-record"; /** * The `.taskless/taskless.json` manifest: its shape, and reading and writing it. @@ -149,7 +150,7 @@ export async function readRawManifest( // manifest. Reading `.version` off `null` would throw a bare TypeError, and // treating it as version 0 has the same consequence as an unparseable file: // the next write replaces whatever is there. - if (!isPlainObject(parsed)) { + if (!isRecord(parsed)) { throw unreadableManifest(path, "its top-level value is not a JSON object"); } @@ -189,8 +190,8 @@ export async function readManifest( return { manifest: { version, - install: isPlainObject(install) ? install : undefined, - rules: isPlainObject(rules) ? rules : undefined, + install: isRecord(install) ? install : undefined, + rules: isRecord(rules) ? rules : undefined, }, raw, }; @@ -219,7 +220,3 @@ export async function writeManifest( } await writeRawManifest(directory, merged); } - -function isPlainObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} diff --git a/packages/cli/src/filesystem/migrations/0002-install.ts b/packages/cli/src/filesystem/migrations/0002-install.ts index 39ac609a..1a1f1ae6 100644 --- a/packages/cli/src/filesystem/migrations/0002-install.ts +++ b/packages/cli/src/filesystem/migrations/0002-install.ts @@ -2,13 +2,10 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import type { Migration } from "../types"; +import { isRecord } from "../../util/is-record"; const MANIFEST_FILE = "taskless.json"; -function isPlainObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - /** * Migration 2 — seed an empty `install` object in taskless.json. * @@ -21,14 +18,14 @@ const migration: Migration = async (directory) => { try { const content = await readFile(manifestPath, "utf8"); const parsed = JSON.parse(content) as unknown; - if (isPlainObject(parsed)) { + if (isRecord(parsed)) { raw = parsed; } } catch { // Missing or unparseable — fall through and write a minimal manifest } - if (!isPlainObject(raw.install)) { + if (!isRecord(raw.install)) { raw.install = {}; await writeFile(manifestPath, JSON.stringify(raw, null, 2) + "\n", "utf8"); } diff --git a/packages/cli/src/filesystem/migrations/0003-drop-installed-at.ts b/packages/cli/src/filesystem/migrations/0003-drop-installed-at.ts index c8962f98..71eef762 100644 --- a/packages/cli/src/filesystem/migrations/0003-drop-installed-at.ts +++ b/packages/cli/src/filesystem/migrations/0003-drop-installed-at.ts @@ -2,13 +2,10 @@ import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import type { Migration } from "../types"; +import { isRecord } from "../../util/is-record"; const MANIFEST_FILE = "taskless.json"; -function isPlainObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - /** * Migration 3 — drop the unused `install.installedAt` timestamp. * @@ -22,7 +19,7 @@ const migration: Migration = async (directory) => { try { const content = await readFile(manifestPath, "utf8"); const parsed = JSON.parse(content) as unknown; - if (isPlainObject(parsed)) { + if (isRecord(parsed)) { raw = parsed; } } catch { @@ -31,7 +28,7 @@ const migration: Migration = async (directory) => { } const install = raw.install; - if (isPlainObject(install) && "installedAt" in install) { + if (isRecord(install) && "installedAt" in install) { delete install.installedAt; await writeFile(manifestPath, JSON.stringify(raw, null, 2) + "\n", "utf8"); }