import { runLintCheck } from './chunk-MMXI6KKS.js';
import { Compiler, writeDotenvReflection } from './chunk-X3FMRUBA.js';
import { reflectDotenvTypes, loadEnv, generateDotenvMarkdown } from './chunk-ZV4SN2CX.js';
import { generateDeclarations, generateImports, generateGlobal } from './chunk-S2ETMZSK.js';
import { installPackage } from './chunk-QLRH4TNM.js';
import { loadConfig } from './chunk-EH3WHYSZ.js';
import { getTsconfigFilePath, getTsconfigChanges, getParsedTypeScriptConfig } from './chunk-VNY3ILRH.js';
import { createUnimport } from './chunk-RWOBQPSN.js';
import { resolveDotenvProperties } from './chunk-7LQATK6V.js';
import { createLog } from './chunk-EAGALHEL.js';
import { writeFile } from './chunk-V3VWCAQH.js';
import { writeLog } from './chunk-EFAYOQEI.js';
import { writeRequest } from './chunk-KGVCCRF4.js';
import { writeResponse } from './chunk-7ZTHEUUT.js';
import { writeError } from './chunk-SWEFGCOO.js';
import { writeId } from './chunk-U7DFED5W.js';
import { __name } from './chunk-6AXVLTU5.js';
import { getObjectDiff } from '@donedeal0/superdiff';
import { LogLevelLabel } from '@storm-software/config-tools/types';
import { parseTypeDefinition } from '@stryke/convert/parse-type-definition';
import { getEnvPaths } from '@stryke/env/get-env-paths';
import { removeDirectory, createDirectory } from '@stryke/fs/helpers';
import { install } from '@stryke/fs/install';
import { isPackageExists } from '@stryke/fs/package-fns';
import { readJsonFile } from '@stryke/fs/read-file';
import { removeFile } from '@stryke/fs/remove-file';
import { hash } from '@stryke/hash/hash';
import { hashDirectory } from '@stryke/hash/hash-files';
import { StormJSON } from '@stryke/json/storm-json';
import { existsSync } from '@stryke/path/exists';
import { findFilePath, relativePath } from '@stryke/path/file-path-fns';
import { joinPaths } from '@stryke/path/join-paths';
import { titleCase } from '@stryke/string-format/title-case';
import { isNumber } from '@stryke/type-checks/is-number';
import { isSetString } from '@stryke/type-checks/is-set-string';
import { isString } from '@stryke/type-checks/is-string';
import { isUndefined } from '@stryke/type-checks/is-undefined';
import { nanoid } from '@stryke/unique-id/nanoid-client';
import defu from 'defu';
import { createJiti } from 'jiti';

