@intlify/cli
Version:
CLI Tooling for i18n development
585 lines (573 loc) • 18.2 kB
JavaScript
;
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);
});
})();