@powerlines/engine 0.45.3 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/_internal/worker.cjs +886 -803
  2. package/dist/_internal/worker.mjs +889 -806
  3. package/dist/_internal/worker.mjs.map +1 -1
  4. package/dist/api.cjs +292 -323
  5. package/dist/api.d.cts +44 -11
  6. package/dist/api.d.cts.map +1 -1
  7. package/dist/api.d.mts +44 -11
  8. package/dist/api.d.mts.map +1 -1
  9. package/dist/api.mjs +292 -323
  10. package/dist/api.mjs.map +1 -1
  11. package/dist/{base-context-Byizvf4F.cjs → base-context-D7G_24-i.cjs} +82 -76
  12. package/dist/{base-context-BSAC5sO9.mjs → base-context-DU0NRHDt.mjs} +85 -79
  13. package/dist/base-context-DU0NRHDt.mjs.map +1 -0
  14. package/dist/context/index.cjs +3 -3
  15. package/dist/context/index.d.cts +44 -617
  16. package/dist/context/index.d.cts.map +1 -1
  17. package/dist/context/index.d.mts +44 -617
  18. package/dist/context/index.d.mts.map +1 -1
  19. package/dist/context/index.mjs +3 -3
  20. package/dist/engine-context-BuJQY312.cjs +91 -0
  21. package/dist/engine-context-BvDfqfY7.mjs +86 -0
  22. package/dist/engine-context-BvDfqfY7.mjs.map +1 -0
  23. package/dist/execution-context-BpRfsnkE.d.mts +644 -0
  24. package/dist/execution-context-BpRfsnkE.d.mts.map +1 -0
  25. package/dist/{execution-context-BYGFYty0.cjs → execution-context-BrX9i_L8.cjs} +449 -364
  26. package/dist/{execution-context-Bkxp1fML.mjs → execution-context-CgDuoi8o.mjs} +451 -366
  27. package/dist/execution-context-CgDuoi8o.mjs.map +1 -0
  28. package/dist/execution-context-CodQucFX.d.cts +644 -0
  29. package/dist/execution-context-CodQucFX.d.cts.map +1 -0
  30. package/dist/index.cjs +15 -16
  31. package/dist/index.d.cts +3 -3
  32. package/dist/index.d.cts.map +1 -1
  33. package/dist/index.d.mts +3 -3
  34. package/dist/index.d.mts.map +1 -1
  35. package/dist/index.mjs +15 -16
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/{tsconfig-QMSxSwBD.cjs → tsconfig-BUDqmOaT.cjs} +13 -13
  38. package/dist/{tsconfig-CI6bla4E.mjs → tsconfig-MeFEs21S.mjs} +14 -14
  39. package/dist/tsconfig-MeFEs21S.mjs.map +1 -0
  40. package/dist/typescript/index.cjs +1 -1
  41. package/dist/typescript/index.d.cts +6 -6
  42. package/dist/typescript/index.d.cts.map +1 -1
  43. package/dist/typescript/index.d.mts +6 -6
  44. package/dist/typescript/index.d.mts.map +1 -1
  45. package/dist/typescript/index.mjs +1 -1
  46. package/package.json +17 -17
  47. package/dist/base-context-BSAC5sO9.mjs.map +0 -1
  48. package/dist/engine-context-CI_0NWIk.cjs +0 -73
  49. package/dist/engine-context-_RMFwG4J.mjs +0 -68
  50. package/dist/engine-context-_RMFwG4J.mjs.map +0 -1
  51. package/dist/execution-context-Bkxp1fML.mjs.map +0 -1
  52. package/dist/tsconfig-CI6bla4E.mjs.map +0 -1
package/dist/api.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as mergeConfigs, i as callHook, s as writeMetaFile, t as PowerlinesExecutionContext } from "./execution-context-Bkxp1fML.mjs";
2
- import { a as isIncludeMatchFound, i as getTsconfigFilePath, r as getParsedTypeScriptConfig } from "./tsconfig-CI6bla4E.mjs";
1
+ import { a as mergeConfigs, i as callHook, s as writeMetaFile, t as PowerlinesExecutionContext } from "./execution-context-CgDuoi8o.mjs";
2
+ import { a as isIncludeMatchFound, i as getTsconfigFilePath, r as getParsedTypeScriptConfig } from "./tsconfig-MeFEs21S.mjs";
3
3
  import { n as createProgram } from "./ts-morph-D0CaA37w.mjs";
4
4
  import { toArray } from "@stryke/convert/to-array";
5
5
  import { resolvePackage } from "@stryke/fs/resolve";
@@ -15,7 +15,6 @@ import { replaceExtension, replacePath } from "@stryke/path/replace";
15
15
  import chalk from "chalk";
16
16
  import defu from "defu";
17
17
  import { joinPaths } from "@stryke/path/join-paths";
18
- import { titleCase } from "@stryke/string-format/title-case";
19
18
  import { getTypescriptFileHeader } from "@powerlines/core/lib/utilities/file-header";
20
19
  import { format, formatFolder } from "@powerlines/core/lib/utilities/format";
21
20
  import { formatLogMessage } from "@storm-software/config-tools/logger/console";
@@ -29,6 +28,7 @@ import { getUnique } from "@stryke/helpers/get-unique";
29
28
  import { omit } from "@stryke/helpers/omit";
30
29
  import { findFileName, findFilePath, relativePath } from "@stryke/path/file-path-fns";
31
30
  import { isParentPath } from "@stryke/path/is-parent-path";
31
+ import { titleCase } from "@stryke/string-format/title-case";
32
32
  import { isError } from "@stryke/type-checks/is-error";
33
33
  import { isFunction } from "@stryke/type-checks/is-function";
34
34
  import { isNumber } from "@stryke/type-checks/is-number";
