@ima/plugin-cli
Version:
IMA.js Plugin CLI tool to build, link, develop IMA.js plugins.
222 lines • 8.68 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseConfigFile = parseConfigFile;
exports.emitSource = emitSource;
exports.processTransformers = processTransformers;
exports.createProcessingPipeline = createProcessingPipeline;
exports.runPlugins = runPlugins;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const logger_1 = require("@ima/dev-utils/logger");
const chalk_1 = __importDefault(require("chalk"));
const configurations_1 = require("./configurations");
const preprocessTransformer_1 = require("../transformers/preprocessTransformer");
const swcTransformer_1 = require("../transformers/swcTransformer");
const CONFIG_BASENAME = 'ima-plugin.config';
/**
* Parses ima.build.js file, initializing the build pipeline.
*/
async function parseConfigFile(cwd, args) {
const configDir = path_1.default.resolve(cwd);
const configFile = await fs_1.default.promises
.readdir(configDir)
.then(files => files.find(fileName => fileName.startsWith(CONFIG_BASENAME)));
// Recursively try to find file up to file system root
if (!configFile) {
const newConfigDir = path_1.default.resolve(cwd, '..');
if (newConfigDir !== configDir) {
return parseConfigFile(path_1.default.resolve(cwd, '..'), args);
}
}
// Define default config
let loadedConfig = [];
if (args.clientServerConfig) {
loadedConfig.push(configurations_1.clientServerConfig);
}
else if (args.nodeConfig) {
loadedConfig.push(configurations_1.nodeConfig);
}
else {
loadedConfig.push(configurations_1.defaultConfig);
}
// Override with custom configuration
if (configFile) {
let configPath = path_1.default.join(configDir, configFile);
if (!configPath.startsWith('/')) {
configPath = 'file:///' + configPath.replace(/\\/g, '/');
}
loadedConfig = (await import(configPath)).default;
loadedConfig = Array.isArray(loadedConfig) ? loadedConfig : [loadedConfig];
}
// Override jsx runtime option from CLI args
if (args.jsxRuntime) {
loadedConfig = loadedConfig.map(config => {
config.jsxRuntime = args.jsxRuntime;
return config;
});
}
return loadedConfig;
}
/**
* Helper function to emit source. If it's not undefined, the source is
* written to the output path. If it is undefined, the original file
* is simply copied.
*/
async function emitSource(source, context, outputDir) {
// Create output directory
if (!fs_1.default.existsSync(outputDir)) {
await fs_1.default.promises.mkdir(outputDir, { recursive: true });
}
// Emit source
if (source) {
const outputPath = path_1.default.join(outputDir, source?.fileName);
return Promise.all([
fs_1.default.promises.writeFile(outputPath, source.code),
source.map && fs_1.default.promises.writeFile(`${outputPath}.map`, source.map),
]);
}
else {
// Just copy files without source
await fs_1.default.promises.copyFile(context.filePath, path_1.default.join(outputDir, context.fileName));
}
}
/**
* Load source file contents and runs transformers on it, provided
* in the ima.build.js config.
*/
async function processTransformers(source, transformers, context) {
if (!transformers) {
return source;
}
const { filePath } = context;
for (const transformerDefinition of transformers) {
const [transform, options] = Array.isArray(transformerDefinition)
? transformerDefinition
: [transformerDefinition];
try {
if (!options) {
source = await transform({ source, context });
continue;
}
if (options.test && !options.test.test(filePath)) {
continue;
}
source = await transform({ source, context });
}
catch (err) {
logger_1.logger.error(`at ${chalk_1.default.magenta(context.filePath)}`);
console.error(err);
// Don't continue in build command
if (context.command === 'build') {
process.exit(1);
}
}
}
return source;
}
/**
* Creates processing pipeline used in build, link and dev scripts.
* It is constructed to run on each file separately.
*/
async function createProcessingPipeline(ctx) {
const { config } = ctx;
const transformersMap = new Map();
const isDevelopment = ctx.command !== 'build' && process.env.NODE_ENV !== 'production';
// Create map of transformers for each output option
config.output.forEach(output => {
// Create default transformers set based on config options
const defaultTransformers = [
typeof output.bundle !== 'undefined' &&
(0, preprocessTransformer_1.preprocessTransformer)({
context: {
client: output.bundle === 'client',
server: output.bundle === 'server',
},
}),
[
(0, swcTransformer_1.createSwcTransformer)({
target: config.target,
sourceMaps: config.sourceMaps,
development: isDevelopment,
type: output.format,
jsxRuntime: config.jsxRuntime,
}),
{ test: /\.(js|jsx)$/ },
],
[
(0, swcTransformer_1.createSwcTransformer)({
target: config.target,
sourceMaps: config.sourceMaps,
development: isDevelopment,
type: output.format,
jsxRuntime: config.jsxRuntime,
syntax: 'typescript',
}),
{ test: /\.(ts|tsx)$/ },
],
].filter(Boolean);
// Check for custom transformers in config
if (Array.isArray(config.transformers)) {
const hasDefaultsPlaceholder = config.transformers.find(t => typeof t === 'string' && t === '...');
transformersMap.set(output.dir, config.transformers
.map(t => hasDefaultsPlaceholder && typeof t === 'string' && t === '...'
? defaultTransformers
: t)
.flat()
.filter(Boolean));
}
else {
transformersMap.set(output.dir, defaultTransformers);
}
});
return async (filePath) => {
const fileName = path_1.default.basename(filePath);
const contextDir = path_1.default.dirname(path_1.default.relative(ctx.inputDir, filePath));
// Read file contents
let source = {
fileName,
code: await fs_1.default.promises.readFile(filePath, 'utf8'),
};
// Run transformers for each output and emit
await Promise.all(config.output.map(async ({ dir, include, exclude }) => {
if ((include &&
((typeof include === 'function' && !include(filePath)) ||
(typeof include === 'object' && !include?.test(filePath)))) ||
(exclude &&
((typeof exclude === 'function' && exclude(filePath)) ||
(typeof exclude === 'object' && exclude?.test(filePath))))) {
return;
}
const outputDir = path_1.default.resolve(ctx.cwd, dir);
const context = {
...ctx,
contextDir,
fileName,
filePath,
outputDir,
};
const outputFilename = path_1.default.join(outputDir, fileName);
ctx.command === 'link' &&
logger_1.logger.write(`${(0, logger_1.printTime)()} ${chalk_1.default.green('Linking')}: ${outputFilename}`);
// Process transforms
source = await processTransformers(source, transformersMap.get(dir), context);
// Write new source
await emitSource(source, context, path_1.default.resolve(dir, contextDir));
}));
};
}
/**
* Runs plugins defined in config file.
*/
async function runPlugins(context) {
if (!context.config?.plugins?.length) {
return;
}
for (const plugin of context.config.plugins) {
await plugin(context);
}
}
//# sourceMappingURL=process.js.map