UNPKG

@gasket/plugin-intl

Version:
247 lines (210 loc) 7.49 kB
const fs = require('fs-extra'); const path = require('path'); const { getPackageDirs, saveJsonFile } = require('./utils/fs-utils'); const { getIntlConfig } = require('./utils/configure-utils'); const debug = require('debug')('gasket:plugin:intl:buildModules'); const rePkgParts = /^(?<name>(?:@[\w-]+\/)?[\w-]+)(?<dir>\/[\w-]+)?$/; class BuildModules { /** * Instantiate a builder to gather locale files * @param {import("@gasket/core").Gasket} gasket - Gasket API */ constructor(gasket) { const { logger, config: { root } } = gasket; const intlConfig = getIntlConfig(gasket); const { modules } = intlConfig; if (Array.isArray(modules)) { this._lookupModuleDirs = modules; } /** @type {Record<string, any>} */ let excludes, localesDir; if (typeof modules === 'object' && !Array.isArray(modules)) { ({ excludes, localesDir } = modules); } this._logger = logger; this._outputDir = path.resolve(path.join(intlConfig.localesDir, 'modules')); this._nodeModulesDir = path.resolve(path.join(root, 'node_modules')); this._lookupDir = localesDir; this._excludes = excludes; } /** * Given a source folder, this function minifies all the files in that folder * and sets a unique hash for each file and saves in the target location * @param {string} srcDir - Source directory path * @param {string} tgtDir - Target directory path * @returns {Promise} promise */ async copyFolder(srcDir, tgtDir) { debug(`Copying folder from ${srcDir} to ${tgtDir}`); const fileNames = await fs.readdir(srcDir); const promises = fileNames.map(async (fileName) => { const srcFile = path.join(srcDir, fileName); const tgtFile = path.join(tgtDir, fileName); if (path.extname(srcFile) === '.json') { return await this.copyFile(srcFile, tgtFile); } }); return await Promise.all(promises); } /** * Copies the source file to proper target location * @param {string} src - full path to source file * @param {string} tgt - target folder location * @returns {Promise} - resolves once the file is saved */ async copyFile(src, tgt) { debug(`Copying file from ${src} to ${tgt}`); await fs.mkdirp(path.dirname(tgt)); const buffer = await fs.readFile(src); const output = JSON.parse(buffer); return saveJsonFile(tgt, output); } /** * Processes locale files from source to target build directory * @param {string} srcDir - Source locale directory * @param {string} tgtDir - Target locale directory * @param {string[]} fileNames - Names of the locale files * @returns {Promise} promise */ processFiles(srcDir, tgtDir, fileNames) { debug(`Processing files in ${srcDir} to target ${tgtDir}`); const promises = fileNames.map(async (fileName) => { const srcFile = path.join(srcDir, fileName); const tgtFile = path.join(tgtDir, fileName); if (path.extname(srcFile) === '.json') { return await this.copyFile(srcFile, tgtFile); } else if ((await fs.lstat(srcFile)).isDirectory()) { return await this.copyFolder(srcFile, tgtFile); } }); return Promise.all(promises); } /** * Reads the source directory and returns the package name e.g. @gasket/next * @param {string} srcDir - Source directory path * @returns {string} package name */ getPackageNameFromDir(srcDir) { const leafFolder = path.dirname(srcDir); const leafFolderName = path.basename(leafFolder); const parentFolder = path.dirname(leafFolder); const parentFolderName = path.basename(parentFolder); let pkgName = leafFolderName; if (parentFolderName.startsWith('@')) { pkgName = `${parentFolderName}/${leafFolderName}`; } return pkgName; } /** * Reads the package.json and returns the package name e.g. @gasket/next * @param {string} srcDir - Source directory path (a locales directory) * @returns {Promise<string>} package name */ async getPackageName(srcDir) { const pkgDir = path.dirname(srcDir); try { const pkg = await fs.readJson(path.join(pkgDir, 'package.json')); return pkg.name || this.getPackageNameFromDir(srcDir); } catch (e) { return this.getPackageNameFromDir(srcDir); } } /** * Processes directories * @param {import('./internal').SrcPkgDir[]} srcPkgDirs - list of dirs to process */ async processDirs(srcPkgDirs) { for (const [pkgName, srcDir] of srcPkgDirs) { const tgtDir = path.join(this._outputDir, pkgName); this._logger.info(`build:locales: Updating locale files for: ${pkgName}`); await fs.remove(tgtDir); await fs.mkdirp(tgtDir); const fileNames = await fs.readdir(srcDir); await this.processFiles(srcDir, tgtDir, fileNames); } this._logger.info(`build:locales: Completed locale files update.`); } /** * Find modules that have /locales folder to process * @returns {Promise<import('./internal').SrcPkgDir[]>} source package directories */ async discoverDirs() { /** @type {import('./internal').SrcPkgDir[]} */ const results = []; for await (const [pkgName, dir] of getPackageDirs(this._nodeModulesDir)) { if (!this._excludes.includes(path.basename(dir))) { const buildDir = path.resolve( path.join(dir, ...this._lookupDir.split('/')) ); try { const stat = await fs.lstat(buildDir); if (stat.isDirectory()) { results.push([pkgName, buildDir]); } } catch (e) { // ignore } } } return results; } /** * Find modules with locale directories to process * @returns {Promise<import('./internal').SrcPkgDir[]>} source package directories */ async gatherModuleDirs() { if (this._lookupModuleDirs) { const promises = this._lookupModuleDirs.map(async (lookupDir) => { const match = lookupDir.match(rePkgParts); const pkgName = match?.groups?.name; if (!pkgName) { this._logger.warn( `build:locales: malformed module name: ${lookupDir}` ); return; } const subDir = (match.groups.dir ?? '/locales').substring(1); const buildDir = path.join( this._nodeModulesDir, ...pkgName.split('/'), ...subDir.split('/') ); try { const stat = await fs.lstat(buildDir); if (stat.isDirectory()) { return [pkgName, buildDir]; } } catch (e) { // skip } this._logger.warn( `build:locales: locales directory not found for: ${lookupDir}` ); }); const results = (await Promise.all(promises)).filter(Boolean); return /** @type {import('./internal').SrcPkgDir[]} */ (results); } return this.discoverDirs(); } /** * Starts the build process */ async run() { await fs.remove(this._outputDir); await fs.mkdirp(this._outputDir); const srcPkgDirs = await this.gatherModuleDirs(); await this.processDirs(srcPkgDirs); } } /** * Discovers locale files under node modules with and copies them to output dir. * @param {import("@gasket/core").Gasket} gasket - Gasket API */ module.exports = async function buildModules(gasket) { const builder = new BuildModules(gasket); await builder.run(); }; module.exports.BuildModules = BuildModules;