@@ -358,11 +358,11 @@ async function installDependencies(context) {
358
358
  //#endregion
359
359
  //#region src/_internal/helpers/resolve-tsconfig.ts
360
360
  function getTsconfigDtsPath(context) {
361
- return joinPaths(relativePath(joinPaths(context.options.cwd, context.options.root), findFilePath(context.typesPath)), findFileName(context.typesPath));
361
+ return joinPaths(relativePath(joinPaths(context.config.cwd, context.config.root), findFilePath(context.typesPath)), findFileName(context.typesPath));
362
362
  }
363
363
  async function resolveTsconfigChanges(context) {
364
- const tsconfig = getParsedTypeScriptConfig(context.options.cwd, context.options.root, context.config.tsconfig, context.config.tsconfigRaw);
365
- const tsconfigJson = await readJsonFile(getTsconfigFilePath(context.options.cwd, context.options.root, context.config.tsconfig));
364
+ const tsconfig = getParsedTypeScriptConfig(context.config.cwd, context.config.root, context.config.tsconfig, context.config.tsconfigRaw);
365
+ const tsconfigJson = await readJsonFile(getTsconfigFilePath(context.config.cwd, context.config.root, context.config.tsconfig));
366
366
  tsconfigJson.compilerOptions ??= {};
367
367
  if (context.config.output.dts !== false) {
368
368
  const dtsRelativePath = getTsconfigDtsPath(context);
@@ -452,249 +452,15 @@ var PowerlinesExecution = class PowerlinesExecution {
452
452
  * The Powerlines context
453
453
  */
454
454
  #context;
455
- async #handleBuild(context) {
456
- await this.callHook("build", {
457
- environment: context,
458
- order: "pre"
459
- });
460
- context.debug("Formatting the generated entry files before the build process starts.");
461
- await formatFolder(context, context.entryPath);
462
- await this.callHook("build", {
463
- environment: context,
464
- order: "normal"
465
- });
466
- if (context.config.output.copy) {
467
- context.debug("Copying project's files from build output directory.");
468
- const destinationPath = isParentPath(appendPath(context.config.output.path, context.config.cwd), appendPath(context.config.root, context.config.cwd)) ? joinPaths(context.config.output.copy.path, relativePath(appendPath(context.config.root, context.config.cwd), appendPath(context.config.output.path, context.config.cwd))) : joinPaths(context.config.output.copy.path, "dist");
469
- const sourcePath = context.config.output.path;
470
- if (existsSync(sourcePath) && sourcePath !== destinationPath) {
471
- context.debug(`Copying files from project's build output directory (${context.config.output.path}) to the project's copy/publish directory (${destinationPath}).`);
472
- await copyFiles(sourcePath, destinationPath);
473
- } else context.warn(`The source path for the copy operation ${!existsSync(sourcePath) ? "does not exist" : "is the same as the destination path"}. Source: ${sourcePath}, Destination: ${destinationPath}. Skipping copying of build output files.`);
474
- if (context.config.output.copy.assets && Array.isArray(context.config.output.copy.assets)) await Promise.all(context.config.output.copy.assets.map(async (asset) => {
475
- context.trace(`Copying asset(s): ${chalk.redBright(context.config.cwd === asset.input ? asset.glob : appendPath(asset.glob, replacePath(asset.input, context.config.cwd)))} -> ${chalk.greenBright(appendPath(asset.glob, replacePath(asset.output, context.config.cwd)))} ${Array.isArray(asset.ignore) && asset.ignore.length > 0 ? ` (ignoring: ${asset.ignore.map((i) => chalk.yellowBright(i)).join(", ")})` : ""}`);
476
- await context.fs.copy(asset, asset.output);
477
- }));
478
- } else context.debug("No copy configuration found for the project output. Skipping the copying of build output files.");
479
- await this.callHook("build", {
480
- environment: context,
481
- order: "post"
482
- });
483
- }
484
- /**
485
- * Get the configured environments
486
- *
487
- * @returns The configured environments
488
- */
489
- async #getEnvironments() {
490
- if (!this.context.config.environments || Object.keys(this.context.config.environments).length <= 1) {
491
- this.context.debug("No environments are configured for this Powerlines project. Using the default environment.");
492
- return [await this.context.getEnvironment()];
493
- }
494
- this.context.debug(`Found ${Object.keys(this.context.config.environments).length} configured environment(s) for this Powerlines project.`);
495
- return (await Promise.all(Object.entries(this.context.config.environments).map(async ([name, config]) => {
496
- if (!await this.context.getEnvironmentSafe(name)) {
497
- const resolvedEnvironment = await this.callHook("configEnvironment", { environment: name }, name, config);
498
- if (resolvedEnvironment) this.context.environments[name] = await this.context.in(resolvedEnvironment);
499
- }
500
- return this.context.environments[name];
501
- }))).filter((context) => isSet(context));
502
- }
503
- /**
504
- * Execute a handler function for each environment
505
- *
506
- * @param handle - The handler function to execute for each environment
507
- */
508
- async #executeEnvironments(handle) {
509
- await Promise.all((await this.#getEnvironments()).map(async (context) => {
510
- return Promise.resolve(handle(context));
511
- }));
512
- }
513
- /**
514
- * Initialize a Powerlines plugin
515
- *
516
- * @param config - The configuration for the plugin
517
- * @returns The initialized plugin instance, or null if the plugin was a duplicate
518
- * @throws Will throw an error if the plugin cannot be found or is invalid
519
- */
520
- async #initPlugin(config) {
521
- let awaited = config;
522
- if (isPromiseLike(config)) awaited = await Promise.resolve(config);
523
- if (!isPluginConfig(awaited)) {
524
- const invalid = findInvalidPluginConfig(awaited);
525
- throw new Error(`Invalid ${invalid && invalid.length > 1 ? "plugins" : "plugin"} specified in the configuration - ${invalid && invalid.length > 0 ? JSON.stringify(awaited) : invalid?.join("\n\n")} \n\nPlease ensure the value is one of the following: \n - an instance of \`Plugin\` \n - a plugin name \n - an object with the \`plugin\` and \`options\` properties \n - a tuple array with the plugin and options \n - a factory function that returns a plugin or array of plugins \n - an array of plugins or plugin configurations`);
526
- }
527
- let plugins;
528
- if (isPlugin(awaited)) plugins = [awaited];
529
- else if (isFunction(awaited)) plugins = toArray(await Promise.resolve(awaited()));
530
- else if (isString(awaited)) {
531
- const resolved = await this.#resolvePlugin(awaited);
532
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(resolved()));
533
- else plugins = toArray(resolved);
534
- } else if (Array.isArray(awaited) && awaited.every(isPlugin)) plugins = awaited;
535
- else if (Array.isArray(awaited) && awaited.every(isPluginConfig)) {
536
- plugins = [];
537
- for (const pluginConfig of awaited) {
538
- const initialized = await this.#initPlugin(pluginConfig);
539
- if (initialized) plugins.push(...initialized);
540
- }
541
- } else if (isPluginConfigTuple(awaited) || isPluginConfigObject(awaited)) {
542
- let pluginConfig;
543
- let pluginOptions;
544
- if (isPluginConfigTuple(awaited)) {
545
- pluginConfig = awaited[0];
546
- pluginOptions = awaited?.length === 2 ? awaited[1] : void 0;
547
- } else {
548
- pluginConfig = awaited.plugin;
549
- pluginOptions = awaited.options;
550
- }
551
- if (isSetString(pluginConfig)) {
552
- const resolved = await this.#resolvePlugin(pluginConfig);
553
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(pluginOptions ? resolved(pluginOptions) : resolved()));
554
- else plugins = toArray(resolved);
555
- } else if (isFunction(pluginConfig)) plugins = toArray(await Promise.resolve(pluginConfig(pluginOptions)));
556
- else if (Array.isArray(pluginConfig) && pluginConfig.every(isPlugin)) plugins = pluginConfig;
557
- else if (isPlugin(pluginConfig)) plugins = toArray(pluginConfig);
558
- }
559
- if (!plugins) throw new Error(`The plugin configuration ${JSON.stringify(awaited)} is invalid. This configuration must point to a valid Powerlines plugin module.`);
560
- if (plugins.length > 0 && !plugins.every(isPlugin)) throw new Error(`The plugin option ${JSON.stringify(plugins)} does not export a valid module. This configuration must point to a valid Powerlines plugin module.`);
561
- const result = [];
562
- for (const plugin of plugins) if (isDuplicate(plugin, this.context.plugins)) this.context.trace(`Duplicate ${chalk.bold.cyanBright(plugin.name)} plugin dependency detected - Skipping initialization.`);
563
- else {
564
- result.push(plugin);
565
- this.context.trace(`Initializing the ${chalk.bold.cyanBright(plugin.name)} plugin...`);
566
- }
567
- return result;
568
- }
569
- async #resolvePlugin(pluginPath) {
570
- if (pluginPath.startsWith("@") && pluginPath.split("/").filter(Boolean).length > 2) {
571
- const splits = pluginPath.split("/").filter(Boolean);
572
- pluginPath = `${splits[0]}/${splits[1]}`;
573
- }
574
- const isInstalled = isPackageExists(pluginPath, { paths: [this.context.config.cwd, this.context.config.root] });
575
- if (!isInstalled && this.context.config.autoInstall) {
576
- this.#context.warn(`The plugin package "${pluginPath}" is not installed. It will be installed automatically.`);
577
- const result = await install(pluginPath, { cwd: this.context.config.root });
578
- if (isNumber(result.exitCode) && result.exitCode > 0) {
579
- this.#context.error(result.stderr);
580
- throw new Error(`An error occurred while installing the build plugin package "${pluginPath}" `);
581
- }
582
- }
583
- try {
584
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(joinPaths(pluginPath, "plugin")));
585
- const result = module.plugin ?? module.default;
586
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
587
- return result;
588
- } catch (error) {
589
- try {
590
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(pluginPath));
591
- const result = module.plugin ?? module.default;
592
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
593
- return result;
594
- } catch {
595
- if (!isInstalled) throw new Error(`The plugin package "${pluginPath}" is not installed. Please install the package using the command: "npm install ${pluginPath} --save-dev"`);
596
- else throw new Error(`An error occurred while importing the build plugin package "${pluginPath}":
597
- ${isError(error) ? error.message : String(error)}
598
-
599
- 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\`.`);
600
- }
601
- }
602
- }
603
- /**
604
- * Generate the Powerlines TypeScript declaration file
605
- *
606
- * @remarks
607
- * This method will generate the TypeScript declaration file for the Powerlines project, including any types provided by plugins.
608
- *
609
- * @param context - The environment context to use for generating the TypeScript declaration file
610
- * @returns A promise that resolves when the TypeScript declaration file has been generated
611
- */
612
- async #types(context) {
613
- context.debug(`Preparing the TypeScript definitions for the Powerlines project.`);
614
- if (context.fs.existsSync(context.typesPath)) await context.fs.remove(context.typesPath);
615
- if (!await resolvePackage("typescript")) throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed.");
616
- context.debug("Running TypeScript compiler for built-in runtime module files.");
617
- let { code, directives } = await emitBuiltinTypes(context, (await context.getBuiltins()).reduce((ret, builtin) => {
618
- const formatted = replacePath(builtin.path, context.config.cwd);
619
- if (!ret.includes(formatted)) ret.push(formatted);
620
- return ret;
621
- }, []));
622
- context.debug(`Generating TypeScript declaration file ${context.typesPath}.`);
623
- const merge = async (currentResult, previousResult) => {
624
- if (!isSetString(currentResult) && !isSetObject(currentResult) && !isSetString(previousResult) && !isSetObject(previousResult)) return {
625
- code,
626
- directives
627
- };
628
- const previous = (await format(context, context.typesPath, isSetString(previousResult) ? previousResult : isSetObject(previousResult) ? previousResult.code : "")).trim().replace(code, "").trim();
629
- const current = (await format(context, context.typesPath, isSetString(currentResult) ? currentResult : isSetObject(currentResult) ? currentResult.code : "")).trim().replace(previous, "").trim().replace(code, "").trim();
630
- return {
631
- directives: [...isSetObject(currentResult) && currentResult.directives ? currentResult.directives : [], ...isSetObject(previousResult) && previousResult.directives ? previousResult.directives : []],
632
- code: await format(context, context.typesPath, `${!previous.includes(getTypescriptFileHeader(context)) && !current.includes(getTypescriptFileHeader(context)) ? `${code}\n` : ""}${previous}\n${current}`.trim())
633
- };
634
- };
635
- const asNextParam = (previousResult) => isObject(previousResult) ? previousResult.code : previousResult;
636
- let result = await this.callHook("types", {
637
- environment: context,
638
- sequential: true,
639
- order: "pre",
640
- result: "merge",
641
- merge,
642
- asNextParam
643
- }, code);
644
- if (result) {
645
- if (isSetObject(result)) {
646
- code = result.code;
647
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
648
- } else if (isSetString(result)) code = result;
649
- }
650
- result = await this.callHook("types", {
651
- environment: context,
652
- sequential: true,
653
- order: "normal",
654
- result: "merge",
655
- merge,
656
- asNextParam
657
- }, code);
658
- if (result) {
659
- if (isSetObject(result)) {
660
- code = result.code;
661
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
662
- } else if (isSetString(result)) code = result;
663
- }
664
- result = await this.callHook("types", {
665
- environment: context,
666
- sequential: true,
667
- order: "post",
668
- result: "merge",
669
- merge,
670
- asNextParam
671
- }, code);
672
- if (result) {
673
- if (isSetObject(result)) {
674
- code = result.code;
675
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
676
- } else if (isSetString(result)) code = result;
677
- }
678
- if (isSetString(code?.trim()) || directives.length > 0) await context.fs.write(context.typesPath, `${directives.length > 0 ? `${directives.map((directive) => `/// <reference types="${directive}" />`).join("\n")}
679
-
680
- ` : ""}${getTypescriptFileHeader(context, {
681
- directive: null,
682
- prettierIgnore: false
683
- })}
684
-
685
- ${formatTypes(code)}
686
- `);
687
- }
688
455
  /**
689
456
  * Initialize a Powerlines API instance
690
457
  *
691
458
  * @param options - The options to initialize the API with
692
459
  * @returns A new instance of the Powerlines API
693
460
  */
