@ima/plugin-cli
Version:
IMA.js Plugin CLI tool to build, link, develop IMA.js plugins.
222 lines • 9.43 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePkgJson = parsePkgJson;
exports.build = build;
exports.watch = watch;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const logger_1 = require("@ima/dev-utils/logger");
const anymatch_1 = __importDefault(require("anymatch"));
const chalk_1 = __importDefault(require("chalk"));
const chokidar_1 = __importDefault(require("chokidar"));
const globby_1 = __importDefault(require("globby"));
const process_1 = require("./process");
const utils_1 = require("./utils");
function parseArgs(args) {
const [command] = args._;
return { ...args, command };
}
/**
* Returns the upmost common part of output dir path of all output options.
*/
function getBaseCommonOutputDir(output) {
const [firstOutputDirParts, ...otherOutputDirsParts] = output.map(output => output.dir.split('/'));
return firstOutputDirParts
.reduce((acc, cur, index) => {
if (otherOutputDirsParts.every(part => part[index] === cur) && cur) {
acc.push(cur);
}
return acc;
}, [])
.join('/');
}
/**
* Loads and parses package.json at given working directory.
*/
async function parsePkgJson(basePath) {
return JSON.parse(await (await fs_1.default.promises.readFile(path_1.default.join(basePath, 'package.json'))).toString());
}
function errorHandler(error) {
logger_1.logger.error('An error occurred while watching files');
console.error(error);
}
/**
* Build command function handler.
*/
async function build(args) {
const parsedArgs = parseArgs(args);
const elapsed = (0, logger_1.time)();
const cwd = process.cwd();
const [pkgJson, configurations] = await Promise.all([
parsePkgJson(cwd),
(0, process_1.parseConfigFile)(cwd, parsedArgs),
]);
logger_1.logger.setSilent(parsedArgs.silent);
logger_1.logger.info(`Building ${chalk_1.default.bold.magenta(pkgJson.name)}`);
// Spawn compilation for each config
await Promise.all(configurations.map(async (config) => {
const inputDir = path_1.default.resolve(cwd, config.inputDir);
config.output.forEach(({ dir, format }) => {
logger_1.logger.info(`${config.inputDir} ${chalk_1.default.blue('→')} ${chalk_1.default.green(dir)} ${chalk_1.default.red(`[${format}]`)}`);
});
// Clean output directories
await (0, utils_1.cleanOutput)(config, cwd);
// Get file paths at input directory
let files = await (0, globby_1.default)(path_1.default.posix.join(inputDir.replace(/\\/g, '/'), './**/*'), {
cwd: cwd.replace(/\\/g, '/'),
});
// Filter files using exclude settings
if (config?.exclude) {
const matcher = typeof config.exclude === 'function'
? config.exclude
: (0, anymatch_1.default)(config.exclude);
files = files.filter(filePath => !matcher(filePath));
}
const context = {
command: 'build',
inputDir,
config,
cwd,
};
// Init processing pipeline
const process = await (0, process_1.createProcessingPipeline)(context);
// Process each file with loaded pipeline
files.forEach(process);
// Run plugins
await (0, process_1.runPlugins)(context);
})).catch(error => {
logger_1.logger.error(error);
process.exit(1);
});
logger_1.logger.success('Finished', { elapsed });
}
/**
* Dev/link command function handler
*/
async function watch(args) {
const cwd = process.cwd();
const parsedArgs = parseArgs(args);
const { command, path: linkPath } = parsedArgs;
const [pkgJson, configurations] = await Promise.all([
parsePkgJson(cwd),
(0, process_1.parseConfigFile)(cwd, parsedArgs),
]);
let title = `Watching ${chalk_1.default.bold.magenta(pkgJson.name)}`;
if (command === 'link' && linkPath) {
const linkedPkgJson = await parsePkgJson(path_1.default.resolve(linkPath));
title = `Linking ${chalk_1.default.bold.magenta(pkgJson.name)} ${chalk_1.default.white('→')} ${chalk_1.default.cyan(linkedPkgJson.name)}`;
}
// Batch file events
const batch = (0, utils_1.createBatcher)(title);
logger_1.logger.setSilent(parsedArgs.silent);
// Spawn watch for each config
configurations.forEach(async (config) => {
const inputDir = path_1.default.resolve(cwd, config.inputDir);
// Cleanup
await (0, utils_1.cleanOutput)(config, cwd);
// Processing pipeline context
const context = {
command,
inputDir,
config,
cwd,
};
// Init processing pipeline
const process = await (0, process_1.createProcessingPipeline)(context);
// Dev watcher
chokidar_1.default
.watch([path_1.default.resolve(inputDir)], {
ignoreInitial: false,
ignored: config.exclude,
persistent: true,
})
.on('error', errorHandler)
.on('all', async (eventName, filePath) => {
const contextPath = path_1.default.relative(inputDir, filePath);
const fileName = path_1.default.basename(filePath);
batch(async () => {
switch (eventName) {
case 'add':
case 'change':
await process(filePath);
break;
case 'unlink':
case 'unlinkDir':
await (0, utils_1.processOutput)(config, async (outputPath) => {
const outputContextPath = path_1.default.join(outputPath, contextPath);
if (fs_1.default.existsSync(outputContextPath)) {
logger_1.logger.write(`${(0, logger_1.printTime)()} ${chalk_1.default.green('Unlink')}: ${fileName}`);
await fs_1.default.promises.rm(outputContextPath, {
recursive: true,
});
}
}, cwd);
break;
default:
return;
}
}, eventName);
})
.on('ready', async () => {
await (0, process_1.runPlugins)(context);
});
/**
* Link watcher, there's only one instance per pkg, it watches the root dist
* directory and copies changes to the linked path.
*/
if (command === 'link' && linkPath) {
const distBaseDir = getBaseCommonOutputDir(config.output);
const outputDir = path_1.default.join(cwd, distBaseDir);
const linkedPath = path_1.default.resolve(linkPath);
const linkedBasePath = path_1.default.resolve(linkedPath, 'node_modules', pkgJson.name);
const linkedDistPath = path_1.default.join(linkedBasePath, distBaseDir);
chokidar_1.default
.watch([
path_1.default.join(cwd, distBaseDir),
Array.isArray(parsedArgs?.additionalWatchPaths) &&
parsedArgs?.additionalWatchPaths,
Array.isArray(config?.additionalWatchPaths) &&
config?.additionalWatchPaths,
].filter(Boolean), {
ignoreInitial: false,
persistent: true,
ignored: [
'**/tsconfig.build.tsbuildinfo/**',
'**/node_modules/**',
'**/.DS_Store/**',
],
})
.on('error', errorHandler)
.on('all', (eventName, filePath) => {
// Handler to link additional non-dist files
const isAdditionalFile = !filePath.startsWith(outputDir);
const contextPath = path_1.default.relative(isAdditionalFile ? cwd : outputDir, filePath);
const linkedOutputPath = path_1.default.join(isAdditionalFile ? linkedBasePath : linkedDistPath, contextPath);
const linkedOutputDir = path_1.default.dirname(linkedOutputPath);
batch(async () => {
switch (eventName) {
case 'add':
case 'change':
if (!fs_1.default.existsSync(linkedOutputDir)) {
await fs_1.default.promises.mkdir(linkedOutputDir, {
recursive: true,
});
}
await fs_1.default.promises.copyFile(filePath, linkedOutputPath);
break;
case 'unlink':
case 'unlinkDir':
await fs_1.default.promises.rm(linkedOutputPath, { recursive: true });
break;
default:
return;
}
}, eventName);
});
}
});
}
//# sourceMappingURL=commands.js.map