diff --git a/deno.lock b/deno.lock index a54fbadb..63a4fdea 100644 --- a/deno.lock +++ b/deno.lock @@ -125,6 +125,7 @@ "https://deno.land/std@0.218.2/assert/assert_array_includes.ts": "1688d76317fd45b7e93ef9e2765f112fdf2b7c9821016cdfb380b9445374aed1", "https://deno.land/std@0.218.2/assert/assert_equals.ts": "4497c56fe7d2993b0d447926702802fc0becb44e319079e8eca39b482ee01b4e", "https://deno.land/std@0.218.2/assert/assert_exists.ts": "24a7bf965e634f909242cd09fbaf38bde6b791128ece08e33ab08586a7cc55c9", + "https://deno.land/std@0.218.2/assert/assert_instance_of.ts": "72dc1faff1e248692d873c89382fa1579dd7b53b56d52f37f9874a75b11ba444", "https://deno.land/std@0.218.2/assert/assert_is_error.ts": "6596f2b5ba89ba2fe9b074f75e9318cda97a2381e59d476812e30077fbdb6ed2", "https://deno.land/std@0.218.2/assert/assert_not_equals.ts": "ac86413ab70ffb14fdfc41740ba579a983fe355ba0ce4a9ab685e6b8e7f6a250", "https://deno.land/std@0.218.2/assert/assert_object_match.ts": "e85e5eef62a56ce364c3afdd27978ccab979288a3e772e6855c270a7b118fa49", @@ -137,6 +138,7 @@ "https://deno.land/std@0.218.2/collections/filter_keys.ts": "bf8eaae2769e6ddd34f23ba1912f7feda2b6f374253c07a6630e9a2e384ba611", "https://deno.land/std@0.218.2/collections/map_entries.ts": "45b8bf3f197ee24abe2e078213a4cf2f4e187ad130649fff34e13b1a03ede478", "https://deno.land/std@0.218.2/collections/max_by.ts": "a601c296d54349097d5b424bc86c84010ab22733b622b480e2ad65927063d7bb", + "https://deno.land/std@0.218.2/collections/partition.ts": "22689dc4834bbcaa06f9ac204ac5502ab0f7087d7fca26bad50319c12434d099", "https://deno.land/std@0.218.2/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", "https://deno.land/std@0.218.2/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", "https://deno.land/std@0.218.2/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", diff --git a/lib/file.ts b/lib/file.ts index 80e71b9e..2a97ccb8 100644 --- a/lib/file.ts +++ b/lib/file.ts @@ -1,7 +1,6 @@ -import { detectEOL, EOL, formatEOL } from "./std/fs.ts"; +import { detectEOL, EOL } from "./std/fs.ts"; import { toUrl } from "./dependency.ts"; import { type DependencyUpdate } from "./update.ts"; -import { parseImportMapJson } from "./import_map.ts"; /** * Write the given array of DependencyUpdate to files. @@ -127,14 +126,9 @@ async function writeToImportMap( /** The dependency update to apply. */ update: FileUpdate<"import_map">, ) { - const content = await Deno.readTextFile(update.path); - const json = parseImportMapJson(content); + let content = await Deno.readTextFile(update.path); for (const dependency of update.dependencies) { - json.imports[dependency.map.key] = toUrl(dependency.to); + content = content.replaceAll(toUrl(dependency.from), toUrl(dependency.to)); } - const eol = detectEOL(content) ?? EOL; - await Deno.writeTextFile( - update.path, - formatEOL(JSON.stringify(json, null, 2), eol) + eol, - ); + await Deno.writeTextFile(update.path, content); } diff --git a/lib/file_test.ts b/lib/file_test.ts index 9f90f56c..d4267b04 100644 --- a/lib/file_test.ts +++ b/lib/file_test.ts @@ -5,6 +5,7 @@ import { ReadTextFileStub, WriteTextFileStub, } from "./testing.ts"; +import { assertInstanceOf } from "./std/assert.ts"; import { assertSnapshot } from "./testing.ts"; import * as DependencyUpdate from "./update.ts"; import { associateByFile, type FileUpdate, write } from "./file.ts"; @@ -15,7 +16,9 @@ LatestSemVerStub.create(LATEST); function toName(path: string) { const base = basename(path); - return base === "mod.ts" ? `${basename(dirname(path))}/mod.ts` : base; + return base === "mod.ts" || base.endsWith(".json") || base.endsWith(".jsonc") + ? basename(dirname(path)) + "/" + base + : base; } async function assertFileUpdateSnapshot( @@ -56,21 +59,26 @@ ReadTextFileStub.create(fs, { readThrough: true }); WriteTextFileStub.create(fs); async function test(path: string, name = toName(path)) { - const updates = await DependencyUpdate.collect( - new URL(path, import.meta.url), - { cwd: new URL(dirname(path), import.meta.url) }, - ); - const results = associateByFile(updates); + try { + const updates = await DependencyUpdate.collect( + new URL(path, import.meta.url), + { cwd: new URL(dirname(path), import.meta.url) }, + ); + const results = associateByFile(updates); - Deno.test("associateByFile - " + name, async (t) => { - await assertFileUpdateSnapshot(t, results); - }); + Deno.test("associateByFile - " + name, async (t) => { + await assertFileUpdateSnapshot(t, results); + }); - Deno.test("write - " + name, async (t) => { - fs.clear(); - await write(results); - await assertFileSystemSnapshot(t, fs); - }); + Deno.test("write - " + name, async (t) => { + fs.clear(); + await write(results); + await assertFileSystemSnapshot(t, fs); + }); + } catch (error) { + // import_map_reffered/deno.json just reffers to another import_map.json + assertInstanceOf(error, SyntaxError); + } } // Test the all cases in test/data @@ -86,8 +94,11 @@ for await ( new URL("../test/data/" + testCase.name, import.meta.url), ) ) { - if (entry.isFile && entry.name === "mod.ts") { - await test(`../test/data/${testCase.name}/mod.ts`); + if ( + entry.isFile && entry.name === "mod.ts" || + entry.name.endsWith(".json") || entry.name.endsWith(".jsonc") + ) { + await test(`../test/data/${testCase.name}/${entry.name}`); } } } diff --git a/lib/std/assert.ts b/lib/std/assert.ts index 6662d034..beedcab0 100644 --- a/lib/std/assert.ts +++ b/lib/std/assert.ts @@ -6,3 +6,4 @@ export { assertObjectMatch } from "https://deno.land/std@0.218.2/assert/assert_o export { assertThrows } from "https://deno.land/std@0.218.2/assert/assert_throws.ts"; export { AssertionError } from "https://deno.land/std@0.218.2/assert/assertion_error.ts"; export { assertRejects } from "https://deno.land/std@0.218.2/assert/assert_rejects.ts"; +export { assertInstanceOf } from "https://deno.land/std@0.218.2/assert/assert_instance_of.ts"; diff --git a/lib/std/collections.ts b/lib/std/collections.ts index d29bb50d..a7116a78 100644 --- a/lib/std/collections.ts +++ b/lib/std/collections.ts @@ -3,3 +3,4 @@ export { maxBy } from "https://deno.land/std@0.218.2/collections/max_by.ts"; export { filterKeys } from "https://deno.land/std@0.218.2/collections/filter_keys.ts"; export { filterEntries } from "https://deno.land/std@0.218.2/collections/filter_entries.ts"; export { mapEntries } from "https://deno.land/std@0.218.2/collections/map_entries.ts"; +export { partition } from "https://deno.land/std@0.218.2/collections/partition.ts"; diff --git a/lib/update.ts b/lib/update.ts index 65eefb3e..fa971396 100644 --- a/lib/update.ts +++ b/lib/update.ts @@ -1,4 +1,4 @@ -import { distinct } from "./std/collections.ts"; +import { distinct, partition } from "./std/collections.ts"; import { fromFileUrl } from "./std/path.ts"; import { createGraph, @@ -152,8 +152,10 @@ export async function collect( ? await tryReadFromJson(toUrl(importMapPath)) : undefined; + const [jsons, esms] = partition(urls, isJsonPath); + await DenoGraph.ensureInit(); - const graph = await createGraph(urls, { + const graph = await createGraph(esms, { load, resolve: importMap?.resolveInner, }); @@ -172,16 +174,17 @@ export async function collect( return update ? updates.push(update) : undefined; }) ), - ...graph.modules - .filter((m) => m.kind === "asserted" && m.mediaType === "Json") - .map(async (m) => { - const results = await _collectFromImportMap(m.specifier, options); - updates.push(...results); - }), + ...jsons.map(async (url) => { + const results = await _collectFromImportMap(url, options); + updates.push(...results); + }), ]); return updates.sort((a, b) => a.to.name.localeCompare(b.to.name)); } +const isJsonPath = (path: string) => + path.endsWith(".json") || path.endsWith(".jsonc"); + const load: NonNullable = async ( specifier, ) => { diff --git a/lib/update_test.ts b/lib/update_test.ts index d21572fb..7580d38d 100644 --- a/lib/update_test.ts +++ b/lib/update_test.ts @@ -1,28 +1,32 @@ import { dirname } from "./std/path.ts"; -import { assertEquals, assertThrows } from "./std/assert.ts"; +import { assertEquals, assertInstanceOf, assertThrows } from "./std/assert.ts"; import { filterKeys } from "./std/collections.ts"; import { basename } from "./std/path.ts"; import { assertSnapshot } from "./testing.ts"; import { LatestSemVerStub } from "./testing.ts"; -import { readImportMapJson } from "./import_map.ts"; import { collect, DependencyUpdate, getVersionChange } from "./update.ts"; -function test( +async function test( path: string, name = basename(path), variation?: string, ) { - Deno.test( - "collect - " + (variation ? `${name} - ${variation}` : name), - async (t) => { - const updates = await collect(new URL(path, import.meta.url), { - cwd: new URL(dirname(path), import.meta.url), - }); - for (const update of updates) { - await assertUpdateSnapshot(t, update); - } - }, - ); + try { + const updates = await collect(new URL(path, import.meta.url), { + cwd: new URL(dirname(path), import.meta.url), + }); + Deno.test( + "collect - " + (variation ? `${name} - ${variation}` : name), + async (t) => { + for (const update of updates) { + await assertUpdateSnapshot(t, update); + } + }, + ); + } catch (error) { + // import_map_reffered/deno.json just reffers to another import_map.json + assertInstanceOf(error, SyntaxError); + } } async function assertUpdateSnapshot( @@ -39,15 +43,6 @@ async function assertUpdateSnapshot( ); } -async function hasImportMap(url: URL) { - try { - await readImportMapJson(url); - return true; - } catch { - return false; - } -} - const LATEST = "123.456.789"; LatestSemVerStub.create(LATEST); @@ -56,7 +51,7 @@ for await ( const testCase of Deno.readDir(new URL("../test/data", import.meta.url)) ) { if (testCase.isFile && testCase.name.endsWith(".ts")) { - test(`../test/data/${testCase.name}`); + await test(`../test/data/${testCase.name}`); } if (testCase.isDirectory) { for await ( @@ -66,14 +61,9 @@ for await ( ) { if ( entry.isFile && entry.name === "mod.ts" || - entry.name.endsWith(".json") && await hasImportMap( - new URL( - `../test/data/${testCase.name}/${entry.name}`, - import.meta.url, - ), - ) + entry.name.endsWith(".json") || entry.name.endsWith("jsonc") ) { - test( + await test( `../test/data/${testCase.name}/${entry.name}`, testCase.name, entry.name, diff --git a/test/data/jsonc/deno.jsonc b/test/data/jsonc/deno.jsonc new file mode 100644 index 00000000..dee039a8 --- /dev/null +++ b/test/data/jsonc/deno.jsonc @@ -0,0 +1,10 @@ +{ + "imports": { + "std/": "https://deno.land/std@0.200.0/", + "deno_graph": "https://deno.land/x/deno_graph@0.50.0/mod.ts", + "node-emoji": "npm:node-emoji@1.0.0", + "flag": "jsr:@luca/flag@1.0.0", + // map root to the project root + "/": "./" + } +} diff --git a/test/integration/cli.ts b/test/integration/cli.ts index 9ed5c216..edb41d84 100644 --- a/test/integration/cli.ts +++ b/test/integration/cli.ts @@ -54,6 +54,11 @@ molt("deno.json", { cwd: "import_map" }); molt("deno.json --write", { cwd: "import_map" }); molt("deno.json --commit --prefix :package:", { cwd: "import_map" }); +// deno.jsonc +molt("deno.jsonc", { cwd: "jsonc" }); +molt("deno.jsonc --write", { cwd: "jsonc" }); +molt("deno.jsonc --commit --prefix :package:", { cwd: "jsonc" }); + //----------------------- // Test implementation //----------------------- diff --git a/test/snapshots/cli.ts.snap b/test/snapshots/cli.ts.snap index 83730a2a..97c95efb 100644 --- a/test/snapshots/cli.ts.snap +++ b/test/snapshots/cli.ts.snap @@ -421,3 +421,49 @@ snapshot[`cli - import_map - "molt deno.json --commit --prefix :package:" 2`] = "Checking for updates " `; + +snapshot[`cli - jsonc - "molt deno.jsonc" 1`] = ` +"📦 @luca/flag 1.0.0 => 123.456.789 +📦 deno.land/std 0.200.0 => 123.456.789 +📦 deno.land/x/deno_graph 0.50.0 => 123.456.789 +📦 node-emoji 1.0.0 => 123.456.789 +" +`; + +snapshot[`cli - jsonc - "molt deno.jsonc" 2`] = ` +"Checking for updates +" +`; + +snapshot[`cli - jsonc - "molt deno.jsonc --write" 1`] = ` +"📦 @luca/flag 1.0.0 => 123.456.789 +📦 deno.land/std 0.200.0 => 123.456.789 +📦 deno.land/x/deno_graph 0.50.0 => 123.456.789 +📦 node-emoji 1.0.0 => 123.456.789 + +💾 deno.jsonc +" +`; + +snapshot[`cli - jsonc - "molt deno.jsonc --write" 2`] = ` +"Checking for updates +" +`; + +snapshot[`cli - jsonc - "molt deno.jsonc --commit --prefix :package:" 1`] = ` +"📦 @luca/flag 1.0.0 => 123.456.789 +📦 deno.land/std 0.200.0 => 123.456.789 +📦 deno.land/x/deno_graph 0.50.0 => 123.456.789 +📦 node-emoji 1.0.0 => 123.456.789 + +📝 :package: bump @luca/flag from 1.0.0 to 123.456.789 +📝 :package: bump deno.land/std from 0.200.0 to 123.456.789 +📝 :package: bump deno.land/x/deno_graph from 0.50.0 to 123.456.789 +📝 :package: bump node-emoji from 1.0.0 to 123.456.789 +" +`; + +snapshot[`cli - jsonc - "molt deno.jsonc --commit --prefix :package:" 2`] = ` +"Checking for updates +" +`; diff --git a/test/snapshots/file_test.ts.snap b/test/snapshots/file_test.ts.snap index b8182476..7e358172 100644 --- a/test/snapshots/file_test.ts.snap +++ b/test/snapshots/file_test.ts.snap @@ -177,6 +177,49 @@ import { printProgress } from "jsr:@luca/flag@123.456.789"; ] `; +snapshot[`associateByFile - import_map_referred/import_map.json 1`] = ` +[ + { + dependencies: [ + { + code: { + span: undefined, + specifier: "https://deno.land/x/dax@0.17.0/mod.ts", + }, + from: { + name: "deno.land/x/dax", + path: "/mod.ts", + protocol: "https:", + version: "0.17.0", + }, + map: { + key: "dax", + resolved: "https://deno.land/x/dax@0.17.0/mod.ts", + }, + to: { + name: "deno.land/x/dax", + path: "/mod.ts", + protocol: "https:", + version: "123.456.789", + }, + }, + ], + kind: "import_map", + }, +] +`; + +snapshot[`write - import_map_referred/import_map.json 1`] = ` +[ + '{ + "imports": { + "dax": "https://deno.land/x/dax@123.456.789/mod.ts" + } +} +', +] +`; + snapshot[`associateByFile - import_map_referred/mod.ts 1`] = ` [ { @@ -275,6 +318,119 @@ export { assertEquals } from "https://deno.land/std@123.456.789/assert/assert_eq ] `; +snapshot[`associateByFile - import_map/deno.json 1`] = ` +[ + { + dependencies: [ + { + code: { + span: undefined, + specifier: "jsr:@luca/flag@1.0.0", + }, + from: { + name: "@luca/flag", + path: "", + protocol: "jsr:", + version: "1.0.0", + }, + map: { + key: "flag", + resolved: "jsr:@luca/flag@1.0.0", + }, + to: { + name: "@luca/flag", + path: "", + protocol: "jsr:", + version: "123.456.789", + }, + }, + { + code: { + span: undefined, + specifier: "https://deno.land/std@0.200.0/", + }, + from: { + name: "deno.land/std", + path: "/", + protocol: "https:", + version: "0.200.0", + }, + map: { + key: "std/", + resolved: "https://deno.land/std@0.200.0/", + }, + to: { + name: "deno.land/std", + path: "/", + protocol: "https:", + version: "123.456.789", + }, + }, + { + code: { + span: undefined, + specifier: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + }, + from: { + name: "deno.land/x/deno_graph", + path: "/mod.ts", + protocol: "https:", + version: "0.50.0", + }, + map: { + key: "deno_graph", + resolved: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + }, + to: { + name: "deno.land/x/deno_graph", + path: "/mod.ts", + protocol: "https:", + version: "123.456.789", + }, + }, + { + code: { + span: undefined, + specifier: "npm:node-emoji@1.0.0", + }, + from: { + name: "node-emoji", + path: "", + protocol: "npm:", + version: "1.0.0", + }, + map: { + key: "node-emoji", + resolved: "npm:node-emoji@1.0.0", + }, + to: { + name: "node-emoji", + path: "", + protocol: "npm:", + version: "123.456.789", + }, + }, + ], + kind: "import_map", + }, +] +`; + +snapshot[`write - import_map/deno.json 1`] = ` +[ + '{ + "imports": { + "std/": "https://deno.land/std@123.456.789/", + "deno_graph": "https://deno.land/x/deno_graph@123.456.789/mod.ts", + "node-emoji": "npm:node-emoji@123.456.789", + "flag": "jsr:@luca/flag@123.456.789", + "/": "./" + } +} +', +] +`; + snapshot[`associateByFile - import_map/mod.ts 1`] = ` [ { @@ -545,6 +701,10 @@ export { createGraph } from "https://deno.land/x/deno_graph@123.456.789/mod.ts"; ] `; +snapshot[`associateByFile - import_map_no_resolve/deno.json 1`] = `[]`; + +snapshot[`write - import_map_no_resolve/deno.json 1`] = `[]`; + snapshot[`associateByFile - import_map_no_resolve/mod.ts 1`] = ` [ { @@ -635,6 +795,120 @@ snapshot[`write - relative_import/mod.ts 1`] = ` ] `; +snapshot[`associateByFile - jsonc/deno.jsonc 1`] = ` +[ + { + dependencies: [ + { + code: { + span: undefined, + specifier: "jsr:@luca/flag@1.0.0", + }, + from: { + name: "@luca/flag", + path: "", + protocol: "jsr:", + version: "1.0.0", + }, + map: { + key: "flag", + resolved: "jsr:@luca/flag@1.0.0", + }, + to: { + name: "@luca/flag", + path: "", + protocol: "jsr:", + version: "123.456.789", + }, + }, + { + code: { + span: undefined, + specifier: "https://deno.land/std@0.200.0/", + }, + from: { + name: "deno.land/std", + path: "/", + protocol: "https:", + version: "0.200.0", + }, + map: { + key: "std/", + resolved: "https://deno.land/std@0.200.0/", + }, + to: { + name: "deno.land/std", + path: "/", + protocol: "https:", + version: "123.456.789", + }, + }, + { + code: { + span: undefined, + specifier: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + }, + from: { + name: "deno.land/x/deno_graph", + path: "/mod.ts", + protocol: "https:", + version: "0.50.0", + }, + map: { + key: "deno_graph", + resolved: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + }, + to: { + name: "deno.land/x/deno_graph", + path: "/mod.ts", + protocol: "https:", + version: "123.456.789", + }, + }, + { + code: { + span: undefined, + specifier: "npm:node-emoji@1.0.0", + }, + from: { + name: "node-emoji", + path: "", + protocol: "npm:", + version: "1.0.0", + }, + map: { + key: "node-emoji", + resolved: "npm:node-emoji@1.0.0", + }, + to: { + name: "node-emoji", + path: "", + protocol: "npm:", + version: "123.456.789", + }, + }, + ], + kind: "import_map", + }, +] +`; + +snapshot[`write - jsonc/deno.jsonc 1`] = ` +[ + '{ + "imports": { + "std/": "https://deno.land/std@123.456.789/", + "deno_graph": "https://deno.land/x/deno_graph@123.456.789/mod.ts", + "node-emoji": "npm:node-emoji@123.456.789", + "flag": "jsr:@luca/flag@123.456.789", + // map root to the project root + "/": "./" + } +} +', +] +`; + snapshot[`associateByFile - updated_and_outdated.ts 1`] = ` [ { diff --git a/test/snapshots/update_test.ts.snap b/test/snapshots/update_test.ts.snap index 1ec512d5..dddc5fd9 100644 --- a/test/snapshots/update_test.ts.snap +++ b/test/snapshots/update_test.ts.snap @@ -585,6 +585,90 @@ snapshot[`collect - relative_import - mod.ts 1`] = ` } `; +snapshot[`collect - jsonc - deno.jsonc 1`] = ` +{ + code: { + span: undefined, + specifier: "jsr:@luca/flag@1.0.0", + }, + from: { + name: "@luca/flag", + path: "", + protocol: "jsr:", + version: "1.0.0", + }, + to: { + name: "@luca/flag", + path: "", + protocol: "jsr:", + version: "123.456.789", + }, +} +`; + +snapshot[`collect - jsonc - deno.jsonc 2`] = ` +{ + code: { + span: undefined, + specifier: "https://deno.land/std@0.200.0/", + }, + from: { + name: "deno.land/std", + path: "/", + protocol: "https:", + version: "0.200.0", + }, + to: { + name: "deno.land/std", + path: "/", + protocol: "https:", + version: "123.456.789", + }, +} +`; + +snapshot[`collect - jsonc - deno.jsonc 3`] = ` +{ + code: { + span: undefined, + specifier: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + }, + from: { + name: "deno.land/x/deno_graph", + path: "/mod.ts", + protocol: "https:", + version: "0.50.0", + }, + to: { + name: "deno.land/x/deno_graph", + path: "/mod.ts", + protocol: "https:", + version: "123.456.789", + }, +} +`; + +snapshot[`collect - jsonc - deno.jsonc 4`] = ` +{ + code: { + span: undefined, + specifier: "npm:node-emoji@1.0.0", + }, + from: { + name: "node-emoji", + path: "", + protocol: "npm:", + version: "1.0.0", + }, + to: { + name: "node-emoji", + path: "", + protocol: "npm:", + version: "123.456.789", + }, +} +`; + snapshot[`collect - updated_and_outdated.ts 1`] = ` { code: {