From d8845a9648d41f77845fb7fdebafc823a03f188e Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 15:36:06 +0800 Subject: [PATCH 1/9] ignore .vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6d13a48..ac4e92f 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ typings/ dist/ .vscode/ +.vscode/** From 39f40679bf3c48f949a7a51571a61955e0d9c7f7 Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 15:38:46 +0800 Subject: [PATCH 2/9] ignore bun.lock --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ac4e92f..9c103f7 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,5 @@ dist/ .vscode/ .vscode/** + +bun.lock \ No newline at end of file From a0070ed937f73dd85d70a21fed8ea87abe2447c1 Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 18:53:45 +0800 Subject: [PATCH 3/9] try update inquirer --- package.json | 3 +- src/cli.ts | 208 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 159 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index da9e9e0..90b842d 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@librescore/sf3": "^0.6.1", "detect-node": "^2.1.0", "i18next": "^21.8.5", - "inquirer": "^8.2.4", + "inquirer": "^12.4.1", "md5": "^2.3.0", "node-fetch": "^2.6.7", "ora": "^5.4.1", @@ -48,7 +48,6 @@ "@crokita/rollup-plugin-node-builtins": "^2.1.3", "@rollup/plugin-json": "^4.1.0", "@types/file-saver": "^2.0.5", - "@types/inquirer": "^8.2.1", "@types/md5": "^2.3.5", "@types/pdfkit": "^0.12.6", "@types/yargs": "^17.0.10", diff --git a/src/cli.ts b/src/cli.ts index 6ca13f2..7eea0a0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -18,7 +18,9 @@ import { InputFileFormat } from "webmscore/schemas"; await i18nextInit; })(); -const inquirer: typeof import("inquirer") = require("inquirer"); +import { input, checkbox, confirm } from '@inquirer/prompts'; + + const ora: typeof import("ora") = require("ora"); const chalk: typeof import("chalk") = require("chalk"); const yargs = require("yargs"); @@ -27,7 +29,7 @@ const argv: any = yargs(hideBin(process.argv)) .usage(i18next.t("cli_usage_hint", { bin: "$0" })) .example( "$0 -i https://musescore.com/user/123/scores/456 -t mp3 -o " + - process.cwd(), + process.cwd(), i18next.t("cli_example_url") ) .example( @@ -98,7 +100,7 @@ const createSpinner = () => { }).start(); }; -const checkboxValidate = (input: number[]) => { +const checkboxValidate = (input) => { return input.length >= 1; }; @@ -114,11 +116,56 @@ const getOutputDir = async (defaultOutput: string) => { let dirNotExistsTries = 0; let lastTryDir: string | null = null; - const { output } = await inquirer.prompt({ - type: "input", - name: "output", + // const { output } = await inquirer.prompt({ + // type: "input", + // name: "output", + // message: i18next.t("cli_output_message"), + // async validate (input: string) { + // if (!input) return false; + + // const dirExists = fs.existsSync(input); + + // if (!dirExists) { + // if (lastTryDir !== input) { + // lastTryDir = input; + // dirNotExistsTries = 0; + // } + + // dirNotExistsTries++; + + // if (dirNotExistsTries >= 2) { + // fs.mkdirSync(input, { recursive: true }); + // } else { + // try { + // fs.accessSync(input); + // } catch (e) { + // return ( + // `${e.message}` + + // "\n " + + // `${chalk.bold(i18next.t("cli_confirm_message"))}` + + // `${chalk.dim(" (Enter ↵)")}` + // ); + // } + // } + // } else if (!fs.statSync(input).isDirectory()) return false; + + // dirNotExistsTries = 0; + + // try { + // fs.accessSync(input); + // } catch (e) { + // return e.message; + // } + + // return true; + // }, + // default: defaultOutput, + // }); + + const output = await input({ message: i18next.t("cli_output_message"), - async validate(input: string) { + default: defaultOutput, + validate: async (input: string) => { if (!input) return false; const dirExists = fs.existsSync(input); @@ -156,8 +203,7 @@ const getOutputDir = async (defaultOutput: string) => { } return true; - }, - default: defaultOutput, + } }); return output; @@ -199,23 +245,40 @@ void (async () => { } // For MacOS, no hint is needed because the paste shortcut is universal. // ask for the page url or path to local file - const { fileInit } = await inquirer.prompt({ - type: "input", - name: "fileInit", + // const { fileInit } = await inquirer.prompt({ + // type: "input", + // name: "fileInit", + // message: i18next.t("cli_input_message"), + // suffix: + // "\n (" + + // i18next.t("cli_input_suffix") + + // `) ${chalk.bgGray(pasteMessage)}\n `, + // validate (input: string) { + // return ( + // input && + // (!!input.match(SCORE_URL_REG) || + // fs.statSync(input).isFile() || + // fs.statSync(input).isDirectory()) + // ); + // }, + // default: argv.input, + // }); + + // TODO, 缺少suffix提示 + const fileInit = await input({ message: i18next.t("cli_input_message"), - suffix: - "\n (" + - i18next.t("cli_input_suffix") + - `) ${chalk.bgGray(pasteMessage)}\n `, - validate(input: string) { + default: argv.input, + validate: (input: string) => { + if (!input) return false; return ( - input && - (!!input.match(SCORE_URL_REG) || - fs.statSync(input).isFile() || - fs.statSync(input).isDirectory()) + !!input.match(SCORE_URL_REG) || + fs.statSync(input).isFile() || + fs.statSync(input).isDirectory() ); }, - default: argv.input, + // transformer: (input: string) => { + // return `${input} ${chalk.bgGray(pasteMessage)}`; + // } }); argv.input = fileInit; @@ -274,15 +337,24 @@ void (async () => { })); // filetype selection spinner.stop(); - types = await inquirer.prompt({ - type: "checkbox", - name: "types", + + // types = await inquirer.prompt({ + // type: "checkbox", + // name: "types", + // message: i18next.t("cli_types_message"), + // choices: typeChoices, + // validate: checkboxValidate, + // pageSize: Infinity, + // default: types, + // }); + + types = await checkbox({ message: i18next.t("cli_types_message"), choices: typeChoices, validate: checkboxValidate, pageSize: Infinity, - default: types, }); + spinner.start(); types = types.types; @@ -397,14 +469,23 @@ void (async () => { // part selection spinner.stop(); - parts = await inquirer.prompt({ - type: "checkbox", - name: "parts", + + // parts = await inquirer.prompt({ + // type: "checkbox", + // name: "parts", + // message: i18next.t("cli_parts_message"), + // choices: partChoices, + // validate: checkboxValidate, + // pageSize: Infinity, + // }); + + parts = await checkbox({ message: i18next.t("cli_parts_message"), choices: partChoices, validate: checkboxValidate, pageSize: Infinity, }); + spinner.start(); // console.log(parts); parts = partChoices.filter((e) => @@ -431,15 +512,25 @@ void (async () => { })); // filetype selection spinner.stop(); - types = await inquirer.prompt({ - type: "checkbox", - name: "types", + + // types = await inquirer.prompt({ + // type: "checkbox", + // name: "types", + // message: i18next.t("cli_types_message"), + // choices: typeChoices, + // validate: checkboxValidate, + // pageSize: Infinity, + // default: types, + // }); + + // TODO: 重复的代码 + types = await checkbox({ message: i18next.t("cli_types_message"), choices: typeChoices, validate: checkboxValidate, pageSize: Infinity, - default: types, }); + spinner.start(); types = types.types; @@ -530,18 +621,26 @@ void (async () => { if (isInteractive) { // confirmation spinner.stop(); - const { confirmed } = await inquirer.prompt({ - type: "confirm", - name: "confirmed", + + // const { confirmed } = await inquirer.prompt({ + // type: "confirm", + // name: "confirmed", + // message: i18next.t("cli_confirm_message"), + // prefix: + // `${chalk.yellow("!")} ` + + // i18next.t("id", { id: scoreinfo.id }) + + // "\n " + + // i18next.t("title", { title: scoreinfo.title }) + + // "\n ", + // default: true, + // }); + + // TODO, 缺少prefix提示 + const confirmed = await confirm({ message: i18next.t("cli_confirm_message"), - prefix: - `${chalk.yellow("!")} ` + - i18next.t("id", { id: scoreinfo.id }) + - "\n " + - i18next.t("title", { title: scoreinfo.title }) + - "\n ", default: true, }); + if (!confirmed) return; // print a blank line @@ -553,10 +652,10 @@ void (async () => { spinner.stop(); console.log( `${chalk.yellow("!")} ` + - i18next.t("id", { id: scoreinfo.id }) + - "\n " + - i18next.t("title", { title: scoreinfo.title }) + - "\n " + i18next.t("id", { id: scoreinfo.id }) + + "\n " + + i18next.t("title", { title: scoreinfo.title }) + + "\n " ); spinner.start(); } @@ -568,15 +667,24 @@ void (async () => { if (isInteractive) { // filetype selection spinner.stop(); - types = await inquirer.prompt({ - type: "checkbox", - name: "types", + + // types = await inquirer.prompt({ + // type: "checkbox", + // name: "types", + // message: i18next.t("cli_types_message"), + // choices: ["midi", "mp3", "pdf"], + // validate: checkboxValidate, + // pageSize: Infinity, + // default: types, + // }); + + types = await checkbox({ message: i18next.t("cli_types_message"), choices: ["midi", "mp3", "pdf"], validate: checkboxValidate, pageSize: Infinity, - default: types, }); + types = types.types; // output directory From a28564ed7ba911a764a2f596cf09f0cc473379db Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 20:59:43 +0800 Subject: [PATCH 4/9] update rollup --- package.json | 12 ++++++------ rollup.config.js | 11 ++++++----- tsconfig.json | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 90b842d..c1b19a6 100644 --- a/package.json +++ b/package.json @@ -45,19 +45,19 @@ "yargs": "^17.5.1" }, "devDependencies": { - "@crokita/rollup-plugin-node-builtins": "^2.1.3", - "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-json": "^6.1.0", "@types/file-saver": "^2.0.5", "@types/md5": "^2.3.5", "@types/pdfkit": "^0.12.6", "@types/yargs": "^17.0.10", "pdfkit": "git+https://github.com/LibreScore/pdfkit.git", - "rollup": "^2.75.3", - "rollup-plugin-commonjs": "^10.1.0", + "rollup": "^4.34.2", + "@rollup/plugin-commonjs": "^28.0.2", "rollup-plugin-node-globals": "^1.4.0", - "rollup-plugin-node-resolve": "^5.2.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-string": "^3.0.0", - "rollup-plugin-typescript": "^1.0.1", + "@rollup/plugin-typescript": "^12.1.2", "svg-to-pdfkit": "^0.1.8", "tslib": "^2.4.0", "typescript": "^4.7.2" diff --git a/rollup.config.js b/rollup.config.js index 96d4f16..840bd8a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,7 +1,7 @@ -import typescript from "rollup-plugin-typescript"; -import resolve from "rollup-plugin-node-resolve"; -import commonjs from "rollup-plugin-commonjs"; -import builtins from "@crokita/rollup-plugin-node-builtins"; +import typescript from "@rollup/plugin-typescript"; +import resolve from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; +import builtins from "rollup-plugin-polyfill-node"; import nodeGlobals from "rollup-plugin-node-globals"; import json from "@rollup/plugin-json"; import { string } from "rollup-plugin-string"; @@ -33,7 +33,8 @@ const basePlugins = [ extensions: [".js", ".ts"], }), commonjs({ - extensions: [".js", ".ts"], + include: "node_modules/**", + transformMixedEsModules: true, }), json(), string({ diff --git a/tsconfig.json b/tsconfig.json index 3fd7b1f..5a8c4c7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es6", "lib": ["dom", "dom.iterable", "es2019"], - "module": "commonjs", + "module": "esnext", "moduleResolution": "node", "esModuleInterop": true, "resolveJsonModule": true, From b794b1934f521ee9aaae6d644b2826c95e61cb49 Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 22:20:05 +0800 Subject: [PATCH 5/9] update to ts 5.7 --- package.json | 4 +- src/cli.ts | 137 +++------------------------------------------------ 2 files changed, 10 insertions(+), 131 deletions(-) diff --git a/package.json b/package.json index c1b19a6..b9126e4 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,8 @@ "rollup-plugin-string": "^3.0.0", "@rollup/plugin-typescript": "^12.1.2", "svg-to-pdfkit": "^0.1.8", - "tslib": "^2.4.0", - "typescript": "^4.7.2" + "tslib": "^2.8.1", + "typescript": "^5.7.3" }, "scripts": { "build": "rollup -c", diff --git a/src/cli.ts b/src/cli.ts index 7eea0a0..0bf7be1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,14 +13,12 @@ import { getFileUrl } from "./file"; import { exportPDF } from "./pdf"; import i18nextInit, { i18next } from "./i18n/index"; import { InputFileFormat } from "webmscore/schemas"; +import { input, checkbox, confirm } from '@inquirer/prompts'; (async () => { await i18nextInit; })(); -import { input, checkbox, confirm } from '@inquirer/prompts'; - - const ora: typeof import("ora") = require("ora"); const chalk: typeof import("chalk") = require("chalk"); const yargs = require("yargs"); @@ -116,52 +114,6 @@ const getOutputDir = async (defaultOutput: string) => { let dirNotExistsTries = 0; let lastTryDir: string | null = null; - // const { output } = await inquirer.prompt({ - // type: "input", - // name: "output", - // message: i18next.t("cli_output_message"), - // async validate (input: string) { - // if (!input) return false; - - // const dirExists = fs.existsSync(input); - - // if (!dirExists) { - // if (lastTryDir !== input) { - // lastTryDir = input; - // dirNotExistsTries = 0; - // } - - // dirNotExistsTries++; - - // if (dirNotExistsTries >= 2) { - // fs.mkdirSync(input, { recursive: true }); - // } else { - // try { - // fs.accessSync(input); - // } catch (e) { - // return ( - // `${e.message}` + - // "\n " + - // `${chalk.bold(i18next.t("cli_confirm_message"))}` + - // `${chalk.dim(" (Enter ↵)")}` - // ); - // } - // } - // } else if (!fs.statSync(input).isDirectory()) return false; - - // dirNotExistsTries = 0; - - // try { - // fs.accessSync(input); - // } catch (e) { - // return e.message; - // } - - // return true; - // }, - // default: defaultOutput, - // }); - const output = await input({ message: i18next.t("cli_output_message"), default: defaultOutput, @@ -244,29 +196,10 @@ void (async () => { pasteMessage = i18next.t("cli_linux_paste_hint"); } // For MacOS, no hint is needed because the paste shortcut is universal. - // ask for the page url or path to local file - // const { fileInit } = await inquirer.prompt({ - // type: "input", - // name: "fileInit", - // message: i18next.t("cli_input_message"), - // suffix: - // "\n (" + - // i18next.t("cli_input_suffix") + - // `) ${chalk.bgGray(pasteMessage)}\n `, - // validate (input: string) { - // return ( - // input && - // (!!input.match(SCORE_URL_REG) || - // fs.statSync(input).isFile() || - // fs.statSync(input).isDirectory()) - // ); - // }, - // default: argv.input, - // }); - - // TODO, 缺少suffix提示 const fileInit = await input({ - message: i18next.t("cli_input_message"), + message: `${i18next.t("cli_input_message")} + (${i18next.t("cli_input_suffix")}) ${chalk.bgGray(pasteMessage)} +`, default: argv.input, validate: (input: string) => { if (!input) return false; @@ -276,9 +209,6 @@ void (async () => { fs.statSync(input).isDirectory() ); }, - // transformer: (input: string) => { - // return `${input} ${chalk.bgGray(pasteMessage)}`; - // } }); argv.input = fileInit; @@ -338,27 +268,14 @@ void (async () => { // filetype selection spinner.stop(); - // types = await inquirer.prompt({ - // type: "checkbox", - // name: "types", - // message: i18next.t("cli_types_message"), - // choices: typeChoices, - // validate: checkboxValidate, - // pageSize: Infinity, - // default: types, - // }); - types = await checkbox({ message: i18next.t("cli_types_message"), choices: typeChoices, validate: checkboxValidate, - pageSize: Infinity, }); spinner.start(); - types = types.types; - // output directory spinner.stop(); const output = await getOutputDir(argv.output); @@ -482,7 +399,7 @@ void (async () => { parts = await checkbox({ message: i18next.t("cli_parts_message"), choices: partChoices, - validate: checkboxValidate, + // validate: checkboxValidate, pageSize: Infinity, }); @@ -513,27 +430,14 @@ void (async () => { // filetype selection spinner.stop(); - // types = await inquirer.prompt({ - // type: "checkbox", - // name: "types", - // message: i18next.t("cli_types_message"), - // choices: typeChoices, - // validate: checkboxValidate, - // pageSize: Infinity, - // default: types, - // }); - // TODO: 重复的代码 types = await checkbox({ message: i18next.t("cli_types_message"), choices: typeChoices, validate: checkboxValidate, - pageSize: Infinity, }); spinner.start(); - - types = types.types; } filetypes = types.map((i) => INDV_DOWNLOADS[i]); @@ -622,22 +526,10 @@ void (async () => { // confirmation spinner.stop(); - // const { confirmed } = await inquirer.prompt({ - // type: "confirm", - // name: "confirmed", - // message: i18next.t("cli_confirm_message"), - // prefix: - // `${chalk.yellow("!")} ` + - // i18next.t("id", { id: scoreinfo.id }) + - // "\n " + - // i18next.t("title", { title: scoreinfo.title }) + - // "\n ", - // default: true, - // }); - - // TODO, 缺少prefix提示 const confirmed = await confirm({ - message: i18next.t("cli_confirm_message"), + message: `${i18next.t("id", { id: scoreinfo.id })} + ${i18next.t("title", { title: scoreinfo.title })} + ${i18next.t("cli_confirm_message")}`, default: true, }); @@ -668,25 +560,12 @@ void (async () => { // filetype selection spinner.stop(); - // types = await inquirer.prompt({ - // type: "checkbox", - // name: "types", - // message: i18next.t("cli_types_message"), - // choices: ["midi", "mp3", "pdf"], - // validate: checkboxValidate, - // pageSize: Infinity, - // default: types, - // }); - types = await checkbox({ message: i18next.t("cli_types_message"), choices: ["midi", "mp3", "pdf"], validate: checkboxValidate, - pageSize: Infinity, }); - types = types.types; - // output directory const output = await getOutputDir(argv.output); spinner.start(); From 6d53f40e174f8a7a2b76a99ead8523451acffe5e Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 22:42:22 +0800 Subject: [PATCH 6/9] enable top-level await --- rollup.config.js | 2 +- src/cli.ts | 826 +++++++++++++++++++++++------------------------ tsconfig.json | 2 +- 3 files changed, 413 insertions(+), 417 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 840bd8a..6a3f6c6 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -96,7 +96,7 @@ export default [ input: "src/cli.ts", output: { file: "dist/cli.js", - format: "cjs", + format: "es", banner: "#!/usr/bin/env node", sourcemap: false, }, diff --git a/src/cli.ts b/src/cli.ts index 0bf7be1..1c7a821 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,9 +15,7 @@ import i18nextInit, { i18next } from "./i18n/index"; import { InputFileFormat } from "webmscore/schemas"; import { input, checkbox, confirm } from '@inquirer/prompts'; -(async () => { - await i18nextInit; -})(); +await i18nextInit; const ora: typeof import("ora") = require("ora"); const chalk: typeof import("chalk") = require("chalk"); @@ -161,105 +159,266 @@ const getOutputDir = async (defaultOutput: string) => { return output; }; -void (async () => { - if (!isNpx()) { - const { installed, latest, isLatest } = await getVerInfo(); - if (!isLatest) { - console.log( - chalk.yellowBright( - i18next.t("cli_outdated_version_message", { - installed: installed, - latest: latest, - }) - ) - ); - } +if (!isNpx()) { + const { installed, latest, isLatest } = await getVerInfo(); + if (!isLatest) { + console.log( + chalk.yellowBright( + i18next.t("cli_outdated_version_message", { + installed: installed, + latest: latest, + }) + ) + ); } +} - let isInteractive = true; - let types; - let filetypes; +let isInteractive = true; +let types; +let filetypes; - // Check if both input and type arguments are used - if (argv.input && argv.type) { - isInteractive = false; - } +// Check if both input and type arguments are used +if (argv.input && argv.type) { + isInteractive = false; +} - if (isInteractive) { - argv.verbose = true; - // Determine platform and paste message - const platform = os.platform(); - let pasteMessage = ""; - if (platform === "win32") { - pasteMessage = i18next.t("cli_windows_paste_hint"); - } else if (platform === "linux") { - pasteMessage = i18next.t("cli_linux_paste_hint"); - } // For MacOS, no hint is needed because the paste shortcut is universal. - - const fileInit = await input({ - message: `${i18next.t("cli_input_message")} +if (isInteractive) { + argv.verbose = true; + // Determine platform and paste message + const platform = os.platform(); + let pasteMessage = ""; + if (platform === "win32") { + pasteMessage = i18next.t("cli_windows_paste_hint"); + } else if (platform === "linux") { + pasteMessage = i18next.t("cli_linux_paste_hint"); + } // For MacOS, no hint is needed because the paste shortcut is universal. + + const fileInit = await input({ + message: `${i18next.t("cli_input_message")} (${i18next.t("cli_input_suffix")}) ${chalk.bgGray(pasteMessage)} `, - default: argv.input, - validate: (input: string) => { - if (!input) return false; - return ( - !!input.match(SCORE_URL_REG) || - fs.statSync(input).isFile() || - fs.statSync(input).isDirectory() + default: argv.input, + validate: (input: string) => { + if (!input) return false; + return ( + !!input.match(SCORE_URL_REG) || + fs.statSync(input).isFile() || + fs.statSync(input).isDirectory() + ); + }, + }); + + argv.input = fileInit; +} + +const spinner = createSpinner(); + +// Check if input is a file or directory +let isFile: boolean; +let isDir: boolean; +try { + isFile = fs.lstatSync(argv.input).isFile(); + isDir = fs.lstatSync(argv.input).isDirectory(); +} catch (_) { + isFile = false; + isDir = false; +} + +// Check if local file or directory +if (isFile || isDir) { + let filePaths: string[] = []; + + if (isDir) { + if (!(argv.input.endsWith("/") || argv.input.endsWith("\\"))) { + argv.input += "/"; + } + await fs.promises + .readdir(argv.input, { withFileTypes: true }) + .then((files) => + files.forEach((file) => { + try { + if (file.isDirectory()) { + return; + } + } catch (err) { + spinner.fail(err.message); + return; + } + filePaths.push(argv.input + file.name); + }) + ); + + if (isInteractive) { + if (argv.type) { + argv.type[argv.type.findIndex((e) => e === "musicxml")] = + "mxl"; + argv.type[argv.type.findIndex((e) => e === "midi")] = "mid"; + types = argv.type.map((e) => + INDV_DOWNLOADS.findIndex((f) => f.fileExt === e) ); - }, - }); + } + // build filetype choices + const typeChoices = INDV_DOWNLOADS.map((d, i) => ({ + name: d.name, + value: i, + })); + // filetype selection + spinner.stop(); - argv.input = fileInit; - } + types = await checkbox({ + message: i18next.t("cli_types_message"), + choices: typeChoices, + validate: checkboxValidate, + }); - const spinner = createSpinner(); + spinner.start(); - // Check if input is a file or directory - let isFile: boolean; - let isDir: boolean; - try { - isFile = fs.lstatSync(argv.input).isFile(); - isDir = fs.lstatSync(argv.input).isDirectory(); - } catch (_) { - isFile = false; - isDir = false; + // output directory + spinner.stop(); + const output = await getOutputDir(argv.output); + spinner.start(); + argv.output = output; + } + } else { + filePaths.push(argv.input); } - // Check if local file or directory - if (isFile || isDir) { - let filePaths: string[] = []; + await Promise.all( + filePaths.map(async (filePath) => { + createDirectoryIfNotExist(filePath); + // validate input file + if (!fs.statSync(filePath).isFile()) { + spinner.fail(i18next.t("cli_file_error")); + return; + } - if (isDir) { - if (!(argv.input.endsWith("/") || argv.input.endsWith("\\"))) { - argv.input += "/"; + if (!isInteractive) { + // validate types + if (argv.type.length === 0) { + spinner.fail(i18next.t("cli_type_error")); + return; + } } - await fs.promises - .readdir(argv.input, { withFileTypes: true }) - .then((files) => - files.forEach((file) => { - try { - if (file.isDirectory()) { - return; - } - } catch (err) { - spinner.fail(err.message); - return; - } - filePaths.push(argv.input + file.name); - }) + + let inputFileExt = path.extname(filePath).substring(1); + + if (inputFileExt === "mid") { + inputFileExt = "midi"; + } + if ( + ![ + "gp", + "gp3", + "gp4", + "gp5", + "gpx", + "gtp", + "kar", + "midi", + "mscx", + "mscz", + "musicxml", + "mxl", + "ptb", + "xml", + ].includes(inputFileExt) + ) { + spinner.fail(i18next.t("cli_file_extension_error")); + return; + } + + // get scoreinfo + let scoreinfo = new ScoreInfoObj( + 0, + path.basename(filePath, "." + inputFileExt) + ); + + // load file + let score: WebMscore; + let metadata: import("webmscore/schemas").ScoreMetadata; + try { + // load local file + const data = await fs.promises.readFile(filePath); + await setMscz(scoreinfo, data.buffer); + if (argv.verbose) { + spinner.info(i18next.t("cli_file_loaded_message")); + spinner.start(); + } + // load score using webmscore + score = await loadMscore( + inputFileExt as InputFileFormat, + scoreinfo ); - if (isInteractive) { - if (argv.type) { - argv.type[argv.type.findIndex((e) => e === "musicxml")] = - "mxl"; - argv.type[argv.type.findIndex((e) => e === "midi")] = "mid"; - types = argv.type.map((e) => - INDV_DOWNLOADS.findIndex((f) => f.fileExt === e) - ); + if (isInteractive && isFile) { + metadata = await score.metadata(); + } + + if (argv.verbose) { + spinner.info(i18next.t("cli_score_loaded_message")); + } + } catch (err) { + if (isFile || argv.verbose) { + spinner.fail(err.message); } + if (argv.verbose) { + spinner.info(i18next.t("cli_input_error")); + } + return; + } + + let parts; + if (isInteractive && isFile) { + // build part choices + const partChoices = metadata.excerpts.map((p) => ({ + name: p.title, + value: p.id, + })); + // console.log(partChoices); + // add the "full score" option as a "part" + partChoices.unshift({ + value: -1, + name: i18next.t("full_score"), + }); + + // part selection + spinner.stop(); + + // parts = await inquirer.prompt({ + // type: "checkbox", + // name: "parts", + // message: i18next.t("cli_parts_message"), + // choices: partChoices, + // validate: checkboxValidate, + // pageSize: Infinity, + // }); + + parts = await checkbox({ + message: i18next.t("cli_parts_message"), + choices: partChoices, + // validate: checkboxValidate, + pageSize: Infinity, + }); + + spinner.start(); + // console.log(parts); + parts = partChoices.filter((e) => + parts.parts.includes(e.value) + ); + // console.log(parts); + } else { + parts = [{ name: i18next.t("full_score"), value: -1 }]; + } + + if (argv.type) { + argv.type[argv.type.findIndex((e) => e === "musicxml")] = + "mxl"; + argv.type[argv.type.findIndex((e) => e === "midi")] = "mid"; + types = argv.type.map((e) => + INDV_DOWNLOADS.findIndex((f) => f.fileExt === e) + ); + } + if (isInteractive && isFile) { // build filetype choices const typeChoices = INDV_DOWNLOADS.map((d, i) => ({ name: d.name, @@ -268,6 +427,7 @@ void (async () => { // filetype selection spinner.stop(); + // TODO: 重复的代码 types = await checkbox({ message: i18next.t("cli_types_message"), choices: typeChoices, @@ -275,369 +435,205 @@ void (async () => { }); spinner.start(); + } + filetypes = types.map((i) => INDV_DOWNLOADS[i]); + + if (isInteractive && isFile) { // output directory spinner.stop(); const output = await getOutputDir(argv.output); spinner.start(); argv.output = output; } - } else { - filePaths.push(argv.input); - } - await Promise.all( - filePaths.map(async (filePath) => { - createDirectoryIfNotExist(filePath); - // validate input file - if (!fs.statSync(filePath).isFile()) { - spinner.fail(i18next.t("cli_file_error")); - return; - } + createDirectoryIfNotExist(argv.output); - if (!isInteractive) { - // validate types - if (argv.type.length === 0) { - spinner.fail(i18next.t("cli_type_error")); - return; - } - } - - let inputFileExt = path.extname(filePath).substring(1); - - if (inputFileExt === "mid") { - inputFileExt = "midi"; - } - if ( - ![ - "gp", - "gp3", - "gp4", - "gp5", - "gpx", - "gtp", - "kar", - "midi", - "mscx", - "mscz", - "musicxml", - "mxl", - "ptb", - "xml", - ].includes(inputFileExt) - ) { - spinner.fail(i18next.t("cli_file_extension_error")); - return; - } - - // get scoreinfo - let scoreinfo = new ScoreInfoObj( - 0, - path.basename(filePath, "." + inputFileExt) - ); - - // load file - let score: WebMscore; - let metadata: import("webmscore/schemas").ScoreMetadata; - try { - // load local file - const data = await fs.promises.readFile(filePath); - await setMscz(scoreinfo, data.buffer); - if (argv.verbose) { - spinner.info(i18next.t("cli_file_loaded_message")); - spinner.start(); - } - // load score using webmscore - score = await loadMscore( - inputFileExt as InputFileFormat, - scoreinfo - ); - - if (isInteractive && isFile) { - metadata = await score.metadata(); - } + // validate output directory + try { + await fs.promises.access(argv.output); + } catch (err) { + spinner.fail(err.message); + return; + } + // export files + const fileName = + scoreinfo.fileName || (await score.titleFilenameSafe()); + // spinner.start(); + for (const type of filetypes) { + for (const part of parts) { + // select part + await score.setExcerptId(part.value); + + // generate file data + const data = await type.action(score); + + // save to filesystem + const n = `${fileName} - ${part.name}.${type.fileExt}`; + const f = path.join(argv.output, n); + await fs.promises.writeFile(f, data); if (argv.verbose) { - spinner.info(i18next.t("cli_score_loaded_message")); - } - } catch (err) { - if (isFile || argv.verbose) { - spinner.fail(err.message); - } - if (argv.verbose) { - spinner.info(i18next.t("cli_input_error")); - } - return; - } - - let parts; - if (isInteractive && isFile) { - // build part choices - const partChoices = metadata.excerpts.map((p) => ({ - name: p.title, - value: p.id, - })); - // console.log(partChoices); - // add the "full score" option as a "part" - partChoices.unshift({ - value: -1, - name: i18next.t("full_score"), - }); - - // part selection - spinner.stop(); - - // parts = await inquirer.prompt({ - // type: "checkbox", - // name: "parts", - // message: i18next.t("cli_parts_message"), - // choices: partChoices, - // validate: checkboxValidate, - // pageSize: Infinity, - // }); - - parts = await checkbox({ - message: i18next.t("cli_parts_message"), - choices: partChoices, - // validate: checkboxValidate, - pageSize: Infinity, - }); - - spinner.start(); - // console.log(parts); - parts = partChoices.filter((e) => - parts.parts.includes(e.value) - ); - // console.log(parts); - } else { - parts = [{ name: i18next.t("full_score"), value: -1 }]; - } - - if (argv.type) { - argv.type[argv.type.findIndex((e) => e === "musicxml")] = - "mxl"; - argv.type[argv.type.findIndex((e) => e === "midi")] = "mid"; - types = argv.type.map((e) => - INDV_DOWNLOADS.findIndex((f) => f.fileExt === e) - ); - } - if (isInteractive && isFile) { - // build filetype choices - const typeChoices = INDV_DOWNLOADS.map((d, i) => ({ - name: d.name, - value: i, - })); - // filetype selection - spinner.stop(); - - // TODO: 重复的代码 - types = await checkbox({ - message: i18next.t("cli_types_message"), - choices: typeChoices, - validate: checkboxValidate, - }); - - spinner.start(); - } - - filetypes = types.map((i) => INDV_DOWNLOADS[i]); - - if (isInteractive && isFile) { - // output directory - spinner.stop(); - const output = await getOutputDir(argv.output); - spinner.start(); - argv.output = output; - } - - createDirectoryIfNotExist(argv.output); - - // validate output directory - try { - await fs.promises.access(argv.output); - } catch (err) { - spinner.fail(err.message); - return; - } - - // export files - const fileName = - scoreinfo.fileName || (await score.titleFilenameSafe()); - // spinner.start(); - for (const type of filetypes) { - for (const part of parts) { - // select part - await score.setExcerptId(part.value); - - // generate file data - const data = await type.action(score); - - // save to filesystem - const n = `${fileName} - ${part.name}.${type.fileExt}`; - const f = path.join(argv.output, n); - await fs.promises.writeFile(f, data); - if (argv.verbose) { - spinner.info( - i18next.t("cli_saved_message", { - file: chalk.underline(f), - }) - ); - } + spinner.info( + i18next.t("cli_saved_message", { + file: chalk.underline(f), + }) + ); } } - }) - ); - spinner.succeed(i18next.t("cli_done_message")); - return; - } else { - // validate input URL - if (!argv.input.match(SCORE_URL_REG)) { - spinner.fail(i18next.t("cli_url_error")); - return; - } - argv.input = argv.input.match(SCORE_URL_REG)[0]; - - // validate types - if (!isInteractive) { - if (argv.type.length === 0) { - spinner.fail(i18next.t("cli_type_error")); - return; - } else if ( - ["mscz", "mscx", "musicxml", "flac", "ogg"].some((e) => - argv.type.includes(e) - ) - ) { - // Fail since user cannot download these types from a URL - spinner.fail(i18next.t("cli_url_type_error")); - return; } + }) + ); + spinner.succeed(i18next.t("cli_done_message")); + process.exit(0); +} else { + // validate input URL + if (!argv.input.match(SCORE_URL_REG)) { + spinner.fail(i18next.t("cli_url_error")); + process.exit(0); + } + argv.input = argv.input.match(SCORE_URL_REG)[0]; + + // validate types + if (!isInteractive) { + if (argv.type.length === 0) { + spinner.fail(i18next.t("cli_type_error")); + process.exit(0); + } else if ( + ["mscz", "mscx", "musicxml", "flac", "ogg"].some((e) => + argv.type.includes(e) + ) + ) { + // Fail since user cannot download these types from a URL + spinner.fail(i18next.t("cli_url_type_error")); + process.exit(0); } + } - // request scoreinfo - let scoreinfo: ScoreInfoHtml = await ScoreInfoHtml.request(argv.input); + // request scoreinfo + let scoreinfo: ScoreInfoHtml = await ScoreInfoHtml.request(argv.input); - // validate musescore URL - if (scoreinfo.id === 0) { - spinner.fail(i18next.t("cli_score_not_found")); - return; - } + // validate musescore URL + if (scoreinfo.id === 0) { + spinner.fail(i18next.t("cli_score_not_found")); + process.exit(0); + } - if (isInteractive) { - // confirmation - spinner.stop(); + if (isInteractive) { + // confirmation + spinner.stop(); - const confirmed = await confirm({ - message: `${i18next.t("id", { id: scoreinfo.id })} + const confirmed = await confirm({ + message: `${i18next.t("id", { id: scoreinfo.id })} ${i18next.t("title", { title: scoreinfo.title })} ${i18next.t("cli_confirm_message")}`, - default: true, - }); + default: true, + }); - if (!confirmed) return; + if (!confirmed) process.exit(0); - // print a blank line - console.log(); + // print a blank line + console.log(); + spinner.start(); + } else { + // print message if verbosity is enabled + if (argv.verbose) { + spinner.stop(); + console.log( + `${chalk.yellow("!")} ` + + i18next.t("id", { id: scoreinfo.id }) + + "\n " + + i18next.t("title", { title: scoreinfo.title }) + + "\n " + ); spinner.start(); - } else { - // print message if verbosity is enabled - if (argv.verbose) { - spinner.stop(); - console.log( - `${chalk.yellow("!")} ` + - i18next.t("id", { id: scoreinfo.id }) + - "\n " + - i18next.t("title", { title: scoreinfo.title }) + - "\n " - ); - spinner.start(); - } } + } - if (argv.type) { - types = argv.type; - } - if (isInteractive) { - // filetype selection - spinner.stop(); + if (argv.type) { + types = argv.type; + } + if (isInteractive) { + // filetype selection + spinner.stop(); - types = await checkbox({ - message: i18next.t("cli_types_message"), - choices: ["midi", "mp3", "pdf"], - validate: checkboxValidate, - }); + types = await checkbox({ + message: i18next.t("cli_types_message"), + choices: ["midi", "mp3", "pdf"], + validate: checkboxValidate, + }); - // output directory - const output = await getOutputDir(argv.output); - spinner.start(); - argv.output = output; - } + // output directory + const output = await getOutputDir(argv.output); + spinner.start(); + argv.output = output; + } - createDirectoryIfNotExist(argv.output); + createDirectoryIfNotExist(argv.output); - // validate output directory - try { - await fs.promises.access(argv.output); - } catch (err) { - spinner.fail(err.message); - return; - } + // validate output directory + try { + await fs.promises.access(argv.output); + } catch (err) { + spinner.fail(err.message); + process.exit(0);; + } - await Promise.all( - types.map(async (type) => { - // download/generate file data - let fileExt: String; - let fileData: Buffer; - switch (type) { - case "midi": { - fileExt = "mid"; - const fileUrl = await getFileUrl( - scoreinfo.id, - "midi", - argv.input - ); - fileData = await fetchBuffer(fileUrl); - break; - } - case "mp3": { - fileExt = "mp3"; - const fileUrl = await getFileUrl( - scoreinfo.id, - "mp3", + await Promise.all( + types.map(async (type) => { + // download/generate file data + let fileExt: String; + let fileData: Buffer; + switch (type) { + case "midi": { + fileExt = "mid"; + const fileUrl = await getFileUrl( + scoreinfo.id, + "midi", + argv.input + ); + fileData = await fetchBuffer(fileUrl); + break; + } + case "mp3": { + fileExt = "mp3"; + const fileUrl = await getFileUrl( + scoreinfo.id, + "mp3", + argv.input + ); + fileData = await fetchBuffer(fileUrl); + break; + } + case "pdf": { + fileExt = "pdf"; + fileData = Buffer.from( + await exportPDF( + scoreinfo, + scoreinfo.sheet, argv.input - ); - fileData = await fetchBuffer(fileUrl); - break; - } - case "pdf": { - fileExt = "pdf"; - fileData = Buffer.from( - await exportPDF( - scoreinfo, - scoreinfo.sheet, - argv.input - ) - ); - break; - } + ) + ); + break; } + } - // save to filesystem - const f = path.join( - argv.output, - `${scoreinfo.fileName}.${fileExt}` + // save to filesystem + const f = path.join( + argv.output, + `${scoreinfo.fileName}.${fileExt}` + ); + await fs.promises.writeFile(f, fileData); + if (argv.verbose) { + spinner.info( + i18next.t("cli_saved_message", { + file: chalk.underline(f), + }) ); - await fs.promises.writeFile(f, fileData); - if (argv.verbose) { - spinner.info( - i18next.t("cli_saved_message", { - file: chalk.underline(f), - }) - ); - } - }) - ); + } + }) + ); - spinner.succeed(i18next.t("cli_done_message")); - return; - } -})(); + spinner.succeed(i18next.t("cli_done_message")); + process.exit(0); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 5a8c4c7..911dc71 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es6", + "target": "es2022", "lib": ["dom", "dom.iterable", "es2019"], "module": "esnext", "moduleResolution": "node", From ccf66a268655099a826156704ca2c912d131a2d2 Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 22:45:44 +0800 Subject: [PATCH 7/9] eliminate [MODULE_TYPELESS_PACKAGE_JSON] Warning --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b9126e4..946670e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "dl-librescore", "version": "0.35.21", + "type": "module", "description": "Download sheet music", "main": "dist/main.user.js", "bin": "dist/cli.js", From 26e3fbf5a5ca7490773985ecd71277a289eb317c Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Tue, 4 Feb 2025 22:54:43 +0800 Subject: [PATCH 8/9] update tsconfig.json --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 911dc71..e2c7dda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es2022", - "lib": ["dom", "dom.iterable", "es2019"], + "lib": ["dom", "dom.iterable", "es2022"], "module": "esnext", "moduleResolution": "node", "esModuleInterop": true, From 86bedec28129eb83609f0a2306ddfa020b9d5513 Mon Sep 17 00:00:00 2001 From: NeroBlackstone Date: Wed, 12 Feb 2025 23:38:36 +0800 Subject: [PATCH 9/9] update conversion inquirer part --- package.json | 2 +- src/cli.ts | 23 +++++++---------------- src/file.ts | 23 ++++++++++++----------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 1a78799..d4a8caf 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@rollup/plugin-typescript": "^12.1.2", "svg-to-pdfkit": "^0.1.8", "tslib": "^2.8.1", - "typescript": "^4.7.2" + "typescript": "^5.7.3" }, "overrides": { "whatwg-url": "14.x" diff --git a/src/cli.ts b/src/cli.ts index 1c7a821..5b702b8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -202,8 +202,8 @@ if (isInteractive) { if (!input) return false; return ( !!input.match(SCORE_URL_REG) || - fs.statSync(input).isFile() || - fs.statSync(input).isDirectory() + (fs.existsSync(input) && fs.statSync(input).isFile()) || + (fs.existsSync(input) && fs.statSync(input).isDirectory()) ); }, }); @@ -384,26 +384,17 @@ if (isFile || isDir) { // part selection spinner.stop(); - // parts = await inquirer.prompt({ - // type: "checkbox", - // name: "parts", - // message: i18next.t("cli_parts_message"), - // choices: partChoices, - // validate: checkboxValidate, - // pageSize: Infinity, - // }); - parts = await checkbox({ message: i18next.t("cli_parts_message"), choices: partChoices, - // validate: checkboxValidate, - pageSize: Infinity, + validate: checkboxValidate, }); - - spinner.start(); + // console.log(partChoices) // console.log(parts); + spinner.start(); + parts = partChoices.filter((e) => - parts.parts.includes(e.value) + parts.includes(e.value) ); // console.log(parts); } else { diff --git a/src/file.ts b/src/file.ts index bebbf26..ed8cecc 100644 --- a/src/file.ts +++ b/src/file.ts @@ -69,9 +69,10 @@ const getApiAuthNetwork = async ( ); if (audioSources !== null) { - audioSources.querySelector( - "option[value='0']" - )?.selected = true; + const option = audioSources.querySelector("option[value='0']"); + if (option) { + (option as HTMLOptionElement).selected = true; + } audioSources.dispatchEvent( new Event("change") @@ -82,18 +83,18 @@ const getApiAuthNetwork = async ( "article[role='dialog'] header > button" ) ?.click(); - } + } }); observer.observe(document.body, { childList: true, subtree: true, }); } else { - const el = - fsBtn.parentElement?.parentElement?.querySelector( - "button" - ) as HTMLButtonElement; - el.click(); + const el = + fsBtn.parentElement?.parentElement?.querySelector( + "button" + ) as HTMLButtonElement; + el.click(); } break; } @@ -105,7 +106,7 @@ const getApiAuthNetwork = async ( // mobile device document.querySelector("#scorePlayButton")?.click(); } else { - el.click(); + el.click(); } break; } @@ -119,7 +120,7 @@ const getApiAuthNetwork = async ( numPages = parentDiv.children.length - 3; let i = 0; - function scrollToNextChild() { + function scrollToNextChild () { let childDiv = parentDiv.children[i]; if (childDiv) { childDiv.scrollIntoView();