694
- static async init(options, initialConfig) {
695
- const api = new PowerlinesExecution(await PowerlinesExecutionContext.init(options, initialConfig ?? {}));
696
- api.#context.config.initialConfig = initialConfig ?? {};
697
- await api.setup();
461
+ static async from(options, initialConfig) {
462
+ const api = new PowerlinesExecution(await PowerlinesExecutionContext.fromInitialConfig(options, initialConfig ?? {}));
463
+ await api.init();
698
464
  return api;
699
465
  }
700
466
  /**
@@ -704,37 +470,6 @@ ${formatTypes(code)}
704
470
  return this.#context;
705
471
  }
706
472
  /**
707
- * Initialize the execution API with the provided configuration options
708
- */
709
- async setup() {
710
- await this.#context.setup();
711
- this.#context.$$internal = {
712
- api: this,
713
- addPlugin: this.addPlugin.bind(this)
714
- };
715
- const timer = this.#context.timer("Initialization");
716
- for (const plugin of this.#context.config.plugins.flatMap((p) => toArray(p)) ?? []) await this.addPlugin(plugin);
717
- if (this.#context.plugins.length === 0) this.#context.warn({
718
- meta: { category: "plugins" },
719
- message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
720
- });
721
- else this.#context.info({
722
- meta: { category: "plugins" },
723
- message: `Loaded ${this.#context.plugins.length} ${titleCase(this.#context.config.framework)} plugin${this.#context.plugins.length > 1 ? "s" : ""}: \n${this.#context.plugins.map((plugin, index) => ` ${index + 1}. ${colorText(plugin.name)}`).join("\n")}`
724
- });
725
- const pluginConfig = await this.callHook("config", {
726
- environment: await this.#context.getEnvironment(),
727
- sequential: true,
728
- result: "merge",
729
- merge: mergeConfigs
730
- });
731
- if (pluginConfig) {
732
- this.#context.config.pluginConfig = pluginConfig;
733
- await this.#context.setup();
734
- }
735
- timer();
736
- }
737
- /**
738
473
  * Generate the Powerlines typescript declaration file
739
474
  *
740
475
  * @remarks
@@ -745,9 +480,8 @@ ${formatTypes(code)}
745
480
  async types(inlineConfig = { command: "types" }) {
746
481
  this.context.debug(" Aggregating configuration options for the Powerlines project");
747
482
  inlineConfig.command ??= "types";
748
- this.context.config.inlineConfig = inlineConfig;
749
- await this.setup();
750
- await this.#executeEnvironments(async (context) => {
483
+ await this.context.setInlineConfig(inlineConfig);
484
+ await this.executeEnvironments(async (context) => {
751
485
  context.debug(`Initializing the processing options for the Powerlines project.`);
752
486
  await this.callHook("configResolved", {
753
487
  environment: context,
@@ -779,7 +513,7 @@ ${formatTypes(code)}
779
513
  context.info(`Running \`prepare\` command as the meta checksum has changed since the last run.`);
780
514
  await this.prepare(defu({ output: { types: false } }, inlineConfig));
781
515
  }
782
- await this.#types(context);
516
+ await this.handleTypes(context);
783
517
  this.context.debug("Formatting files generated during the types step.");
784
518
  await format(context, context.typesPath, await context.fs.read(context.typesPath) ?? "");
785
519
  await writeMetaFile(context);
@@ -796,9 +530,8 @@ ${formatTypes(code)}
796
530
  */
