diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml new file mode 100644 index 00000000..3df3a888 --- /dev/null +++ b/.github/workflows/update.yml @@ -0,0 +1,34 @@ +name: Update + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update: + name: Update + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure Git + run: | + git config --global user.name '${{ github.actor }}' + git config --global user.email '${{ github.actor }}@users.noreply.github.com' + + - name: Run Molt + run: deno task update --commit --pre-commit test --summary title.txt --report body.txt + + - name: Push changes + run: git push + + - name: Create Pull Request + run: | + gh pr create --title "$(cat title.txt)" --body "$(cat body.txt)" diff --git a/cli.ts b/cli.ts index 365ad6d3..ce8fede6 100644 --- a/cli.ts +++ b/cli.ts @@ -2,13 +2,14 @@ import { existsSync } from "./lib/std/fs.ts"; import { distinct } from "./lib/std/collections.ts"; import { parse as parseJsonc } from "./lib/std/jsonc.ts"; import { dirname, extname, join } from "./lib/std/path.ts"; -import { colors, Command, List, Select } from "./lib/x/cliffy.ts"; +import { colors, Command, Input, List, Select } from "./lib/x/cliffy.ts"; +import { $ } from "./lib/x/dax.ts"; import { URI } from "./lib/uri.ts"; import { DependencyUpdate } from "./lib/update.ts"; import { FileUpdate } from "./lib/file.ts"; -import { commitAll } from "./lib/git.ts"; +import { GitCommitSequence } from "./lib/git.ts"; -const { gray, yellow, bold } = colors; +const { gray, yellow, bold, cyan } = colors; const checkCommand = new Command() .description("Check for the latest version of dependencies") @@ -21,18 +22,7 @@ async function checkAction( ...entrypoints: string[] ) { _ensureJsFiles(entrypoints); - console.log("šŸ”Ž Checking for updates..."); - const updates = await Promise.all( - entrypoints.map(async (entrypoint) => - await DependencyUpdate.collect(entrypoint, { - importMap: options.importMap ?? await _findImportMap(entrypoint), - }) - ), - ).then((results) => results.flat()); - if (!updates.length) { - console.log("šŸµ No updates found"); - return; - } + const updates = await _collect(entrypoints, options); _list(updates); const action = await Select.prompt({ message: "Choose an action", @@ -48,9 +38,13 @@ async function checkAction( case "write": return _write(updates); case "commit": { + const prefix = await Input.prompt({ + message: "Prefix for commit messages", + default: "build(deps): ", + }); const suggestions = _getTasks(); if (!suggestions.length) { - return _commit(updates); + return _commit(updates, { prefix }); } const preCommit = await List.prompt( { @@ -64,7 +58,7 @@ async function checkAction( suggestions, }, ); - return _commit(updates, { preCommit, postCommit }); + return _commit(updates, { preCommit, postCommit, prefix }); } } } @@ -79,6 +73,12 @@ const updateCommand = new Command() .option("--post-commit ", "Run tasks after each commit", { depends: ["commit"], }) + .option("--prefix ", "Prefix for commit messages", { + depends: ["commit"], + default: "build(deps): ", + }) + .option("--summary ", "Write a summary of changes to file") + .option("--report ", "Write a report of changes to file") .arguments("") .action(updateAction); @@ -88,26 +88,38 @@ async function updateAction( importMap?: string; preCommit?: string[]; postCommit?: string[]; + prefix: string; + summary?: string; + report?: string; }, ...entrypoints: string[] ) { - console.log("šŸ”Ž Checking for updates..."); - const updates = await Promise.all( - entrypoints.map(async (entrypoint) => - await DependencyUpdate.collect(entrypoint, { - importMap: options.importMap ?? await _findImportMap(entrypoint), - }) - ), - ).then((results) => results.flat()); - if (!updates.length) { - console.log("šŸµ No updates found"); - return; - } + const updates = await _collect(entrypoints, options); _list(updates); if (options.commit) { return _commit(updates, options); } - return _write(updates); + return _write(updates, options); +} + +async function _collect( + entrypoints: string[], + options: { importMap?: string }, +): Promise { + return await $.progress("Checking for updates").with(async () => { + const updates = await Promise.all( + entrypoints.map(async (entrypoint) => + await DependencyUpdate.collect(entrypoint, { + importMap: options.importMap ?? await _findImportMap(entrypoint), + }) + ), + ).then((results) => results.flat()); + if (!updates.length) { + console.log("šŸµ No updates found"); + Deno.exit(0); + } + return updates; + }); } async function _findImportMap(entrypoint: string): Promise { @@ -158,41 +170,81 @@ function _list(updates: DependencyUpdate[]) { console.log(); } -function _write(updates: DependencyUpdate[]) { - console.log(); - console.log("Writing changes..."); - const results = FileUpdate.collect(updates); - FileUpdate.writeAll(results, { - onWrite: (module) => console.log(` šŸ’¾ ${URI.relative(module.specifier)}`), +async function _write( + updates: DependencyUpdate[], + options?: { + summary?: string; + report?: string; + }, +) { + const results = await FileUpdate.collect(updates); + await FileUpdate.writeAll(results, { + onWrite: (module) => console.log(`šŸ’¾ ${URI.relative(module.specifier)}`), }); + console.log(); + if (options?.summary) { + await Deno.writeTextFile(options.summary, "Update dependencies"); + console.log(`šŸ“„ ${options.summary}`); + } + if (options?.report) { + const content = distinct( + updates.map((u) => `- ${u.name} ${u.version.from} => ${u.version.to}`), + ).join("\n"); + await Deno.writeTextFile(options.report, content); + console.log(`šŸ“„ ${options.report}`); + } } -function _commit( +async function _commit( updates: DependencyUpdate[], options?: { preCommit?: string[]; postCommit?: string[]; + prefix: string; + summary?: string; + report?: string; }, ) { - console.log("\nCommitting changes..."); - commitAll(updates, { + const commits = GitCommitSequence.from(updates, { groupBy: (dependency) => dependency.name, - preCommit: () => { - options?.preCommit?.forEach((task) => _task(task)); - }, - postCommit: (commit) => { - console.log(`šŸ“ ${commit.message}`); - options?.postCommit?.forEach((task) => _task(task)); - }, + composeCommitMessage: ({ group, version }) => + `${options?.prefix}bump ${group}` + + (version?.from ? ` from ${version?.from}` : "") + + (version?.to ? ` to ${version?.to}` : ""), + preCommit: options?.preCommit + ? async (commit) => { + console.log(`\nšŸ“ Commiting "${commit.message}"...`); + for (const task of options?.preCommit ?? []) { + await _task(task); + } + } + : undefined, + postCommit: options?.postCommit + ? async (commit) => { + console.log(`šŸ“ ${commit.message}`); + for (const task of options?.postCommit ?? []) { + await _task(task); + } + } + : undefined, }); + await GitCommitSequence.exec(commits); + console.log(); + if (options?.summary) { + await Deno.writeTextFile(options.summary, _summary(commits)); + console.log(`šŸ“„ ${options.summary}`); + } + if (options?.report) { + await Deno.writeTextFile(options.report, _report(commits)); + console.log(`šŸ“„ ${options.report}`); + } } -function _task(task: string): void { - const { code, stderr } = new Deno.Command(Deno.execPath(), { - args: ["task", task], - }).outputSync(); - if (code !== 0) { - console.error(new TextDecoder().decode(stderr)); +async function _task(task: string) { + console.log(`\nšŸ”Ø Running task ${cyan(task)}...`); + try { + await $`deno task -q ${task}`; + } catch { Deno.exit(1); } } @@ -250,6 +302,22 @@ async function _findFileUp(entrypoint: string, root: string) { return hits; } +function _summary(sequence: GitCommitSequence): string { + if (sequence.commits.length === 0) { + return "No updates"; + } + if (sequence.commits.length === 1) { + return sequence.commits[0].message; + } + const groups = sequence.commits.map((commit) => commit.group).join(", "); + const full = `Updated ${groups}`; + return (full.length <= 50) ? full : "Updated dependencies"; +} + +function _report(sequence: GitCommitSequence): string { + return sequence.commits.map((commit) => `- ${commit.message}`).join("\n"); +} + const main = new Command() .name("molt") .description("A tool for updating dependencies in Deno projects") diff --git a/deno.json b/deno.json index f944fb6e..433c608f 100644 --- a/deno.json +++ b/deno.json @@ -1,11 +1,11 @@ { "tasks": { - "run": "deno run --allow-env --allow-read --allow-net=deno.land --allow-write=.", + "run": "deno run --allow-env --allow-read --allow-write=. --allow-run --allow-net=deno.land", "lock": "deno cache --lock=deno.lock --lock-write ./lib/**/*.ts && git add deno.lock", "test": "deno test -A", "test:fast": "deno task test --no-check", "dev": "deno fmt && deno lint && deno task lock && deno task test", - "update": "deno task -q run --allow-run ./cli.ts check lib/**/*.ts", + "update": "deno task -q run ./cli.ts check lib/**/*.ts", "install": "deno install -f -A --name molt cli.ts" }, "fmt": { diff --git a/deno.lock b/deno.lock index c7436045..040ca0c2 100644 --- a/deno.lock +++ b/deno.lock @@ -19,6 +19,64 @@ "https://deno.land/std@0.196.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", "https://deno.land/std@0.196.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", "https://deno.land/std@0.196.0/path/win32.ts": "4fca292f8d116fd6d62f243b8a61bd3d6835a9f0ede762ba5c01afe7c3c0aa12", + "https://deno.land/std@0.201.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.201.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.201.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", + "https://deno.land/std@0.201.0/fmt/colors.ts": "87544aa2bc91087bb37f9c077970c85bfb041b48e4c37356129d7b450a415b6f", + "https://deno.land/std@0.201.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", + "https://deno.land/std@0.201.0/fs/copy.ts": "23cc1c465babe5ca4d69778821e2f8addc44593e30a5ca0b902b3784eed75bb6", + "https://deno.land/std@0.201.0/fs/empty_dir.ts": "2e52cd4674d18e2e007175c80449fc3d263786a1361e858d9dfa9360a6581b47", + "https://deno.land/std@0.201.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.201.0/fs/ensure_file.ts": "39ac83cc283a20ec2735e956adf5de3e8a3334e0b6820547b5772f71c49ae083", + "https://deno.land/std@0.201.0/fs/ensure_link.ts": "c15e69c48556d78aae31b83e0c0ece04b7b8bc0951412f5b759aceb6fde7f0ac", + "https://deno.land/std@0.201.0/fs/ensure_symlink.ts": "b389c8568f0656d145ac7ece472afe710815cccbb2ebfd19da7978379ae143fe", + "https://deno.land/std@0.201.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", + "https://deno.land/std@0.201.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8", + "https://deno.land/std@0.201.0/fs/expand_glob.ts": "52b8b6f5b1fa585c348250da1c80ce5d820746cb4a75d874b3599646f677d3a7", + "https://deno.land/std@0.201.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", + "https://deno.land/std@0.201.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", + "https://deno.land/std@0.201.0/fs/walk.ts": "a16146724a6aaf9efdb92023a74e9805195c3469900744ce5de4113b07b29779", + "https://deno.land/std@0.201.0/io/buf_reader.ts": "0bd8ad26255945b5f418940db23db03bee0c160dbb5ae4627e2c0be3b361df6a", + "https://deno.land/std@0.201.0/io/buffer.ts": "4d6883daeb2e698579c4064170515683d69f40f3de019bfe46c5cf31e74ae793", + "https://deno.land/std@0.201.0/path/_basename.ts": "057d420c9049821f983f784fd87fa73ac471901fb628920b67972b0f44319343", + "https://deno.land/std@0.201.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.201.0/path/_dirname.ts": "355e297236b2218600aee7a5301b937204c62e12da9db4b0b044993d9e658395", + "https://deno.land/std@0.201.0/path/_extname.ts": "eaaa5aae1acf1f03254d681bd6a8ce42a9cb5b7ff2213a9d4740e8ab31283664", + "https://deno.land/std@0.201.0/path/_format.ts": "4a99270d6810f082e614309164fad75d6f1a483b68eed97c830a506cc589f8b4", + "https://deno.land/std@0.201.0/path/_from_file_url.ts": "6eadfae2e6f63ad9ee46b26db4a1b16583055c0392acedfb50ed2fc694b6f581", + "https://deno.land/std@0.201.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.201.0/path/_is_absolute.ts": "05dac10b5e93c63198b92e3687baa2be178df5321c527dc555266c0f4f51558c", + "https://deno.land/std@0.201.0/path/_join.ts": "815f5e85b042285175b1492dd5781240ce126c23bd97bad6b8211fe7129c538e", + "https://deno.land/std@0.201.0/path/_normalize.ts": "a19ec8706b2707f9dd974662a5cd89fad438e62ab1857e08b314a8eb49a34d81", + "https://deno.land/std@0.201.0/path/_os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.201.0/path/_parse.ts": "0f9b0ff43682dd9964eb1c4398610c4e165d8db9d3ac9d594220217adf480cfa", + "https://deno.land/std@0.201.0/path/_relative.ts": "27bdeffb5311a47d85be26d37ad1969979359f7636c5cd9fcf05dcd0d5099dc5", + "https://deno.land/std@0.201.0/path/_resolve.ts": "7a3616f1093735ed327e758313b79c3c04ea921808ca5f19ddf240cb68d0adf6", + "https://deno.land/std@0.201.0/path/_to_file_url.ts": "a141e4a525303e1a3a0c0571fd024552b5f3553a2af7d75d1ff3a503dcbb66d8", + "https://deno.land/std@0.201.0/path/_to_namespaced_path.ts": "0d5f4caa2ed98ef7a8786286df6af804b50e38859ae897b5b5b4c8c5930a75c8", + "https://deno.land/std@0.201.0/path/_util.ts": "4e191b1bac6b3bf0c31aab42e5ca2e01a86ab5a0d2e08b75acf8585047a86221", + "https://deno.land/std@0.201.0/path/basename.ts": "bdfa5a624c6a45564dc6758ef2077f2822978a6dbe77b0a3514f7d1f81362930", + "https://deno.land/std@0.201.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.201.0/path/dirname.ts": "b6533f4ee4174a526dec50c279534df5345836dfdc15318400b08c62a62a39dd", + "https://deno.land/std@0.201.0/path/extname.ts": "62c4b376300795342fe1e4746c0de518b4dc9c4b0b4617bfee62a2973a9555cf", + "https://deno.land/std@0.201.0/path/format.ts": "110270b238514dd68455a4c54956215a1aff7e37e22e4427b7771cefe1920aa5", + "https://deno.land/std@0.201.0/path/from_file_url.ts": "9f5cb58d58be14c775ec2e57fc70029ac8b17ed3bd7fe93e475b07280adde0ac", + "https://deno.land/std@0.201.0/path/glob.ts": "593e2c3573883225c25c5a21aaa8e9382a696b8e175ea20a3b6a1471ad17aaed", + "https://deno.land/std@0.201.0/path/is_absolute.ts": "0b92eb35a0a8780e9f16f16bb23655b67dace6a8e0d92d42039e518ee38103c1", + "https://deno.land/std@0.201.0/path/join.ts": "31c5419f23d91655b08ec7aec403f4e4cd1a63d39e28f6e42642ea207c2734f8", + "https://deno.land/std@0.201.0/path/mod.ts": "6e1efb0b13121463aedb53ea51dabf5639a3172ab58c89900bbb72b486872532", + "https://deno.land/std@0.201.0/path/normalize.ts": "6ea523e0040979dd7ae2f1be5bf2083941881a252554c0f32566a18b03021955", + "https://deno.land/std@0.201.0/path/parse.ts": "be8de342bb9e1924d78dc4d93c45215c152db7bf738ec32475560424b119b394", + "https://deno.land/std@0.201.0/path/posix.ts": "0a1c1952d132323a88736d03e92bd236f3ed5f9f079e5823fae07c8d978ee61b", + "https://deno.land/std@0.201.0/path/relative.ts": "8bedac226afd360afc45d451a6c29fabceaf32978526bcb38e0c852661f66c61", + "https://deno.land/std@0.201.0/path/resolve.ts": "133161e4949fc97f9ca67988d51376b0f5eef8968a6372325ab84d39d30b80dc", + "https://deno.land/std@0.201.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", + "https://deno.land/std@0.201.0/path/to_file_url.ts": "00e6322373dd51ad109956b775e4e72e5f9fa68ce2c6b04e4af2a6eed3825d31", + "https://deno.land/std@0.201.0/path/to_namespaced_path.ts": "1b1db3055c343ab389901adfbda34e82b7386bcd1c744d54f9c1496ee0fd0c3d", + "https://deno.land/std@0.201.0/path/win32.ts": "8b3f80ef7a462511d5e8020ff490edcaa0a0d118f1b1e9da50e2916bdd73f9dd", + "https://deno.land/std@0.201.0/streams/read_all.ts": "ee319772fb0fd28302f97343cc48dfcf948f154fd0d755d8efe65814b70533be", + "https://deno.land/std@0.201.0/streams/reader_from_stream_reader.ts": "fa4971e5615a010e49492c5d1688ca1a4d17472a41e98b498ab89a64ebd7ac73", + "https://deno.land/std@0.201.0/streams/write_all.ts": "aec90152978581ea62d56bb53a5cbf487e6a89c902f87c5969681ffbdf32b998", "https://deno.land/std@0.204.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", "https://deno.land/std@0.204.0/assert/_diff.ts": "58e1461cc61d8eb1eacbf2a010932bf6a05b79344b02ca38095f9b805795dc48", "https://deno.land/std@0.204.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", @@ -118,6 +176,7 @@ "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_generic_suggestions.ts": "5e6ee1190b4dd5af261ae2ff0196dec7f1988ea9c41c6288cfaece293703002c", "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/_utils.ts": "498ae639d7666599d612b615ee85de9103b3c3a913d5196f6b265072674258c7", "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/deps.ts": "2560142f070bb2668e2e8a74683c799461648b9aad01bbf36b3cad3851d712e6", + "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/input.ts": "81821244f895cc4db32c2511c17e21fb48fd7606e300605aeb2a231ab1680544", "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/list.ts": "e5d3e1a6d931b9736d03eec2425fb7b4d2b8d1461a84e210b4787edda414dca4", "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/select.ts": "c10902aeaca02a55d9b846934958dd166ee39c741faebdaa9800689e402186cf", "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_layout.ts": "e4a518da28333de95ad791208b9930025987c8b93d5f8b7f30b377b3e26b24e1", @@ -129,6 +188,43 @@ "https://deno.land/x/cliffy@v1.0.0-rc.3/table/deps.ts": "1226c4d39d53edc81d7c3e661fb8a79f2e704937c276c60355cd4947a0fe9153", "https://deno.land/x/cliffy@v1.0.0-rc.3/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", + "https://deno.land/x/dax@0.35.0/mod.ts": "3fc382546bf3c7b90aa458aa144be7c6e8aed3e8c2680289f9c8694d986b7247", + "https://deno.land/x/dax@0.35.0/src/command.ts": "6e7db06015b4ad6decbf59cc5fcb6bd4b03a46276f7e3f3472204c11b2109e0e", + "https://deno.land/x/dax@0.35.0/src/command_handler.ts": "841cee0ce12b19eea6c7fcaeaa40a9e3ef4bf50c36cf02afbe3ab7b41f8571eb", + "https://deno.land/x/dax@0.35.0/src/commands/args.ts": "a138aef24294e3cbf13cef08f4836d018e8dd99fd06ad82e7e7f08ef680bbc1d", + "https://deno.land/x/dax@0.35.0/src/commands/cat.ts": "229dc854f80ea8f1ebd811190fc31e5cf0fe39f76c2de1c27e256cb831237cb0", + "https://deno.land/x/dax@0.35.0/src/commands/cd.ts": "239fee1606881dbc3f778a761d1d4557c21a63063c15ab58883a32e7466b7177", + "https://deno.land/x/dax@0.35.0/src/commands/cp_mv.ts": "58205a82a9404e444c7c5caf98b5dd2b350c668c0b421546a038b76ea8b6a53d", + "https://deno.land/x/dax@0.35.0/src/commands/echo.ts": "247909de5b8ea20218daab419f3aad37b69763052272aca3633fe8e7f83148cd", + "https://deno.land/x/dax@0.35.0/src/commands/exit.ts": "c619e52d744dfa3e8fa954026f1c5302d8be991c775553efc85a0f224b77b6ff", + "https://deno.land/x/dax@0.35.0/src/commands/export.ts": "b6ecad1203cfe606d69da6c16736f31acf211e864e6822484d85cea1cb7d5528", + "https://deno.land/x/dax@0.35.0/src/commands/mkdir.ts": "9381ecdc0e0203d941f89027b6ef2865393bf0a66670bf5f5aaa6a49669244c7", + "https://deno.land/x/dax@0.35.0/src/commands/printenv.ts": "473c39b457cae91e9ca029ad420642b9a410257fb699674660c886c6ebe72ebc", + "https://deno.land/x/dax@0.35.0/src/commands/pwd.ts": "5438aea979027bfa5c64c2a7f1073389735ea986f6abe2174ec21bcb70a2156f", + "https://deno.land/x/dax@0.35.0/src/commands/rm.ts": "d911ff4e2e0b3d3c5d426c7b735313741ad762d9e25a743f101a1b05447eecf8", + "https://deno.land/x/dax@0.35.0/src/commands/sleep.ts": "d1183fa8e31ba85a7b88666e854c7aa6e53e1d4c65e39f20a05d8ea4b82efca3", + "https://deno.land/x/dax@0.35.0/src/commands/test.ts": "a221f82c209fd53756e9c02c475b9d5833284513853e90fdaaf0c1e1d9cfbf30", + "https://deno.land/x/dax@0.35.0/src/commands/touch.ts": "5953dbde8732da47ade9b7554a638ea06a8b67a59842e638fb79f7aebe392650", + "https://deno.land/x/dax@0.35.0/src/commands/unset.ts": "8d4abb29f53c3de0c10ba6d51e3d55bce745160f7430396ede58156e8f2b747c", + "https://deno.land/x/dax@0.35.0/src/common.ts": "c0e809c591400dbadb25197f2819c59fec6b897c94c1aba6a026d5d1eee9cb53", + "https://deno.land/x/dax@0.35.0/src/console/confirm.ts": "d9128d10b77fcc0a8df2784f71c79df68f5c8e00a34b04547b9ba9ddf1c97f96", + "https://deno.land/x/dax@0.35.0/src/console/logger.ts": "e0ab5025915cef70df03681c756e211f25bb2e4331f82ed4256b17ddd9e794ea", + "https://deno.land/x/dax@0.35.0/src/console/mod.ts": "29ae1f8250b74a477e26a3b6ccf647badf5d8f8e2a9e6c4aa0d5df9e3bbbb273", + "https://deno.land/x/dax@0.35.0/src/console/multiSelect.ts": "31003744e58f45f720271bd034d8cfba1055c954ba02d77a2f2eb21e4c1ed55a", + "https://deno.land/x/dax@0.35.0/src/console/progress/format.ts": "15ddbb8051580f88ed499281e12ca6f881f875ab73268d7451d7113ee130bd7d", + "https://deno.land/x/dax@0.35.0/src/console/progress/interval.ts": "80188d980a27c2eb07c31324365118af549641442f0752fe7c3b0c91832e5046", + "https://deno.land/x/dax@0.35.0/src/console/progress/mod.ts": "70080a5d06ab2c58e948225e1e5144458fbc36fbfa61672ac82bb2f6c6991bad", + "https://deno.land/x/dax@0.35.0/src/console/prompt.ts": "78c645b41a7562133d05a10901ae4d682cb22bfaf0b5a21cc8475ca2a946aee1", + "https://deno.land/x/dax@0.35.0/src/console/select.ts": "c9d7124d975bf34d52ea1ac88fd610ed39db8ee6505b9bb53f371cef2f56c6ab", + "https://deno.land/x/dax@0.35.0/src/console/utils.ts": "954c99397dcd2cb3f1ccf50055085f17c9ffb31b25b3c5719776de81e23935f4", + "https://deno.land/x/dax@0.35.0/src/deps.ts": "709fcfef942331cbc97c1faf37dbff8b97c411fac1d142106027ca5bbe64df59", + "https://deno.land/x/dax@0.35.0/src/lib/mod.ts": "c992db99c8259ae3bf2d35666585dfefda84cf7cf4e624e42ea2ac7367900fe0", + "https://deno.land/x/dax@0.35.0/src/lib/rs_lib.generated.js": "381f2f60b458bcb0a6fec1310c2c3b6447339f6995df206b9a4d0c3747ee8c36", + "https://deno.land/x/dax@0.35.0/src/path.ts": "5e1ea6139a975d31d6a5ca62c96c095ff7ddcf5c34ef8b75ab0ea04f87ac579b", + "https://deno.land/x/dax@0.35.0/src/pipes.ts": "3aa984c0d031f4221953e228ba89452a86068a80d2811fddb9c60737cd4ab174", + "https://deno.land/x/dax@0.35.0/src/request.ts": "a2b20859de7a0fbe10584a41de435942ee4726f0b637b1cb55d7f632f4efc74f", + "https://deno.land/x/dax@0.35.0/src/result.ts": "0908b69c16b25c3b258f6b2ada12e124686df5f7ea2b98daa27a83973c7b118c", + "https://deno.land/x/dax@0.35.0/src/shell.ts": "9475a015d5493197f9611b1259c5dd6d27c7c2ab9c3711606cd4b47412568ee1", "https://deno.land/x/deno_graph@0.58.0/deno_graph_wasm.generated.js": "3e0b0aa16a27bd2f354fc48629fc961d283c470787a335217cdcdacb5b6db544", "https://deno.land/x/deno_graph@0.58.0/loader.ts": "a2e757383908f4a51659fe1b1203386887ebb17756bac930a64856d613d8d57d", "https://deno.land/x/deno_graph@0.58.0/media_type.ts": "a89a1b38d07c160e896de9ceb99285ba8391940140558304171066b5c3ef7609", @@ -136,11 +232,13 @@ "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66", "https://deno.land/x/import_map@v0.15.0/lib/import_map.generated.js": "617626ef749703190de2f781e1e4019d858c9c6ba61a7cae39a14499a5a20628", "https://deno.land/x/import_map@v0.15.0/mod.ts": "dbd28bcdb5fba6eb6b7d0d2c300a5b537a13624bcb463311d44fc970d9f0ddad", + "https://deno.land/x/outdent@v0.8.0/src/index.ts": "6dc3df4108d5d6fedcdb974844d321037ca81eaaa16be6073235ff3268841a22", "https://deno.land/x/unknownutil@v3.10.0/inspect.ts": "33e61bdfed94cd586d66600813b528fa93046a2802d8144277b92f0fa5e5f10e", "https://deno.land/x/unknownutil@v3.10.0/is.ts": "957784493b678a8905728c0998577443db3c66b4c8f9eaad56e6dc97167d2d0d", "https://deno.land/x/unknownutil@v3.10.0/mod.ts": "50f0bdb70664c65b2954d4b9bb0f95f9bb4c77c4469331fda2475a4ffbb51a15", "https://deno.land/x/unknownutil@v3.10.0/util.ts": "5c9b52ef699e569ccec9d4c33148545bf5328c6a5e2f6f56f651ce6bdd44eb1e", "https://deno.land/x/wasmbuild@0.14.1/cache.ts": "89eea5f3ce6035a1164b3e655c95f21300498920575ade23161421f5b01967f4", - "https://deno.land/x/wasmbuild@0.14.1/loader.ts": "d98d195a715f823151cbc8baa3f32127337628379a02d9eb2a3c5902dbccfc02" + "https://deno.land/x/wasmbuild@0.14.1/loader.ts": "d98d195a715f823151cbc8baa3f32127337628379a02d9eb2a3c5902dbccfc02", + "https://deno.land/x/which@0.3.0/mod.ts": "3e10d07953c14e4ddc809742a3447cef14202cdfe9be6678a1dfc8769c4487e6" } } diff --git a/lib/file.ts b/lib/file.ts index c0ba3250..7f1a3017 100644 --- a/lib/file.ts +++ b/lib/file.ts @@ -16,16 +16,16 @@ export const FileUpdate = { writeAll, }; -function collect( +async function collect( dependencies: DependencyUpdate[], -): FileUpdate[] { +): Promise { /** A map of module specifiers to the module content updates. */ const results = new Map, FileUpdate>(); for (const dependency of dependencies) { const referrer = dependency.map?.source ?? dependency.referrer; const current = results.get(referrer) ?? { specifier: referrer, - content: Deno.readTextFileSync(new URL(referrer)), + content: await Deno.readTextFile(new URL(referrer)), dependencies: [], } satisfies FileUpdate; const content = dependency.map @@ -40,20 +40,20 @@ function collect( return Array.from(results.values()); } -export function writeAll( +export async function writeAll( updates: FileUpdate[], options?: { - onWrite?: (result: FileUpdate) => void; + onWrite?: (result: FileUpdate) => void | Promise; }, -): void { - updates.forEach((it) => { - write(it); - options?.onWrite?.(it); - }); +) { + for (const update of updates) { + await write(update); + await options?.onWrite?.(update); + } } -export function write( +export async function write( result: FileUpdate, -): void { - Deno.writeTextFileSync(new URL(result.specifier), result.content); +) { + await Deno.writeTextFile(new URL(result.specifier), result.content); } diff --git a/lib/file_test.ts b/lib/file_test.ts index a35151c0..b627316f 100644 --- a/lib/file_test.ts +++ b/lib/file_test.ts @@ -17,14 +17,14 @@ import { URI } from "./uri.ts"; describe("collect", () => { it("direct import", async () => { - const results = FileUpdate.collect( + const results = await FileUpdate.collect( await DependencyUpdate.collect("./test/fixtures/direct-import/mod.ts"), ); assertEquals(results.length, 2); }); it("import map", async () => { - const results = FileUpdate.collect( + const results = await FileUpdate.collect( await DependencyUpdate.collect( "./test/fixtures/import-map/mod.ts", { importMap: "./test/fixtures/import-map/deno.json" }, @@ -42,7 +42,7 @@ describe("collect", () => { }); it("import map with no resolve", async () => { - const results = FileUpdate.collect( + const results = await FileUpdate.collect( await DependencyUpdate.collect( "./test/fixtures/import-map-no-resolve/mod.ts", { importMap: "./test/fixtures/import-map-no-resolve/deno.json" }, @@ -56,13 +56,14 @@ describe("collect", () => { describe("writeAll", () => { let output: Map; - let writeTextFileSyncStub: Stub; + let writeTextFileStub: Stub; beforeAll(() => { output = new Map(); - writeTextFileSyncStub = stub( + writeTextFileStub = stub( Deno, - "writeTextFileSync", // deno-lint-ignore require-await + "writeTextFile", + // deno-lint-ignore require-await async (path, data) => { output.set(path.toString(), data.toString()); }, @@ -70,37 +71,37 @@ describe("writeAll", () => { }); afterAll(() => { - writeTextFileSyncStub.restore(); + writeTextFileStub.restore(); }); it("direct import", async () => { - const results = FileUpdate.collect( + const results = await FileUpdate.collect( await DependencyUpdate.collect("./test/fixtures/direct-import/mod.ts"), ); - FileUpdate.writeAll(results); + await FileUpdate.writeAll(results); assertExists(output.get(URI.from("test/fixtures/direct-import/mod.ts"))); assertExists(output.get(URI.from("test/fixtures/direct-import/lib.ts"))); }); it("import map", async () => { - const results = FileUpdate.collect( + const results = await FileUpdate.collect( await DependencyUpdate.collect( "./test/fixtures/import-map/mod.ts", { importMap: "./test/fixtures/import-map/deno.json" }, ), ); - FileUpdate.writeAll(results); + await FileUpdate.writeAll(results); assertExists(output.get(URI.from("test/fixtures/import-map/deno.json"))); }); it("import map with no resolve", async () => { - const results = FileUpdate.collect( + const results = await FileUpdate.collect( await DependencyUpdate.collect( "./test/fixtures/import-map-no-resolve/mod.ts", { importMap: "./test/fixtures/import-map-no-resolve/deno.json" }, ), ); - FileUpdate.writeAll(results); + await FileUpdate.writeAll(results); assertExists( output.get(URI.from("test/fixtures/import-map-no-resolve/mod.ts")), ); diff --git a/lib/git.ts b/lib/git.ts index 8c8f7244..4667d54f 100644 --- a/lib/git.ts +++ b/lib/git.ts @@ -12,8 +12,8 @@ export interface CommitProps { export interface CommitOptions { groupBy?: (dependency: DependencyUpdate) => string; composeCommitMessage?: (props: CommitProps) => string; - preCommit?: (commit: GitCommit) => void; - postCommit?: (commit: GitCommit) => void; + preCommit?: (commit: GitCommit) => void | Promise; + postCommit?: (commit: GitCommit) => void | Promise; gitAddOptions?: string[]; gitCommitOptions?: string[]; } @@ -40,9 +40,7 @@ export interface GitCommit extends CommitProps { } export const GitCommit = { - sequence: createGitCommitSequence, exec: execGitCommit, - execAll: execGitCommitSequence, }; export interface GitCommitSequence { @@ -50,11 +48,16 @@ export interface GitCommitSequence { options: CommitOptions; } +export const GitCommitSequence = { + from: createGitCommitSequence, + exec: execGitCommitSequence, +}; + export function commitAll( updates: DependencyUpdate[], options?: CommitOptions, -): void { - execGitCommitSequence( +) { + return execGitCommitSequence( createGitCommitSequence(updates, { ...defaultCommitOptions, ...options, @@ -75,39 +78,41 @@ function createGitCommitSequence( } groups.get(key)!.push(u); } - const version = createVersionProp(updates); const commits: GitCommit[] = Array.from(groups.entries()).map(( [group, updates], - ) => ({ - group, - version, - message: _options.composeCommitMessage({ group, version }), - updates, - })); + ) => { + const version = createVersionProp(updates); + return ({ + group, + version, + message: _options.composeCommitMessage({ group, version }), + updates, + }); + }); return { commits, options: _options }; } -function execGitCommitSequence( +async function execGitCommitSequence( sequence: GitCommitSequence, ) { for (const commit of sequence.commits) { - execGitCommit(commit, sequence.options); + await execGitCommit(commit, sequence.options); } } -function execGitCommit( +async function execGitCommit( commit: GitCommit, options?: CommitOptions, ) { - const results = FileUpdate.collect(commit.updates); - FileUpdate.writeAll(results); - options?.preCommit?.(commit); - _add(results, options?.gitAddOptions ?? []); - _commit(commit.message, options?.gitCommitOptions ?? []); - options?.postCommit?.(commit); + const results = await FileUpdate.collect(commit.updates); + await FileUpdate.writeAll(results); + await options?.preCommit?.(commit); + await _add(results, options?.gitAddOptions ?? []); + await _commit(commit.message, options?.gitCommitOptions ?? []); + await options?.postCommit?.(commit); } -function _add( +async function _add( results: FileUpdate[], options: string[], ) { @@ -115,20 +120,20 @@ function _add( const command = new Deno.Command("git", { args: ["add", ...options, ...files], }); - const { code } = command.outputSync(); + const { code } = await command.output(); if (code !== 0) { throw new Error(`git add failed: ${code}`); } } -function _commit( +async function _commit( message: string, options: string[], ) { const command = new Deno.Command("git", { args: ["commit", ...options, "-m", message], }); - const { code } = command.outputSync(); + const { code } = await command.output(); if (code !== 0) { throw new Error(`git commit failed: ${code}`); } diff --git a/lib/git_test.ts b/lib/git_test.ts index 6e76a510..956cac85 100644 --- a/lib/git_test.ts +++ b/lib/git_test.ts @@ -15,7 +15,7 @@ import { DependencyUpdate } from "./update.ts"; import { commitAll } from "./git.ts"; const DenoCommandOriginal = Deno.Command; -const readTextFileSyncOriginal = Deno.readTextFileSync; +const readTextFileOriginal = Deno.readTextFile; class DenoCommandStub { static commands: string[] = []; @@ -26,7 +26,7 @@ class DenoCommandStub { }); DenoCommandStub.commands.push(command); } - outputSync() { + output() { return { code: 0 }; } static clear() { @@ -37,27 +37,29 @@ class DenoCommandStub { describe("commitAll()", () => { let output: { path: string; content: string }[] = []; let updates: DependencyUpdate[]; - let writeTextFileSyncStub: Stub; - let readTextFileSyncStub: Stub; + let writeTextFileStub: Stub; + let readTextFileStub: Stub; beforeAll(async () => { updates = await DependencyUpdate.collect( "./test/fixtures/direct-import/mod.ts", ); - writeTextFileSyncStub = stub( + writeTextFileStub = stub( Deno, - "writeTextFileSync", - (path, data) => { + "writeTextFile", + // deno-lint-ignore require-await + async (path, data) => { output.push({ path: path.toString(), content: data.toString(), }); }, ); - readTextFileSyncStub = stub( + readTextFileStub = stub( Deno, - "readTextFileSync", - (path) => { + "readTextFile", + // deno-lint-ignore require-await + async (path) => { const file = output.findLast((file) => file.path === path.toString()); return file!.content; }, @@ -66,20 +68,20 @@ describe("commitAll()", () => { }); afterAll(() => { - writeTextFileSyncStub.restore(); - readTextFileSyncStub.restore(); + writeTextFileStub.restore(); + readTextFileStub.restore(); Deno.Command = DenoCommandOriginal; }); - beforeEach(() => { + beforeEach(async () => { for ( const file of [ "./test/fixtures/direct-import/mod.ts", "./test/fixtures/direct-import/lib.ts", ] ) { - const content = readTextFileSyncOriginal(file); - Deno.writeTextFileSync(new URL(URI.from(file)), content); + const content = await readTextFileOriginal(file); + await Deno.writeTextFile(new URL(URI.from(file)), content); } }); @@ -88,8 +90,8 @@ describe("commitAll()", () => { output = []; }); - it("no grouping", () => { - commitAll(updates); + it("no grouping", async () => { + await commitAll(updates); assertEquals(DenoCommandStub.commands.length, 2); assertArrayIncludes( DenoCommandStub.commands, @@ -100,8 +102,8 @@ describe("commitAll()", () => { ); }); - it("group by dependency name", () => { - commitAll(updates, { + it("group by dependency name", async () => { + await commitAll(updates, { groupBy: (update) => update.name, composeCommitMessage: ({ group }) => `build(deps): update ${group}`, }); @@ -119,8 +121,8 @@ describe("commitAll()", () => { ); }); - it("group by module (file) name", () => { - commitAll(updates, { + it("group by module (file) name", async () => { + await commitAll(updates, { groupBy: (update) => URI.relative(update.referrer), composeCommitMessage: ({ group }) => `build(deps): update ${group}`, }); diff --git a/lib/x/cliffy.ts b/lib/x/cliffy.ts index ba1c8034..2edb1022 100644 --- a/lib/x/cliffy.ts +++ b/lib/x/cliffy.ts @@ -2,3 +2,4 @@ export { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/command. export { colors } from "https://deno.land/x/cliffy@v1.0.0-rc.3/ansi/colors.ts"; export { Select } from "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/select.ts"; export { List } from "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/list.ts"; +export { Input } from "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/input.ts"; diff --git a/lib/x/dax.ts b/lib/x/dax.ts new file mode 100644 index 00000000..ac0f1151 --- /dev/null +++ b/lib/x/dax.ts @@ -0,0 +1 @@ +export { $ } from "https://deno.land/x/dax@0.35.0/mod.ts";