From 4da9cc2e6659cc34aa871ab22d23a6f5bf1d1f33 Mon Sep 17 00:00:00 2001 From: hasundue Date: Fri, 2 Aug 2024 17:41:15 +0900 Subject: [PATCH 1/2] fix(core/locks): throw on unsupported version of lockfile --- core/locks.ts | 19 +++++++++++++++---- core/locks_test.ts | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/core/locks.ts b/core/locks.ts index c1b798d4..d9d11465 100644 --- a/core/locks.ts +++ b/core/locks.ts @@ -35,23 +35,30 @@ export const empty: LockfileJson = { * * @param dependency The dependency to create the lock for. * @param target The target version to update the dependency to. + * @param original The original lockfile to create the partial lock from. + * + * @returns The `LockfileJson` object representing the partial lock. + * @throws If the version of the original lockfile is not supported. */ export function create( dependency: DependencySpec, target: string, - extracted: LockfileJson, + original: LockfileJson, ): Promise { + if (original.version !== VERSION) { + throw new Error(`Unsupported lockfile version: ${original.version}`); + } return isRemote(dependency) - ? createRemoteLock(dependency, extracted) + ? createRemoteLock(dependency, original) : createPackageLock(dependency as DependencySpec<"jsr" | "npm">, target); } async function createRemoteLock( dep: DependencySpec<"http" | "https">, - extracted: LockfileJson, + original: LockfileJson, ): Promise { const { kind, name, constraint, path } = dep; - const reqs = Object.keys(extracted.remote).filter((req) => + const reqs = Object.keys(original.remote).filter((req) => [kind, name, path ?? ""].every((part) => req.includes(part)) ); const lockfile = parseFromJson("", empty); @@ -238,6 +245,7 @@ async function getJsrDependencies( * @param lockfile The `Lockfile` object to extract the partial lock for the dependency from. * @param dependency The dependency to extract the partial lock for. * @returns The `LockfileJson` object representing the partial lock. + * @throws If the lockfile version is not supported. * * @example * ```ts @@ -249,6 +257,9 @@ export async function extract( lockfile: LockfileJson, dependency: DependencySpec, ): Promise { + if (lockfile.version !== VERSION) { + throw new Error(`Unsupported lockfile version: ${lockfile.version}`); + } return isRemote(dependency) ? await extractRemote(lockfile, dependency) : extractPackage(lockfile, dependency as DependencySpec<"jsr" | "npm">); diff --git a/core/locks_test.ts b/core/locks_test.ts index b7a83b49..dfa6512e 100644 --- a/core/locks_test.ts +++ b/core/locks_test.ts @@ -1,5 +1,5 @@ import * as fs from "@chiezo/amber/fs"; -import { assertEquals } from "@std/assert"; +import { assertEquals, assertRejects } from "@std/assert"; import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; import { parse } from "./specs.ts"; import { create, extract, type LockfileJson, query } from "./locks.ts"; @@ -50,11 +50,23 @@ describe("create", () => { beforeEach(() => fs.mock()); afterEach(() => fs.dispose()); + it("should throw an error for an unsupported lockfile version", async () => { + await assertRejects( + async () => { + await create(parse("jsr:@std/testing@^0.222.0"), "0.222.0", { + version: "4", + } as LockfileJson); + }, + Error, + "Unsupported lockfile version: 4", + ); + }); + it("should create a partial lock for a package with a patch update", async () => { const lock = await create( parse("jsr:@std/assert@^0.222.0"), "0.222.1", - {} as LockfileJson, + { version: "3" } as LockfileJson, ); assertEquals(lock, { version: "3", @@ -86,7 +98,7 @@ describe("create", () => { const lock = await create( parse("jsr:@std/assert@^0.226.0"), "0.226.0", - {} as LockfileJson, + { version: "3" } as LockfileJson, ); assertEquals(lock, { version: "3", @@ -116,7 +128,7 @@ describe("create", () => { const lock = await create( parse("jsr:@core/match@^0.2.0"), "0.2.5", - {} as LockfileJson, + { version: "3" } as LockfileJson, ); assertEquals(lock, { version: "3", @@ -149,7 +161,7 @@ describe("create", () => { const lock = await create( parse("npm:@conventional-commits/parser@^0.4.0"), "0.4.1", - {} as LockfileJson, + { version: "3" } as LockfileJson, ); assertEquals(lock, { version: "3", @@ -246,6 +258,19 @@ describe("extract", () => { beforeEach(() => fs.mock()); afterEach(() => fs.dispose()); + it("should throw an error for an unsupported lockfile version", async () => { + await assertRejects( + async () => { + await extract( + { version: "4" } as LockfileJson, + parse("jsr:@std/testing@^0.222.0"), + ); + }, + Error, + "Unsupported lockfile version: 4", + ); + }); + it("should return undefined for an unlocked package", async () => { const dep = parse("jsr:@std/testing@^0.222.0"); const lock = await extract(LOCKFILE, dep); From c5d6966c612b513e885159bc99932ecb65eba71c Mon Sep 17 00:00:00 2001 From: hasundue Date: Fri, 2 Aug 2024 18:09:21 +0900 Subject: [PATCH 2/2] fix(cli): skip unsupported version of lock file --- cli/fixtures/deno.lock.future | 55 +++++++++++++++++++++++++++++++++++ cli/main.ts | 2 +- cli/main_test.ts | 19 ++++++++++++ cli/src/files.ts | 16 ++++++++-- 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 cli/fixtures/deno.lock.future diff --git a/cli/fixtures/deno.lock.future b/cli/fixtures/deno.lock.future new file mode 100644 index 00000000..e3ccb29b --- /dev/null +++ b/cli/fixtures/deno.lock.future @@ -0,0 +1,55 @@ +{ + "version": "4", + "packages": { + "specifiers": { + "jsr:@luca/flag@^1.0.0": "jsr:@luca/flag@1.0.0", + "npm:@conventional-commits/parser@^0.3.0": "npm:@conventional-commits/parser@0.3.0" + }, + "jsr": { + "@luca/flag@1.0.0": { + "integrity": "1c76cf54839a86d0929a619c61bd65bb73d7d8a4e31788e48c720dbc46c5d546" + } + }, + "npm": { + "@conventional-commits/parser@0.3.0": { + "integrity": "sha512-d4zk0gf9hQAILHE1pXWuIuqM/OBsvcOxcUgGZN4PQkIXQWvjbFOkoSv6HztKFEqfZEY95fe85XqBWLWqWtaW6g==", + "dependencies": { + "unist-util-visit": "unist-util-visit@2.0.3", + "unist-util-visit-parents": "unist-util-visit-parents@3.1.1" + } + }, + "@types/unist@2.0.10": { + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", + "dependencies": {} + }, + "unist-util-is@4.1.0": { + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dependencies": {} + }, + "unist-util-visit-parents@3.1.1": { + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dependencies": { + "@types/unist": "@types/unist@2.0.10", + "unist-util-is": "unist-util-is@4.1.0" + } + }, + "unist-util-visit@2.0.3": { + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dependencies": { + "@types/unist": "@types/unist@2.0.10", + "unist-util-is": "unist-util-is@4.1.0", + "unist-util-visit-parents": "unist-util-visit-parents@3.1.1" + } + } + } + }, + "remote": { + "https://deno.land/std@0.222.0/bytes/copy.ts": "f29c03168853720dfe82eaa57793d0b9e3543ebfe5306684182f0f1e3bfd422a" + }, + "workspace": { + "dependencies": [ + "jsr:@luca/flag@^1.0.0", + "npm:@conventional-commits/parser@^0.3.0" + ] + } +} diff --git a/cli/main.ts b/cli/main.ts index 05c86836..ca2cbb27 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -52,7 +52,7 @@ main.action(async function (options, ...source) { const lock = options.lock === false ? undefined - : options.lock ?? await findLock(); + : await findLock(options.lock); source = source.length ? source : config ? [] : await findSource(); diff --git a/cli/main_test.ts b/cli/main_test.ts index b935d225..19c7517e 100644 --- a/cli/main_test.ts +++ b/cli/main_test.ts @@ -118,6 +118,25 @@ describe("CLI", () => { ); }); + it("should ignore an unsupported version of lockfile with a warning", async () => { + const { stdout, stderr } = await molt("--lock deno.lock.future"); + assertEquals( + stdout, + dedent` + 📦 @conventional-commits/parser ^0.3.0 → ^0.4.0 + 📦 deno.land/std 0.222.0 → 0.224.0 + `, + ); + assertEquals( + stderr, + dedent` + Unsupported lockfile version: '4'. Please update the lock file manually. + Collecting dependencies + Fetching updates + `, + ); + }); + it("should filter dependencies with `--only`", async () => { const { stdout } = await molt("--only flag"); assertEquals( diff --git a/cli/src/files.ts b/cli/src/files.ts index 26893570..12829b08 100644 --- a/cli/src/files.ts +++ b/cli/src/files.ts @@ -1,3 +1,4 @@ +import { VERSION } from "@molt/core/locks"; import { exists } from "@std/fs"; import { parse } from "@std/jsonc"; @@ -16,10 +17,19 @@ async function hasImports(config: string): Promise { return jsonc !== null && typeof jsonc === "object" && "imports" in jsonc; } -export async function findLock() { - if (await exists("deno.lock")) { - return "deno.lock"; +export async function findLock(path?: string) { + path ??= await exists("deno.lock") ? "deno.lock" : undefined; + if (!path) { + return; } + const { version } = JSON.parse(await Deno.readTextFile(path)); + if (version !== VERSION) { + console.warn( + `Unsupported lockfile version: '${version}'. Please update the lock file manually.`, + ); + return; + } + return path; } export async function findSource() {