UNPKG

@feroomjs/tools

Version:
589 lines (566 loc) 25.6 kB
'use strict'; var path = require('path'); var glob = require('glob'); var node_fs = require('node:fs'); var fs = require('fs'); var esbuild = require('esbuild'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var esbuild__namespace = /*#__PURE__*/_interopNamespaceDefault(esbuild); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } const banner = () => `[${"@feroomjs/tools"}][${new Date().toISOString().replace('T', ' ').replace(/\.\d{3}z$/i, '')}] `; /* istanbul ignore file */ function logError(error) { console.error('' + '' + banner() + error + ''); } function panic(error) { logError(error); return new Error(error); } function isTextFile(path) { return path.endsWith('.js') || path.endsWith('.map') || path.endsWith('.css') || path.endsWith('.json') || path.endsWith('.txt') || path.endsWith('.mjs') || path.endsWith('.cjs') || path.endsWith('.md') || path.endsWith('.html'); } const rootPath = process.cwd(); function buildPath(...parts) { return parts[0].startsWith('/') ? path.join(...parts) : path.join(rootPath, ...parts); } function unbuildPath(path) { if (path.startsWith(rootPath)) { return path.slice(rootPath.length + 1); } return path; } function getFilesByPattern(include = [], exclude = []) { return __awaiter(this, void 0, void 0, function* () { const files = {}; for (const path of (include || []).map(p => buildPath(p))) { for (const file of (yield globPromise(path))) { files[file] = true; } } for (const path of (exclude || []).map(p => buildPath(p))) { for (const file of (yield globPromise(path))) { files[file] = false; } } return Object.keys(files).filter(file => files[file]); }); } function globPromise(path) { return new Promise((resolve, reject) => { glob(path, { nodir: true, }, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); }); } const pkg = JSON.parse(node_fs.readFileSync(buildPath('./package.json')).toString()); function getLockVersion(dep) { return JSON.parse(node_fs.readFileSync(buildPath('node_modules', dep, 'package.json')).toString()).version; } let i = 1; const logger = { clear: () => i = 1, title: (text) => console.log(`\n${''}=== ${text} ===${''}\n`), step: (text) => console.log(`${''}${i++}. ${text}${''}\n`), info: (text) => console.log(`${''}${text}${''}`), warn: (text) => console.log(`${''}[WARNING] ${text}${''}`), error: (text) => console.log(`${''}${text}${''}`), dev: (text) => console.log(`\n${''}${'' + ''}${text}${''}\n`), }; function getVueRoutesExports(vueRoutes) { const map = getVueRoutesMap(vueRoutes); let content = ''; for (const [file, name] of Object.entries(map)) { content += `export { default as ${name} } from '${file}';\n`; } return content; } function getVueRoutesMap(vueRoutes) { const components = {}; let i = 0; iterateChildren(vueRoutes); function iterateChildren(root) { for (const entry of root) { if (entry.component) { components[entry.component] = components[entry.component] || `router_page_$${i++}`; } if (entry.children) { iterateChildren(entry.children); } } } return components; } function getVueRenderedRoutes(vueRoutes, moduleId) { const _routes = JSON.parse(JSON.stringify(vueRoutes)); const map = getVueRoutesMap(vueRoutes); iterateChildren(_routes); function iterateChildren(root) { for (const entry of root) { if (entry.component && !entry.component.startsWith('async () => ')) { entry.component = `async () => (await import('${moduleId}')).${map[entry.component]}`; } if (entry.children) { iterateChildren(entry.children); } } } return _routes; } const esbuildWatchPlugin = (onReBuild, log) => { let firstRun = true; return { name: 'feroom-watch-cb', setup(build) { if (log) { build.onStart(() => firstRun ? logger.title('Build started...') : logger.title('Change detected. Re-build started...')); } build.onEnd((result) => { // logger.dev('Build ended') firstRun = false; void onReBuild(result); }); }, }; }; class FeRoomConfigFile { constructor(path, watch = false) { this.watch = watch; this.files = [ './feroom/config.ts', './feroom/config.js', './feroom.config.ts', './feroom.config.js', './feroom.config.json', ]; this.listeners = []; if (typeof path === 'string') { this.files = [path]; } else if (typeof path === 'object') { if (watch) { throw new Error('Can not instantiate FeRoomConfigFile in watch mode when pre-rendered config passed as an argument.'); } this.data = path; } } get() { return __awaiter(this, void 0, void 0, function* () { if (!this.data) { this.data = yield this.readConfig(); // await readFeRoomConfigFile(this.files) } return this.data; }); } onChange(cb) { this.listeners.push(cb); } readConfig() { let filePath = ''; logger.step('Looking for FeRoom config file...'); for (const file of this.files) { filePath = buildPath(file); if (fs.existsSync(filePath)) { break; } else { filePath = ''; } } if (!filePath) { throw new Error('Feroom config file was not found. ' + this.files.join(', ')); } logger.step('Importing FeRoom config file from ' + filePath); const isJs = filePath.endsWith('.js'); const isTs = filePath.endsWith('.ts'); const isJson = filePath.endsWith('.json'); if (!isJs && !isTs && !isJson) throw new Error(`Config file "${unbuildPath(filePath)}" has unsupported format. Please use .json, .js or .ts`); if (isJson) { return JSON.parse(fs.readFileSync(filePath).toString()); } return this.buildConfig(filePath); } buildConfig(filePath) { return __awaiter(this, void 0, void 0, function* () { logger.step('Building FeRoom config...'); const escfg = { entryPoints: [filePath], bundle: true, packages: 'external', format: 'esm', platform: 'node', write: false, outfile: 'feroom.config.js', plugins: [], }; if (this.watch) { return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { var _a; (_a = escfg.plugins) === null || _a === void 0 ? void 0 : _a.push(esbuildWatchPlugin((bld) => __awaiter(this, void 0, void 0, function* () { if (bld.outputFiles && bld.outputFiles[0]) { const data = yield this.loadJsConf(bld.outputFiles[0].text); resolve(data); this.fireChange(data); } else { throw new Error('Failed to build FeRoom config :('); } }))); const ctx = yield esbuild__namespace.context(escfg); yield ctx.watch(); })); } else { const bld = yield esbuild__namespace.build(escfg); if (bld.outputFiles && bld.outputFiles[0]) { return this.loadJsConf(bld.outputFiles[0].text); } throw new Error('Failed to build FeRoom config :('); } }); } loadJsConf(content) { return __awaiter(this, void 0, void 0, function* () { const filePath = buildPath(`feroom.config-${new Date().getTime()}.mjs`); fs.writeFileSync(filePath, content); try { const data = (yield import(filePath)).default; //(require(filePath) as { default: TFeRoomConfig}).default fs.unlinkSync(filePath); return data; } catch (e) { fs.unlinkSync(filePath); throw e; } }); } fireChange(newConfig) { this.data = newConfig; this.rendered = undefined; this.listeners.forEach(cb => void cb(newConfig)); } render() { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; return __awaiter(this, void 0, void 0, function* () { if (!this.rendered) { const data = JSON.parse(JSON.stringify(yield this.get())); data.registerOptions = data.registerOptions || {}; const id = data.registerOptions.id || (pkg === null || pkg === void 0 ? void 0 : pkg.name); if (!id) throw panic('Could not resolve module id. Please use option "registerOptions.id".'); if (!data.registerOptions.entry) { data.registerOptions.entry = (pkg === null || pkg === void 0 ? void 0 : pkg.module) || (pkg === null || pkg === void 0 ? void 0 : pkg.main); } if ((_a = data.extensions) === null || _a === void 0 ? void 0 : _a.vueRoutes) { data.extensions.vueRoutes = getVueRenderedRoutes(data.extensions.vueRoutes, id); } if ((_c = (_b = data.buildOptions) === null || _b === void 0 ? void 0 : _b.dependencies) === null || _c === void 0 ? void 0 : _c.lockVersion) { data.registerOptions.lockDependency = {}; for (const dep of data.buildOptions.dependencies.lockVersion) { const version = getLockVersion(dep); data.registerOptions.lockDependency[dep] = version; if (((_d = data.registerOptions) === null || _d === void 0 ? void 0 : _d.importNpmDependencies) && ((_e = data.registerOptions) === null || _e === void 0 ? void 0 : _e.importNpmDependencies[dep])) { const npmDep = data.registerOptions.importNpmDependencies[dep]; if (npmDep.version && npmDep.version !== version) { logger.warn(`registerOptions.importNpmDependencies["${dep}"] has different version from what's installed in node_modules. Changing version to ${version}.`); } npmDep.version = version; } } } if (((_g = (_f = data.buildOptions) === null || _f === void 0 ? void 0 : _f.dependencies) === null || _g === void 0 ? void 0 : _g.bundle) && ((_h = data.registerOptions) === null || _h === void 0 ? void 0 : _h.importNpmDependencies)) { for (const dep of data.buildOptions.dependencies.bundle) { const npmDep = data.registerOptions.importNpmDependencies[dep]; if (npmDep) { logger.warn(`When bundling "${dep}" it must be wrong to pass importNpmDependencies["${dep}"] as there should not be such external dependency.`); } } } if (((_k = (_j = data.buildOptions) === null || _j === void 0 ? void 0 : _j.dependencies) === null || _k === void 0 ? void 0 : _k.bundle) && ((_m = (_l = data.buildOptions) === null || _l === void 0 ? void 0 : _l.dependencies) === null || _m === void 0 ? void 0 : _m.lockVersion)) { for (const dep of data.buildOptions.dependencies.bundle) { if (data.buildOptions.dependencies.lockVersion.includes(dep)) { logger.warn(`Dependency "${dep}" set for locking version and for bundling in. Only one of the options make sense.`); } } } this.rendered = data; } return this.rendered; }); } } class FeRoomRegister { constructor(opts) { this.opts = opts; } getUrl(path) { if (path.startsWith('/')) path = path.slice(1); let host = this.opts.host; if (host.endsWith('/')) host = host.slice(0, host.length - 1); return host + '/' + path; } register(opts) { var _a; return __awaiter(this, void 0, void 0, function* () { const conf = yield ((opts === null || opts === void 0 ? void 0 : opts.conf) instanceof FeRoomConfigFile ? opts.conf : new FeRoomConfigFile(opts === null || opts === void 0 ? void 0 : opts.conf)).render(); const id = ((_a = conf.registerOptions) === null || _a === void 0 ? void 0 : _a.id) || pkg.name; const files = yield this.gatherFiles(conf, opts === null || opts === void 0 ? void 0 : opts.files); try { yield this.postModule({ id, version: pkg.version, files, activate: opts === null || opts === void 0 ? void 0 : opts.activate, }); logger.info(`\n✔ Module "${id}" Registered on ${this.opts.host}`); } catch (e) { logger.error(`Failed to register module "${id}"`); logger.error(e.message); throw (e); } }); } gatherFiles(conf, replace) { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function* () { logger.step('Lookup files...'); const files = {}; const paths = yield getFilesByPattern(((_a = conf.registerOptions) === null || _a === void 0 ? void 0 : _a.include) || pkg.files, (_b = conf.registerOptions) === null || _b === void 0 ? void 0 : _b.exclude); for (const path of paths) { const relPath = unbuildPath(path); if (replace && replace[relPath]) { continue; } else if (isTextFile(path)) { files[relPath] = node_fs.readFileSync(path).toString(); } else { files[relPath] = node_fs.readFileSync(path); } const dts = relPath.endsWith('.d.ts'); logger.info(`• ${dts ? '' + '' : ''}${relPath} ${'' + ''}${this.opts.host}`); } if (replace) { for (const [relPath, file] of Object.entries(replace)) { files[relPath] = file; const dts = relPath.endsWith('.d.ts'); logger.info(`• ${dts ? '' + '' : ''}${relPath} ${'' + ''}${this.opts.host}`); } } if (!files['feroom.config.json']) { logger.info(`${''}• feroom.config.json ${''}${this.opts.host}`); } files['feroom.config.json'] = JSON.stringify({ registerOptions: conf.registerOptions, extensions: conf.extensions }); if (!files['package.json']) { files['package.json'] = JSON.stringify(pkg); logger.info(`${''}• package.json ${''}${this.opts.host}`); } if (!files[(_c = conf.registerOptions) === null || _c === void 0 ? void 0 : _c.entry] && !files[(((_d = conf.registerOptions) === null || _d === void 0 ? void 0 : _d.entry) || '').replace(/^\.\//, '')]) { logger.warn(`Entry "${(_e = conf.registerOptions) === null || _e === void 0 ? void 0 : _e.entry}" file is not included in files list`); } return files; }); } postModule(module) { return __awaiter(this, void 0, void 0, function* () { logger.step('Posting files...'); const res = yield fetch(this.getUrl('feroom-module/register'), { method: 'POST', headers: { 'content-type': 'application/json', 'accept': 'text/plain', }, body: JSON.stringify(module), }); if (res.status > 299) { const text = String(res.status) + ' ' + (yield res.text()); logger.error(text); throw new Error(text); } }); } } function getVirtualIndex(conf) { var _a, _b; const buildOptions = conf.buildOptions || {}; let content = ''; if (buildOptions.input) { content += `export * from '${buildOptions.input.replace(/\.ts$/, '')}';\n`; } if (buildOptions.css) { const cssOpts = buildOptions.css; let cssPath; if (typeof cssOpts === 'string') { cssPath = cssOpts; } else if (cssOpts.fileName) { cssPath = cssOpts.fileName; } if (cssPath) { cssPath = path.join(path.dirname(buildOptions.output || ''), cssPath); content += `__loadCss(window.__feroom.modulesPrefixPath + '${((_a = conf.registerOptions) === null || _a === void 0 ? void 0 : _a.id) || pkg.name}/${cssPath}');\n`; } } content += getVueRoutesExports(((_b = conf.extensions) === null || _b === void 0 ? void 0 : _b.vueRoutes) || []); return content; } const esbuildFeRoomPlugin = (conf) => ({ name: 'feroom', setup(build) { build.onResolve({ filter: /feroom-virtual-index\.ts$/ }, (args) => { return { path: path.resolve(args.resolveDir, 'feroom-virtual-index.ts'), }; }); build.onLoad({ filter: /feroom-virtual-index\.ts$/ }, () => __awaiter(this, void 0, void 0, function* () { return ({ contents: getVirtualIndex(yield conf.get()), loader: 'ts', }); })); build.onResolve({ filter: /^@feroom-ext\// }, () => { return { external: true }; }); build.onEnd((result) => __awaiter(this, void 0, void 0, function* () { const path$1 = build.initialOptions.outfile ? path.dirname(build.initialOptions.outfile) : build.initialOptions.outdir || ''; const renderedConfig = JSON.stringify(Object.assign(Object.assign({}, (yield conf.render())), { devServer: undefined }), null, ' '); if (build.initialOptions.write) { fs.writeFileSync(buildPath(path$1, 'feroom.config.json'), renderedConfig); } if (result.outputFiles) { result.outputFiles.push({ path: buildPath(path$1, 'feroom.config.json'), contents: new Uint8Array(Buffer.from(renderedConfig)), text: renderedConfig, }); } })); }, }); function genEsbuildConfig(config, onReBuild) { var _a, _b, _c, _d, _e, _f; return __awaiter(this, void 0, void 0, function* () { const conf = yield config.get(); const buildOptions = conf.buildOptions || {}; const plugins = []; if ((_a = conf.buildOptions) === null || _a === void 0 ? void 0 : _a.vue) { plugins.push(((yield import('esbuild-plugin-vue-next'))).default()); } if ((_b = conf.buildOptions) === null || _b === void 0 ? void 0 : _b.css) { plugins.push(((yield import('esbuild-sass-plugin'))).sassPlugin({ quietDeps: true, })); } plugins.push(esbuildFeRoomPlugin(config)); if (onReBuild) { plugins.push(esbuildWatchPlugin(onReBuild, true)); } const paths = {}; if ((_c = buildOptions.dependencies) === null || _c === void 0 ? void 0 : _c.lockVersion) { for (const dep of buildOptions.dependencies.lockVersion) { const version = getLockVersion(dep); paths[dep] = `${dep}@${version}`; logger.step(`Locking version of "${dep}" to v${version}`); } } if ((_d = buildOptions.dependencies) === null || _d === void 0 ? void 0 : _d.bundle) { for (const dep of buildOptions.dependencies.bundle) { logger.step(`Bundling in "${dep}"`); } } const bundle = ((_e = buildOptions.dependencies) === null || _e === void 0 ? void 0 : _e.bundle) ? [(_f = buildOptions.dependencies) === null || _f === void 0 ? void 0 : _f.bundle].flat(1) : []; return { entryPoints: ['./feroom-virtual-index.ts'], alias: paths, bundle: true, external: [ // extrnalising dependencies ...Object.keys(pkg.dependencies || {}).filter(dep => !bundle.includes(dep)), ...Object.keys(pkg.peerDependencies || {}).filter(dep => !bundle.includes(dep)), // externalising from buildOptions.dependencies.lockVersion ...Object.entries(paths).map(p => p[1]), // ext dynamic imports '@feroom-ext/*', ], format: 'esm', write: !onReBuild, outfile: buildOptions.output || '', plugins, }; }); } function esBuildBundle(confPath) { return __awaiter(this, void 0, void 0, function* () { const config = confPath instanceof FeRoomConfigFile ? confPath : new FeRoomConfigFile(confPath); const esbuildConfig = yield genEsbuildConfig(config); logger.step('Bundling up files...'); return yield esbuild__namespace.build(esbuildConfig); }); } function esBuildCtx(confPath, onReBuild) { return __awaiter(this, void 0, void 0, function* () { const config = confPath instanceof FeRoomConfigFile ? confPath : new FeRoomConfigFile(confPath); const esbuildConfig = yield genEsbuildConfig(config, onReBuild); logger.step('Bundling up files...'); return yield esbuild__namespace.context(esbuildConfig); }); } exports.FeRoomConfigFile = FeRoomConfigFile; exports.FeRoomRegister = FeRoomRegister; exports.buildPath = buildPath; exports.esBuildBundle = esBuildBundle; exports.esBuildCtx = esBuildCtx; exports.esbuildFeRoomPlugin = esbuildFeRoomPlugin; exports.getFilesByPattern = getFilesByPattern; exports.getLockVersion = getLockVersion; exports.globPromise = globPromise; exports.logger = logger; exports.pkg = pkg; exports.rootPath = rootPath; exports.unbuildPath = unbuildPath;