diff --git a/cli.ts b/cli.ts index 0390e561..c69608b3 100644 --- a/cli.ts +++ b/cli.ts @@ -8,7 +8,7 @@ import { URI } from "./lib/uri.ts"; import { DependencyUpdate } from "./lib/update.ts"; import { FileUpdate } from "./lib/file.ts"; import { GitCommitSequence } from "./lib/git.ts"; -import { parseSemVer, resolveLatestSemVer } from "./lib/dependency.ts"; +import { parseSemVer, resolveLatestURL } from "./lib/dependency.ts"; const { gray, yellow, bold, cyan } = colors; @@ -163,7 +163,7 @@ function _list(updates: DependencyUpdate[]) { distinct( list.map((u) => { const source = URI.relative(u.map?.source ?? u.referrer); - return ` ${source} ` + gray(u.version.from); + return ` ${source} ` + gray(u.version.from ?? ""); }), ).forEach((line) => console.log(line)); } @@ -328,6 +328,17 @@ function _formatPrefix(prefix: string | undefined) { return prefix ? prefix.trimEnd() + " " : ""; } +async function _version() { + const version = parseSemVer(import.meta.url) ?? + await $.progress("Fetching version info").with(async () => { + const url = await resolveLatestURL( + new URL("https://deno.land/x/molt/cli.ts"), + ); + return url ? parseSemVer(url.href) : undefined; + }) ?? "unknown"; + console.log(version); +} + const main = new Command() .name("molt") .description("A tool for updating dependencies in Deno projects") @@ -337,15 +348,7 @@ const main = new Command() .versionOption( "-v, --version", "Print version info.", - async function () { - const version = parseSemVer(import.meta.url) ?? - await $.progress("Fetching version info").with(() => { - return resolveLatestSemVer( - new URL("https://deno.land/x/molt@0.0.0/cli.ts"), - ); - }) ?? "unknown"; - console.log(version); - }, + _version, ) .command("check", checkCommand) .command("update", updateCommand); diff --git a/deno.json b/deno.json index f40adf06..63249a19 100644 --- a/deno.json +++ b/deno.json @@ -1,9 +1,10 @@ { "tasks": { - "cache": "deno cache ./lib/*/*.ts --reload --lock", + "cache": "deno cache ./lib/*/*.ts --lock", "lock": "deno task -q cache --lock-write && git add deno.lock", - "check": "deno check ./*.ts ./lib/*/*.ts", + "check": "deno check ./*.ts ./lib/**/*.ts", "test": "deno test -A --no-check", + "test:update": "deno task -q test -- --update", "dev": "deno fmt && deno lint && deno task lock && deno task -q check && deno task -q test", "install": "deno install -f --name molt --allow-env --allow-read --allow-net --allow-write=. --allow-run=git,deno cli.ts", "run:molt": "deno run --allow-env --allow-read --allow-write --allow-net=deno.land,registry.npmjs.org --allow-run=git,deno ./cli.ts", diff --git a/deno.lock b/deno.lock index e542eabe..8727c008 100644 --- a/deno.lock +++ b/deno.lock @@ -96,44 +96,84 @@ "https://deno.land/std@0.204.0/collections/distinct.ts": "01595bd8e608a5e2de8a8631055587e6f01d68861e463eef34f8e295713500b8", "https://deno.land/std@0.204.0/collections/max_by.ts": "9d5940986aac51b2e4feaebef9cd8bf6e1eceeee5edcf3303e334b737f99520d", "https://deno.land/std@0.204.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", + "https://deno.land/std@0.204.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", + "https://deno.land/std@0.204.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.204.0/fs/ensure_file.ts": "39ac83cc283a20ec2735e956adf5de3e8a3334e0b6820547b5772f71c49ae083", "https://deno.land/std@0.204.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8", "https://deno.land/std@0.204.0/json/common.ts": "ecd5e87d45b5f0df33238ed8b1746e1444da7f5c86ae53d0f0b04280f41a25bb", "https://deno.land/std@0.204.0/jsonc/parse.ts": "c1096e2b7ffb4996d7ed841dfdb29a4fccc78edcc55299beaa20d6fe5facf7b6", "https://deno.land/std@0.204.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", + "https://deno.land/std@0.204.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", + "https://deno.land/std@0.204.0/path/_common/common.ts": "9e4233b2eeb50f8b2ae10ecc2108f58583aea6fd3e8907827020282dc2b76143", "https://deno.land/std@0.204.0/path/_common/constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", "https://deno.land/std@0.204.0/path/_common/dirname.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", + "https://deno.land/std@0.204.0/path/_common/format.ts": "11aa62e316dfbf22c126917f5e03ea5fe2ee707386555a8f513d27ad5756cf96", + "https://deno.land/std@0.204.0/path/_common/from_file_url.ts": "ef1bf3197d2efbf0297a2bdbf3a61d804b18f2bcce45548ae112313ec5be3c22", + "https://deno.land/std@0.204.0/path/_common/glob_to_reg_exp.ts": "5c3c2b79fc2294ec803d102bd9855c451c150021f452046312819fbb6d4dc156", + "https://deno.land/std@0.204.0/path/_common/is_glob.ts": "567dce5c6656bdedfc6b3ee6c0833e1e4db2b8dff6e62148e94a917f289c06ad", "https://deno.land/std@0.204.0/path/_common/normalize.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", "https://deno.land/std@0.204.0/path/_common/normalize_string.ts": "88c472f28ae49525f9fe82de8c8816d93442d46a30d6bb5063b07ff8a89ff589", "https://deno.land/std@0.204.0/path/_common/relative.ts": "1af19d787a2a84b8c534cc487424fe101f614982ae4851382c978ab2216186b4", "https://deno.land/std@0.204.0/path/_common/strip_trailing_separators.ts": "7ffc7c287e97bdeeee31b155828686967f222cd73f9e5780bfe7dfb1b58c6c65", "https://deno.land/std@0.204.0/path/_common/to_file_url.ts": "a8cdd1633bc9175b7eebd3613266d7c0b6ae0fb0cff24120b6092ac31662f9ae", + "https://deno.land/std@0.204.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", "https://deno.land/std@0.204.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", + "https://deno.land/std@0.204.0/path/basename.ts": "04bb5ef3e86bba8a35603b8f3b69537112cdd19ce64b77f2522006da2977a5f3", + "https://deno.land/std@0.204.0/path/common.ts": "f4d061c7d0b95a65c2a1a52439edec393e906b40f1caf4604c389fae7caa80f5", "https://deno.land/std@0.204.0/path/dirname.ts": "88a0a71c21debafc4da7a4cd44fd32e899462df458fbca152390887d41c40361", "https://deno.land/std@0.204.0/path/extname.ts": "2da4e2490f3b48b7121d19fb4c91681a5e11bd6bd99df4f6f47d7a71bb6ecdf2", + "https://deno.land/std@0.204.0/path/format.ts": "3457530cc85d1b4bab175f9ae73998b34fd456c830d01883169af0681b8894fb", + "https://deno.land/std@0.204.0/path/from_file_url.ts": "e7fa233ea1dff9641e8d566153a24d95010110185a6f418dd2e32320926043f8", + "https://deno.land/std@0.204.0/path/glob.ts": "9c77cf47db1d786e2ebf66670824d03fd84ecc7c807cac24441eb9d5cb6a2986", "https://deno.land/std@0.204.0/path/is_absolute.ts": "67232b41b860571c5b7537f4954c88d86ae2ba45e883ee37d3dec27b74909d13", "https://deno.land/std@0.204.0/path/join.ts": "98d3d76c819af4a11a81d5ba2dbb319f1ce9d63fc2b615597d4bcfddd4a89a09", + "https://deno.land/std@0.204.0/path/mod.ts": "2d62a0a8b78a60e8e6f485d881bac6b61d58573b11cf585fb7c8fc50d9b20d80", + "https://deno.land/std@0.204.0/path/normalize.ts": "aa95be9a92c7bd4f9dc0ba51e942a1973e2b93d266cd74f5ca751c136d520b66", + "https://deno.land/std@0.204.0/path/parse.ts": "d87ff0deef3fb495bc0d862278ff96da5a06acf0625ca27769fc52ac0d3d6ece", "https://deno.land/std@0.204.0/path/posix/_util.ts": "ecf49560fedd7dd376c6156cc5565cad97c1abe9824f4417adebc7acc36c93e5", + "https://deno.land/std@0.204.0/path/posix/basename.ts": "a630aeb8fd8e27356b1823b9dedd505e30085015407caa3396332752f6b8406a", + "https://deno.land/std@0.204.0/path/posix/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", "https://deno.land/std@0.204.0/path/posix/dirname.ts": "f48c9c42cc670803b505478b7ef162c7cfa9d8e751b59d278b2ec59470531472", "https://deno.land/std@0.204.0/path/posix/extname.ts": "ee7f6571a9c0a37f9218fbf510c440d1685a7c13082c348d701396cc795e0be0", + "https://deno.land/std@0.204.0/path/posix/format.ts": "b94876f77e61bfe1f147d5ccb46a920636cd3cef8be43df330f0052b03875968", + "https://deno.land/std@0.204.0/path/posix/from_file_url.ts": "b97287a83e6407ac27bdf3ab621db3fccbf1c27df0a1b1f20e1e1b5acf38a379", + "https://deno.land/std@0.204.0/path/posix/glob.ts": "86c3f06d1c98303613c74650961c3e24bdb871cde2a97c3ae7f0f6d4abbef445", "https://deno.land/std@0.204.0/path/posix/is_absolute.ts": "159900a3422d11069d48395568217eb7fc105ceda2683d03d9b7c0f0769e01b8", "https://deno.land/std@0.204.0/path/posix/join.ts": "0c0d84bdc344876930126640011ec1b888e6facf74153ffad9ef26813aa2a076", + "https://deno.land/std@0.204.0/path/posix/mod.ts": "6bfa8a42d85345b12dbe8571028ca2c62d460b6ef968125e498602b43b6cf6b6", "https://deno.land/std@0.204.0/path/posix/normalize.ts": "11de90a94ab7148cc46e5a288f7d732aade1d616bc8c862f5560fa18ff987b4b", + "https://deno.land/std@0.204.0/path/posix/parse.ts": "199208f373dd93a792e9c585352bfc73a6293411bed6da6d3bc4f4ef90b04c8e", "https://deno.land/std@0.204.0/path/posix/relative.ts": "e2f230608b0f083e6deaa06e063943e5accb3320c28aef8d87528fbb7fe6504c", "https://deno.land/std@0.204.0/path/posix/resolve.ts": "51579d83159d5c719518c9ae50812a63959bbcb7561d79acbdb2c3682236e285", + "https://deno.land/std@0.204.0/path/posix/separator.ts": "0b6573b5f3269a3164d8edc9cefc33a02dd51003731c561008c8bb60220ebac1", "https://deno.land/std@0.204.0/path/posix/to_file_url.ts": "08d43ea839ee75e9b8b1538376cfe95911070a655cd312bc9a00f88ef14967b6", + "https://deno.land/std@0.204.0/path/posix/to_namespaced_path.ts": "c9228a0e74fd37e76622cd7b142b8416663a9b87db643302fa0926b5a5c83bdc", "https://deno.land/std@0.204.0/path/relative.ts": "23d45ede8b7ac464a8299663a43488aad6b561414e7cbbe4790775590db6349c", "https://deno.land/std@0.204.0/path/resolve.ts": "5b184efc87155a0af9fa305ff68a109e28de9aee81fc3e77cd01380f19daf867", + "https://deno.land/std@0.204.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", "https://deno.land/std@0.204.0/path/to_file_url.ts": "edaafa089e0bce386e1b2d47afe7c72e379ff93b28a5829a5885e4b6c626d864", + "https://deno.land/std@0.204.0/path/to_namespaced_path.ts": "cf8734848aac3c7527d1689d2adf82132b1618eff3cc523a775068847416b22a", "https://deno.land/std@0.204.0/path/windows/_util.ts": "f32b9444554c8863b9b4814025c700492a2b57ff2369d015360970a1b1099d54", + "https://deno.land/std@0.204.0/path/windows/basename.ts": "8a9dbf7353d50afbc5b221af36c02a72c2d1b2b5b9f7c65bf6a5a2a0baf88ad3", + "https://deno.land/std@0.204.0/path/windows/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", "https://deno.land/std@0.204.0/path/windows/dirname.ts": "5c2aa541384bf0bd9aca821275d2a8690e8238fa846198ef5c7515ce31a01a94", + "https://deno.land/std@0.204.0/path/windows/extname.ts": "07f4fa1b40d06a827446b3e3bcc8d619c5546b079b8ed0c77040bbef716c7614", + "https://deno.land/std@0.204.0/path/windows/format.ts": "343019130d78f172a5c49fdc7e64686a7faf41553268961e7b6c92a6d6548edf", + "https://deno.land/std@0.204.0/path/windows/from_file_url.ts": "d53335c12b0725893d768be3ac6bf0112cc5b639d2deb0171b35988493b46199", + "https://deno.land/std@0.204.0/path/windows/glob.ts": "0286fb89ecd21db5cbf3b6c79e2b87c889b03f1311e66fb769e6b905d4142332", "https://deno.land/std@0.204.0/path/windows/is_absolute.ts": "245b56b5f355ede8664bd7f080c910a97e2169972d23075554ae14d73722c53c", "https://deno.land/std@0.204.0/path/windows/join.ts": "e6600bf88edeeef4e2276e155b8de1d5dec0435fd526ba2dc4d37986b2882f16", + "https://deno.land/std@0.204.0/path/windows/mod.ts": "c3d1a36fbf9f6db1320bcb4fbda8de011d25461be3497105e15cbea1e3726198", "https://deno.land/std@0.204.0/path/windows/normalize.ts": "9deebbf40c81ef540b7b945d4ccd7a6a2c5a5992f791e6d3377043031e164e69", + "https://deno.land/std@0.204.0/path/windows/parse.ts": "120faf778fe1f22056f33ded069b68e12447668fcfa19540c0129561428d3ae5", "https://deno.land/std@0.204.0/path/windows/relative.ts": "026855cd2c36c8f28f1df3c6fbd8f2449a2aa21f48797a74700c5d872b86d649", "https://deno.land/std@0.204.0/path/windows/resolve.ts": "5ff441ab18a2346abadf778121128ee71bda4d0898513d4639a6ca04edca366b", + "https://deno.land/std@0.204.0/path/windows/separator.ts": "ae21f27015f10510ed1ac4a0ba9c4c9c967cbdd9d9e776a3e4967553c397bd5d", "https://deno.land/std@0.204.0/path/windows/to_file_url.ts": "8e9ea9e1ff364aa06fa72999204229952d0a279dbb876b7b838b2b2fea55cce3", + "https://deno.land/std@0.204.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", "https://deno.land/std@0.204.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", "https://deno.land/std@0.204.0/testing/bdd.ts": "3f446df5ef8e856a869e8eec54c8482590415741ff0b6358a00c43486cc15769", + "https://deno.land/std@0.204.0/testing/snapshot.ts": "d53cc4ad3250e3a826df9a1a90bc19c9a92c8faa8fd508d16b5e6ce8699310ca", "https://deno.land/x/async@v2.0.2/mutex.ts": "312dcad7468c82f84fd018be157df451361ed19bdc12fd59af8d12b2e6c3ae28", "https://deno.land/x/cliffy@v1.0.0-rc.3/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", "https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/ansi_escapes.ts": "193b3c3a4e520274bd8322ca4cab1c3ce38070bed1898cb2ade12a585dddd7c9", diff --git a/lib/__snapshots__/file_test.ts.snap b/lib/__snapshots__/file_test.ts.snap new file mode 100644 index 00000000..d5c5e37c --- /dev/null +++ b/lib/__snapshots__/file_test.ts.snap @@ -0,0 +1,51 @@ +export const snapshot = {}; + +snapshot[`writeAll > direct import 1`] = ` +'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph@0.59.1/mod.ts"; +import { emojify } from "npm:node-emoji@2.1.0"; +import { noop } from "./lib.ts"; +' +`; + +snapshot[`writeAll > direct import 2`] = ` +'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +export const noop = () => {}; +' +`; + +snapshot[`writeAll > import map 1`] = ` +'{ + "imports": { + "std/": "https://deno.land/std@0.200.0/", + "deno_graph": "https://deno.land/x/deno_graph@0.59.1/mod.ts", + "node-emoji": "npm:node-emoji@2.1.0", + "/": "./" + } +}' +`; + +snapshot[`writeAll > import map 2`] = ` +'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +export const noop = () => {}; +' +`; + +snapshot[`writeAll > import map with no resolve 1`] = ` +'import {} from "https://deno.land/std@0.204.0/version.ts"; +' +`; + +snapshot[`writeAll > unversioned specifiers 1`] = ` +'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph@0.59.1/mod.ts"; +import { emojify } from "npm:node-emoji@2.1.0"; +import { noop } from "./lib.ts"; +' +`; + +snapshot[`writeAll > unversioned specifiers 2`] = ` +'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +export const noop = () => {}; +' +`; diff --git a/lib/__snapshots__/git_test.ts.snap b/lib/__snapshots__/git_test.ts.snap new file mode 100644 index 00000000..4f23f932 --- /dev/null +++ b/lib/__snapshots__/git_test.ts.snap @@ -0,0 +1,40 @@ +export const snapshot = {}; + +snapshot[`commitAll() > no grouping 1`] = ` +FileSystemFake(2) [Map] { + "file:///home/shun/molt/test/fixtures/direct-import/lib.ts" => 'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +export const noop = () => {}; +', + "file:///home/shun/molt/test/fixtures/direct-import/mod.ts" => 'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph@0.59.1/mod.ts"; +import { emojify } from "npm:node-emoji@2.1.0"; +import { noop } from "./lib.ts"; +', +} +`; + +snapshot[`commitAll() > group by dependency name 1`] = ` +FileSystemFake(2) [Map] { + "file:///home/shun/molt/test/fixtures/direct-import/lib.ts" => 'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +export const noop = () => {}; +', + "file:///home/shun/molt/test/fixtures/direct-import/mod.ts" => 'import { VERSION } from "https://deno.land/std@0.200.0/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph@0.50.0/mod.ts"; +import { emojify } from "npm:node-emoji@2.1.0"; +import { noop } from "./lib.ts"; +', +} +`; + +snapshot[`commitAll() > group by module (file) name 1`] = ` +FileSystemFake(2) [Map] { + "file:///home/shun/molt/test/fixtures/direct-import/lib.ts" => 'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +export const noop = () => {}; +', + "file:///home/shun/molt/test/fixtures/direct-import/mod.ts" => 'import { VERSION } from "https://deno.land/std@0.204.0/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph@0.59.1/mod.ts"; +import { emojify } from "npm:node-emoji@2.1.0"; +import { noop } from "./lib.ts"; +', +} +`; diff --git a/lib/__snapshots__/update_test.ts.snap b/lib/__snapshots__/update_test.ts.snap new file mode 100644 index 00000000..4971ef78 --- /dev/null +++ b/lib/__snapshots__/update_test.ts.snap @@ -0,0 +1,384 @@ +export const snapshot = {}; + +snapshot[`_create > https://deno.land/std 1`] = ` +{ + code: { + span: undefined, + specifier: "https://deno.land/std@0.1.0/version.ts", + }, + map: undefined, + name: "deno.land/std", + path: "/version.ts", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "https://deno.land/std@0.1.0/version.ts", + to: "https://deno.land/std@0.204.0/version.ts", + }, + type: undefined, + version: { + from: "0.1.0", + to: "0.204.0", + }, +} +`; + +snapshot[`_create > https://deno.land/std - unversioned 1`] = ` +{ + code: { + span: undefined, + specifier: "https://deno.land/std/version.ts", + }, + map: undefined, + name: "deno.land/std", + path: "/version.ts", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "https://deno.land/std/version.ts", + to: "https://deno.land/std@0.204.0/version.ts", + }, + type: undefined, + version: { + from: undefined, + to: "0.204.0", + }, +} +`; + +snapshot[`_create > https://deno.land/x/deno_graph 1`] = ` +{ + code: { + span: undefined, + specifier: "https://deno.land/x/deno_graph@0.1.0/mod.ts", + }, + map: undefined, + name: "deno.land/x/deno_graph", + path: "/mod.ts", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "https://deno.land/x/deno_graph@0.1.0/mod.ts", + to: "https://deno.land/x/deno_graph@0.59.1/mod.ts", + }, + type: undefined, + version: { + from: "0.1.0", + to: "0.59.1", + }, +} +`; + +snapshot[`_create > npm:node-emoji 1`] = ` +{ + code: { + span: undefined, + specifier: "npm:node-emoji@1.0.0", + }, + map: undefined, + name: "node-emoji", + path: "", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "npm:node-emoji@1.0.0", + to: "npm:node-emoji@2.1.0", + }, + type: undefined, + version: { + from: "1.0.0", + to: "2.1.0", + }, +} +`; + +snapshot[`_create > npm:node-emoji - unversioned 1`] = ` +{ + code: { + span: undefined, + specifier: "npm:node-emoji", + }, + map: undefined, + name: "node-emoji", + path: "", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "npm:node-emoji", + to: "npm:node-emoji@2.1.0", + }, + type: undefined, + version: { + from: undefined, + to: "2.1.0", + }, +} +`; + +snapshot[`collect > direct import 1`] = ` +[ + { + code: { + span: { + end: { + character: 66, + line: 0, + }, + start: { + character: 24, + line: 0, + }, + }, + specifier: "https://deno.land/std@0.200.0/version.ts", + }, + map: undefined, + name: "deno.land/std", + path: "/version.ts", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/lib.ts", + specifier: { + from: "https://deno.land/std@0.200.0/version.ts", + to: "https://deno.land/std@0.204.0/version.ts", + }, + type: undefined, + version: { + from: "0.200.0", + to: "0.204.0", + }, + }, + { + code: { + span: { + end: { + character: 74, + line: 1, + }, + start: { + character: 28, + line: 1, + }, + }, + specifier: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + }, + map: undefined, + name: "deno.land/x/deno_graph", + path: "/mod.ts", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + to: "https://deno.land/x/deno_graph@0.59.1/mod.ts", + }, + type: undefined, + version: { + from: "0.50.0", + to: "0.59.1", + }, + }, + { + code: { + span: { + end: { + character: 46, + line: 2, + }, + start: { + character: 24, + line: 2, + }, + }, + specifier: "npm:node-emoji@1.0.0", + }, + map: undefined, + name: "node-emoji", + path: "", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "npm:node-emoji@1.0.0", + to: "npm:node-emoji@2.1.0", + }, + type: undefined, + version: { + from: "1.0.0", + to: "2.1.0", + }, + }, + { + code: { + span: { + end: { + character: 66, + line: 0, + }, + start: { + character: 24, + line: 0, + }, + }, + specifier: "https://deno.land/std@0.200.0/version.ts", + }, + map: undefined, + name: "deno.land/std", + path: "/version.ts", + referrer: "file:///home/shun/molt/test/fixtures/direct-import/mod.ts", + specifier: { + from: "https://deno.land/std@0.200.0/version.ts", + to: "https://deno.land/std@0.204.0/version.ts", + }, + type: undefined, + version: { + from: "0.200.0", + to: "0.204.0", + }, + }, +] +`; + +snapshot[`collect > import map 1`] = ` +[ + { + code: { + span: { + end: { + character: 66, + line: 0, + }, + start: { + character: 24, + line: 0, + }, + }, + specifier: "https://deno.land/std@0.200.0/version.ts", + }, + map: undefined, + name: "deno.land/std", + path: "/version.ts", + referrer: "file:///home/shun/molt/test/fixtures/import-map/lib.ts", + specifier: { + from: "https://deno.land/std@0.200.0/version.ts", + to: "https://deno.land/std@0.204.0/version.ts", + }, + type: undefined, + version: { + from: "0.200.0", + to: "0.204.0", + }, + }, + { + code: { + span: { + end: { + character: 40, + line: 1, + }, + start: { + character: 28, + line: 1, + }, + }, + specifier: "deno_graph", + }, + map: { + from: "deno_graph", + source: "file:///home/shun/molt/test/fixtures/import-map/deno.json", + to: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + }, + name: "deno.land/x/deno_graph", + path: "/mod.ts", + referrer: "file:///home/shun/molt/test/fixtures/import-map/mod.ts", + specifier: { + from: "https://deno.land/x/deno_graph@0.50.0/mod.ts", + to: "https://deno.land/x/deno_graph@0.59.1/mod.ts", + }, + type: undefined, + version: { + from: "0.50.0", + to: "0.59.1", + }, + }, + { + code: { + span: { + end: { + character: 36, + line: 2, + }, + start: { + character: 24, + line: 2, + }, + }, + specifier: "node-emoji", + }, + map: { + from: "node-emoji", + source: "file:///home/shun/molt/test/fixtures/import-map/deno.json", + to: "npm:node-emoji@1.0.0", + }, + name: "node-emoji", + path: "", + referrer: "file:///home/shun/molt/test/fixtures/import-map/mod.ts", + specifier: { + from: "npm:node-emoji@1.0.0", + to: "npm:node-emoji@2.1.0", + }, + type: undefined, + version: { + from: "1.0.0", + to: "2.1.0", + }, + }, + { + code: { + span: { + end: { + character: 40, + line: 0, + }, + start: { + character: 24, + line: 0, + }, + }, + specifier: "std/version.ts", + }, + map: { + from: "std/", + source: "file:///home/shun/molt/test/fixtures/import-map/deno.json", + to: "https://deno.land/std@0.200.0/", + }, + name: "deno.land/std", + path: "/version.ts", + referrer: "file:///home/shun/molt/test/fixtures/import-map/mod.ts", + specifier: { + from: "https://deno.land/std@0.200.0/version.ts", + to: "https://deno.land/std@0.204.0/version.ts", + }, + type: undefined, + version: { + from: "0.200.0", + to: "0.204.0", + }, + }, +] +`; + +snapshot[`applyToModule > https://deno.land/x/deno_graph 1`] = ` +'import { VERSION } from "https://deno.land/std@0.200.0/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph@0.59.1/mod.ts"; +import { emojify } from "npm:node-emoji@1.0.0"; +import { noop } from "./lib.ts"; +' +`; + +snapshot[`applyToModule > npm:node-emoji 1`] = ` +'import { VERSION } from "https://deno.land/std@0.200.0/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph@0.50.0/mod.ts"; +import { emojify } from "npm:node-emoji@2.1.0"; +import { noop } from "./lib.ts"; +' +`; + +snapshot[`applyToImportMap > deno_graph 1`] = ` +'{ + "imports": { + "std/": "https://deno.land/std@0.200.0/", + "deno_graph": "https://deno.land/x/deno_graph@0.59.1/mod.ts", + "node-emoji": "npm:node-emoji@1.0.0", + "/": "./" + } +}' +`; diff --git a/lib/dependency.ts b/lib/dependency.ts index dc0b853b..ec2d8ff2 100644 --- a/lib/dependency.ts +++ b/lib/dependency.ts @@ -23,25 +23,22 @@ export function parseSemVer( return match[0].slice(1) as SemVerString; } -export interface Dependency { +export interface DependencyProps { name: string; - version: SemVerString; - path: Path; + version?: SemVerString; + path?: Path; } export const Dependency = { parseProps, - resolveLatestSemVer, + resolveLatestURL, }; -function parseProps( - url: URL, -): Maybe { +function parseProps(url: URL): DependencyProps { const body = url.hostname + url.pathname; const semver = parseSemVer(url.href); if (!semver) { - // The specifier does not contain a semver. - return; + return { name: body }; } const atSemver = "@" + semver; const name = body.split(atSemver)[0]; @@ -49,30 +46,26 @@ function parseProps( return { name, version: semver, path: path as Path }; } -export async function resolveLatestSemVer( +export async function resolveLatestURL( url: URL, -): Promise> { +): Promise> { const props = parseProps(url); - if (!props) { - // The specifier is does not contain a semver. - return; - } - await LatestSemverCache.lock(props.name); + await LatestUrlCache.lock(props.name); const result = await _resolve(url, props); - LatestSemverCache.unlock(props.name); + LatestUrlCache.unlock(props.name); return result; } async function _resolve( url: URL, - props: Dependency, -): Promise> { - const cached = LatestSemverCache.get(props.name); + props: DependencyProps, +): Promise> { + const cached = LatestUrlCache.get(props.name); if (cached) { return cached; } if (cached === null) { - // The dependency is already found to be up to date. + // The dependency is already found to be up to date or unable to resolve. return; } switch (url.protocol) { @@ -91,35 +84,36 @@ async function _resolve( `Could not find the latest version of ${props.name} from registry.`, ); } - const latestSemVer: string = json["dist-tags"].latest; + const latestSemVer = json["dist-tags"].latest as SemVerString; if (latestSemVer === props.version) { // The dependency is up to date - LatestSemverCache.set(props.name, null); + LatestUrlCache.set(props.name, null); return; } - return LatestSemverCache.set(props.name, latestSemVer as SemVerString); + return LatestUrlCache.set( + props.name, + new URL(`npm:${props.name}@${latestSemVer}${props.path}`), + ); } case "http:": case "https:": { const response = await fetch( - url.protocol + "//" + props.name + props.path, + url.protocol + "//" + props.name + (props.path ?? ""), { method: "HEAD" }, ); await response.arrayBuffer(); if (!response.redirected) { - // The host did not redirect to a url with semver - LatestSemverCache.set(props.name, null); + // The host did not redirect + LatestUrlCache.set(props.name, null); return; } - const latestSemVer = parseSemVer(response.url); - if ( - !latestSemVer || // The host redirected to a url without semver - latestSemVer === props.version // The dependency is already up to date - ) { - LatestSemverCache.set(props.name, null); + const latest = new URL(response.url); + if (latest === url) { + // The dependency is already up to date + LatestUrlCache.set(props.name, null); return; } - return LatestSemverCache.set(props.name, latestSemVer); + return LatestUrlCache.set(props.name, latest); } case "node:": case "file:": @@ -130,9 +124,9 @@ async function _resolve( } } -class LatestSemverCache { +class LatestUrlCache { static #mutex = new Map(); - static #cache = new Map(); + static #cache = new Map(); static lock(name: string): Promise { const mutex = this.#mutex.get(name) ?? @@ -146,15 +140,15 @@ class LatestSemverCache { mutex.release(); } - static get(name: string): SemVerString | null | undefined { + static get(name: string): URL | null | undefined { return this.#cache.get(name); } - static set( + static set( name: string, - semver: T, + url: T, ): T { - this.#cache.set(name, semver); - return semver; + this.#cache.set(name, url); + return url; } } diff --git a/lib/dependency_test.ts b/lib/dependency_test.ts index b75c28f5..4d5c4581 100644 --- a/lib/dependency_test.ts +++ b/lib/dependency_test.ts @@ -1,6 +1,7 @@ import { describe, it } from "./std/testing.ts"; import { assertEquals } from "./std/assert.ts"; import { Dependency, parseSemVer } from "./dependency.ts"; +import { Path, SemVerString } from "./types.ts"; describe("parseSemVer", () => { it("https://deno.land/std", () => @@ -23,8 +24,8 @@ describe("parseProps()", () => { ), { name: "deno.land/std", - version: "0.1.0", - path: "/version.ts", + version: "0.1.0" as SemVerString, + path: "/version.ts" as Path, }, )); it("https://deno.land/std (no semver)", () => @@ -32,7 +33,9 @@ describe("parseProps()", () => { Dependency.parseProps( new URL("https://deno.land/std/version.ts"), ), - undefined, + { + name: "deno.land/std/version.ts", + }, )); it("https://deno.land/x/hono (with a leading 'v')", () => assertEquals( @@ -41,8 +44,8 @@ describe("parseProps()", () => { ), { name: "deno.land/x/hono", - version: "v0.1.0", - path: "", + version: "v0.1.0" as SemVerString, + path: "" as Path, }, )); it("npm:node-emoji", () => @@ -52,8 +55,8 @@ describe("parseProps()", () => { ), { name: "node-emoji", - version: "1.0.0", - path: "", + version: "1.0.0" as SemVerString, + path: "" as Path, }, )); }); diff --git a/lib/file_test.ts b/lib/file_test.ts index b27befef..fa25f77d 100644 --- a/lib/file_test.ts +++ b/lib/file_test.ts @@ -1,14 +1,14 @@ import { afterEach, + assertSnapshot, assertSpyCalls, beforeEach, describe, it, - type Stub, } from "./std/testing.ts"; import { assertArrayIncludes, assertEquals } from "./std/assert.ts"; import { - assertSomeSpyCallArg, + assertFindSpyCallArg, createWriteTextFileStub, FileSystemFake, } from "./testing.ts"; @@ -56,7 +56,7 @@ describe("collect", () => { describe("writeAll", () => { let fs: FileSystemFake; - let writeTextFileStub: Stub; + let writeTextFileStub: ReturnType; afterEach(() => { writeTextFileStub.restore(); @@ -67,25 +67,27 @@ describe("writeAll", () => { writeTextFileStub = createWriteTextFileStub(fs); }); - it("direct import", async () => { + it("direct import", async (t) => { const results = await FileUpdate.collect( await DependencyUpdate.collect("./test/fixtures/direct-import/mod.ts"), ); await FileUpdate.writeAll(results); - assertSomeSpyCallArg( + const call_1 = assertFindSpyCallArg( writeTextFileStub, 0, new URL("../test/fixtures/direct-import/mod.ts", import.meta.url), ); - assertSomeSpyCallArg( + await assertSnapshot(t, call_1.args[1]); + const call_2 = assertFindSpyCallArg( writeTextFileStub, 0, new URL("../test/fixtures/direct-import/lib.ts", import.meta.url), ); + await assertSnapshot(t, call_2.args[1]); assertSpyCalls(writeTextFileStub, 2); }); - it("import map", async () => { + it("import map", async (t) => { const results = await FileUpdate.collect( await DependencyUpdate.collect( "./test/fixtures/import-map/mod.ts", @@ -93,20 +95,22 @@ describe("writeAll", () => { ), ); await FileUpdate.writeAll(results); - assertSomeSpyCallArg( + const call_1 = assertFindSpyCallArg( writeTextFileStub, 0, new URL("../test/fixtures/import-map/deno.json", import.meta.url), ); - assertSomeSpyCallArg( + await assertSnapshot(t, call_1.args[1]); + const call_2 = assertFindSpyCallArg( writeTextFileStub, 0, new URL("../test/fixtures/import-map/lib.ts", import.meta.url), ); + await assertSnapshot(t, call_2.args[1]); assertSpyCalls(writeTextFileStub, 2); }); - it("import map with no resolve", async () => { + it("import map with no resolve", async (t) => { const results = await FileUpdate.collect( await DependencyUpdate.collect( "./test/fixtures/import-map-no-resolve/mod.ts", @@ -114,11 +118,32 @@ describe("writeAll", () => { ), ); await FileUpdate.writeAll(results); - assertSomeSpyCallArg( + const call = assertFindSpyCallArg( writeTextFileStub, 0, new URL("../test/fixtures/import-map-no-resolve/mod.ts", import.meta.url), ); + await assertSnapshot(t, call.args[1]); assertSpyCalls(writeTextFileStub, 1); }); + + it("unversioned specifiers", async (t) => { + const results = await FileUpdate.collect( + await DependencyUpdate.collect("./test/fixtures/unversioned/mod.ts"), + ); + await FileUpdate.writeAll(results); + const call_1 = assertFindSpyCallArg( + writeTextFileStub, + 0, + new URL("../test/fixtures/unversioned/mod.ts", import.meta.url), + ); + await assertSnapshot(t, call_1.args[1]); + const call_2 = assertFindSpyCallArg( + writeTextFileStub, + 0, + new URL("../test/fixtures/unversioned/lib.ts", import.meta.url), + ); + await assertSnapshot(t, call_2.args[1]); + assertSpyCalls(writeTextFileStub, 2); + }); }); diff --git a/lib/git_test.ts b/lib/git_test.ts index 23df3d6d..4ab110ac 100644 --- a/lib/git_test.ts +++ b/lib/git_test.ts @@ -1,15 +1,14 @@ import { afterAll, + assertSnapshot, assertSpyCalls, beforeAll, beforeEach, - type ConstructorSpy, describe, it, - type Stub, } from "./std/testing.ts"; import { - assertSomeSpyCall, + assertFindSpyCall, createCommandStub, createReadTextFileStub, createWriteTextFileStub, @@ -26,9 +25,9 @@ function normalizePath(path: string) { describe("commitAll()", () => { let updates: DependencyUpdate[]; let fileSystemFake: FileSystemFake; - let writeTextFileStub: Stub; - let readTextFileStub: Stub; - let CommandStub: ConstructorSpy; + let writeTextFileStub: ReturnType; + let readTextFileStub: ReturnType; + let CommandStub: ReturnType; beforeAll(async () => { updates = await DependencyUpdate.collect( @@ -54,7 +53,7 @@ describe("commitAll()", () => { }); // "git add src/fixtures/mod.ts src/fixtures/lib.ts", - it("no grouping", async () => { + it("no grouping", async (t) => { await commitAll(updates); // TODO: Can't test this because of the order of targets is not guaranteed. // assertSomeSpyCall(CommandStub, { @@ -63,39 +62,40 @@ describe("commitAll()", () => { // { args: ["add", "src/fixtures/mod.ts", "src/fixtures/lib.ts"] }, // ], // }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { args: ["commit", "-m", "build(deps): update dependencies"] }, ], }); assertSpyCalls(CommandStub, 2); + await assertSnapshot(t, fileSystemFake); }); - it("group by dependency name", async () => { + it("group by dependency name", async (t) => { await commitAll(updates, { groupBy: (update) => update.name, composeCommitMessage: ({ group }) => `build(deps): update ${group}`, }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { args: ["add", normalizePath("test/fixtures/direct-import/mod.ts")] }, ], }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { args: ["commit", "-m", "build(deps): update node-emoji"] }, ], }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { args: ["add", normalizePath("test/fixtures/direct-import/mod.ts")] }, ], }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { @@ -116,27 +116,28 @@ describe("commitAll()", () => { // }, // ], // }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { args: ["commit", "-m", "build(deps): update deno.land/std"] }, ], }); assertSpyCalls(CommandStub, 6); + await assertSnapshot(t, fileSystemFake); }); - it("group by module (file) name", async () => { + it("group by module (file) name", async (t) => { await commitAll(updates, { groupBy: (update) => URI.relative(update.referrer), composeCommitMessage: ({ group }) => `build(deps): update ${group}`, }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { args: ["add", normalizePath("test/fixtures/direct-import/mod.ts")] }, ], }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { @@ -150,13 +151,13 @@ describe("commitAll()", () => { }, ], }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { args: ["add", normalizePath("test/fixtures/direct-import/lib.ts")] }, ], }); - assertSomeSpyCall(CommandStub, { + assertFindSpyCall(CommandStub, { args: [ "git", { @@ -171,5 +172,6 @@ describe("commitAll()", () => { ], }); assertSpyCalls(CommandStub, 4); + await assertSnapshot(t, fileSystemFake); }); }); diff --git a/lib/import_map_test.ts b/lib/import_map_test.ts index 6eb4bc48..2ab5dcec 100644 --- a/lib/import_map_test.ts +++ b/lib/import_map_test.ts @@ -20,6 +20,10 @@ describe("readFromJson()", () => { URI.from("../test/fixtures/import-map/deno.json", import.meta.url), ); assertExists(importMap); + assertEquals( + importMap.specifier, + URI.from("./test/fixtures/import-map/deno.json"), + ); }); it("test/fixtures/import-map-referred/import_map.json", async () => { const importMap = await ImportMap.readFromJson( diff --git a/lib/std/testing.ts b/lib/std/testing.ts index 9d2fe12c..21f26109 100644 --- a/lib/std/testing.ts +++ b/lib/std/testing.ts @@ -6,6 +6,7 @@ export { describe, it, } from "https://deno.land/std@0.204.0/testing/bdd.ts"; +export { assertSnapshot } from "https://deno.land/std@0.204.0/testing/snapshot.ts"; export { assertSpyCall, assertSpyCallArg, @@ -14,6 +15,7 @@ export { type ExpectedSpyCall, type Spy, spy, + type SpyCall, type Stub, stub, } from "https://pax.deno.dev/hasundue/deno_std@feat-constructor-spy/testing/mock.ts"; diff --git a/lib/testing.ts b/lib/testing.ts index d674cb78..83bcb299 100644 --- a/lib/testing.ts +++ b/lib/testing.ts @@ -9,8 +9,12 @@ import { stub, } from "./std/testing.ts"; import { AssertionError } from "./std/assert.ts"; +import { URI } from "./uri.ts"; -export function createCommandStub(): ConstructorSpy { +export function createCommandStub(): ConstructorSpy< + Deno.Command, + ConstructorParameters +> { const CommandSpy = spy(Deno.Command); return class extends CommandSpy { #output: Deno.CommandOutput = { @@ -35,7 +39,7 @@ export function createCommandStub(): ConstructorSpy { }; } -export class FileSystemFake extends Map {} +export class FileSystemFake extends Map, string> {} export function createReadTextFileStub( fs: FileSystemFake, @@ -43,35 +47,33 @@ export function createReadTextFileStub( readThrough?: boolean; }, ): Stub { - const decoder = new TextDecoder(); + const original = Deno.readTextFile; return stub( Deno, "readTextFile", async (path) => { - const data = fs.get(path) ?? options?.readThrough - ? await Deno.readFile(path) + return fs.get(URI.from(path)) ?? options?.readThrough + ? await original(path) : _throw(new Deno.errors.NotFound(`File not found: ${path}`)); - return decoder.decode(data); }, ); } export function createWriteTextFileStub( fs: FileSystemFake, -): Stub { - const encoder = new TextEncoder(); +) { return stub( Deno, "writeTextFile", // deno-lint-ignore require-await async (path, data) => { - fs.set(path, encoder.encode(data.toString())); + fs.set(URI.from(path), data.toString()); }, ); } /** Asserts that a spy is called as expected at any index. */ -export function assertSomeSpyCall< +export function assertFindSpyCall< Self, Args extends unknown[], Return, @@ -79,7 +81,7 @@ export function assertSomeSpyCall< spy: Spy, expected: ExpectedSpyCall, ) { - const some = spy.calls.some((_, index) => { + const call = spy.calls.find((_, index) => { try { assertSpyCall(spy, index, expected); return true; @@ -87,12 +89,13 @@ export function assertSomeSpyCall< return false; } }); - if (!some) { + if (!call) { throw new AssertionError("Expected spy call does not exist"); } + return call; } -export function assertSomeSpyCallArg< +export function assertFindSpyCallArg< Self, Args extends unknown[], Return, @@ -102,7 +105,7 @@ export function assertSomeSpyCallArg< argIndex: number, expected: ExpectedArg, ) { - const some = spy.calls.some((_, index) => { + const call = spy.calls.find((_, index) => { try { assertSpyCallArg(spy, index, argIndex, expected); return true; @@ -110,9 +113,10 @@ export function assertSomeSpyCallArg< return false; } }); - if (!some) { + if (!call) { throw new AssertionError("Expected spy call does not exist"); } + return call; } /** Utility function to throw an error. */ diff --git a/lib/update.ts b/lib/update.ts index 8adf771a..e2974498 100644 --- a/lib/update.ts +++ b/lib/update.ts @@ -6,21 +6,26 @@ import { load as defaultLoad, type ModuleJson, } from "./x/deno_graph.ts"; -import { RelativePath, URI } from "./uri.ts"; -import type { Maybe, SemVerString } from "./types.ts"; +import { URI } from "./uri.ts"; +import type { Maybe } from "./types.ts"; import { ImportMap, ImportMapJson } from "./import_map.ts"; -import { Dependency } from "./dependency.ts"; +import { Dependency, type DependencyProps, parseSemVer } from "./dependency.ts"; type DependencyJson = NonNullable[number]; +export type VersionProp = { + from?: string; + to: string; +}; + /** Representation of a dependency update. */ -export interface DependencyUpdate extends Omit { +export interface DependencyUpdate extends Omit { /** The fully resolved specifier of the dependency. */ - specifier: URI<"http" | "https" | "npm">; - version: { - from: SemVerString; - to: SemVerString; + specifier: { + from: URI<"http" | "https" | "npm">; + to: URI<"http" | "https" | "npm">; }; + version: VersionProp; /** The code of the dependency. */ code?: { /** The original specifier of the dependency appeared in the code. */ @@ -128,24 +133,22 @@ export async function _create( referrer: URI<"file">, options?: { importMap?: ImportMap; + force?: boolean; }, ): Promise { - if (!dependency?.code?.specifier && !dependency?.type?.specifier) { + const specifier = dependency.code?.specifier ?? dependency.type?.specifier; + if (!specifier) { throw new Error( `The dependency ${dependency.specifier} in ${ URI.relative(referrer) } has no resolved specifier.`, ); } - const specifier = dependency.code?.specifier ?? dependency.type?.specifier!; - const newSemVer = await Dependency.resolveLatestSemVer(new URL(specifier)); - if (!newSemVer) { - return; - } - const props = Dependency.parseProps(new URL(specifier)); - if (!props) { + const latest = await Dependency.resolveLatestURL(new URL(specifier)); + if (!latest) { return; } + const props = Dependency.parseProps(latest); const mapped = options?.importMap?.resolve( dependency.specifier, referrer, @@ -153,7 +156,10 @@ export async function _create( return { ...props, // We prefer to put the fully resolved specifier here. - specifier: URI.ensure("http", "https", "npm")(specifier), + specifier: { + from: URI.ensure("http", "https", "npm")(specifier), + to: URI.ensure("http", "https", "npm")(latest.href), + }, code: dependency.code && { // We prefer to put the original specifier here. specifier: dependency.specifier, @@ -165,8 +171,8 @@ export async function _create( span: dependency.type.span, }, version: { - from: props.version as SemVerString, - to: newSemVer as SemVerString, + from: parseSemVer(specifier), + to: props.version!, // Latest URL must have a semver }, referrer, map: mapped @@ -193,7 +199,7 @@ function applyToModule( } const line = span.start.line; const lines = content.split("\n"); - lines[line] = lines[line].replace(update.version.from, update.version.to); + lines[line] = lines[line].replace(update.specifier.from, update.specifier.to); return lines.join("\n"); } @@ -205,28 +211,12 @@ export function applyToImportMap( ): string { const json = JSON.parse(content) as ImportMapJson; json.imports[update.map!.from] = update.map!.to.replace( - update.version.from, - update.version.to, + update.specifier.from, + update.specifier.to, ); return JSON.stringify(json, null, 2); } -export type DependencyUpdateWithRelativePath = - & Omit - & { - specifier: RelativePath; - map?: { - source: RelativePath | URI<"http" | "https" | "npm">; - from: string; - to: URI<"http" | "https" | "npm">; - }; - }; - -export type VersionProp = { - from?: string; - to: string; -}; - export function createVersionProp( dependencies: DependencyUpdate[], ): Maybe { diff --git a/lib/update_test.ts b/lib/update_test.ts index bcf7c3e3..cdd05714 100644 --- a/lib/update_test.ts +++ b/lib/update_test.ts @@ -1,5 +1,5 @@ // deno-lint-ignore-file no-explicit-any -import { beforeAll, describe, it } from "./std/testing.ts"; +import { assertSnapshot, beforeAll, describe, it } from "./std/testing.ts"; import { assertEquals, assertExists, @@ -12,7 +12,7 @@ import { _create, createVersionProp, DependencyUpdate } from "./update.ts"; import { ImportMap } from "./import_map.ts"; describe("_create", () => { - it("https://deno.land/std", async () => { + it("https://deno.land/std", async (t) => { const update = await _create({ specifier: "https://deno.land/std@0.1.0/version.ts", code: { @@ -20,17 +20,19 @@ describe("_create", () => { } as any, }, URI.from("test/fixtures/direct-import/mod.ts")); assertExists(update); + await assertSnapshot(t, update); }); - it("https://deno.land/std - no semver", async () => { + it("https://deno.land/std - unversioned", async (t) => { const update = await _create({ specifier: "https://deno.land/std/version.ts", code: { specifier: "https://deno.land/std/version.ts", } as any, }, URI.from("test/fixtures/direct-import/mod.ts")); - assertEquals(update, undefined); + assertExists(update); + await assertSnapshot(t, update); }); - it("https://deno.land/x/deno_graph", async () => { + it("https://deno.land/x/deno_graph", async (t) => { const update = await _create({ specifier: "https://deno.land/x/deno_graph@0.1.0/mod.ts", code: { @@ -38,8 +40,9 @@ describe("_create", () => { } as any, }, URI.from("test/fixtures/direct-import/mod.ts")); assertExists(update); + await assertSnapshot(t, update); }); - it("npm:node-emoji", async () => { + it("npm:node-emoji", async (t) => { const update = await _create({ specifier: "npm:node-emoji@1.0.0", code: { @@ -47,6 +50,17 @@ describe("_create", () => { } as any, }, URI.from("test/fixtures/direct-import/mod.ts")); assertExists(update); + await assertSnapshot(t, update); + }); + it("npm:node-emoji - unversioned", async (t) => { + const update = await _create({ + specifier: "npm:node-emoji", + code: { + specifier: "npm:node-emoji", + } as any, + }, URI.from("test/fixtures/direct-import/mod.ts")); + assertExists(update); + await assertSnapshot(t, update); }); }); @@ -76,7 +90,10 @@ describe("_create - with import map", () => { // to: "0.203.0", }, path: "/version.ts", - specifier: "https://deno.land/std@0.200.0/version.ts", + specifier: { + from: "https://deno.land/std@0.200.0/version.ts", + // to: "https://deno.land/std@0.203.0/version.ts", + }, code: { specifier: "std/version.ts" }, referrer: URI.from("test/fixtures/import-map/mod.ts"), map: { @@ -89,13 +106,14 @@ describe("_create - with import map", () => { }); describe("collect", () => { - it("direct import", async () => { + it("direct import", async (t) => { const updates = await DependencyUpdate.collect( "./test/fixtures/direct-import/mod.ts", ); assertEquals(updates.length, 4); + await assertSnapshot(t, updates); }); - it("import map", async () => { + it("import map", async (t) => { const updates = await DependencyUpdate.collect( "./test/fixtures/import-map/mod.ts", { @@ -103,6 +121,7 @@ describe("collect", () => { }, ); assertEquals(updates.length, 4); + await assertSnapshot(t, updates); }); }); @@ -115,9 +134,9 @@ describe("applyToModule", () => { ); content = await Deno.readTextFile("./test/fixtures/direct-import/mod.ts"); }); - it("https://deno.land/x/deno_graph", () => { + it("https://deno.land/x/deno_graph", async (t) => { const update = updates.find((update) => - update.specifier.includes("deno.land/x/deno_graph") + update.specifier.from.includes("deno.land/x/deno_graph") )!; const result = DependencyUpdate.applyToModule( update, @@ -125,10 +144,11 @@ describe("applyToModule", () => { ); assertExists(result); assertNotEquals(result, content); + await assertSnapshot(t, result); }); - it("npm:node-emoji", () => { + it("npm:node-emoji", async (t) => { const update = updates.find((update) => - update.specifier.includes("node-emoji") + update.specifier.from.includes("node-emoji") )!; const result = DependencyUpdate.applyToModule( update, @@ -136,6 +156,7 @@ describe("applyToModule", () => { ); assertExists(result); assertNotEquals(result, content); + await assertSnapshot(t, result); }); }); @@ -149,7 +170,7 @@ describe("applyToImportMap", () => { ); content = await Deno.readTextFile("test/fixtures/import-map/deno.json"); }); - it("deno_graph", () => { + it("deno_graph", async (t) => { const update = updates.find((update) => update.code?.specifier === "deno_graph" )!; @@ -159,6 +180,7 @@ describe("applyToImportMap", () => { ); assertExists(result); assertNotEquals(result, content); + await assertSnapshot(t, result); }); }); diff --git a/test/fixtures/unversioned/lib.ts b/test/fixtures/unversioned/lib.ts new file mode 100644 index 00000000..9a543dbb --- /dev/null +++ b/test/fixtures/unversioned/lib.ts @@ -0,0 +1,2 @@ +import { VERSION } from "https://deno.land/std/version.ts"; +export const noop = () => {}; diff --git a/test/fixtures/unversioned/mod.ts b/test/fixtures/unversioned/mod.ts new file mode 100644 index 00000000..f2055d2c --- /dev/null +++ b/test/fixtures/unversioned/mod.ts @@ -0,0 +1,4 @@ +import { VERSION } from "https://deno.land/std/version.ts"; +import { createGraph } from "https://deno.land/x/deno_graph/mod.ts"; +import { emojify } from "npm:node-emoji"; +import { noop } from "./lib.ts";