// ../../node_modules/.pnpm/hookable@5.5.3/node_modules/hookable/dist/index.mjs
function flatHooks(configHooks, hooks = {}, parentName) {
  for (const key in configHooks) {
    const subHook = configHooks[key];
    const name = parentName ? `${parentName}:${key}` : key;
    if (typeof subHook === "object" && subHook !== null) {
      flatHooks(subHook, hooks, name);
    } else if (typeof subHook === "function") {
      hooks[name] = subHook;
    }
  }
  return hooks;
}
__name(flatHooks, "flatHooks");
var defaultTask = { run: /* @__PURE__ */ __name((function_) => function_(), "run") };
var _createTask = /* @__PURE__ */ __name(() => defaultTask, "_createTask");
var createTask = typeof console.createTask !== "undefined" ? console.createTask : _createTask;
function serialTaskCaller(hooks, args) {
  const name = args.shift();
  const task = createTask(name);
  return hooks.reduce(
    (promise, hookFunction) => promise.then(() => task.run(() => hookFunction(...args))),
    Promise.resolve()
  );
}
__name(serialTaskCaller, "serialTaskCaller");
function parallelTaskCaller(hooks, args) {
  const name = args.shift();
  const task = createTask(name);
  return Promise.all(hooks.map((hook) => task.run(() => hook(...args))));
}
__name(parallelTaskCaller, "parallelTaskCaller");
function callEachWith(callbacks, arg0) {
  for (const callback of [...callbacks]) {
    callback(arg0);
  }
}
__name(callEachWith, "callEachWith");
var Hookable = class {
  static {
    __name(this, "Hookable");
  }
  constructor() {
    this._hooks = {};
    this._before = void 0;
    this._after = void 0;
    this._deprecatedMessages = void 0;
    this._deprecatedHooks = {};
    this.hook = this.hook.bind(this);
    this.callHook = this.callHook.bind(this);
    this.callHookWith = this.callHookWith.bind(this);
  }
  hook(name, function_, options = {}) {
    if (!name || typeof function_ !== "function") {
      return () => {
      };
    }
    const originalName = name;
    let dep;
    while (this._deprecatedHooks[name]) {
      dep = this._deprecatedHooks[name];
      name = dep.to;
    }
    if (dep && !options.allowDeprecated) {
      let message = dep.message;
      if (!message) {
        message = `${originalName} hook has been deprecated` + (dep.to ? `, please use ${dep.to}` : "");
      }
      if (!this._deprecatedMessages) {
        this._deprecatedMessages = /* @__PURE__ */ new Set();
      }
      if (!this._deprecatedMessages.has(message)) {
        console.warn(message);
        this._deprecatedMessages.add(message);
      }
    }
    if (!function_.name) {
      try {
        Object.defineProperty(function_, "name", {
          get: /* @__PURE__ */ __name(() => "_" + name.replace(/\W+/g, "_") + "_hook_cb", "get"),
          configurable: true
        });
      } catch {
      }
    }
    this._hooks[name] = this._hooks[name] || [];
    this._hooks[name].push(function_);
    return () => {
      if (function_) {
        this.removeHook(name, function_);
        function_ = void 0;
      }
    };
  }
  hookOnce(name, function_) {
    let _unreg;
    let _function = /* @__PURE__ */ __name((...arguments_) => {
      if (typeof _unreg === "function") {
        _unreg();
      }
      _unreg = void 0;
      _function = void 0;
      return function_(...arguments_);
    }, "_function");
    _unreg = this.hook(name, _function);
    return _unreg;
  }
  removeHook(name, function_) {
    if (this._hooks[name]) {
      const index = this._hooks[name].indexOf(function_);
      if (index !== -1) {
        this._hooks[name].splice(index, 1);
      }
      if (this._hooks[name].length === 0) {
        delete this._hooks[name];
      }
    }
  }
  deprecateHook(name, deprecated) {
    this._deprecatedHooks[name] = typeof deprecated === "string" ? { to: deprecated } : deprecated;
    const _hooks = this._hooks[name] || [];
    delete this._hooks[name];
    for (const hook of _hooks) {
      this.hook(name, hook);
    }
  }
  deprecateHooks(deprecatedHooks) {
    Object.assign(this._deprecatedHooks, deprecatedHooks);
    for (const name in deprecatedHooks) {
      this.deprecateHook(name, deprecatedHooks[name]);
    }
  }
  addHooks(configHooks) {
    const hooks = flatHooks(configHooks);
    const removeFns = Object.keys(hooks).map(
      (key) => this.hook(key, hooks[key])
    );
    return () => {
      for (const unreg of removeFns.splice(0, removeFns.length)) {
        unreg();
      }
    };
  }
  removeHooks(configHooks) {
    const hooks = flatHooks(configHooks);
    for (const key in hooks) {
      this.removeHook(key, hooks[key]);
    }
  }
  removeAllHooks() {
    for (const key in this._hooks) {
      delete this._hooks[key];
    }
  }
  callHook(name, ...arguments_) {
    arguments_.unshift(name);
    return this.callHookWith(serialTaskCaller, name, ...arguments_);
  }
  callHookParallel(name, ...arguments_) {
    arguments_.unshift(name);
    return this.callHookWith(parallelTaskCaller, name, ...arguments_);
  }
  callHookWith(caller, name, ...arguments_) {
    const event = this._before || this._after ? { name, args: arguments_, context: {} } : void 0;
    if (this._before) {
      callEachWith(this._before, event);
    }
    const result = caller(
      name in this._hooks ? [...this._hooks[name]] : [],
      arguments_
    );
    if (result instanceof Promise) {
      return result.finally(() => {
        if (this._after && event) {
          callEachWith(this._after, event);
        }
      });
    }
    if (this._after && event) {
      callEachWith(this._after, event);
    }
    return result;
  }
  beforeEach(function_) {
    this._before = this._before || [];
    this._before.push(function_);
    return () => {
      if (this._before !== void 0) {
        const index = this._before.indexOf(function_);
        if (index !== -1) {
          this._before.splice(index, 1);
        }
      }
    };
  }
  afterEach(function_) {
    this._after = this._after || [];
    this._after.push(function_);
    return () => {
      if (this._after !== void 0) {
        const index = this._after.indexOf(function_);
        if (index !== -1) {
          this._after.splice(index, 1);
        }
      }
    };
  }
};
function createHooks() {
  return new Hookable();
}
__name(createHooks, "createHooks");
var Engine = class {
  static {
    __name(this, "Engine");
  }
  #initialized = false;
  /**
  * The engine hooks - these allow the plugins to hook into the engines processing
  */
  #hooks;
  /**
  * The plugins provided in the options
  */
  #plugins = [];
  /**
  * The options provided to Storm Stack
  */
  options;
  /**
  * The resolved options provided to Storm Stack
  */
  context;
  /**
  * The logger for the plugin
  */
  log;
  /**
  * The default environment variables to apply
  */
  defaultEnv = {};
  constructor(options, workspaceConfig) {
    this.options = options;
    this.log = createLog("engine", this.options);
    this.context = this.options;
    this.context.options = options;
    this.context.override ??= {};
    this.context.workspaceConfig = workspaceConfig;
    this.context.errorsFile ??= joinPaths(this.context.workspaceConfig.workspaceRoot, "errors.json");
    this.context.envPaths = getEnvPaths({
      orgId: "storm-software",
      appId: "storm-stack",
      workspaceRoot: this.context.workspaceConfig?.workspaceRoot
    });
    if (!this.context.envPaths.cache) {
      throw new Error("The cache directory could not be determined.");
    }
    this.context.envPaths.cache = joinPaths(this.context.envPaths.cache, hash(this.options.projectRoot));
    this.context.resolver = createJiti(this.context.workspaceConfig.workspaceRoot, {
      interopDefault: true,
      fsCache: joinPaths(this.context.envPaths.cache, "jiti"),
      moduleCache: true
    });
  }
  /**
  * Initialize the engine
  */
  async init() {
    this.log(LogLevelLabel.TRACE, "Initializing Storm Stack engine");
    this.#hooks = createHooks();
    this.context = defu(this.context, await this.loadConfig() ?? {});
    this.context.minify ??= this.context.mode === "production";
    const checksum = await hashDirectory(this.context.projectRoot, {
      ignore: [
        "node_modules",
        ".git",
        ".nx",
        ".cache",
        ".storm",
        "tmp"
      ]
    });
    this.context.meta ??= {
      buildId: nanoid(24),
      releaseId: nanoid(24),
      checksum,
      timestamp: Date.now()
    };
    this.context.tsconfig ??= getTsconfigFilePath(this.context);
    this.context.artifactsDir ??= ".storm";
    this.context.runtimeDir = joinPaths(this.context.artifactsDir, "runtime");
    this.context.typesDir = joinPaths(this.context.artifactsDir, "types");
    const runtimeDir = joinPaths(this.context.workspaceConfig.workspaceRoot, this.context.projectRoot, this.context.runtimeDir);
    this.context.unimportPresets = [
      {
        imports: [
          "StormJSON"
        ],
        from: "@stryke/json"
      },
      {
        imports: [
          "StormURL"
        ],
        from: "@stryke/url"
      },
      {
        imports: [
          "StormError",
          "createStormError",
          "isStormError",
          "getErrorFromUnknown"
        ],
        from: joinPaths(runtimeDir, "error")
      },
      {
        imports: [
          "StormRequest"
        ],
        from: joinPaths(runtimeDir, "request")
      },
      {
        imports: [
          "StormResponse"
        ],
        from: joinPaths(runtimeDir, "response")
      },
      {
        imports: [
          "StormLog"
        ],
        from: joinPaths(runtimeDir, "log")
      },
      {
        imports: [
          "uniqueId",
          "getRandom"
        ],
        from: joinPaths(runtimeDir, "id")
      }
    ];
    const metaFilePath = joinPaths(this.context.projectRoot, this.context.artifactsDir, "meta.json");
    try {
      if (existsSync(metaFilePath)) {
        this.context.persistedMeta = await readJsonFile(metaFilePath);
      }
    } catch {
      this.context.persistedMeta = void 0;
      await removeFile(metaFilePath);
    }
    this.context.outputPath ??= joinPaths("dist", this.context.projectRoot);
    for (const preset of this.context.presets ?? []) {
      await this.addPreset(preset);
    }
    for (const plugin of this.context.plugins ?? []) {
      await this.addPlugin(plugin);
    }
    if (!this.context.plugins) {
      this.log(LogLevelLabel.WARN, "No Storm Stack plugins were specified in the options. Please ensure this is correct, as it is generally not recommended.");
    } else {
      for (const plugin of this.#plugins) {
        await Promise.resolve(plugin.addHooks(this.#hooks));
      }
    }
    this.log(LogLevelLabel.TRACE, "Checking the Storm Stack project configuration");
    const packageJsonPath = joinPaths(this.context.projectRoot, "package.json");
    if (!existsSync(packageJsonPath)) {
      throw new Error(`Cannot find a \`package.json\` configuration file in ${this.context.projectRoot}.`);
    }
    this.context.packageJson = await readJsonFile(packageJsonPath);
    const projectJsonPath = joinPaths(this.context.projectRoot, "project.json");
    if (existsSync(projectJsonPath)) {
      this.context.projectJson = await readJsonFile(projectJsonPath);
      this.context.name ??= this.context.projectJson?.name;
      this.context.projectType ??= this.context.projectJson?.projectType;
    }
    if (this.context.projectType === "application" && this.context.options.entry) {
      if (isSetString(this.context.options.entry)) {
        this.context.resolvedEntry = [
          {
            ...this.resolveEntry(this.context, this.context.options.entry),
            input: parseTypeDefinition(this.context.options.entry)
          }
        ];
      } else if (Array.isArray(this.context.options.entry) && this.context.options.entry.filter(Boolean).length > 0) {
        this.context.resolvedEntry = this.context.options.entry.map((entry) => ({
          ...this.resolveEntry(this.context, entry),
          input: parseTypeDefinition(entry)
        })).filter(Boolean);
      }
    }
    await this.#hooks.callHook("init:context", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while checking the Storm Stack project configuration: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while checking the Storm Stack project configuration", {
        cause: error
      });
    });
    if (!isPackageExists("typescript")) {
      throw new Error(`The TypeScript package is not installed. Please install the package using the command: "npm install typescript --save-dev"`);
    }
    this.log(LogLevelLabel.TRACE, `Checking and installing missing project dependencies.`);
    await Promise.all([
      installPackage(this.log, this.context, "@stryke/types", true),
      installPackage(this.log, this.context, "@storm-stack/types", true),
      this.context.projectType === "application" && installPackage(this.log, this.context, "@stryke/type-checks"),
      this.context.projectType === "application" && installPackage(this.log, this.context, "@stryke/json"),
      this.context.projectType === "application" && installPackage(this.log, this.context, "@stryke/url")
    ].filter(Boolean));
    await this.#hooks.callHook("init:installs", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while installing project dependencies: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while installing project dependencies", {
        cause: error
      });
    });
    this.log(LogLevelLabel.TRACE, `Checking the TypeScript configuration file (tsconfig.json): ${this.context.tsconfig}`);
    const originalTsconfigJson = await readJsonFile(this.context.tsconfig);
    const json = await getTsconfigChanges(this.context);
    if (!json.include?.some((filterPattern) => filterPattern.includes(joinPaths(this.context.artifactsDir, "**/*.ts")))) {
      json.include ??= [];
      json.include.push(joinPaths(this.context.artifactsDir, "**/*.ts"));
    }
    await this.writeFile(this.context.tsconfig, StormJSON.stringify(json));
    await this.#hooks.callHook("init:tsconfig", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while resolving the TypeScript options: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while resolving the TypeScript options", {
        cause: error
      });
    });
    const result = getObjectDiff(originalTsconfigJson, await readJsonFile(this.context.tsconfig), {
      ignoreArrayOrder: true,
      showOnly: {
        statuses: [
          "added",
          "deleted",
          "updated"
        ],
        granularity: "deep"
      }
    });
    const changes = [];
    const getChanges = /* @__PURE__ */ __name((difference, property) => {
      if (difference.status === "added" || difference.status === "deleted" || difference.status === "updated") {
        if (difference.diff) {
          for (const diff of difference.diff) {
            getChanges(diff, property ? `${property}.${difference.property}` : difference.property);
          }
        } else {
          changes.push({
            field: property ? `${property}.${difference.property}` : difference.property,
            status: difference.status,
            previous: difference.status === "added" ? "---" : StormJSON.stringify(difference.previousValue),
            current: difference.status === "deleted" ? "---" : StormJSON.stringify(difference.currentValue)
          });
        }
      }
    }, "getChanges");
    for (const diff of result.diff) {
      getChanges(diff);
    }
    if (changes.length > 0) {
      this.log(LogLevelLabel.WARN, `Updating the following configuration values in the "tsconfig.json" file:

  ${changes.map((change, i) => `${i + 1}. ${titleCase(change.status)} the ${change.field} field:
   - Previous: ${change.previous}
   - Current: ${change.current}
  `).join("\n")}
  `);
    }
    this.context.resolvedTsconfig = await getParsedTypeScriptConfig(this.context);
    if (!this.context.resolvedTsconfig) {
      throw new Error("Failed to parse the TypeScript configuration file.");
    }
    this.context.unimport = createUnimport(this.log, this.context);
    await this.context.unimport.init();
    const handleTransform = /* @__PURE__ */ __name(async (context, sourceFile) => {
      await this.#hooks.callHook("build:transform", this.context, sourceFile).catch((error) => {
        this.log(LogLevelLabel.ERROR, `An error occured while transforming the Storm Stack project: ${error.message} 
${error.stack ?? ""}`);
        throw new Error("An error occured while transforming the Storm Stack project", {
          cause: error
        });
      });
    }, "handleTransform");
    this.context.compiler = new Compiler(this.context, handleTransform);
    this.context.resolvedDotenv = await this.resolveDotenvOptions();
    if (isSetString(this.context.resolvedDotenv.docgen)) {
      this.context.resolvedDotenv.docgen = this.context.resolvedDotenv.docgen.replace(this.context.projectRoot, "").trim();
    }
    this.log(LogLevelLabel.TRACE, "Storm Stack engine has been initialized");
    this.#initialized = true;
    return this.context;
  }
  /**
  * Clean any previously prepared artifacts
  */
  async clean() {
    if (!this.#initialized) {
      await this.init();
    }
    this.log(LogLevelLabel.INFO, "\u{1F9F9} Cleaning the previous Storm Stack artifacts");
    await Promise.all([
      removeDirectory(joinPaths(this.context.projectRoot, this.context.artifactsDir)),
      removeDirectory(joinPaths(this.context.workspaceConfig.workspaceRoot, this.context.outputPath?.replace(this.context.workspaceConfig.workspaceRoot, "") || joinPaths("dist", this.context.projectRoot)))
    ]);
    await this.#hooks.callHook("clean:execute", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while cleaning the Storm Stack project: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while cleaning the Storm Stack project", {
        cause: error
      });
    });
    this.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 autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  */
  async prepare(autoClean = true) {
    if (!this.#initialized) {
      await this.init();
    }
    if (existsSync(joinPaths(this.context.projectRoot, this.context.artifactsDir)) && autoClean) {
      await this.clean();
    }
    this.log(LogLevelLabel.INFO, "Preparing the Storm Stack project");
    if (!existsSync(joinPaths(this.context.projectRoot, this.context.artifactsDir))) {
      await createDirectory(joinPaths(this.context.projectRoot, this.context.artifactsDir));
    }
    if (!existsSync(joinPaths(this.context.projectRoot, this.context.runtimeDir))) {
      await createDirectory(joinPaths(this.context.projectRoot, this.context.runtimeDir));
    }
    if (!existsSync(joinPaths(this.context.projectRoot, this.context.typesDir))) {
      await createDirectory(joinPaths(this.context.projectRoot, this.context.typesDir));
    }
    await this.writeDotenvReflection();
    await this.writeFile(joinPaths(this.context.projectRoot, this.context.artifactsDir, "meta.json"), StormJSON.stringify(this.context.meta));
    this.context.persistedMeta = this.context.meta;
    await this.writeDeclarations();
    await this.#hooks.callHook("prepare:types", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while writing type artifacts: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while generating the type artifacts", {
        cause: error
      });
    });
    if (this.context.projectType === "application") {
      await this.context.unimport.dumpImports();
      const runtimeDir = joinPaths(this.context.projectRoot, this.context.runtimeDir);
      this.log(LogLevelLabel.TRACE, `Writing to runtime directory "${runtimeDir}"`);
      await Promise.all([
        this.writeFile(joinPaths(runtimeDir, "request.ts"), writeRequest()),
        this.writeFile(joinPaths(runtimeDir, "response.ts"), writeResponse()),
        this.writeFile(joinPaths(runtimeDir, "error.ts"), writeError()),
        this.writeFile(joinPaths(runtimeDir, "id.ts"), writeId()),
        this.writeFile(joinPaths(runtimeDir, "log.ts"), writeLog())
      ]);
      await this.#hooks.callHook("prepare:runtime", this.context).catch((error) => {
        this.log(LogLevelLabel.ERROR, `An error occured while generating the runtime artifacts: ${error.message} 
${error.stack ?? ""}`);
        throw new Error("An error occured while generating the runtime artifacts", {
          cause: error
        });
      });
      await this.#hooks.callHook("prepare:entry", this.context).catch((error) => {
        this.log(LogLevelLabel.ERROR, `An error occured while creating entry artifacts: ${error.message} 
${error.stack ?? ""}`);
        throw new Error("An error occured while creating entry artifacts", {
          cause: error
        });
      });
      await this.#hooks.callHook("prepare:deploy", this.context).catch((error) => {
        this.log(LogLevelLabel.ERROR, `An error occured while creating deployment artifacts: ${error.message} 
${error.stack ?? ""}`);
        throw new Error("An error occured while creating deployment artifacts", {
          cause: error
        });
      });
    }
    await this.#hooks.callHook("prepare:misc", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while creating miscellaneous artifacts: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while creating miscellaneous artifacts", {
        cause: error
      });
    });
    this.context.resolvedTsconfig = await getParsedTypeScriptConfig(this.context);
    if (!this.context.resolvedTsconfig) {
      throw new Error("Failed to parse the TypeScript configuration file.");
    }
  }
  /**
  * Lint the project
  *
  * @param autoPrepare - Whether to automatically prepare the project if it has not been prepared
  * @param autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  * @param lintDuringBuild - Whether to lint the project during the build process
  */
  async lint(autoPrepare = true, autoClean = true, lintDuringBuild = false) {
    if (!this.#initialized) {
      await this.init();
    }
    if (this.context.persistedMeta?.checksum !== this.context.meta.checksum) {
      if (autoPrepare) {
        this.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
        await this.prepare(autoClean);
      } else {
        throw new Error("The Storm Stack project has not been prepared. Please run the `prepare` command before trying to lint the project.");
      }
    }
    this.log(LogLevelLabel.INFO, "Linting the Storm Stack project");
    await runLintCheck(this.log, this.context, {
      lintDuringBuild,
      eslintOptions: {
        cacheLocation: joinPaths(this.context.envPaths.cache, "eslint")
      }
    });
    this.log(LogLevelLabel.TRACE, "Storm Stack documentation generation completed");
  }
  /**
  * Build the project
  *
  * @param autoPrepare - Whether to automatically prepare the project if it has not been prepared
  * @param autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  */
  async build(autoPrepare = true, autoClean = true) {
    if (!this.#initialized) {
      await this.init();
    }
    if (this.context.persistedMeta?.checksum !== this.context.meta.checksum) {
      if (autoPrepare) {
        this.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
        await this.prepare(autoClean);
      } else {
        throw new Error("The Storm Stack project has not been prepared. Please run the `prepare` command before trying to build the project.");
      }
    }
    if (this.context.skipLint === false) {
      await this.lint(autoPrepare, autoClean, true);
    }
    this.log(LogLevelLabel.INFO, "Building the Storm Stack project");
    await this.#hooks.callHook("build:execute", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while building the Storm Stack project: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while building the Storm Stack project", {
        cause: error
      });
    });
  }
  /**
  * Generate the documentation for the project
  *
  * @param autoPrepare - Whether to automatically prepare the project if it has not been prepared
  * @param autoClean - Whether to automatically clean the previous build artifacts before preparing the project
  */
  async docs(autoPrepare = true, autoClean = true) {
    if (!this.#initialized) {
      await this.init();
    }
    if (this.context.persistedMeta?.checksum !== this.context.meta.checksum) {
      if (autoPrepare) {
        this.log(LogLevelLabel.INFO, "The Storm Stack project has been modified since the last time `prepare` was ran. Re-preparing the project.");
        await this.prepare(autoClean);
      } else {
        throw new Error("The Storm Stack project has not been prepared. Please run the `prepare` command before trying to build the project.");
      }
    }
    this.log(LogLevelLabel.INFO, "Generating documentation for the Storm Stack project");
    await this.writeDotenvDoc();
    await this.#hooks.callHook("docs:generate", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while generating documentation for the Storm Stack project: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while generating documentation for the Storm Stack project", {
        cause: error
      });
    });
    this.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.
  */
  async finalize() {
    this.log(LogLevelLabel.TRACE, "Storm Stack finalize execution started");
    await this.#hooks.callHook("finalize:execute", this.context).catch((error) => {
      this.log(LogLevelLabel.ERROR, `An error occured while finalizing the Storm Stack project: ${error.message} 
${error.stack ?? ""}`);
      throw new Error("An error occured while finalizing the Storm Stack project", {
        cause: error
      });
    });
    this.log(LogLevelLabel.TRACE, "Storm Stack finalize execution completed");
  }
  resolveEntry(options, entry) {
    const parsed = parseTypeDefinition(entry);
    const entryFile = parsed.file.replace(options.projectRoot, "").replaceAll("\\", "/").replaceAll(/^(?:\.\/)*/g, "");
    return {
      file: joinPaths(options.projectRoot, options.artifactsDir, `entry-${hash({
        file: entryFile,
        name: parsed.name
      }, {
        maxLength: 24
      }).replaceAll("-", "0").replaceAll("_", "1")}.ts`),
      name: parsed.name
    };
  }
  /**
  * Add a Storm Stack preset to the build process
  *
  * @param preset - The import path of the preset to add
  */
  async addPreset(preset) {
    if (preset && !this.#plugins.some((p) => typeof preset === "string" ? p.installPath === preset : p.installPath === preset[0])) {
      const pluginConfig = typeof preset === "string" ? [
        preset,
        {}
      ] : preset;
      const isInstalled = isPackageExists(pluginConfig[0], {
        paths: [
          this.context.workspaceConfig.workspaceRoot,
          this.context.projectRoot
        ]
      });
      if (!isInstalled && this.context.skipInstalls !== true) {
        this.log(LogLevelLabel.WARN, `The preset package "${pluginConfig[0]}" is not installed. It will be installed automatically.`);
        const result = await install(pluginConfig[0], {
          cwd: this.context.projectRoot
        });
        if (isNumber(result.exitCode) && result.exitCode > 0) {
          this.log(LogLevelLabel.ERROR, result.stderr);
          throw new Error(`An error occurred while installing the build preset package "${pluginConfig[0]}" `);
        }
      }
      let presetInstance;
      try {
        const module = await this.context.resolver.import(this.context.resolver.esmResolve(pluginConfig[0]));
        const PresetConstructor = module.default;
        presetInstance = new PresetConstructor(pluginConfig[1]);
      } catch (error) {
        if (!isInstalled) {
          throw new Error(`The preset 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 preset package "${pluginConfig[0]}":
${error.message}

Note: Please ensure the preset package's default export is a class that extends \`Plugin\` with a constructor that excepts zero arguments.`);
        }
      }
      if (!presetInstance) {
        throw new Error(`The preset package "${pluginConfig[0]}" does not export a valid module.`);
      }
      if (!presetInstance.name) {
        throw new Error(`The module in the build preset package "${pluginConfig[0]}" must export a \`name\` string value.`);
      }
      if (!presetInstance.addHooks) {
        throw new Error(`The build preset "${presetInstance.name}" must export an \`addHooks\` function.`);
      }
      if (presetInstance.dependencies) {
        for (const dependency of presetInstance.dependencies) {
          await this.addPlugin(dependency);
        }
      }
      this.log(LogLevelLabel.INFO, `Successfully initialized the "${presetInstance.name}" preset`);
      this.#plugins.push(presetInstance);
    }
  }
  /**
  * Add a Storm Stack plugin to the build process
  *
  * @param plugin - The import path of the plugin to add
  */
  async addPlugin(plugin) {
    if (plugin && !this.#plugins.some((p) => typeof plugin === "string" ? p.installPath === plugin : p.installPath === plugin[0])) {
      const pluginConfig = typeof plugin === "string" ? [
        plugin,
        {}
      ] : plugin;
      const isInstalled = isPackageExists(pluginConfig[0], {
        paths: [
          this.context.workspaceConfig.workspaceRoot,
          this.context.projectRoot
        ]
      });
      if (!isInstalled && this.context.skipInstalls !== true) {
        this.log(LogLevelLabel.WARN, `The plugin package "${pluginConfig[0]}" is not installed. It will be installed automatically.`);
        const result = await install(pluginConfig[0], {
          cwd: this.context.projectRoot
        });
        if (isNumber(result.exitCode) && result.exitCode > 0) {
          this.log(LogLevelLabel.ERROR, result.stderr);
          throw new Error(`An error occurred while installing the build plugin package "${pluginConfig[0]}" `);
        }
      }
      let pluginInstance;
      try {
        const module = await this.context.resolver.import(this.context.resolver.esmResolve(pluginConfig[0]));
        const PluginConstructor = module.default;
        pluginInstance = new PluginConstructor(pluginConfig[1]);
      } catch (error) {
        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]}":
${error.message}

Note: Please ensure the plugin package's default export is a class that extends \`Plugin\` with a constructor that excepts zero arguments.`);
        }
      }
      if (!pluginInstance) {
        throw new Error(`The plugin package "${pluginConfig[0]}" does not export a valid module.`);
      }
      if (!pluginInstance.name) {
        throw new Error(`The module in the build plugin package "${pluginConfig[0]}" must export a \`name\` string value.`);
      }
      if (!pluginInstance.addHooks) {
        throw new Error(`The build plugin "${pluginInstance.name}" must export an \`addHooks\` function.`);
      }
      this.log(LogLevelLabel.INFO, `Successfully initialized the "${pluginInstance.name}" plugin`);
      this.#plugins.push(pluginInstance);
    }
  }
  /**
  * Load the Storm Stack configuration
  *
  * @returns The Storm Stack configuration
  */
  async loadConfig() {
    return loadConfig(this.context.projectRoot, this.context.mode, joinPaths(this.context.envPaths.cache, "jiti"));
  }
  /**
  * Resolve the dotenv options
  *
  * @returns The resolved dotenv options
  */
  async resolveDotenvOptions() {
    const dotenv = {};
    dotenv.types = await reflectDotenvTypes(this.log, this.context);
    dotenv.additionalFiles = this.context.dotenv?.additionalFiles ?? [];
    dotenv.docgen = this.context.dotenv?.docgen ?? joinPaths(this.context.projectRoot, "docs", "dotenv.md");
    dotenv.replace = Boolean(this.context.dotenv?.replace ?? this.context.projectType === "application");
    const env = defu(await loadEnv(this.context, dotenv, this.context.envPaths.cache, this.context.envPaths.config, this.context.packageJson, this.context.workspaceConfig), this.defaultEnv, {
      APP_NAME: this.context.name || this.context.packageJson.name?.replace(`/${this.context.workspaceConfig.namespace}`, ""),
      APP_VERSION: this.context.packageJson.version,
      BUILD_ID: this.context.meta.buildId,
      BUILD_TIMESTAMP: this.context.meta.timestamp,
      BUILD_CHECKSUM: this.context.meta.checksum,
      RELEASE_ID: this.context.meta.releaseId,
      MODE: this.context.mode,
      ORG: this.context.workspaceConfig.organization,
      ORGANIZATION: this.context.workspaceConfig.organization,
      PLATFORM: this.context.platform,
      STACKTRACE: this.context.mode === "development",
      ENVIRONMENT: this.context.mode,
      DEVELOPMENT: this.context.mode === "development",
      STAGING: this.context.mode === "staging",
      PRODUCTION: this.context.mode === "production",
      DEBUG: this.context.mode === "development"
    });
    dotenv.values = Object.keys(env).reduce((ret, key) => {
      let value = env[key];
      if (isString(value)) {
        value = `"${value.replaceAll('"', "")}"`;
      } else if (!isUndefined(value)) {
        value = String(value);
      }
      ret[key.replaceAll("(", "").replaceAll(")", "")] = value;
      return ret;
    }, env);
    return dotenv;
  }
  /**
  * Write the dotenv reflection file
  */
  async writeDotenvReflection() {
    await writeDotenvReflection(this.log, this.context, this.context.resolvedDotenv.types.variables.reflection, "variables");
    if (this.context.resolvedDotenv.types.secrets?.reflection) {
      await writeDotenvReflection(this.log, this.context, this.context.resolvedDotenv.types.secrets?.reflection, "secrets");
    }
  }
  /**
  * Write the Typescript declarations
  */
  async writeDeclarations() {
    const dtsFile = joinPaths(this.context.projectRoot, this.context.typesDir, "env.d.ts");
    this.log(LogLevelLabel.TRACE, `Writing a declaration file in "${dtsFile}"`);
    let content = "";
    if (this.context.resolvedDotenv.types.variables.reflection) {
      content = generateDeclarations(this.context.resolvedDotenv.types.variables);
    }
    const dtsFilePath = findFilePath(dtsFile);
    if (dtsFilePath && !existsSync(dtsFilePath)) {
      await createDirectory(dtsFilePath);
    }
    return Promise.all([
      content && this.writeFile(dtsFile, content),
      this.writeFile(joinPaths(this.context.projectRoot, this.context.typesDir, "modules.d.ts"), generateImports(relativePath(joinPaths(this.context.projectRoot, this.context.typesDir), joinPaths(this.context.projectRoot, this.context.runtimeDir)))),
      this.writeFile(joinPaths(this.context.projectRoot, this.context.typesDir, "global.d.ts"), generateGlobal(relativePath(joinPaths(this.context.projectRoot, this.context.typesDir), joinPaths(this.context.projectRoot, this.context.runtimeDir))))
    ].filter(Boolean));
  }
  /**
  * Write the dotenv documentation markdown file
  */
  async writeDotenvDoc() {
    if (isSetString(this.context.resolvedDotenv.docgen)) {
      const vars = await resolveDotenvProperties(this.log, this.context, "variables");
      const docgenFile = joinPaths(this.context.projectRoot, this.context.resolvedDotenv.docgen.replace(this.context.projectRoot, "").trim());
      this.log(LogLevelLabel.TRACE, `Documenting environment variables configuration in "${docgenFile}"`);
      const docgenFilePath = findFilePath(docgenFile);
      if (docgenFilePath && !existsSync(docgenFilePath)) {
        await createDirectory(docgenFilePath);
      }
      return this.writeFile(docgenFile, generateDotenvMarkdown(this.context.packageJson, vars));
    }
    this.log(LogLevelLabel.INFO, "The `dotenv.docgen` option was set to `false`. Skipping dotenv documentation.");
  }
  /**
  * Writes a file to the file system
  */
  async writeFile(filepath, content) {
    this.log(LogLevelLabel.TRACE, `Writing file ${filepath} to disk`);
    return writeFile(this.log, filepath, content);
  }
};

export { Engine };
//# sourceMappingURL=chunk-MQSGF4IY.js.map
//# sourceMappingURL=chunk-MQSGF4IY.js.map