UNPKG

@intlify/cli

Version:

CLI Tooling for i18n development

585 lines (573 loc) 18.2 kB
'use strict'; const yargs = require('yargs'); const helpers = require('yargs/helpers'); const path = require('pathe'); const fs = require('fs'); const shared = require('@intlify/shared'); const core = require('@intlify/core'); const node = require('@intlify/utils/node'); const createDebug = require('debug'); const colorette = require('colorette'); const index = require('./shared/cli.89208cd7.cjs'); const ignore = require('ignore'); require('@intlify/bundle-utils'); require('fast-glob'); require('diff-match-patch'); require('jsonc-eslint-parser'); require('yaml-eslint-parser'); require('cosmiconfig'); require('prettier'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const yargs__default = /*#__PURE__*/_interopDefaultCompat(yargs); const path__default = /*#__PURE__*/_interopDefaultCompat(path); const createDebug__default = /*#__PURE__*/_interopDefaultCompat(createDebug); const ignore__default = /*#__PURE__*/_interopDefaultCompat(ignore); function isSFCParserError(err) { return "errors" in err && "filepath" in err; } const dirname = path__default.dirname(new URL((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))).pathname); const debug$3 = createDebug__default("@intlify/cli:i18n"); const DEFAULT_LOCALE = "en-US"; let resources = null; let context = null; async function loadI18nResources() { const dirents = await fs.promises.readdir(path__default.resolve(dirname, "../locales"), { withFileTypes: true }); return dirents.reduce( async (acc, dir) => { if (dir.isFile()) { const data = await fs.promises.readFile( path__default.resolve(dirname, "../locales", dir.name), { encoding: "utf-8" } ); const { name } = path__default.parse(dir.name); debug$3("load i18n resource", name, data); const messages = await acc; messages[name] = JSON.parse( data ); } return acc; }, Promise.resolve({}) ); } function getLocale() { return node.getNavigatorLanguage() || DEFAULT_LOCALE; } function t(key, ...args) { if (context == null) { console.error( "cannot initialize CoreContext with @intlify/core createCoreContext" ); return key; } const ret = Reflect.apply(core.translate, null, [context, key, ...args]); return shared.isString(ret) ? ret : key; } async function initI18n() { try { resources = await loadI18nResources(); context = core.createCoreContext({ locale: getLocale(), fallbackLocale: DEFAULT_LOCALE, fallbackWarn: false, missingWarn: false, warnHtmlMessage: false, fallbackFormat: true, messages: resources }); } catch (e) { debug$3("load i18n resource errors", e.message); throw e; } } const debug$2 = createDebug__default("@intlify/cli:compile"); function defineCommand$2() { const command = "compile"; const aliases = "cp"; const describe = t("compile the i18n resources"); const builder = (args) => { return args.option("source", { type: "string", alias: "s", describe: t("the i18n resource source path"), demandOption: true }).option("output", { type: "string", alias: "o", describe: t("the compiled i18n resource output path") }).option("mode", { type: "string", alias: "m", default: "production", describe: t( "the compiled i18n resource mode, 'production' or 'development' (default: 'production')" ) }).option("format", { type: "string", alias: "f", default: "function", describe: t( "resource compilation format, 'function' or 'ast' (default: 'function')" ) }); }; const handler = async (args) => { const output = args.output != null ? path__default.resolve(process.cwd(), args.output) : process.cwd(); const ret = await index.compile(args.source, output, { mode: args.mode, ast: args.format === "ast", onCompile: (source, output2) => { console.log( colorette.green( t("Success compilation: {source} -> {output}", { source, output: output2 }) ) ); }, onError: (code, source, output2, msg) => { switch (code) { case index.CompileErrorCodes.NOT_SUPPORTED_FORMAT: const parsed = path__default.parse(source); console.warn( colorette.yellow( t("{source}: Ignore compilation due to not supported '{ext}'", { ext: parsed.ext }) ) ); break; case index.CompileErrorCodes.INTERNAL_COMPILE_WARNING: console.log( colorette.yellow( t("Warning compilation: {source} -> {output}, {msg}", { source, output: output2, msg }) ) ); break; case index.CompileErrorCodes.INTERNAL_COMPILE_ERROR: console.error( colorette.red( t("Error compilation: {source} -> {output}, {msg}", { source, output: output2, msg }) ) ); break; } } }); debug$2("compile: ", ret); }; return { command, aliases, describe, builder, handler }; } function typeGuard(o, className) { return o instanceof className; } class RequireError extends Error { } function defineFail(userError) { return (msg, err) => { if (msg) { console.error(msg); console.warn(colorette.red(colorette.bold(msg))); process.exit(1); } else { if (typeGuard(err, userError)) { console.warn(colorette.yellow(colorette.bold(err.message))); process.exit(0); } else { throw err; } } }; } const DEFAULT_IGNORE_FILENAME = ".intlifyignore"; function checkType(type) { if (type !== "custom-block") { throw new RequireError( t(`'--type' is not supported except for 'custom-block'`) ); } } function checkSource(argsLength, source) { if (source == null && argsLength === 1) { throw new RequireError( t( `if you don't specify some files at the end of the command, the '\u2014-source' option is required` ) ); } } async function readIgnore(cwd, path$1) { if (path$1 == null) { const defaultIgnorePath = path.resolve(cwd, DEFAULT_IGNORE_FILENAME); return await index.exists(defaultIgnorePath) ? await fs.promises.readFile(defaultIgnorePath, "utf8") : void 0; } else { return await index.exists(path$1, true) ? await fs.promises.readFile(path$1, "utf8") : void 0; } } async function getSFCFiles(args = {}) { const { source, files, cwd, ignore: _ignore } = args; const _files = await index.getSourceFiles({ source, files }, [ (file) => { const parsed = path.parse(file); return parsed.ext === ".vue"; } ]); if (cwd && _ignore) { return ignore__default().add(_ignore).filter(_files.map((file) => path.relative(cwd, file))).map((file) => path.resolve(cwd, file)); } else { return _files; } } const debug$1 = createDebug__default("@intlify/cli:annotate"); function defineCommand$1() { const command = "annotate"; const aliases = "at"; const describe = t("annotate the attributes"); const builder = (args) => { return args.option("source", { type: "string", alias: "s", describe: t("the source path") }).option("type", { type: "string", alias: "t", describe: t("the annotation type") }).option("force", { type: "boolean", alias: "f", describe: t("forced applying of attributes") }).option("details", { type: "boolean", alias: "V", describe: t("annotated result detail options") }).option("attrs", { type: "array", alias: "a", describe: t("the attributes to annotate") }).option("vue", { type: "number", alias: "v", describe: t("the vue template compiler version"), default: 3 }).option("ignore", { type: "string", alias: "i", describe: t( "the ignore configuration path files passed at the end of the options or `--source` option" ) }).option("dryRun", { type: "boolean", alias: "d", describe: t("target files without annotating") }).fail(defineFail(RequireError)); }; const handler = async (args) => { args.type = args.type || "custom-block"; const { source, force, details, attrs, vue, ignore, dryRun } = args; debug$1("annotate args:", args); checkType(args.type); checkSource(args._.length, source); if (dryRun) { console.log(); console.log(colorette.yellowBright(colorette.bold(`${t("dry run mode")}:`))); console.log(); } let counter = 0; let passCounter = 0; let fineCounter = 0; let warnCounter = 0; let forceCounter = 0; let ignoreCounter = 0; let errorCounter = 0; function printStats() { console.log(""); console.log( colorette.white(colorette.bold(t("{count} annotateable files ", { count: counter }))) ); console.log( colorette.green(colorette.bold(t("{count} annotated files", { count: fineCounter }))) ); if (details) { console.log( colorette.white(colorette.bold(t("{count} passed files", { count: passCounter }))) ); } console.log(colorette.yellow(t("{count} warned files", { count: warnCounter }))); if (details) { console.log(colorette.yellow(t("{count} forced files", { count: forceCounter }))); console.log( colorette.yellow(t("{count} ignored files", { count: ignoreCounter })) ); } console.log(colorette.red(t("{count} error files", { count: errorCounter }))); console.log(""); } let status = "fine"; const onWarn = warnHnadler(() => status = "warn"); const cwd = process.cwd(); const _ignore = await readIgnore(cwd, ignore); const files = await getSFCFiles({ source, files: args._, cwd, ignore: _ignore }); for (const file of files) { const data = await fs.promises.readFile(file, "utf8"); let annotated = null; try { status = "none"; annotated = await index.annotate(data, file, { type: "i18n", force, attrs, vue, onWarn }); if (index.hasDiff(annotated, data)) { status = "fine"; } if (status === "fine") { fineCounter++; console.log(colorette.green(`${file}: ${t("annotate")}`)); if (!dryRun) { await fs.promises.writeFile(file, annotated, "utf8"); } } else if (status === "none") { passCounter++; console.log(colorette.white(`${file}: ${t("pass annotate")}`)); } else if (status === "warn") { warnCounter++; if (force) { forceCounter++; console.log(colorette.yellow(`${file}: ${t("force annotate")}`)); if (!dryRun) { await fs.promises.writeFile(file, annotated, "utf8"); } } else { ignoreCounter++; console.log(colorette.yellow(`${file}: ${t("ignore annotate")}`)); } } } catch (e) { status = "error"; errorCounter++; if (isSFCParserError(e)) { console.error(colorette.red(colorette.bold(`${e.message} at ${e.filepath}`))); e.erorrs.forEach((err) => console.error(colorette.red(` ${err.message}`))); } else if (e instanceof index.SFCAnnotateError) { console.error( colorette.red(`${e.filepath}: ${t(e.message)}`) // eslint-disable-line @typescript-eslint/no-explicit-any, @intlify/vue-i18n/no-dynamic-keys ); } else { console.error(colorette.red(e.message)); } if (!dryRun) { throw e; } } counter++; } printStats(); }; return { command, aliases, describe, builder, handler }; } function warnHnadler(cb) { return (code, args, block) => { debug$1(`annotate warning: block => ${JSON.stringify(block)}`); switch (code) { case index.AnnotateWarningCodes.NOT_SUPPORTED_TYPE: console.log( colorette.yellow(t(`Unsupported '{type}' block content type: {actual}`, args)) ); case index.AnnotateWarningCodes.LANG_MISMATCH_IN_ATTR_AND_CONTENT: console.log( colorette.yellow( t( "Detected lang mismatch in `lang` attr ('{lang}') and block content ('{content}')", args ) ) ); case index.AnnotateWarningCodes.LANG_MISMATCH_IN_OPTION_AND_CONTENT: console.log( colorette.yellow( t( "Detected lang mismatch in `lang` option ('{lang}') and block content ('{content}')", args ) ) ); case index.AnnotateWarningCodes.LANG_MISMATCH_IN_SRC_AND_CONTENT: console.log( colorette.yellow( t( "Detected lang mismatch in block `src` ('{src}') and block content ('{content}')", args ) ) ); default: debug$1(`annotate warning: ${code}`); cb(); break; } }; } const debug = createDebug__default("@intlify/cli:format"); function defineCommand() { const command = "format"; const aliases = "ft"; const describe = t("format for single-file components"); const builder = (args) => { return args.option("source", { type: "string", alias: "s", describe: t("the source path") }).option("type", { type: "string", alias: "t", describe: t("the format type") }).option("prettier", { type: "string", alias: "p", describe: t("the config file path of prettier") }).option("vue", { type: "number", alias: "v", describe: t("the vue template compiler version"), default: 3 }).option("ignore", { type: "string", alias: "i", describe: t( "the ignore configuration path files passed at the end of the options or `--source` option" ) }).option("dryRun", { type: "boolean", alias: "d", describe: t("target files without formatting") }).fail(defineFail(RequireError)); }; const handler = async (args) => { args.type = args.type || "custom-block"; const { source, prettier, ignore, vue, dryRun } = args; debug("format args:", args); checkType(args.type); checkSource(args._.length, source); const cwd = process.cwd(); const prettierConfig = prettier ? await index.getPrettierConfig(path__default.resolve(cwd, prettier)) : { filepath: "", config: {} }; debug("prettier config", prettierConfig); if (dryRun) { console.log(); console.log(colorette.yellowBright(colorette.bold(`${t("dry run mode")}:`))); console.log(); } let formattedCounter = 0; let noChangeCounter = 0; let errorCounter = 0; const _ignore = await readIgnore(cwd, ignore); const files = await getSFCFiles({ source, files: args._, cwd, ignore: _ignore }); for (const file of files) { try { const data = await fs.promises.readFile(file, "utf8"); const formatted = await index.format(data, file, { vue, prettier: prettierConfig?.config }); if (index.hasDiff(formatted, data)) { formattedCounter++; console.log(colorette.green(`${file}: ${t("formatted")}`)); } else { noChangeCounter++; console.log(colorette.blue(`${file}: ${t("no change")}`)); } if (!dryRun) { await fs.promises.writeFile(file, formatted, "utf8"); } } catch (e) { errorCounter++; if (e instanceof index.FormatLangNotFoundError) { console.error( colorette.red(`${e.filepath}: ${t(e.message)}`) // eslint-disable-line @typescript-eslint/no-explicit-any, @intlify/vue-i18n/no-dynamic-keys ); } else if (isSFCParserError(e)) { console.error(colorette.red(`${e.message} at ${e.filepath}`)); e.erorrs.forEach((err) => console.error(colorette.red(` ${err.message}`))); } else { console.error(colorette.red(e.message)); } if (!dryRun) { throw e; } } } console.log(""); console.log( colorette.whiteBright(colorette.bold(t("{count} formattable files", { count: files.length }))) ); console.log( colorette.greenBright( colorette.bold(t("{count} formatted files", { count: formattedCounter })) ) ); console.log( colorette.blueBright(colorette.bold(t("{count} no change files", { count: noChangeCounter }))) ); if (dryRun) { console.log( colorette.redBright(colorette.bold(t("{count} error files", { count: errorCounter }))) ); } console.log(""); }; return { command, aliases, describe, builder, handler }; } (async () => { await initI18n(); yargs__default(helpers.hideBin(process.argv)).scriptName("intlify").usage(t("Usage: $0 <command> [options]")).command(defineCommand$2()).command(defineCommand$1()).command(defineCommand()).demandCommand().help().version().argv; process.on("uncaughtException", (err) => { console.error(`uncaught exception: ${err} `); process.exit(1); }); process.on("unhandledRejection", (reason, p) => { console.error("unhandled rejection at:", p, "reason:", reason); process.exit(1); }); })();