UNPKG

@storm-stack/core

Version:

A build toolkit and runtime used by Storm Software in TypeScript applications

203 lines (200 loc) 6.89 kB
import { __STORM_STACK_IS_PLUGIN__ } from './chunk-S6IVF7HS.js'; import { extendLog, createLog } from './chunk-K7ITYUIA.js'; import { init_esm_shims, __name } from './chunk-QH7NXH7H.js'; import { LogLevelLabel } from '@storm-software/config-tools/types'; import { isValidRange } from '@stryke/fs/semver-fns'; import { camelCase } from '@stryke/string-format/camel-case'; import { kebabCase } from '@stryke/string-format/kebab-case'; import { getPackageName, getPackageVersion } from '@stryke/string-format/package'; import { titleCase } from '@stryke/string-format/title-case'; import { isObject } from '@stryke/type-checks/is-object'; import defu from 'defu'; import { subset } from 'semver'; // src/base/plugin.ts init_esm_shims(); var Plugin = class { static { __name(this, "Plugin"); } #log; #primaryKeyFields = []; /** * A list of plugin modules required as dependencies by the current Plugin. * * @remarks * These plugins will be called prior to the current Plugin. */ dependencies = []; /** * The configuration options for the plugin * * @remarks * This is used to store the configuration options for the plugin, which can be accessed by the plugin's methods. */ options; /** * A property to identify the object as a Storm Stack Plugin. */ get [__STORM_STACK_IS_PLUGIN__]() { return true; } /** * A list of dependencies that are required for the plugin to work. These dependencies will be installed when Storm Stack CLI is run. */ packageDeps = {}; /** * The name of the plugin * * @remarks * This is used to identify the plugin's name used in {@link Context.options}, logs, and other output. */ get name() { let name = kebabCase(this.overrideName || this.constructor.name); if (name.startsWith("plugin-")) { name = name.replace(/^plugin-/g, "").trim(); } if (name.endsWith("-plugin")) { name = name.replace(/-plugin$/g, "").trim(); } return name; } /** * The primary keys for the plugin's options. * * @remarks * This is used to identify when a two instances of the plugin are the same and can be de-duplicated. */ get primaryKeys() { return this.primaryKeyFields.map((primaryKeyField) => this.options[primaryKeyField]); } /** * Returns true if the plugin is a singleton. Singleton plugins can only be instantiated once (so whenever other plugins specify them as dependencies, they will be de-duplicated). * * @remarks * A plugin is considered a singleton if it has zero primary key option fields defined. */ get isSingleton() { return this.primaryKeyFields.length === 0; } /** * The identifier for the plugin used in the {@link isSame} method * * @remarks * Child plugins can override this to provide a more or less specific identifier. This is used to identify the plugin's options in {@link Context.options}. */ get identifier() { return camelCase(`${this.primaryKeys.includes(this.name) ? "" : this.name}${this.isSingleton ? "" : `-${this.primaryKeys.join("-")}`}`); } /** * A property to override the plugin's {@link name} field. * * @remarks * This is useful for plugins that need to have a different name than the default one derived from the class name. */ get overrideName() { return void 0; } /** * A list of primary keys for the plugin's options. * * @remarks * This is used to identify when a two instances of the plugin are the same and can be de-duplicated. */ get primaryKeyFields() { return this.#primaryKeyFields ?? []; } /** * The logger function to use */ get log() { if (!this.#log) { this.#log = this.options.log ? extendLog(this.options.log, `${titleCase(this.name)} Plugin`) : createLog(`${titleCase(this.name)} Plugin`); } return this.#log; } /** * The constructor for the plugin * * @param options - The configuration options for the plugin */ constructor(options) { this.options = options; } /** * Checks if the current plugin is the same as another plugin based on primary key fields. * * @param plugin - The plugin to compare with. * @returns `true` if the plugins are the same, `false` otherwise. */ isSame(plugin) { return this.identifier === plugin.identifier || this.name === plugin.name && this.isSingleton && plugin.isSingleton; } /** * Adds hooks to the engine's hook system. * * @param hooks - The hooks to add to the engine. */ addHooks(hooks) { hooks.addHooks({ "init:begin": this.#initBegin.bind(this) }); } /** * Gets the resolved options for the plugin. * * @param context - The context to use * @returns The resolved options for the plugin */ getOptions(context) { context.options.plugins ??= {}; context.options.plugins[this.identifier] ??= {}; return context.options.plugins[this.identifier]; } /** * Initializes the plugin's context with required installations. * * @param context - The context to initialize. */ async #initBegin(context) { this.log(LogLevelLabel.TRACE, `Adding required installations for the project.`); this.options = defu(this.options, context.options.plugins[this.identifier] ?? {}); if (Object.keys(this.packageDeps).length > 0) { context.packageDeps = Object.entries(this.packageDeps).reduce((ret, [dependency, value]) => { const packageName = getPackageName(dependency); const currentVersion = getPackageVersion(dependency) || (isObject(value) ? value.version : void 0); const currentType = isObject(value) ? value.type : value; const match = Object.keys(ret).find((dep) => getPackageName(dep) === packageName); if (match) { const matchedVersion = getPackageVersion(match) || (isObject(ret[match]) ? ret[match].version : void 0); const matchedType = isObject(ret[match]) ? ret[match].type : ret[match]; const type = currentType === "dependency" || matchedType === "dependency" ? "dependency" : currentType || matchedType; if (currentVersion || matchedVersion) { if (!currentVersion || !isValidRange(currentVersion)) { ret[packageName] = { version: matchedVersion, type }; } else if (!matchedVersion || !isValidRange(matchedVersion)) { ret[packageName] = { version: currentVersion, type }; } else { ret[packageName] = { version: subset(matchedVersion, currentVersion) ? matchedVersion : currentVersion, type }; } } if (match !== packageName) { delete ret[match]; } } else { ret[dependency] = value; } return ret; }, {}); } } }; export { Plugin };