797
531
  async prepare(inlineConfig = { command: "prepare" }) {
798
532
  inlineConfig.command ??= "prepare";
799
- this.context.config.inlineConfig = inlineConfig;
800
- await this.setup();
801
- await this.#executeEnvironments(async (context) => {
533
+ await this.context.setInlineConfig(inlineConfig);
534
+ await this.executeEnvironments(async (context) => {
802
535
  context.debug(`Initializing the processing options for the Powerlines project.`);
803
536
  await this.callHook("configResolved", {
804
537
  environment: context,
@@ -848,7 +581,7 @@ ${formatTypes(code)}
848
581
  environment: context,
849
582
  order: "post"
850
583
  });
851
- if (context.config.output.types !== false) await this.#types(context);
584
+ if (context.config.output.types !== false) await this.handleTypes(context);
852
585
  this.context.debug("Formatting files generated during the prepare step.");
853
586
  await Promise.all([formatFolder(context, context.builtinsPath), formatFolder(context, context.entryPath)]);
854
587
  await writeMetaFile(context);
@@ -867,7 +600,7 @@ ${formatTypes(code)}
867
600
  async new(inlineConfig) {
868
601
  inlineConfig.command ??= "new";
869
602
  await this.prepare(inlineConfig);
870
- await this.#executeEnvironments(async (context) => {
603
+ await this.executeEnvironments(async (context) => {
871
604
  context.debug("Initializing the processing options for the Powerlines project.");
872
605
  await this.callHook("new", {
873
606
  environment: context,
@@ -916,7 +649,7 @@ ${formatTypes(code)}
916
649
  async clean(inlineConfig = { command: "clean" }) {
917
650
  inlineConfig.command ??= "clean";
918
651
  await this.prepare(inlineConfig);
919
- await this.#executeEnvironments(async (context) => {
652
+ await this.executeEnvironments(async (context) => {
920
653
  context.debug("Cleaning the project's dist and artifacts directories.");
921
654
  await context.fs.remove(joinPaths(context.config.cwd, context.config.output.path));
922
655
  await context.fs.remove(joinPaths(context.config.cwd, context.config.root, context.config.output.artifactsPath));
@@ -935,7 +668,7 @@ ${formatTypes(code)}
935
668
  async lint(inlineConfig = { command: "lint" }) {
936
669
  inlineConfig.command ??= "lint";
937
670
  await this.prepare(inlineConfig);
938
- await this.#executeEnvironments(async (context) => {
671
+ await this.executeEnvironments(async (context) => {
939
672
  await this.callHook("lint", {
940
673
  environment: context,
941
674
  sequential: false
@@ -951,7 +684,7 @@ ${formatTypes(code)}
951
684
  async test(inlineConfig = { command: "test" }) {
952
685
  inlineConfig.command ??= "test";
953
686
  await this.prepare(inlineConfig);
954
- await this.#executeEnvironments(async (context) => {
687
+ await this.executeEnvironments(async (context) => {
955
688
  await this.callHook("test", {
956
689
  environment: context,
957
690
  sequential: false
@@ -969,17 +702,15 @@ ${formatTypes(code)}
969
702
  */
970
703
  async build(inlineConfig = { command: "build" }) {
971
704
  inlineConfig.command ??= "build";
705
+ await this.context.setInlineConfig(inlineConfig);
972
706
  await this.context.generateChecksum();
973
707
  if (this.context.meta.checksum !== this.context.persistedMeta?.checksum || this.context.config.skipCache) {
974
708
  this.context.info(!this.context.persistedMeta?.checksum ? "No previous build cache found. Preparing the project for the initial build." : this.context.meta.checksum !== this.context.persistedMeta.checksum ? "The project has been modified since the last time `prepare` was ran. Re-preparing the project." : "The project is configured to skip cache. Re-preparing the project.");
975
709
  await this.prepare(inlineConfig);
976
- } else {
977
- this.context.config.inlineConfig = inlineConfig;
978
- await this.context.setup();
979
710
  }
980
- if (this.context.config.singleBuild) await this.#handleBuild(await this.#context.toEnvironment());
981
- else await this.#executeEnvironments(async (context) => {
982
- await this.#handleBuild(context);
711
+ if (this.context.config.singleBuild) await this.handleBuild(await this.#context.toEnvironment());
712
+ else await this.executeEnvironments(async (context) => {
713
+ await this.handleBuild(context);
983
714
  });
984
715
  }
985
716
  /**
@@ -990,8 +721,9 @@ ${formatTypes(code)}
990
721
  */
991
722
  async docs(inlineConfig = { command: "docs" }) {
992
723
  inlineConfig.command ??= "docs";
724
+ await this.context.setInlineConfig(inlineConfig);
993
725
  await this.prepare(inlineConfig);
994
- await this.#executeEnvironments(async (context) => {
726
+ await this.executeEnvironments(async (context) => {
995
727
  context.debug("Writing documentation for the Powerlines project artifacts.");
996
728
  await this.callHook("docs", { environment: context });
997
729
  });
@@ -1006,8 +738,9 @@ ${formatTypes(code)}
1006
738
  */
1007
739
  async deploy(inlineConfig = { command: "deploy" }) {
1008
740
  inlineConfig.command ??= "deploy";
741
+ await this.context.setInlineConfig(inlineConfig);
1009
742
  await this.prepare(inlineConfig);
1010
- await this.#executeEnvironments(async (context) => {
743
+ await this.executeEnvironments(async (context) => {
1011
744
  await this.callHook("deploy", { environment: context });
1012
745
  });
1013
746
  }
@@ -1020,7 +753,7 @@ ${formatTypes(code)}
1020
753
  * @returns A promise that resolves when the finalization process has completed
1021
754
  */
1022
755
  async finalize() {
1023
- await this.#executeEnvironments(async (context) => {
756
+ await this.executeEnvironments(async (context) => {
1024
757
  await this.callHook("finalize", { environment: context });
1025
758
  await context.fs.dispose();
1026
759
  if (existsSync(context.cachePath) && !(await listFiles(joinPaths(context.cachePath, "**/*")))?.length) await removeDirectory(context.cachePath);
@@ -1052,13 +785,40 @@ ${formatTypes(code)}
1052
785
  this.#context = context;
1053
786
  }
1054
787
  /**
788
+ * Initialize the execution API with the provided configuration options
789
+ */
790
+ async init() {
791
+ this.#context.$$internal = {
792
+ api: this,
793
+ addPlugin: this.addPlugin.bind(this)
794
+ };
795
+ const timer = this.#context.timer("Initialization");
796
+ for (const plugin of this.#context.config.plugins.flatMap((p) => toArray(p)) ?? []) await this.addPlugin(plugin);
797
+ if (this.#context.plugins.length === 0) this.#context.warn({
798
+ meta: { category: "plugins" },
799
+ message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
800
+ });
801
+ else this.#context.info({
802
+ meta: { category: "plugins" },
803
+ message: `Loaded ${this.#context.plugins.length} ${titleCase(this.#context.config.framework)} plugin${this.#context.plugins.length > 1 ? "s" : ""}: \n${this.#context.plugins.map((plugin, index) => ` ${index + 1}. ${colorText(plugin.name)}`).join("\n")}`
804
+ });
805
+ const pluginConfig = await this.callHook("config", {
806
+ environment: await this.#context.getEnvironment(),
807
+ sequential: true,
808
+ result: "merge",
809
+ merge: mergeConfigs
810
+ });
811
+ if (pluginConfig) await this.context.setPluginConfig(pluginConfig);
812
+ timer();
813
+ }
814
+ /**
1055
815
  * Add a Powerlines plugin used in the build process
1056
816
  *
1057
817
  * @param config - The import path of the plugin to add
1058
818
  */
1059
819
  async addPlugin(config) {
1060
820
  if (config) {
1061
- const result = await this.#initPlugin(config);
821
+ const result = await this.initPlugin(config);
1062
822
  if (!result) return;
1063
823
  for (const plugin of result) {
1064
824
  this.context.debug({
@@ -1069,6 +829,239 @@ ${formatTypes(code)}
1069
829
  }
1070
830
  }
1071
831
  }
832
+ /**
833
+ * Get the configured environments
834
+ *
835
+ * @returns The configured environments
836
+ */
837
+ async getEnvironments() {
838
+ if (!this.context.config.environments || Object.keys(this.context.config.environments).length <= 1) {
839
+ this.context.debug("No environments are configured for this Powerlines project. Using the default environment.");
840
+ return [await this.context.getEnvironment()];
841
+ }
842
+ this.context.debug(`Found ${Object.keys(this.context.config.environments).length} configured environment(s) for this Powerlines project.`);
843
+ return (await Promise.all(Object.entries(this.context.config.environments).map(async ([name, config]) => {
844
+ if (!await this.context.getEnvironmentSafe(name)) {
845
+ const resolvedEnvironment = await this.callHook("configEnvironment", { environment: name }, name, config);
846
+ if (resolvedEnvironment) this.context.environments[name] = await this.context.createEnvironment(resolvedEnvironment);
847
+ }
848
+ return this.context.environments[name];
849
+ }))).filter((context) => isSet(context));
850
+ }
851
+ /**
852
+ * Execute a handler function for each environment
853
+ *
854
+ * @param handle - The handler function to execute for each environment
855
+ */
856
+ async executeEnvironments(handle) {
857
+ await Promise.all((await this.getEnvironments()).map(async (context) => {
858
+ return Promise.resolve(handle(context));
859
+ }));
860
+ }
861
+ /**
862
+ * Initialize a Powerlines plugin
863
+ *
864
+ * @param config - The configuration for the plugin
865
+ * @returns The initialized plugin instance, or null if the plugin was a duplicate
866
+ * @throws Will throw an error if the plugin cannot be found or is invalid
867
+ */
868
+ async initPlugin(config) {
869
+ let awaited = config;
870
+ if (isPromiseLike(config)) awaited = await Promise.resolve(config);
871
+ if (!isPluginConfig(awaited)) {
872
+ const invalid = findInvalidPluginConfig(awaited);
873
+ throw new Error(`Invalid ${invalid && invalid.length > 1 ? "plugins" : "plugin"} specified in the configuration - ${invalid && invalid.length > 0 ? JSON.stringify(awaited) : invalid?.join("\n\n")} \n\nPlease ensure the value is one of the following: \n - an instance of \`Plugin\` \n - a plugin name \n - an object with the \`plugin\` and \`options\` properties \n - a tuple array with the plugin and options \n - a factory function that returns a plugin or array of plugins \n - an array of plugins or plugin configurations`);
874
+ }
875
+ let plugins;
876
+ if (isPlugin(awaited)) plugins = [awaited];
877
+ else if (isFunction(awaited)) plugins = toArray(await Promise.resolve(awaited()));
878
+ else if (isString(awaited)) {
879
+ const resolved = await this.resolvePlugin(awaited);
880
+ if (isFunction(resolved)) plugins = toArray(await Promise.resolve(resolved()));
881
+ else plugins = toArray(resolved);
882
+ } else if (Array.isArray(awaited) && awaited.every(isPlugin)) plugins = awaited;
883
+ else if (Array.isArray(awaited) && awaited.every(isPluginConfig)) {
884
+ plugins = [];
885
+ for (const pluginConfig of awaited) {
886
+ const initialized = await this.initPlugin(pluginConfig);
887
+ if (initialized) plugins.push(...initialized);
888
+ }
889
+ } else if (isPluginConfigTuple(awaited) || isPluginConfigObject(awaited)) {
890
+ let pluginConfig;
891
+ let pluginOptions;
892
+ if (isPluginConfigTuple(awaited)) {
893
+ pluginConfig = awaited[0];
894
+ pluginOptions = awaited?.length === 2 ? awaited[1] : void 0;
895
+ } else {
896
+ pluginConfig = awaited.plugin;
897
+ pluginOptions = awaited.options;
898
+ }
899
+ if (isSetString(pluginConfig)) {
900
+ const resolved = await this.resolvePlugin(pluginConfig);
901
+ if (isFunction(resolved)) plugins = toArray(await Promise.resolve(pluginOptions ? resolved(pluginOptions) : resolved()));
902
+ else plugins = toArray(resolved);
903
+ } else if (isFunction(pluginConfig)) plugins = toArray(await Promise.resolve(pluginConfig(pluginOptions)));
904
+ else if (Array.isArray(pluginConfig) && pluginConfig.every(isPlugin)) plugins = pluginConfig;
905
+ else if (isPlugin(pluginConfig)) plugins = toArray(pluginConfig);
906
+ }
907
+ if (!plugins) throw new Error(`The plugin configuration ${JSON.stringify(awaited)} is invalid. This configuration must point to a valid Powerlines plugin module.`);
908
+ if (plugins.length > 0 && !plugins.every(isPlugin)) throw new Error(`The plugin option ${JSON.stringify(plugins)} does not export a valid module. This configuration must point to a valid Powerlines plugin module.`);
909
+ const result = [];
910
+ for (const plugin of plugins) if (isDuplicate(plugin, this.context.plugins)) this.context.trace(`Duplicate ${chalk.bold.cyanBright(plugin.name)} plugin dependency detected - Skipping initialization.`);
911
+ else {
912
+ result.push(plugin);
913
+ this.context.trace(`Initializing the ${chalk.bold.cyanBright(plugin.name)} plugin...`);
914
+ }
915
+ return result;
916
+ }
917
+ async resolvePlugin(pluginPath) {
918
+ if (pluginPath.startsWith("@") && pluginPath.split("/").filter(Boolean).length > 2) {
919
+ const splits = pluginPath.split("/").filter(Boolean);
920
+ pluginPath = `${splits[0]}/${splits[1]}`;
921
+ }
922
+ const isInstalled = isPackageExists(pluginPath, { paths: [this.context.config.cwd, this.context.config.root] });
923
+ if (!isInstalled && this.context.config.autoInstall) {
924
+ this.#context.warn(`The plugin package "${pluginPath}" is not installed. It will be installed automatically.`);
925
+ const result = await install(pluginPath, { cwd: this.context.config.root });
926
+ if (isNumber(result.exitCode) && result.exitCode > 0) {
927
+ this.#context.error(result.stderr);
928
+ throw new Error(`An error occurred while installing the build plugin package "${pluginPath}" `);
929
+ }
930
+ }
931
+ try {
932
+ const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(joinPaths(pluginPath, "plugin")));
933
+ const result = module.plugin ?? module.default;
934
+ if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
935
+ return result;
936
+ } catch (error) {
937
+ try {
938
+ const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(pluginPath));
939
+ const result = module.plugin ?? module.default;
940
+ if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
941
+ return result;
942
+ } catch {
943
+ if (!isInstalled) throw new Error(`The plugin package "${pluginPath}" is not installed. Please install the package using the command: "npm install ${pluginPath} --save-dev"`);
944
+ else throw new Error(`An error occurred while importing the build plugin package "${pluginPath}":
945
+ ${isError(error) ? error.message : String(error)}
946
+
947
+ 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\`.`);
948
+ }
949
+ }
950
+ }
951
+ async handleBuild(context) {
952
+ await this.callHook("build", {
953
+ environment: context,
954
+ order: "pre"
955
+ });
956
+ context.debug("Formatting the generated entry files before the build process starts.");
957
+ await formatFolder(context, context.entryPath);
958
+ await this.callHook("build", {
959
+ environment: context,
960
+ order: "normal"
961
+ });
962
+ if (context.config.output.copy) {
963
+ context.debug("Copying project's files from build output directory.");
964
+ const destinationPath = isParentPath(appendPath(context.config.output.path, context.config.cwd), appendPath(context.config.root, context.config.cwd)) ? joinPaths(context.config.output.copy.path, relativePath(appendPath(context.config.root, context.config.cwd), appendPath(context.config.output.path, context.config.cwd))) : joinPaths(context.config.output.copy.path, "dist");
965
+ const sourcePath = context.config.output.path;
966
+ if (existsSync(sourcePath) && sourcePath !== destinationPath) {
967
+ context.debug(`Copying files from project's build output directory (${context.config.output.path}) to the project's copy/publish directory (${destinationPath}).`);
968
+ await copyFiles(sourcePath, destinationPath);
969
+ } else context.warn(`The source path for the copy operation ${!existsSync(sourcePath) ? "does not exist" : "is the same as the destination path"}. Source: ${sourcePath}, Destination: ${destinationPath}. Skipping copying of build output files.`);
970
+ if (context.config.output.copy.assets && Array.isArray(context.config.output.copy.assets)) await Promise.all(context.config.output.copy.assets.map(async (asset) => {
971
+ context.trace(`Copying asset(s): ${chalk.redBright(context.config.cwd === asset.input ? asset.glob : appendPath(asset.glob, replacePath(asset.input, context.config.cwd)))} -> ${chalk.greenBright(appendPath(asset.glob, replacePath(asset.output, context.config.cwd)))} ${Array.isArray(asset.ignore) && asset.ignore.length > 0 ? ` (ignoring: ${asset.ignore.map((i) => chalk.yellowBright(i)).join(", ")})` : ""}`);
972
+ await context.fs.copy(asset, asset.output);
973
+ }));
974
+ } else context.debug("No copy configuration found for the project output. Skipping the copying of build output files.");
975
+ await this.callHook("build", {
976
+ environment: context,
977
+ order: "post"
978
+ });
979
+ }
980
+ /**
981
+ * Generate the Powerlines TypeScript declaration file
982
+ *
983
+ * @remarks
984
+ * This method will generate the TypeScript declaration file for the Powerlines project, including any types provided by plugins.
985
+ *
986
+ * @param context - The environment context to use for generating the TypeScript declaration file
987
+ * @returns A promise that resolves when the TypeScript declaration file has been generated
988
+ */
989
+ async handleTypes(context) {
990
+ context.debug(`Preparing the TypeScript definitions for the Powerlines project.`);
991
+ if (context.fs.existsSync(context.typesPath)) await context.fs.remove(context.typesPath);
992
+ if (!await resolvePackage("typescript")) throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed.");
993
+ context.debug("Running TypeScript compiler for built-in runtime module files.");
994
+ let { code, directives } = await emitBuiltinTypes(context, (await context.getBuiltins()).reduce((ret, builtin) => {
995
+ const formatted = replacePath(builtin.path, context.config.cwd);
996
+ if (!ret.includes(formatted)) ret.push(formatted);
997
+ return ret;
998
+ }, []));
999
+ context.debug(`Generating TypeScript declaration file ${context.typesPath}.`);
1000
+ const merge = async (currentResult, previousResult) => {
1001
+ if (!isSetString(currentResult) && !isSetObject(currentResult) && !isSetString(previousResult) && !isSetObject(previousResult)) return {
1002
+ code,
1003
+ directives
1004
+ };
1005
+ const previous = (await format(context, context.typesPath, isSetString(previousResult) ? previousResult : isSetObject(previousResult) ? previousResult.code : "")).trim().replace(code, "").trim();
1006
+ const current = (await format(context, context.typesPath, isSetString(currentResult) ? currentResult : isSetObject(currentResult) ? currentResult.code : "")).trim().replace(previous, "").trim().replace(code, "").trim();
1007
+ return {
1008
+ directives: [...isSetObject(currentResult) && currentResult.directives ? currentResult.directives : [], ...isSetObject(previousResult) && previousResult.directives ? previousResult.directives : []],
1009
+ code: await format(context, context.typesPath, `${!previous.includes(getTypescriptFileHeader(context)) && !current.includes(getTypescriptFileHeader(context)) ? `${code}\n` : ""}${previous}\n${current}`.trim())
1010
+ };
1011
+ };
1012
+ const asNextParam = (previousResult) => isObject(previousResult) ? previousResult.code : previousResult;
1013
+ let result = await this.callHook("types", {
1014
+ environment: context,
1015
+ sequential: true,
1016
+ order: "pre",
1017
+ result: "merge",
1018
+ merge,
1019
+ asNextParam
1020
+ }, code);
1021
+ if (result) {
1022
+ if (isSetObject(result)) {
1023
+ code = result.code;
1024
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
1025
+ } else if (isSetString(result)) code = result;
1026
+ }
1027
+ result = await this.callHook("types", {
1028
+ environment: context,
1029
+ sequential: true,
1030
+ order: "normal",
1031
+ result: "merge",
1032
+ merge,
1033
+ asNextParam
1034
+ }, code);
1035
+ if (result) {
1036
+ if (isSetObject(result)) {
1037
+ code = result.code;
1038
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
1039
+ } else if (isSetString(result)) code = result;
1040
+ }
1041
+ result = await this.callHook("types", {
1042
+ environment: context,
1043
+ sequential: true,
1044
+ order: "post",
1045
+ result: "merge",
1046
+ merge,
1047
+ asNextParam
1048
+ }, code);
1049
+ if (result) {
1050
+ if (isSetObject(result)) {
1051
+ code = result.code;
1052
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
1053
+ } else if (isSetString(result)) code = result;
1054
+ }
1055
+ if (isSetString(code?.trim()) || directives.length > 0) await context.fs.write(context.typesPath, `${directives.length > 0 ? `${directives.map((directive) => `/// <reference types="${directive}" />`).join("\n")}
1056
+
1057
+ ` : ""}${getTypescriptFileHeader(context, {
1058
+ directive: null,
1059
+ prettierIgnore: false
1060
+ })}
1061
+
1062
+ ${formatTypes(code)}
1063
+ `);
1064
+ }
1072
1065
  };
1073
1066
 
1074
1067
  //#endregion
@@ -1089,37 +1082,13 @@ var PowerlinesAPI = class PowerlinesAPI extends PowerlinesExecution {
1089
1082
  * @param initialConfig - The initial configuration for the API, which can be used to provide additional context or override certain configuration values during initialization. This is particularly useful when initializing the API from a CLI command, where the CLI flags can be passed as part of the initial configuration to ensure they are properly merged with the configuration file and made available to plugins during their setup and execution.
1090
1083
  * @returns A new instance of the Powerlines API
1091
1084
  */
1092
- static async init(options, initialConfig) {
1093
- const api = new PowerlinesAPI(await PowerlinesExecutionContext.init({
1085
+ static async from(options, initialConfig) {
1086
+ const api = new PowerlinesAPI(await PowerlinesExecutionContext.fromInitialConfig({
1094
1087
  executionId: uuid(),
1095
1088
  executionIndex: 0,
1096
1089
  ...options
1097
1090
  }, initialConfig ?? {}));
1098
- api.context.$$internal = {
1099
- api,
1100
- addPlugin: api.addPlugin.bind(api)
1101
- };
1102
- const timer = api.context.timer("Initialization");
1103
- for (const plugin of api.context.config.plugins.flatMap((p) => toArray(p)) ?? []) await api.addPlugin(plugin);
1104
- if (api.context.plugins.length === 0) api.context.warn({
1105
- meta: { category: "plugins" },
1106
- message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
1107
- });
1108
- else api.context.info({
1109
- meta: { category: "plugins" },
1110
- message: `Loaded ${api.context.plugins.length} ${titleCase(api.context.config.framework)} plugin${api.context.plugins.length > 1 ? "s" : ""}: \n${api.context.plugins.map((plugin, index) => ` ${index + 1}. ${colorText(plugin.name)}`).join("\n")}`
1111
- });
1112
- const pluginConfig = await api.callHook("config", {
1113
- environment: await api.context.getEnvironment(),
1114
- sequential: true,
1115
- result: "merge",
1116
- merge: mergeConfigs
1117
- });
1118
- if (isSetObject(pluginConfig)) {
1119
- api.context.config.pluginConfig = pluginConfig;
1120
- await api.context.setup();
1121
- }
1122
- timer();
1091
+ await api.init();
1123
1092
  return api;
1124
1093
  }
1125
1094
  /**