@storm-stack/core
Version:
A build toolkit and runtime used by Storm Software in TypeScript applications
329 lines (323 loc) • 13.3 kB
JavaScript
import { finalize } from './chunk-QOGDZ7VT.js';
import { init } from './chunk-OVTQGVVL.js';
import { lint } from './chunk-CGQHYHSA.js';
import { _new } from './chunk-HZ263YUL.js';
import { prepare, getPersistedMeta, getChecksum, createContext } from './chunk-3MGEBIVG.js';
import { build } from './chunk-4LKKF5CO.js';
import { clean } from './chunk-YLHFO6NT.js';
import { docs } from './chunk-OPZLKH33.js';
import { __STORM_STACK_IS_PLUGIN__ } from './chunk-S6IVF7HS.js';
import { init_esm_shims, __name } from './chunk-QH7NXH7H.js';
import { LogLevelLabel } from '@storm-software/config-tools/types';
import { install } from '@stryke/fs/install';
import { isPackageExists } from '@stryke/fs/package-fns';
import { joinPaths } from '@stryke/path/join-paths';
import { camelCase } from '@stryke/string-format/camel-case';
import { isError } from '@stryke/type-checks/is-error';
import { isNumber } from '@stryke/type-checks/is-number';
import { isSetObject } from '@stryke/type-checks/is-set-object';
import { isSetString } from '@stryke/type-checks/is-set-string';
import chalk from 'chalk';
import defu from 'defu';
import { createHooks } from 'hookable';
// src/base/engine.ts
init_esm_shims();
// src/lib/utilities/plugin-helpers.ts
init_esm_shims();
function isPluginInstance(plugin) {
return isSetObject(plugin) && __STORM_STACK_IS_PLUGIN__ in plugin && plugin[__STORM_STACK_IS_PLUGIN__] === true;
}
__name(isPluginInstance, "isPluginInstance");
function isPluginConfigObject(plugin) {
return isSetObject(plugin) && "plugin" in plugin && isSetString(plugin.plugin);
}
__name(isPluginConfigObject, "isPluginConfigObject");
// src/base/engine.ts
var Engine = class _Engine {
static {
__name(this, "Engine");
}
inlineConfig;
/**
* The Storm Stack context
*/
#context;
/**
* The engine hooks - these allow the plugins to hook into the engines processing
*/
#hooks;
/**
* The plugins provided in the options
*/
#plugins = [];
/**
* The Storm Stack context
*/
get context() {
return this.#context;
}
/**
* The Storm Stack engine hooks
*/
get hooks() {
return this.#hooks;
}
/**
* Create a new Storm Stack Engine instance
*
* @param inlineConfig - The inline configuration for the Storm Stack engine
*/
constructor(inlineConfig) {
this.inlineConfig = inlineConfig;
}
/**
* Initialize the engine
*/
static async create(inlineConfig) {
const engine = new _Engine(inlineConfig);
await engine.init();
return engine;
}
/**
* Create a new Storm Stack project
*
* @remarks
* This method will create a new Storm Stack project in the current directory.
*
* @param inlineConfig - The inline configuration for the new command
* @returns A promise that resolves when the project has been created
*/
async new(_inlineConfig = {
command: "new"
}) {
this.#context.log(LogLevelLabel.INFO, "\u{1F195} Creating a new Storm Stack project");
await _new(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.TRACE, "Storm Stack - New command completed");
}
/**
* Clean any previously prepared artifacts
*
* @remarks
* This method will remove the previous Storm Stack artifacts from the project.
*
* @param inlineConfig - The inline configuration for the clean command
* @returns A promise that resolves when the clean command has completed
*/
async clean(_inlineConfig = {
command: "clean"
}) {
this.#context.log(LogLevelLabel.INFO, "\u{1F9F9} Cleaning the previous Storm Stack artifacts");
await clean(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.TRACE, "Storm Stack - Clean command completed");
}
/**
* Prepare the Storm Stack project prior to building
*
* @remarks
* This method will create the necessary directories, and write the artifacts files to the project.
*
* @param inlineConfig - The inline configuration for the prepare command
* @returns A promise that resolves when the prepare command has completed
*/
async prepare(_inlineConfig = {
command: "prepare"
}) {
this.#context.log(LogLevelLabel.INFO, "\u{1F3D7}\uFE0F Preparing the Storm Stack project");
await prepare(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.TRACE, "Storm Stack preparation completed");
}
/**
* Lint the project
*
* @param inlineConfig - The inline configuration for the lint command
* @returns A promise that resolves when the lint command has completed
*/
async lint(inlineConfig = {
command: "lint"
}) {
if (this.#context.persistedMeta?.checksum !== this.#context.meta.checksum) {
this.#context.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
await this.prepare(inlineConfig);
}
this.#context.log(LogLevelLabel.INFO, "\u{1F4CB} Linting the Storm Stack project");
await lint(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.TRACE, "Storm Stack linting completed");
}
/**
* Build the project
*
* @remarks
* This method will build the Storm Stack project, generating the necessary artifacts.
*
* @param inlineConfig - The inline configuration for the build command
* @returns A promise that resolves when the build command has completed
*/
async build(inlineConfig = {
command: "build"
}) {
const persistedMeta = await getPersistedMeta(this.#context);
const checksum = await getChecksum(this.#context.options.projectRoot);
if (persistedMeta?.checksum !== checksum) {
this.#context.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
await this.prepare(inlineConfig);
}
this.#context.log(LogLevelLabel.INFO, "\u{1F4E6} Building the Storm Stack project");
await build(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.TRACE, "Storm Stack build completed");
}
/**
* Generate the documentation for the project
*
* @param inlineConfig - The inline configuration for the docs command
* @returns A promise that resolves when the documentation generation has completed
*/
async docs(inlineConfig = {
command: "docs"
}) {
if (this.#context.persistedMeta?.checksum !== this.#context.meta.checksum) {
this.#context.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
await this.prepare(inlineConfig);
}
this.#context.log(LogLevelLabel.INFO, "Generating documentation for the Storm Stack project");
await docs(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.TRACE, "Storm Stack documentation generation completed");
}
/**
* Finalization process
*
* @remarks
* This step includes any final processes or clean up required by Storm Stack. It will be run after each Storm Stack command.
*
* @returns A promise that resolves when the finalization process has completed
*/
async finalize() {
this.#context.log(LogLevelLabel.TRACE, "Storm Stack finalize execution started");
await finalize(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.TRACE, "Storm Stack finalize execution completed");
}
/**
* Initialize the engine
*/
async init() {
this.#hooks = createHooks();
this.#context = await createContext(this.inlineConfig);
this.#context.log(LogLevelLabel.TRACE, "\u2699\uFE0F Initializing Storm Stack engine");
for (const plugin of this.#context.options.userConfig.plugins ?? []) {
await this.addPlugin(plugin);
}
if (this.#plugins.length === 0) {
this.#context.log(LogLevelLabel.WARN, "No Storm Stack plugins or presets were specified in the options. Please ensure this is correct, as it is generally not recommended.");
} else {
for (const plugin of this.#plugins) {
plugin.addHooks(this.#hooks);
}
}
await init(this.#context, this.#hooks);
this.#context.log(LogLevelLabel.INFO, "Storm Stack engine has been initialized");
}
/**
* Add a Storm Stack plugin used in the build process
*
* @param config - The import path of the plugin to add
*/
async addPlugin(config) {
if (config) {
const instance = await this.initPlugin(config);
if (!instance) {
return;
}
if (instance.dependencies) {
for (const dependency of instance.dependencies) {
await this.addPlugin(dependency);
}
}
this.#context.log(LogLevelLabel.DEBUG, `Successfully initialized the ${chalk.bold.cyanBright(instance.name)} plugin`);
this.#plugins.push(instance);
}
}
/**
* Initialize a Storm Stack plugin
*
* @param plugin - The import path of the plugin to add
*/
async initPlugin(plugin) {
if (!plugin || !isSetString(plugin) && !Array.isArray(plugin) && !isPluginConfigObject(plugin) && !isPluginInstance(plugin)) {
throw new Error(`Invalid plugin specified in the configuration - ${JSON.stringify(plugin)}. Please ensure the value is a plugin name, an object with the \`plugin\` and \`props\` properties, or an instance of \`Plugin\`.`);
}
let pluginInstance;
if (isPluginInstance(plugin)) {
pluginInstance = plugin;
pluginInstance.options ??= {};
pluginInstance.options.log ??= this.#context.log;
} else {
const pluginConfig = isSetString(plugin) ? [
plugin,
{}
] : Array.isArray(plugin) ? plugin : [
plugin.plugin,
plugin.props
];
let installPath = pluginConfig[0];
if (installPath.startsWith("@") && installPath.split("/").filter(Boolean).length > 2) {
const splits = installPath.split("/").filter(Boolean);
installPath = `${splits[0]}/${splits[1]}`;
}
const isInstalled = isPackageExists(installPath, {
paths: [
this.#context.options.workspaceConfig.workspaceRoot,
this.#context.options.projectRoot
]
});
if (!isInstalled && this.#context.options.skipInstalls !== true) {
this.#context.log(LogLevelLabel.WARN, `The plugin package "${installPath}" is not installed. It will be installed automatically.`);
const result = await install(installPath, {
cwd: this.#context.options.projectRoot
});
if (isNumber(result.exitCode) && result.exitCode > 0) {
this.#context.log(LogLevelLabel.ERROR, result.stderr);
throw new Error(`An error occurred while installing the build plugin package "${installPath}" `);
}
}
try {
const module = await this.#context.resolver.import(this.#context.resolver.esmResolve(joinPaths(pluginConfig[0], "plugin")));
const PluginConstructor = module.plugin ?? module.default;
pluginInstance = new PluginConstructor({
...pluginConfig[1] ?? {},
log: this.#context.log
});
} catch (error) {
try {
const module = await this.#context.resolver.import(this.#context.resolver.esmResolve(pluginConfig[0]));
const PluginConstructor = module.plugin ?? module.default;
pluginInstance = new PluginConstructor({
...pluginConfig[1] ?? {},
log: this.#context.log
});
} catch {
if (!isInstalled) {
throw new Error(`The plugin package "${pluginConfig[0]}" is not installed. Please install the package using the command: "npm install ${pluginConfig[0]} --save-dev"`);
} else {
throw new Error(`An error occurred while importing the build plugin package "${pluginConfig[0]}":
${isError(error) ? error.message : String(error)}
Note: Please ensure the plugin package's default export is a class that extends \`Plugin\` with a constructor that excepts a single arguments of type \`PluginOptions\`.`);
}
}
}
}
if (!isPluginInstance(pluginInstance)) {
throw new Error(`The plugin option ${JSON.stringify(plugin)} does not export a valid module.`);
}
this.#context.options.plugins[pluginInstance.identifier] = defu(pluginInstance.options ?? {}, isSetObject(this.#context.options.plugins[pluginInstance.identifier]) ? this.#context.options.plugins[pluginInstance.identifier] : {}, camelCase(pluginInstance.name) !== camelCase(pluginInstance.identifier) && isSetObject(this.#context.options.plugins[pluginInstance.name]) ? this.#context.options.plugins[pluginInstance.name] : {});
pluginInstance.options = this.#context.options.plugins[pluginInstance.identifier];
const duplicatePlugin = this.#plugins.find((plugin2) => plugin2.isSame(pluginInstance));
if (duplicatePlugin) {
this.#context.log(LogLevelLabel.TRACE, `Duplicate ${chalk.bold.cyanBright(duplicatePlugin.identifier)} plugin dependency detected - Skipping initialization.`);
duplicatePlugin.options = defu(duplicatePlugin.options ?? {}, this.#context.options.plugins[pluginInstance.identifier]);
this.#context.options.plugins[duplicatePlugin.identifier] = duplicatePlugin.options;
return null;
}
this.#context.log(LogLevelLabel.TRACE, `Initializing the ${chalk.bold.cyanBright(pluginInstance.name)} plugin...`);
return pluginInstance;
}
};
export { Engine };