zotero-plugin-scaffold
Version:
A scaffold for Zotero plugin development.
258 lines (251 loc) • 7.2 kB
JavaScript
import { readFile, writeFile } from 'node:fs/promises';
import { glob } from 'tinyglobby';
import process from 'node:process';
import readline from 'node:readline';
import { isPlainObject } from 'es-toolkit';
import util from 'node:util';
import { isDebug } from 'std-env';
const factory = (...formats) =>
new Proxy(util.styleText.bind(util, formats), {
get: (_, format) =>
util.inspect.colors[format] && factory(...formats, format),
});
const styleText = factory();
var LOG_LEVEL = /* @__PURE__ */ ((LOG_LEVEL2) => {
LOG_LEVEL2[LOG_LEVEL2["TRACE"] = 0] = "TRACE";
LOG_LEVEL2[LOG_LEVEL2["DEBUG"] = 1] = "DEBUG";
LOG_LEVEL2[LOG_LEVEL2["INFO"] = 2] = "INFO";
LOG_LEVEL2[LOG_LEVEL2["WARN"] = 3] = "WARN";
LOG_LEVEL2[LOG_LEVEL2["ERROR"] = 4] = "ERROR";
return LOG_LEVEL2;
})(LOG_LEVEL || {});
const SYMBOLS = {
SUCCESS: styleText.green("\u2714"),
INFO: styleText.blue("\u2139"),
FAIL: styleText.red("\u2716"),
TIP: styleText.blue("\u2192"),
ERROR: styleText.bgRed(" ERROR "),
WARN: styleText.bgYellow(" WARN "),
DEBUG: styleText.grey("\u2699"),
NONE: ""
};
const DEFAULT_OPTIONS = {
SPACE: 0,
NEW_LINE: false
};
const LOG_METHODS_CONFIG = {
error: {
level: 4 /* ERROR */,
symbol: SYMBOLS.ERROR,
wrapNewLine: true
},
warn: {
level: 3 /* WARN */,
symbol: SYMBOLS.WARN,
wrapNewLine: true
},
tip: {
level: 2 /* INFO */,
symbol: SYMBOLS.TIP
},
info: {
level: 2 /* INFO */,
symbol: SYMBOLS.INFO
},
debug: {
level: 1 /* DEBUG */,
symbol: SYMBOLS.DEBUG
},
success: {
level: 2 /* INFO */,
symbol: SYMBOLS.SUCCESS
},
fail: {
level: 4 /* ERROR */,
symbol: SYMBOLS.FAIL
},
log: {
level: 2 /* INFO */,
symbol: SYMBOLS.NONE
}
};
class Logger {
static instance;
currentLogLevel;
constructor(level) {
this.currentLogLevel = this.determineLogLevel(level);
}
/**
* Determine the appropriate log level
*/
determineLogLevel(level) {
if (isDebug)
return 0 /* TRACE */;
const envLevel = process.env.ZOTERO_PLUGIN_LOG_LEVEL;
return envLevel ? LOG_LEVEL[envLevel] : level ?? 2 /* INFO */;
}
static getInstance() {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
setLogLevel(level) {
this.currentLogLevel = LOG_LEVEL[level];
}
get level() {
return this.currentLogLevel;
}
/**
* Generic log formatting logic
*/
formatContent(content) {
if (typeof content === "string")
return content;
if (content instanceof Error)
return this.formatError(content);
if (isPlainObject(content))
return JSON.stringify(content, null, 2);
return String(content);
}
formatError(error) {
return `${styleText.red(error.name)}: ${styleText.red(error.message)}
${error.stack}`;
}
/**
* Core logging method
*/
logInternal(content, config, options = {}) {
if (this.currentLogLevel > config.level)
return;
const { space = DEFAULT_OPTIONS.SPACE, newLine = DEFAULT_OPTIONS.NEW_LINE } = options;
const formattedContent = this.formatContent(content);
const output = [
" ".repeat(space),
config.symbol,
formattedContent
].join(" ");
if (config.wrapNewLine)
this.newLine();
console.log(output);
if (newLine || config.wrapNewLine)
this.newLine();
}
// Public API methods
error(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.error, options);
}
warn(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.warn, options);
}
tip(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.tip, options);
}
info(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.info, options);
}
debug(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.debug, options);
}
success(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.success, options);
}
fail(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.fail, options);
}
ready(content) {
this.logInternal(styleText.green(content), LOG_METHODS_CONFIG.success);
}
log(content, options) {
this.logInternal(content, LOG_METHODS_CONFIG.log, options);
}
clear() {
const blank = process.stdout.rows > 2 ? "\n".repeat(process.stdout.rows - 2) : "";
console.log(blank);
readline.cursorTo(process.stdout, 0, 0);
readline.clearScreenDown(process.stdout);
}
newLine() {
console.log();
}
}
const logger = Logger.getInstance();
function dateFormat(fmt, date) {
let ret;
const opt = {
"Y+": date.getFullYear().toString(),
"m+": (date.getMonth() + 1).toString(),
"d+": date.getDate().toString(),
"H+": date.getHours().toString(),
"M+": date.getMinutes().toString(),
"S+": date.getSeconds().toString()
};
for (const k in opt) {
ret = new RegExp(`(${k})`).exec(fmt);
if (ret) {
fmt = fmt.replace(
ret[1],
ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
);
}
}
return fmt;
}
function toArray(value) {
return Array.isArray(value) ? value : [value];
}
function template(str, data, regex = /\{\{(.+?)\}\}/g) {
return Array.from(str.matchAll(regex)).reduce((acc, match) => {
return acc.replace(match[0], data[match[1]]);
}, str);
}
function parseRepoUrl(url) {
if (!url)
throw new Error("Parse repository URL failed.");
const match = url.match(/:\/\/.+com\/([^/]+)\/([^.]+)\.git$/);
if (!match) {
throw new Error("Parse repository URL failed.");
}
const [, owner, repo] = match;
return { owner, repo };
}
function replace(contents, from, to) {
const froms = Array.isArray(from) ? from : [from];
const tos = Array.isArray(to) ? to : Array.from({ length: froms.length }, () => to);
if (froms.length !== tos.length) {
throw new Error("The lengths of 'from' and 'to' must be equal");
}
return froms.reduce((result, pattern, index) => result.replace(pattern, tos[index]), contents);
}
async function replaceInFile({ files, from, to, isGlob = true }) {
const paths = isGlob ? await glob(files) : toArray(files);
await Promise.all(paths.map(async (path) => {
const contents = await readFile(path, "utf-8");
const newContents = replace(contents, from, to);
if (contents !== newContents)
await writeFile(path, newContents);
}));
}
async function replaceDefine(dist, define) {
const replaceMap = new Map(
Object.keys(define).map((key) => [
new RegExp(`__${key}__`, "g"),
define[key]
])
);
logger.debug(`replace map: ${replaceMap}`);
await replaceInFile({
files: `${dist}/addon/**/*`,
// files: [
// `${dist}/addon/**/*.html`,
// `${dist}/addon/**/*.xhtml`,
// `${dist}/addon/**/*.ftl`,
// `${dist}/addon/**/*.css`,
// `${dist}/addon/**/*.json`,
// ],
from: Array.from(replaceMap.keys()),
to: Array.from(replaceMap.values())
// isGlob: false,
});
}
export { LOG_LEVEL as L, replaceInFile as a, toArray as b, replaceDefine as c, dateFormat as d, logger as l, parseRepoUrl as p, replace as r, styleText as s, template as t };