@powerlines/engine 0.45.3 → 0.46.0

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 +844 -789
  2. package/dist/_internal/worker.mjs +847 -792
  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-BCG0xN2e.cjs} +70 -64
  12. package/dist/{base-context-BSAC5sO9.mjs → base-context-Cmo6TTh7.mjs} +73 -67
  13. package/dist/base-context-Cmo6TTh7.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-BjFMVQEE.mjs +86 -0
  21. package/dist/engine-context-BjFMVQEE.mjs.map +1 -0
  22. package/dist/engine-context-DOsGtgD9.cjs +91 -0
  23. package/dist/execution-context-BdZt7wWa.d.mts +631 -0
  24. package/dist/execution-context-BdZt7wWa.d.mts.map +1 -0
  25. package/dist/execution-context-CU6iNchD.d.cts +631 -0
  26. package/dist/execution-context-CU6iNchD.d.cts.map +1 -0
  27. package/dist/{execution-context-Bkxp1fML.mjs → execution-context-Cp32TarF.mjs} +421 -364
  28. package/dist/execution-context-Cp32TarF.mjs.map +1 -0
  29. package/dist/{execution-context-BYGFYty0.cjs → execution-context-DgqxcDDx.cjs} +419 -362
  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
@@ -24,7 +24,7 @@ import { isError } from "@stryke/type-checks/is-error";
24
24
  import { isFunction } from "@stryke/type-checks/is-function";
25
25
  import { isNumber } from "@stryke/type-checks/is-number";
26
26
  import { isObject } from "@stryke/type-checks/is-object";
27
- import { isPromiseLike } from "@stryke/type-checks/is-promise";
27
+ import { isPromise, isPromiseLike } from "@stryke/type-checks/is-promise";
28
28
  import { isSet } from "@stryke/type-checks/is-set";
29
29
  import { isSetObject } from "@stryke/type-checks/is-set-object";
30
30
  import { isSetString } from "@stryke/type-checks/is-set-string";
@@ -38,21 +38,21 @@ import { deepClone } from "@stryke/helpers/deep-clone";
38
38
  import { joinPaths as joinPaths$1 } from "@stryke/path/join";
39
39
  import { uuid } from "@stryke/unique-id/uuid";
40
40
  import { getUniqueInputs, isTypeDefinition, resolveInputsSync } from "@powerlines/core/lib/entry";
41
- import { toBool } from "@stryke/convert/to-bool";
42
41
  import { getEnvPaths } from "@stryke/env/get-env-paths";
43
42
  import { relativeToWorkspaceRoot } from "@stryke/fs/get-workspace-root";
44
43
  import { murmurhash } from "@stryke/hash";
45
44
  import { hashDirectory } from "@stryke/hash/node";
46
45
  import { fetchRequest } from "@stryke/http/fetch";
47
46
  import { isEqual } from "@stryke/path/is-equal";
48
- import { kebabCase } from "@stryke/string-format/kebab-case";
49
47
  import { match, tsconfigPathsToRegExp } from "bundle-require";
50
48
  import { resolveCompatibilityDates } from "compatx";
51
49
  import { create } from "flat-cache";
52
50
  import { parse } from "oxc-parser";
53
51
  import { Agent, Response, interceptors, setGlobalDispatcher } from "undici";
54
52
  import "@stryke/fs/remove-file";
53
+ import { kebabCase } from "@stryke/string-format/kebab-case";
55
54
  import { LogCategories } from "@powerlines/core";
55
+ import { messageParent } from "jest-worker";
56
56
  import * as $ from "@stryke/capnp";
57
57
  import { readFileBuffer, readFileBufferSync, writeFileBuffer } from "@stryke/fs/buffer";
58
58
  import { correctPath, stripStars } from "@stryke/path/correct-path";
@@ -73,8 +73,8 @@ import ts from "typescript";
73
73
  import { loadUserConfigFile } from "@powerlines/core/lib/config";
74
74
  import { tryGetWorkspaceConfig } from "@storm-software/config-tools/get-config";
75
75
  import { isDevelopment, isProduction, isTest } from "@stryke/env/environment-checks";
76
- import { readJsonFile as readJsonFile$1 } from "@stryke/fs";
77
- import { joinPaths as joinPaths$2 } from "@stryke/path";
76
+ import { isFile as isFile$1, readJsonFile as readJsonFile$1 } from "@stryke/fs";
77
+ import { findFilePath as findFilePath$1, joinPaths as joinPaths$2, relativePath as relativePath$1 } from "@stryke/path";
78
78
  import { formatDistanceToNowStrict } from "date-fns/formatDistanceToNowStrict";
79
79
  import { createJiti } from "jiti";
80
80
  import { getField } from "@stryke/helpers/get-field";
@@ -84,9 +84,9 @@ import { getObjectDiff } from "@donedeal0/superdiff";
84
84
  import { StormJSON } from "@stryke/json/storm-json";
85
85
 
86
86
  //#region src/_internal/helpers/environment.ts
87
- function createEnvironment(name, config = {}) {
87
+ function createEnvironment(name, config) {
88
88
  return defu(config.environments?.[name] ?? {}, {
89
- environmentId: uuid(),
89
+ id: uuid(),
90
90
  name,
91
91
  title: config.title ?? titleCase(config.name),
92
92
  ssr: false,
@@ -112,7 +112,7 @@ function createEnvironment(name, config = {}) {
112
112
  } : void 0
113
113
  });
114
114
  }
115
- function createDefaultEnvironment(config = {}) {
115
+ function createDefaultEnvironment(config) {
116
116
  return createEnvironment(DEFAULT_ENVIRONMENT, config);
117
117
  }
118
118
 
@@ -169,16 +169,16 @@ async function writeMetaFile(context) {
169
169
 
170
170
  //#endregion
171
171
  //#region src/_internal/ipc/send.ts
172
- function sendWriteLogMessage(context, meta, message) {
172
+ function formatWriteLogMessage(context, meta, message) {
173
173
  const combinedMeta = {
174
174
  ...context.logger.options,
175
175
  ...isSetObject(meta) ? meta : { type: meta }
176
176
  };
177
- process.send?.({
177
+ return {
178
178
  id: uuid(),
179
179
  type: "write-log",
180
- executionId: combinedMeta.executionId || context.config.executionId,
181
- executionIndex: combinedMeta.executionIndex ?? context.config.executionIndex,
180
+ executionId: combinedMeta.executionId || context.options.executionId,
181
+ executionIndex: combinedMeta.executionIndex ?? context.options.executionIndex,
182
182
  environment: combinedMeta.environment,
183
183
  timestamp: Date.now(),
184
184
  payload: {
@@ -188,8 +188,8 @@ function sendWriteLogMessage(context, meta, message) {
188
188
  logId: combinedMeta.logId || uuid(),
189
189
  timestamp: combinedMeta.timestamp ?? Date.now(),
190
190
  name: combinedMeta.name || context.config.name,
191
- executionId: combinedMeta.executionId || context.config.executionId,
192
- executionIndex: combinedMeta.executionIndex ?? context.config.executionIndex,
191
+ executionId: combinedMeta.executionId || context.options.executionId,
192
+ executionIndex: combinedMeta.executionIndex ?? context.options.executionIndex,
193
193
  command: combinedMeta.command || context.config.command,
194
194
  hook: combinedMeta.hook,
195
195
  environment: combinedMeta.environment,
@@ -198,7 +198,21 @@ function sendWriteLogMessage(context, meta, message) {
198
198
  },
199
199
  message
200
200
  }
201
- });
201
+ };
202
+ }
203
+ function childProcessSend(message) {
204
+ process.send?.(message);
205
+ }
206
+ function workerThreadSend(message) {
207
+ messageParent(message);
208
+ }
209
+ function send(message) {
210
+ if (process.env.POWERLINES_EXECUTION_THREAD_TYPE === "child-process") childProcessSend(message);
211
+ else if (process.env.POWERLINES_EXECUTION_THREAD_TYPE === "worker-thread") workerThreadSend(message);
212
+ else console.warn("No IPC mechanism available to send message:", message);
213
+ }
214
+ function sendWriteLogMessage(context, meta, message) {
215
+ send(formatWriteLogMessage(context, meta, message));
202
216
  }
203
217
 
204
218
  //#endregion
@@ -1996,40 +2010,40 @@ var VirtualFileSystem = class VirtualFileSystem {
1996
2010
  /**
1997
2011
  * Get the path to the tsconfig.json file.
1998
2012
  *
1999
- * @param workspaceRoot - The root directory of the workspace.
2000
- * @param projectRoot - The root directory of the project.
2013
+ * @param cwd - The root directory of the workspace.
2014
+ * @param root - The root directory of the project.
2001
2015
  * @param tsconfig - The path to the tsconfig.json file.
2002
2016
  * @returns The absolute path to the tsconfig.json file.
2003
2017
  * @throws If the tsconfig.json file does not exist.
2004
2018
  */
2005
- function getTsconfigFilePath(workspaceRoot, projectRoot, tsconfig) {
2019
+ function getTsconfigFilePath(cwd, root, tsconfig) {
2006
2020
  let tsconfigFilePath;
2007
- if (tsconfig) tsconfigFilePath = tryTsconfigFilePath(workspaceRoot, projectRoot, tsconfig);
2021
+ if (tsconfig) tsconfigFilePath = tryTsconfigFilePath(cwd, root, tsconfig);
2008
2022
  else {
2009
- tsconfigFilePath = tryTsconfigFilePath(workspaceRoot, projectRoot, "tsconfig.app.json");
2023
+ tsconfigFilePath = tryTsconfigFilePath(cwd, root, "tsconfig.app.json");
2010
2024
  if (!tsconfigFilePath) {
2011
- tsconfigFilePath = tryTsconfigFilePath(workspaceRoot, projectRoot, "tsconfig.lib.json");
2012
- if (!tsconfigFilePath) tsconfigFilePath = tryTsconfigFilePath(workspaceRoot, projectRoot, "tsconfig.json");
2025
+ tsconfigFilePath = tryTsconfigFilePath(cwd, root, "tsconfig.lib.json");
2026
+ if (!tsconfigFilePath) tsconfigFilePath = tryTsconfigFilePath(cwd, root, "tsconfig.json");
2013
2027
  }
2014
2028
  }
2015
- if (!tsconfigFilePath) throw new Error(`Cannot find the \`tsconfig.json\` configuration file for the project at ${projectRoot}.`);
2029
+ if (!tsconfigFilePath) throw new Error(`Cannot find the \`tsconfig.json\` configuration file for the project at ${root}.`);
2016
2030
  return tsconfigFilePath;
2017
2031
  }
2018
2032
  /**
2019
2033
  * Get the path to the tsconfig.json file.
2020
2034
  *
2021
- * @param workspaceRoot - The root directory of the workspace.
2022
- * @param projectRoot - The root directory of the project.
2035
+ * @param cwd - The root directory of the workspace.
2036
+ * @param root - The root directory of the project.
2023
2037
  * @param tsconfig - The path to the tsconfig.json file.
2024
2038
  * @returns The absolute path to the tsconfig.json file.
2025
2039
  * @throws If the tsconfig.json file does not exist.
2026
2040
  */
2027
- function tryTsconfigFilePath(workspaceRoot, projectRoot, tsconfig) {
2041
+ function tryTsconfigFilePath(cwd, root, tsconfig) {
2028
2042
  let tsconfigFilePath = tsconfig;
2029
2043
  if (!existsSync(tsconfigFilePath)) {
2030
- tsconfigFilePath = appendPath(tsconfig, projectRoot);
2044
+ tsconfigFilePath = appendPath(tsconfig, root);
2031
2045
  if (!existsSync(tsconfigFilePath)) {
2032
- tsconfigFilePath = appendPath(tsconfig, appendPath(projectRoot, workspaceRoot));
2046
+ tsconfigFilePath = appendPath(tsconfig, appendPath(root, cwd));
2033
2047
  if (!existsSync(tsconfigFilePath)) return;
2034
2048
  }
2035
2049
  }
@@ -2167,17 +2181,17 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2167
2181
  */
2168
2182
  resolver;
2169
2183
  /**
2170
- * The options provided to the Powerlines process
2184
+ * The options provided to the Powerlines process, resolved with default values and merged with any configuration provided by plugins or other sources. This is typically the final configuration used during the build process, but may also include additional options that are relevant to the context and its interactions with the Powerlines engine.
2171
2185
  */
2172
2186
  options;
2173
2187
  /**
2174
- * The input options used to initialize the context, which may be used when cloning the context to ensure the same configuration is applied to the new context
2188
+ * The parsed `package.json` file for the project
2175
2189
  */
2176
- initialOptions = {};
2190
+ packageJson;
2177
2191
  /**
2178
- * The initial configuration provided when initializing the context, which may be used during the setup process to ensure that the configuration is properly merged and applied to the context. This is typically the user configuration provided in the Powerlines configuration file, but may also include additional configuration options provided by plugins or other sources.
2192
+ * The parsed `project.json` file for the project
2179
2193
  */
2180
- initialConfig = {};
2194
+ projectJson = void 0;
2181
2195
  /**
2182
2196
  * The parsed configuration file for the project
2183
2197
  */
@@ -2216,8 +2230,8 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2216
2230
  * @returns A promise that resolves to the cloned context.
2217
2231
  */
2218
2232
  async clone() {
2219
- const clone = new PowerlinesBaseContext();
2220
- await clone.init(this.options, this.initialConfig);
2233
+ const clone = new PowerlinesBaseContext(this.options, this.initialConfig);
2234
+ await clone.init();
2221
2235
  return clone;
2222
2236
  }
2223
2237
  /**
@@ -2300,7 +2314,7 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2300
2314
  * @returns A logger client instance that can be used to generate log messages with consistent formatting and metadata.
2301
2315
  */
2302
2316
  createLogger(options, logFn) {
2303
- return createLogger$1(this.options.name || this.options.root, {
2317
+ return createLogger$1(this.options.name || this.options.root || "powerlines", {
2304
2318
  ...this.configFile.config,
2305
2319
  ...this.options,
2306
2320
  ...options
@@ -2316,6 +2330,24 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2316
2330
  return extendLogger(this.logger, options);
2317
2331
  }
2318
2332
  /**
2333
+ * The input options used to initialize the context, which may be used when cloning the context to ensure the same configuration is applied to the new context
2334
+ */
2335
+ initialOptions = {};
2336
+ /**
2337
+ * The initial configuration provided when initializing the context, which may be used during the setup process to ensure that the configuration is properly merged and applied to the context. This is typically the user configuration provided in the Powerlines configuration file, but may also include additional configuration options provided by plugins or other sources.
2338
+ */
2339
+ initialConfig = {};
2340
+ /**
2341
+ * Initialize the context with the provided configuration options and set up the resolver and user configuration file. This method is called during the construction of the context and can also be called when cloning the context to ensure that the new context has the same configuration and resolver setup as the original context.
2342
+ *
2343
+ * @param options - The configuration options to initialize the context with, which can include properties such as the project root, mode, log level, and other settings that affect the behavior of the context and its plugins.
2344
+ * @param initialConfig - The initial configuration to initialize the context with, which is typically the user configuration provided in the Powerlines configuration file. This can also include additional configuration options provided by plugins or other sources that should be merged with the user configuration during initialization
2345
+ */
2346
+ constructor(options, initialConfig = {}) {
2347
+ this.initialOptions = options;
2348
+ this.initialConfig = initialConfig;
2349
+ }
2350
+ /**
2319
2351
  * Retrieve the workspace configuration for the current project, if it exists. This function will look for a configuration file in the project root and return its contents as a JavaScript object. If no configuration file is found, it will return undefined.
2320
2352
  *
2321
2353
  * @returns A promise that resolves to the workspace configuration object, or undefined if no configuration file is found.
@@ -2327,64 +2359,47 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2327
2359
  } : void 0);
2328
2360
  }
2329
2361
  /**
2330
- * Determine the default mode for the current execution based on the environment and workspace configuration. This function will check the `NODE_ENV` environment variable to determine if the current environment is development, production, or test. If `NODE_ENV` is not set, it will look for a `mode` property in the workspace configuration file. If no mode is specified in the workspace configuration, it will default to "production".
2331
- *
2332
- * @returns A promise that resolves to the default mode for the current execution, which can be "development", "production", or "test".
2333
- */
2334
- async getDefaultMode() {
2335
- const workspaceConfig = await this.getWorkspaceConfig();
2336
- return isProduction ? "production" : isDevelopment ? "development" : isTest ? "test" : workspaceConfig?.mode || "production";
2337
- }
2338
- /**
2339
- * Determine the default log level for the current execution based on the environment and workspace configuration. This function will check the `logLevel` property in the workspace configuration file and resolve it to a `LogLevelResolvedConfig` value. If no log level is specified in the workspace configuration, it will default to "info" for development mode and "warn" for production mode.
2340
- *
2341
- * @returns A promise that resolves to the default log level for the current execution, which can be "fatal", "error", "warn", "info", "debug", or "trace".
2342
- */
2343
- async getDefaultLogLevel() {
2344
- const workspaceConfig = await this.getWorkspaceConfig();
2345
- return resolveLogLevel(workspaceConfig?.logLevel ? workspaceConfig.logLevel === "success" || workspaceConfig.logLevel === "performance" ? "info" : workspaceConfig.logLevel === "all" ? "debug" : workspaceConfig.logLevel === "fatal" ? "error" : workspaceConfig.logLevel : void 0, this.options?.mode || this.initialOptions?.mode || workspaceConfig?.mode || await this.getDefaultMode());
2346
- }
2347
- /**
2348
2362
  * Initialize the context with the provided configuration options
2349
2363
  *
2350
2364
  * @remarks
2351
2365
  * This method will set up the resolver and load the user configuration file based on the provided options. It is called during the construction of the context and can also be called when cloning the context to ensure that the new context has the same configuration and resolver setup.
2352
- *
2353
- * @param options - The configuration options to initialize the context with
2354
- * @param initialConfig - The initial configuration to initialize the context with
2355
2366
  */
2356
- async init(options, initialConfig) {
2357
- this.initialOptions = { ...options };
2358
- this.initialConfig = { ...initialConfig };
2367
+ async init() {
2359
2368
  if (!this.powerlinesPath) {
2360
2369
  const powerlinesPath = await resolvePackage("powerlines");
2361
2370
  if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
2362
2371
  this.powerlinesPath = powerlinesPath;
2363
2372
  }
2364
- const cwd = options.cwd || this.options?.cwd || process.cwd();
2365
- const root = replacePath((options.root || this.options?.root) && (options.root || this.options.root).replace(/^\.\/?/, "") && !isEqual(options.root || this.options.root, cwd) ? options.root || this.options.root : ".", cwd);
2366
- this.options = defu({
2367
- name: options.name || this.initialConfig.name,
2368
- root,
2369
- cwd,
2370
- mode: options.mode || this.initialConfig.mode,
2371
- logLevel: options.logLevel || this.initialConfig.logLevel,
2372
- framework: options.framework || this.initialConfig.framework,
2373
- organization: options.organization || this.initialConfig.organization,
2374
- configFile: options.configFile || this.initialConfig.configFile
2375
- }, this.options ?? {}, {
2373
+ this.options = defu(this.initialOptions, this.initialConfig, {
2374
+ cwd: process.cwd(),
2376
2375
  mode: await this.getDefaultMode(),
2377
- logLevel: await this.getDefaultLogLevel()
2376
+ logLevel: await this.getDefaultLogLevel(),
2377
+ framework: "powerlines"
2378
2378
  });
2379
+ if (!this.options.root) if (this.options.configFile) {
2380
+ const configFile = appendPath(this.options.configFile, this.options.cwd);
2381
+ if (!existsSync$1(configFile)) throw new Error(`The user-provided configuration file at "${this.options.configFile}" does not exist. Please ensure this path is correct and try again.`);
2382
+ if (!isFile$1(configFile)) throw new Error(`The user-provided configuration file at "${this.options.configFile}" is not a file. Please ensure this path is correct and try again.`);
2383
+ this.options.root = relativePath$1(this.options.cwd, findFilePath$1(configFile));
2384
+ } else this.options.root = ".";
2385
+ else this.options.root = replacePath(this.options.root, this.options.cwd);
2379
2386
  this.resolver = createResolver({
2380
- workspaceRoot: cwd,
2381
- root,
2387
+ workspaceRoot: this.options.cwd,
2388
+ root: this.options.root,
2382
2389
  cacheDir: this.envPaths.cache,
2383
2390
  mode: this.options.mode
2384
2391
  });
2392
+ const projectJsonPath = joinPaths$2(appendPath(this.options.root, this.options.cwd), "project.json");
2393
+ if (existsSync$1(projectJsonPath)) this.projectJson = await readJsonFile$1(projectJsonPath);
2394
+ const packageJsonPath = joinPaths$2(appendPath(this.options.root, this.options.cwd), "package.json");
2395
+ if (existsSync$1(packageJsonPath)) {
2396
+ this.packageJson = await readJsonFile$1(packageJsonPath);
2397
+ this.options.organization ??= isSetObject(this.packageJson?.author) ? kebabCase(this.packageJson?.author?.name) : kebabCase(this.packageJson?.author);
2398
+ }
2385
2399
  this.configFile = await loadUserConfigFile(this.options, this.resolver);
2386
- if (!this.options.name) {
2387
- if (this.configFile.config) {
2400
+ if (this.configFile.config) {
2401
+ if (isSetString(this.configFile.configFile)) this.options.configFile ??= replacePath(this.configFile.configFile, this.options.cwd);
2402
+ if (!this.options.name) {
2388
2403
  if (isSetObject(this.configFile.config) && isSetString(this.configFile.config.name)) this.options.name = this.configFile.config.name;
2389
2404
  else if (Array.isArray(this.configFile.config)) {
2390
2405
  for (const config of this.configFile.config) if (isSetObject(config) && isSetString(config.name)) {
@@ -2393,22 +2408,27 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2393
2408
  }
2394
2409
  }
2395
2410
  }
2396
- if (!this.options.name) {
2397
- const packageJsonPath = joinPaths$2(appendPath(this.options.root, this.options.cwd), "package.json");
2398
- if (existsSync$1(packageJsonPath)) {
2399
- const packageJson = await readJsonFile$1(packageJsonPath);
2400
- this.options.name = packageJson.name;
2401
- }
2402
- if (!this.options.name) {
2403
- const projectJsonPath = joinPaths$2(appendPath(this.options.root, this.options.cwd), "project.json");
2404
- if (existsSync$1(projectJsonPath)) {
2405
- const projectJson = await readJsonFile$1(projectJsonPath);
2406
- this.options.name = projectJson.name;
2407
- }
2408
- }
2409
- }
2411
+ if (!this.options.name) this.options.name = this.projectJson?.name || this.packageJson?.name;
2410
2412
  }
2411
2413
  }
2414
+ /**
2415
+ * Determine the default mode for the current execution based on the environment and workspace configuration. This function will check the `NODE_ENV` environment variable to determine if the current environment is development, production, or test. If `NODE_ENV` is not set, it will look for a `mode` property in the workspace configuration file. If no mode is specified in the workspace configuration, it will default to "production".
2416
+ *
2417
+ * @returns A promise that resolves to the default mode for the current execution, which can be "development", "production", or "test".
2418
+ */
2419
+ async getDefaultMode() {
2420
+ const workspaceConfig = await this.getWorkspaceConfig();
2421
+ return isProduction ? "production" : isDevelopment ? "development" : isTest ? "test" : workspaceConfig?.mode || "production";
2422
+ }
2423
+ /**
2424
+ * Determine the default log level for the current execution based on the environment and workspace configuration. This function will check the `logLevel` property in the workspace configuration file and resolve it to a `LogLevelResolvedConfig` value. If no log level is specified in the workspace configuration, it will default to "info" for development mode and "warn" for production mode.
2425
+ *
2426
+ * @returns A promise that resolves to the default log level for the current execution, which can be "fatal", "error", "warn", "info", "debug", or "trace".
2427
+ */
2428
+ async getDefaultLogLevel() {
2429
+ const workspaceConfig = await this.getWorkspaceConfig();
2430
+ return resolveLogLevel(workspaceConfig?.logLevel ? workspaceConfig.logLevel === "success" || workspaceConfig.logLevel === "performance" ? "info" : workspaceConfig.logLevel === "all" ? "debug" : workspaceConfig.logLevel === "fatal" ? "error" : workspaceConfig.logLevel : void 0, this.options?.mode || this.initialOptions?.mode || workspaceConfig?.mode || await this.getDefaultMode());
2431
+ }
2412
2432
  };
2413
2433
 
2414
2434
  //#endregion
@@ -2420,27 +2440,13 @@ setGlobalDispatcher(new Agent({ keepAliveTimeout: 1e4 }).compose(interceptors.re
2420
2440
  timeoutFactor: 2,
2421
2441
  retryAfter: true
2422
2442
  })));
2423
- const SKIP_CLONING_PROPS = [
2424
- "dependencies",
2425
- "devDependencies",
2426
- "persistedMeta",
2427
- "packageJson",
2428
- "projectJson",
2429
- "tsconfig",
2430
- "resolver",
2431
- "fs",
2432
- "$$internal"
2443
+ const UNRESOLVED_CONFIG_NAMES = [
2444
+ "initialConfig",
2445
+ "userConfig",
2446
+ "inlineConfig",
2447
+ "pluginConfig"
2433
2448
  ];
2434
2449
  var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
2435
- /**
2436
- * Internal references storage
2437
- *
2438
- * @danger
2439
- * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
2440
- *
2441
- * @internal
2442
- */
2443
- #internal = {};
2444
2450
  #checksum = null;
2445
2451
  #buildId = uuid();
2446
2452
  #releaseId = uuid();
@@ -2448,24 +2454,22 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
2448
2454
  #tsconfig;
2449
2455
  #parserCache;
2450
2456
  #requestCache;
2457
+ #configProxy;
2451
2458
  /**
2452
- * Create a new Storm context from the workspace root and user config.
2459
+ * Create a new context from the workspace root and user config.
2453
2460
  *
2454
2461
  * @param options - The options for resolving the context.
2455
2462
  * @returns A promise that resolves to the new context.
2456
2463
  */
2457
- static async init(options, initialConfig) {
2458
- const context = new PowerlinesContext(options);
2459
- await context.init(options, initialConfig);
2460
- const powerlinesPath = await resolvePackage("powerlines");
2461
- if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
2462
- context.powerlinesPath = powerlinesPath;
2464
+ static async fromInitialConfig(options, initialConfig) {
2465
+ const context = new PowerlinesContext(options, initialConfig);
2466
+ await context.init();
2463
2467
  return context;
2464
2468
  }
2465
2469
  /**
2466
- * The options provided to the Powerlines process
2470
+ * The options provided to the Powerlines process, resolved with default values and merged with any configuration provided by plugins or other sources. This is typically the final configuration used during the build process, but may also include additional options that are relevant to the context and its interactions with the Powerlines engine.
2467
2471
  */
2468
- options;
2472
+ options = {};
2469
2473
  /**
2470
2474
  * An object containing the dependencies that should be installed for the project
2471
2475
  */
@@ -2479,39 +2483,33 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
2479
2483
  */
2480
2484
  persistedMeta = void 0;
2481
2485
  /**
2482
- * The parsed `package.json` file for the project
2486
+ * The resolved tsconfig file paths for the project
2483
2487
  */
2484
- packageJson;
2488
+ resolvePatterns = [];
2485
2489
  /**
2486
- * The parsed `project.json` file for the project
2490
+ * The input options used to initialize the context, which may be used when cloning the context to ensure the same configuration is applied to the new context
2487
2491
  */
2488
- projectJson = void 0;
2492
+ initialOptions = {};
2489
2493
  /**
2490
- * The resolved tsconfig file paths for the project
2494
+ * The resolved configuration for this context
2491
2495
  */
2492
- resolvePatterns = [];
2496
+ resolvedConfig = {};
2493
2497
  /**
2494
- * Internal context fields and methods
2495
- *
2496
- * @danger
2497
- * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
2498
- *
2499
- * @internal
2498
+ * The configuration options that were overridden by plugins during the build process, which may include additional properties or modifications made during the configuration loading process.
2500
2499
  */
2501
- get $$internal() {
2502
- return this.#internal;
2503
- }
2500
+ overriddenConfig = {};
2504
2501
  /**
2505
- * Internal context fields and methods
2506
- *
2507
- * @danger
2508
- * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
2509
- *
2510
- * @internal
2502
+ * The configuration options provided inline during execution, such as CLI flags or other parameters that may be relevant to the command being executed. These options can be used to override or supplement the configuration options defined in a configuration file on disk, and are typically provided as part of the execution context when running a Powerlines command.
2511
2503
  */
2512
- set $$internal(value) {
2513
- this.#internal = value;
2514
- }
2504
+ inlineConfig = {};
2505
+ /**
2506
+ * The configuration options read from a configuration file on disk, which may be used to resolve the final configuration for the context. This typically includes the user configuration options defined in the `powerlines.config.ts` file, as well as any inline configuration options provided during execution.
2507
+ */
2508
+ userConfig = {};
2509
+ /**
2510
+ * The configuration options provided by plugins added by the user (and other plugins)
2511
+ */
2512
+ pluginConfig = {};
2515
2513
  /**
2516
2514
  * The resolved entry type definitions for the project
2517
2515
  */
@@ -2566,13 +2564,14 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
2566
2564
  * The resolved configuration options
2567
2565
  */
2568
2566
  get config() {
2569
- return this.resolvedConfig;
2567
+ if (!this.#configProxy) this.#configProxy = this.createConfigProxy();
2568
+ return this.#configProxy;
2570
2569
  }
2571
2570
  /**
2572
2571
  * Get the path to the artifacts directory for the project
2573
2572
  */
2574
2573
  get artifactsPath() {
2575
- return joinPaths$1(this.config.cwd, this.config.root, this.config.output.artifactsPath);
2574
+ return joinPaths$1(this.config.cwd, this.config.root, this.config.output?.artifactsPath || `.${this.config.framework || "powerlines"}`);
2576
2575
  }
2577
2576
  /**
2578
2577
  * Get the path to the builtin modules used by the project
@@ -2629,7 +2628,7 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
2629
2628
  * Additional arguments provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed.
2630
2629
  */
2631
2630
  get additionalArgs() {
2632
- return Object.entries(this.config.inlineConfig?.additionalArgs ?? {}).reduce((ret, [key, value]) => {
2631
+ return Object.entries(this.config.inlineConfig.additionalArgs ?? {}).reduce((ret, [key, value]) => {
2633
2632
  const formattedKey = key.replace(/^--?/, "");
2634
2633
  if (ret[formattedKey]) if (Array.isArray(ret[formattedKey])) if (Array.isArray(value)) ret[formattedKey] = [...toArray(ret[formattedKey]), ...value];
2635
2634
  else ret[formattedKey] = [...toArray(ret[formattedKey]), value];
@@ -2658,29 +2657,6 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
2658
2657
  }, {}) : this.config.resolve.alias : {});
2659
2658
  }
2660
2659
  /**
2661
- * Create a new logger instance
2662
- *
2663
- * @param options - The configuration options to use for the logger instance, which can be used to customize the appearance and behavior of the log messages generated by the logger. This is typically the name of the plugin or module that is creating the logger instance.
2664
- * @param logFn - The custom logging function to use for logging messages, which can be used to override the default logging behavior of the original logger.
2665
- * @returns A logger client instance that can be used to generate log messages with consistent formatting and metadata.
2666
- */
2667
- createLogger(options, logFn) {
2668
- let logger;
2669
- if (toBool(process.env.POWERLINES_WORKER_THREAD_EXECUTION)) logger = createLogger(this.config.name, {
2670
- ...this.options,
2671
- ...this.config,
2672
- ...options
2673
- }, (meta, message) => sendWriteLogMessage(this, meta, message));
2674
- else logger = createLogger(this.config.name, {
2675
- ...this.options,
2676
- ...this.config,
2677
- ...options
2678
- });
2679
- if (this.config.customLogger) logger = withCustomLogger(logger, this.config.customLogger);
2680
- if (logFn) logger = withLogFn(logger, logFn);
2681
- return logger;
2682
- }
2683
- /**
2684
2660
  * The log level for the context, which determines the minimum level of log messages that will be emitted by the logger. This is resolved based on the configuration options provided by the user, and can be set to different levels for development, production, and test environments. The log level can also be overridden by plugins or other parts of the build process to provide more granular control over logging output.
2685
2661
  */
2686
2662
  get logLevel() {
@@ -2742,25 +2718,38 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
2742
2718
  }).filter(Boolean);
2743
2719
  }
2744
2720
  /**
2745
- * Creates a new StormContext instance.
2721
+ * Creates a new Context instance.
2746
2722
  *
2747
2723
  * @param options - The options to use for creating the context, including the resolved configuration and workspace settings.
2724
+ * @param initialConfig - The initial configuration provided by the user, which can be used to resolve the final configuration for the context. This typically includes the user configuration options defined in the `powerlines.config.ts` file, as well as any inline configuration options provided during execution.
2748
2725
  */
2749
- constructor(options) {
2750
- super();
2751
- this.options = options;
2726
+ constructor(options, initialConfig) {
2727
+ super(options, initialConfig);
2728
+ this.initialOptions = options;
2729
+ this.initialConfig = initialConfig;
2752
2730
  }
2753
2731
  /**
2754
- * Creates a clone of the current context with the same configuration and workspace settings. This can be useful for running multiple builds in parallel or for creating isolated contexts for different parts of the build process.
2755
- *
2756
- * @remarks
2757
- * The cloned context will have the same configuration and workspace settings as the original context, but will have a different build ID, release ID, and timestamp. The virtual file system and caches will also be separate between the original and cloned contexts.
2732
+ * Create a new logger instance
2758
2733
  *
2759
- * @returns A promise that resolves to the cloned context.
2734
+ * @param options - The configuration options to use for the logger instance, which can be used to customize the appearance and behavior of the log messages generated by the logger. This is typically the name of the plugin or module that is creating the logger instance.
2735
+ * @param logFn - The custom logging function to use for logging messages, which can be used to override the default logging behavior of the original logger.
2736
+ * @returns A logger client instance that can be used to generate log messages with consistent formatting and metadata.
2760
2737
  */
2761
- async clone() {
2762
- const clone = await PowerlinesContext.init(this.options, this.initialConfig);
2763
- return this.copyTo(clone);
2738
+ createLogger(options, logFn) {
2739
+ let logger;
2740
+ if (isSetString(process.env.POWERLINES_EXECUTION_THREAD_TYPE)) logger = createLogger(this.config.name, {
2741
+ ...this.options,
2742
+ ...this.config,
2743
+ ...options
2744
+ }, (meta, message) => sendWriteLogMessage(this, meta, message));
2745
+ else logger = createLogger(this.config.name, {
2746
+ ...this.options,
2747
+ ...this.config,
2748
+ ...options
2749
+ });
2750
+ if (this.config.customLogger) logger = withCustomLogger(logger, this.config.customLogger);
2751
+ if (logFn) logger = withLogFn(logger, logFn);
2752
+ return logger;
2764
2753
  }
2765
2754
  /**
2766
2755
  * A function to perform HTTP fetch requests
@@ -3090,206 +3079,233 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
3090
3079
  /**
3091
3080
  * Generates a checksum representing the current context state
3092
3081
  *
3093
- * @param root - The root directory of the project to generate the checksum for
3082
+ * @param path - The root directory of the project to generate the checksum for
3094
3083
  * @returns A promise that resolves to a string representing the checksum
3095
3084
  */
3096
- async generateChecksum(root = this.config.root) {
3097
- this.#checksum = await hashDirectory(root, { ignore: [
3098
- "node_modules",
3099
- ".git",
3100
- ".nx",
3101
- ".cache",
3102
- "tmp",
3103
- "dist"
3104
- ] });
3105
- return this.#checksum;
3085
+ async generateChecksum(path) {
3086
+ return hashDirectory(path || appendPath(this.options.root, this.options.cwd));
3106
3087
  }
3107
3088
  /**
3108
- * Initialize the context with the provided configuration options
3089
+ * A setter function to populate the inline config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the inline configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any inline configuration provided during execution.
3090
+ *
3091
+ * @param config - The inline configuration values to set.
3092
+ * @returns A promise that resolves when the inline configuration values have been set.
3109
3093
  */
3110
- async setup() {
3111
- this.resolvedConfig = mergeConfig({
3112
- root: this.options.root,
3113
- cwd: this.options.cwd,
3114
- inlineConfig: this.config.inlineConfig ?? {},
3115
- userConfig: this.config.userConfig ?? {},
3116
- initialConfig: this.config.initialConfig ?? {},
3117
- pluginConfig: this.config.pluginConfig ?? {}
3118
- }, getConfigProps(this.config.inlineConfig), getConfigProps(this.config.userConfig), getConfigProps(this.config.initialConfig), getConfigProps(this.config.pluginConfig), this.options, {
3119
- name: this.projectJson?.name || this.packageJson?.name,
3094
+ async setInlineConfig(config) {
3095
+ this.logger.debug({
3096
+ meta: { category: "config" },
3097
+ message: `Updating inline configuration object: \n${this.logConfig(config)}`
3098
+ });
3099
+ this.inlineConfig = config;
3100
+ await this.resolveConfig();
3101
+ }
3102
+ /**
3103
+ * A setter function to populate the plugin config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the plugin configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any plugin configuration provided during execution.
3104
+ *
3105
+ * @param config - The plugin configuration values to set.
3106
+ * @returns A promise that resolves when the plugin configuration values have been set.
3107
+ */
3108
+ async setPluginConfig(config) {
3109
+ this.logger.debug({
3110
+ meta: { category: "config" },
3111
+ message: `Updating plugin configuration object: \n${this.logConfig(config)}`
3112
+ });
3113
+ this.pluginConfig = config;
3114
+ await this.resolveConfig();
3115
+ }
3116
+ /**
3117
+ * A function to merge the various configuration objects (initial, user, inline, and plugin) into a single resolved configuration object that can be used throughout the Powerlines process. This function takes into account the different sources of configuration and their respective priorities, ensuring that the final configuration reflects the intended settings for the project. The merged configuration is then returned as a new object that can be accessed through the `config` property of the context.
3118
+ *
3119
+ * @returns The merged configuration object that combines the initial, user, inline, and plugin configurations.
3120
+ */
3121
+ mergeConfig() {
3122
+ return mergeConfig({
3123
+ mode: this.initialOptions.mode,
3124
+ framework: this.initialOptions.framework,
3125
+ initialOptions: this.initialOptions,
3126
+ options: this.options,
3127
+ inlineConfig: this.inlineConfig,
3128
+ userConfig: this.userConfig,
3129
+ initialConfig: this.initialConfig,
3130
+ pluginConfig: this.pluginConfig
3131
+ }, getConfigProps(this.overriddenConfig), omit(this.options, ["mode", "framework"]), getConfigProps(this.inlineConfig), getConfigProps(this.userConfig), getConfigProps(this.initialConfig), getConfigProps(this.pluginConfig), {
3120
3132
  version: this.packageJson?.version,
3121
3133
  description: this.packageJson?.description
3122
3134
  }, {
3123
3135
  environments: {},
3124
3136
  resolve: {}
3125
3137
  });
3126
- await this.innerSetup();
3127
3138
  }
3128
3139
  /**
3129
- * The resolved configuration for this context
3130
- */
3131
- resolvedConfig = {};
3132
- /**
3133
- * Creates a clone of the current context with the same configuration and workspace settings. This can be useful for running multiple builds in parallel or for creating isolated contexts for different parts of the build process.
3134
- *
3135
- * @remarks
3136
- * The cloned context will have the same configuration and workspace settings as the original context, but will have a different build ID, release ID, and timestamp. The virtual file system and caches will also be separate between the original and cloned contexts.
3140
+ * A setter function to populate the user config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the user configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any inline configuration provided during execution.
3137
3141
  *
3138
- * @returns The cloned context.
3142
+ * @param config - The user configuration values to set.
3143
+ * @returns A promise that resolves when the user configuration values have been set.
3139
3144
  */
3140
- copyTo(context) {
3141
- for (const [key, value] of Object.entries(this)) if (!SKIP_CLONING_PROPS.includes(key)) if (isObject(value) || Array.isArray(value)) context[key] = deepClone(value);
3142
- else context[key] = value;
3143
- context.initialConfig = deepClone(this.initialConfig);
3144
- context.initialOptions = deepClone(this.initialOptions);
3145
- context.options = deepClone(this.options);
3146
- context.dependencies = deepClone(this.dependencies);
3147
- context.devDependencies = deepClone(this.devDependencies);
3148
- context.persistedMeta = this.persistedMeta ? deepClone(this.persistedMeta) : void 0;
3149
- context.packageJson = deepClone(this.packageJson);
3150
- context.projectJson = this.projectJson ? deepClone(this.projectJson) : void 0;
3151
- context.tsconfig ??= deepClone(this.tsconfig);
3152
- context.resolver ??= this.resolver;
3153
- context.fs ??= this.#fs;
3154
- context.$$internal = this.$$internal;
3155
- return context;
3145
+ async setUserConfig(config) {
3146
+ this.logger.debug({
3147
+ meta: { category: "config" },
3148
+ message: `Updating user configuration object: \n${this.logConfig(config)}`
3149
+ });
3150
+ this.userConfig = config;
3151
+ await this.resolveConfig();
3156
3152
  }
3157
3153
  /**
3158
3154
  * Initialize the context with the provided configuration options
3159
- *
3160
- * @remarks
3161
- * This method will set up the resolver and load the user configuration file based on the provided options. It is called during the construction of the context and can also be called when cloning the context to ensure that the new context has the same configuration and resolver setup.
3162
- *
3163
- * @param options - The configuration options to initialize the context with
3164
- */
3165
- async init(options, initialConfig) {
3166
- await super.init(options, initialConfig ?? {});
3167
- this.options.executionId = options.executionId ?? this.options.executionId;
3168
- this.options.executionIndex = options.executionIndex ?? this.options.executionIndex ?? 0;
3169
- const projectJsonPath = joinPaths$1(this.options.cwd, this.options.root, "project.json");
3170
- if (existsSync(projectJsonPath)) this.projectJson = await readJsonFile(projectJsonPath);
3171
- const packageJsonPath = joinPaths$1(this.options.cwd, this.options.root, "package.json");
3172
- if (existsSync(packageJsonPath)) {
3173
- this.packageJson = await readJsonFile(packageJsonPath);
3174
- this.options.organization ??= isSetObject(this.packageJson?.author) ? kebabCase(this.packageJson?.author?.name) : kebabCase(this.packageJson?.author);
3175
- }
3176
- this.#checksum = await this.generateChecksum(joinPaths$1(this.options.cwd, this.options.root));
3177
- const userConfig = this.configFile.config ? Array.isArray(this.configFile.config) && this.configFile.config.length > this.options.executionIndex ? this.configFile.config[this.options.executionIndex] : this.configFile.config : {};
3178
- this.resolvedConfig = {
3179
- cwd: this.options.cwd,
3180
- root: this.options.root,
3181
- ...this.initialOptions,
3182
- ...initialConfig,
3183
- ...userConfig,
3184
- inlineConfig: {},
3185
- pluginConfig: {},
3186
- initialConfig,
3187
- userConfig
3188
- };
3155
+ */
3156
+ async init() {
3157
+ await super.init();
3158
+ this.options.executionId = this.initialOptions.executionId || uuid();
3159
+ this.options.executionIndex = this.initialOptions.executionIndex ?? 0;
3160
+ this.#checksum = await this.generateChecksum();
3161
+ const result = this.configFile.config && toArray(this.configFile.config).length > this.options.executionIndex ? toArray(this.configFile.config)[this.options.executionIndex] : this.configFile.config;
3162
+ if (!result) this.logger.warn(`No configuration found in ${this.options.configFile} for execution index ${this.options.executionIndex}.`);
3163
+ else await this.setUserConfig(isFunction(result) ? await Promise.resolve(result(this.options)) : result);
3189
3164
  }
3190
3165
  /**
3191
3166
  * Initialize the context with the provided configuration options
3192
3167
  */
3193
- async innerSetup() {
3194
- const logger = this.extendLogger({ category: "config" });
3195
- this.config.plugins = (this.config.initialConfig.plugins ?? []).concat(this.config.userConfig.plugins ?? [], this.config.inlineConfig.plugins ?? []);
3196
- this.config.output = defu(this.config.output ?? {}, {
3168
+ async resolveConfig() {
3169
+ const mergedConfig = this.mergeConfig();
3170
+ this.logger.trace({
3171
+ meta: { category: "config" },
3172
+ message: `Pre-setup Powerlines configuration object: \n --- Pre-Resolved Config --- \n${this.logConfig(mergedConfig)} \n --- Initial Config --- \n${this.logConfig(this.initialConfig)} \n --- User Config --- \n${this.logConfig(this.userConfig)} \n --- Inline Config --- \n${this.logConfig(this.inlineConfig)} \n --- Plugin Config --- \n${this.logConfig(this.pluginConfig)}`
3173
+ });
3174
+ mergedConfig.output = defu(mergedConfig.output ?? {}, {
3197
3175
  copy: { assets: [
3198
3176
  { glob: "LICENSE" },
3199
3177
  {
3200
- input: this.config.root,
3178
+ input: mergedConfig.root,
3201
3179
  glob: "*.md"
3202
3180
  },
3203
3181
  {
3204
- input: this.config.root,
3182
+ input: mergedConfig.root,
3205
3183
  glob: "package.json"
3206
3184
  }
3207
3185
  ] },
3208
3186
  dts: true
3209
3187
  });
3210
- logger.trace(`Pre-setup Powerlines configuration object: \n${formatLogMessage({
3211
- ...omit(this.config, [
3212
- "inlineConfig",
3213
- "userConfig",
3214
- "initialConfig",
3215
- "pluginConfig",
3216
- "plugins"
3217
- ]),
3218
- inlineConfig: isSetObject(this.config.inlineConfig) ? omit(this.config.inlineConfig, ["plugins"]) : void 0,
3219
- userConfig: isSetObject(this.config.userConfig) ? omit(this.config.userConfig, ["plugins"]) : void 0,
3220
- initialConfig: isSetObject(this.config.initialConfig) ? omit(this.config.initialConfig, ["plugins"]) : void 0,
3221
- pluginConfig: isSetObject(this.config.pluginConfig) ? omit(this.config.pluginConfig, ["plugins"]) : void 0
3222
- })}`);
3223
- if (!this.initialOptions.mode && !this.config.userConfig?.mode && !this.config.inlineConfig?.mode && !this.config.initialConfig?.mode && !this.config.pluginConfig?.mode) {
3224
- this.options.mode = "production";
3225
- this.config.mode = "production";
3226
- }
3227
- if (!this.initialOptions.framework && !this.config.userConfig?.framework && !this.config.inlineConfig?.framework && !this.config.initialConfig?.framework && !this.config.pluginConfig?.framework) {
3228
- this.options.framework = "powerlines";
3229
- this.config.framework = "powerlines";
3230
- }
3231
- if (!this.config.userConfig?.projectType && !this.config.inlineConfig?.projectType && !this.config.initialConfig?.projectType && !this.config.pluginConfig?.projectType) this.config.projectType = "application";
3232
- if (!this.config.userConfig?.platform && !this.config.inlineConfig?.platform && !this.config.initialConfig?.platform && !this.config.pluginConfig?.platform) this.config.platform = "neutral";
3233
- this.config.compatibilityDate = resolveCompatibilityDates(this.config.inlineConfig.compatibilityDate ?? this.config.userConfig.compatibilityDate ?? this.config.initialConfig.compatibilityDate ?? this.config.pluginConfig.compatibilityDate, "latest");
3234
- this.config.input = getUniqueInputs(this.config.input);
3235
- if (this.config.name?.startsWith("@") && this.config.name.split("/").filter(Boolean).length > 1) this.config.name = this.config.name.split("/").filter(Boolean)[1];
3236
- this.config.title ??= titleCase(this.config.name);
3237
- if (this.config.resolve.external) this.config.resolve.external = getUnique(this.config.resolve.external);
3238
- if (this.config.resolve.noExternal) this.config.resolve.noExternal = getUnique(this.config.resolve.noExternal);
3239
- this.config.plugins = (this.config.plugins ?? []).flatMap((plugin) => toArray(plugin)).filter(Boolean).reduce((ret, plugin) => {
3188
+ if (!mergedConfig.mode) mergedConfig.mode = "production";
3189
+ if (!mergedConfig.framework) mergedConfig.framework = "powerlines";
3190
+ if (!mergedConfig.projectType) mergedConfig.projectType = "application";
3191
+ if (!mergedConfig.platform) mergedConfig.platform = "neutral";
3192
+ mergedConfig.compatibilityDate = resolveCompatibilityDates(mergedConfig.compatibilityDate, "latest");
3193
+ this.resolvedConfig = mergedConfig;
3194
+ this.#configProxy = this.createConfigProxy();
3195
+ mergedConfig.input = getUniqueInputs(mergedConfig.input);
3196
+ if (mergedConfig.name?.startsWith("@") && mergedConfig.name.split("/").filter(Boolean).length > 1) mergedConfig.name = mergedConfig.name.split("/").filter(Boolean)[1];
3197
+ mergedConfig.title ??= titleCase(mergedConfig.name);
3198
+ if (mergedConfig.resolve.external) mergedConfig.resolve.external = getUnique(mergedConfig.resolve.external);
3199
+ if (mergedConfig.resolve.noExternal) mergedConfig.resolve.noExternal = getUnique(mergedConfig.resolve.noExternal);
3200
+ mergedConfig.plugins = (mergedConfig.plugins ?? []).flatMap((plugin) => toArray(plugin)).filter(Boolean).reduce((ret, plugin) => {
3240
3201
  if (isPlugin(plugin) && isDuplicate(plugin, ret.filter((p) => isPlugin(p)))) return ret;
3241
3202
  ret.push(plugin);
3242
3203
  return ret;
3243
3204
  }, []);
3244
- if (!this.config.userConfig?.logLevel && !this.config.initialConfig?.logLevel && !this.config.pluginConfig?.logLevel && !this.config.inlineConfig?.logLevel) if (this.config.mode === "development") this.config.logLevel = DEFAULT_DEVELOPMENT_LOG_LEVEL;
3245
- else if (this.config.mode === "test") this.config.logLevel = DEFAULT_TEST_LOG_LEVEL;
3246
- else this.config.logLevel = DEFAULT_PRODUCTION_LOG_LEVEL;
3247
- if (!this.config.userConfig?.tsconfig && !this.config.initialConfig?.tsconfig && !this.config.pluginConfig?.tsconfig && !this.config.inlineConfig?.tsconfig) this.config.tsconfig = getTsconfigFilePath(this.config.cwd, this.config.root);
3248
- else if (this.config.tsconfig) this.config.tsconfig = replacePath(replacePathTokens(this, this.config.tsconfig), this.config.cwd);
3249
- this.config.output.format = getUnique(toArray(this.config.output?.format ?? (this.config.projectType === "library" ? ["cjs", "esm"] : ["esm"])));
3250
- if (this.config.output.path) this.config.output.path = appendPath(replacePathTokens(this, this.config.output.path), this.config.cwd);
3251
- else this.config.output.path = appendPath(joinPaths$1(this.config.root, "dist"), this.config.cwd);
3252
- if (this.config.output.copy !== false) {
3253
- this.config.output.copy ??= {};
3254
- if (!this.config.root.replace(/^\.\/?/, "")) this.config.output.copy.path = this.config.output.copy.path ? appendPath(replacePathTokens(this, this.config.output.copy.path), this.config.cwd) : this.config.output.path;
3255
- else this.config.output.copy.path = appendPath(replacePathTokens(this, this.config.output.copy.path || joinPaths$1("dist", this.config.root)), this.config.cwd);
3256
- }
3257
- if (this.config.output.types !== false) this.config.output.types = appendPath(replacePathTokens(this, this.config.userConfig?.output?.types || this.config.inlineConfig?.output?.types || this.config.initialConfig?.output?.types || this.config.pluginConfig?.output?.types || joinPaths$1(this.config.root, `${this.config.framework ?? "powerlines"}.d.ts`)), this.config.cwd);
3258
- if (this.config.output.copy && this.config.output.copy.path && this.config.output.copy.assets && Array.isArray(this.config.output.copy.assets)) this.config.output.copy.assets = getUniqueBy(this.config.output.copy.assets.map((asset) => {
3205
+ if (!mergedConfig.logLevel) if (mergedConfig.mode === "development") mergedConfig.logLevel = DEFAULT_DEVELOPMENT_LOG_LEVEL;
3206
+ else if (mergedConfig.mode === "test") mergedConfig.logLevel = DEFAULT_TEST_LOG_LEVEL;
3207
+ else mergedConfig.logLevel = DEFAULT_PRODUCTION_LOG_LEVEL;
3208
+ if (mergedConfig.tsconfig) mergedConfig.tsconfig = replacePath(replacePathTokens(this, mergedConfig.tsconfig), mergedConfig.cwd);
3209
+ else mergedConfig.tsconfig = getTsconfigFilePath(mergedConfig.cwd, mergedConfig.root);
3210
+ mergedConfig.output.format = getUnique(toArray(mergedConfig.output?.format ?? (mergedConfig.projectType === "library" ? ["cjs", "esm"] : ["esm"])));
3211
+ if (mergedConfig.output.path) mergedConfig.output.path = appendPath(replacePathTokens(this, mergedConfig.output.path), mergedConfig.cwd);
3212
+ else mergedConfig.output.path = appendPath(joinPaths$1(mergedConfig.root, "dist"), mergedConfig.cwd);
3213
+ mergedConfig.output.copy ??= {};
3214
+ if (mergedConfig.output.copy !== false) if (!mergedConfig.root.replace(/^\.\/?/, "")) mergedConfig.output.copy.path = mergedConfig.output.copy.path ? appendPath(replacePathTokens(this, mergedConfig.output.copy.path), mergedConfig.cwd) : mergedConfig.output.path;
3215
+ else mergedConfig.output.copy.path = appendPath(replacePathTokens(this, mergedConfig.output.copy.path || joinPaths$1("dist", mergedConfig.root)), mergedConfig.cwd);
3216
+ if (mergedConfig.output.types !== false) mergedConfig.output.types = appendPath(replacePathTokens(this, mergedConfig.output.types || joinPaths$1(mergedConfig.root, `${mergedConfig.framework ?? "powerlines"}.d.ts`)), mergedConfig.cwd);
3217
+ if (mergedConfig.output.copy && mergedConfig.output.copy.path && mergedConfig.output.copy.assets && Array.isArray(mergedConfig.output.copy.assets)) mergedConfig.output.copy.assets = getUniqueBy(mergedConfig.output.copy.assets.map((asset) => {
3259
3218
  return {
3260
3219
  glob: isSetObject(asset) ? asset.glob : asset,
3261
- input: isString(asset) || !asset.input || asset.input === "." || asset.input === "/" || asset.input === "./" ? this.options.cwd : isParentPath(asset.input, this.config.cwd) || isEqual(asset.input, this.config.cwd) ? asset.input : appendPath(asset.input, this.config.cwd),
3262
- output: isSetObject(asset) && asset.output ? isParentPath(asset.output, this.config.cwd) ? asset.output : appendPath(joinPaths$1(this.config.output.copy.path, replacePath(replacePath(asset.output, replacePath(this.config.output.copy.path, this.config.cwd)), this.config.output.copy.path)), this.config.cwd) : appendPath(this.config.output.copy.path, this.config.cwd),
3220
+ input: isString(asset) || !asset.input || asset.input === "." || asset.input === "/" || asset.input === "./" ? mergedConfig.cwd : isParentPath(asset.input, mergedConfig.cwd) || isEqual(asset.input, mergedConfig.cwd) ? asset.input : appendPath(asset.input, mergedConfig.cwd),
3221
+ output: isSetObject(asset) && asset.output ? isParentPath(asset.output, mergedConfig.cwd) ? asset.output : appendPath(joinPaths$1(mergedConfig.output.copy.path, replacePath(replacePath(asset.output, replacePath(mergedConfig.output.copy.path, mergedConfig.cwd)), mergedConfig.output.copy.path)), mergedConfig.cwd) : appendPath(mergedConfig.output.copy.path, mergedConfig.cwd),
3263
3222
  ignore: isSetObject(asset) && asset.ignore ? toArray(asset.ignore) : void 0
3264
3223
  };
3265
3224
  }), (a) => `${a.input}-${a.glob}-${a.output}`);
3266
- if (!this.config.userConfig?.output?.sourceMap && !this.config.initialConfig?.output?.sourceMap && !this.config.inlineConfig?.output?.sourceMap && !this.config.pluginConfig?.output?.sourceMap) if (this.config.mode === "development") this.config.output.sourceMap = true;
3267
- else this.config.output.sourceMap = false;
3268
- if (!this.config.userConfig?.output?.minify && !this.config.initialConfig?.output?.minify && !this.config.inlineConfig?.output?.minify && !this.config.pluginConfig?.output?.minify) if (this.config.mode === "production") this.config.output.minify = true;
3269
- else this.config.output.minify = false;
3270
- if (!this.config.userConfig?.output?.artifactsPath && !this.config.initialConfig?.output?.artifactsPath && !this.config.inlineConfig?.output?.artifactsPath && !this.config.pluginConfig?.output?.artifactsPath) this.config.output.artifactsPath = `.${this.config.framework || "powerlines"}`;
3271
- if (this.config.output.copy && this.config.output.copy.assets) this.config.output.copy.assets = this.config.output.copy.assets.map((asset) => ({
3225
+ if (!mergedConfig.output?.sourceMap) if (mergedConfig.mode === "development") mergedConfig.output.sourceMap = true;
3226
+ else mergedConfig.output.sourceMap = false;
3227
+ if (!mergedConfig.output.minify) if (mergedConfig.mode === "production") mergedConfig.output.minify = true;
3228
+ else mergedConfig.output.minify = false;
3229
+ if (!mergedConfig.output.artifactsPath) mergedConfig.output.artifactsPath = `.${mergedConfig.framework || "powerlines"}`;
3230
+ if (mergedConfig.output.copy && mergedConfig.output.copy.assets) mergedConfig.output.copy.assets = mergedConfig.output.copy.assets.map((asset) => ({
3272
3231
  ...asset,
3273
3232
  glob: replacePathTokens(this, asset.glob),
3274
3233
  ignore: asset.ignore ? asset.ignore.map((ignore) => replacePathTokens(this, ignore)) : void 0,
3275
3234
  input: replacePathTokens(this, asset.input),
3276
3235
  output: replacePathTokens(this, asset.output)
3277
3236
  }));
3278
- if (isSetString(this.config.output?.storage) && this.config.output.storage === "virtual" || isSetObject(this.config.output?.storage) && Object.values(this.config.output.storage).every((adapter) => adapter.preset === "virtual")) this.config.output.overwrite = true;
3237
+ if (isSetString(mergedConfig.output?.storage) && mergedConfig.output.storage === "virtual" || isSetObject(mergedConfig.output?.storage) && Object.values(mergedConfig.output.storage).every((adapter) => adapter.preset === "virtual")) mergedConfig.output.overwrite = true;
3238
+ this.resolvedConfig = mergedConfig;
3239
+ this.#configProxy = this.createConfigProxy();
3240
+ this.logger.debug({
3241
+ meta: { category: "config" },
3242
+ message: `Resolved Powerlines configuration object: \n --- Resolved Config --- \n${this.logConfig(this.resolvedConfig)} \n --- Initial Config --- \n${this.logConfig(this.initialConfig)} \n --- User Config --- \n${this.logConfig(this.userConfig)} \n --- Inline Config --- \n${this.logConfig(this.inlineConfig)} \n --- Plugin Config --- \n${this.logConfig(this.pluginConfig)}`
3243
+ });
3279
3244
  this.#fs ??= await VirtualFileSystem.create(this);
3280
- if (isSetObject(this.config.inlineConfig) && isSetObject(this.config.userConfig) && isSetObject(this.config.initialConfig) && isSetObject(this.config.pluginConfig)) logger.debug(`Resolved Powerlines configuration object: \n${formatLogMessage({
3281
- ...omit(this.config, [
3282
- "inlineConfig",
3283
- "userConfig",
3284
- "initialConfig",
3285
- "pluginConfig",
3286
- "plugins"
3287
- ]),
3288
- inlineConfig: isSetObject(this.config.inlineConfig) ? omit(this.config.inlineConfig, ["plugins"]) : void 0,
3289
- userConfig: isSetObject(this.config.userConfig) ? omit(this.config.userConfig, ["plugins"]) : void 0,
3290
- initialConfig: isSetObject(this.config.initialConfig) ? omit(this.config.initialConfig, ["plugins"]) : void 0,
3291
- pluginConfig: isSetObject(this.config.pluginConfig) ? omit(this.config.pluginConfig, ["plugins"]) : void 0
3292
- })}`);
3245
+ }
3246
+ logConfig(config) {
3247
+ return formatLogMessage({
3248
+ ...omit(config, ["plugins"]),
3249
+ plugins: config.plugins ? config.plugins.flatMap((plugin) => toArray(plugin)).map((plugin) => String(isSetString(plugin) ? plugin : isPromise(plugin) ? "<promise>" : isFunction(plugin) ? plugin.name || "<anonymous function>" : Array.isArray(plugin) ? plugin[0] || "<anonymous function plugin>" : "<unknown plugin>")) : void 0
3250
+ });
3251
+ }
3252
+ createConfigProxy() {
3253
+ return new Proxy(this.resolvedConfig, {
3254
+ /**
3255
+ * A trap for the `delete` operator.
3256
+ * @param target - The original object which is being proxied.
3257
+ * @param key - The name or `Symbol` of the property to delete.
3258
+ * @returns A `boolean` indicating whether or not the property was deleted.
3259
+ */
3260
+ deleteProperty: (target, key) => {
3261
+ if (UNRESOLVED_CONFIG_NAMES.includes(key.toString())) throw new Error(`Cannot delete property ${key.toString()} from config - it is only intended to be used as a reference.`);
3262
+ Reflect.deleteProperty(this.overriddenConfig, key);
3263
+ return Reflect.deleteProperty(target, key);
3264
+ },
3265
+ /**
3266
+ * A trap for getting a property value.
3267
+ * @param target - The original object which is being proxied.
3268
+ * @param key - The name or `Symbol` of the property to get.
3269
+ * @param receiver - The proxy or an object that inherits from the proxy.
3270
+ */
3271
+ get: (target, key, receiver) => {
3272
+ if (UNRESOLVED_CONFIG_NAMES.includes(key.toString())) {
3273
+ if (key === "initialConfig") return this.initialConfig;
3274
+ if (key === "userConfig") return this.userConfig;
3275
+ if (key === "inlineConfig") return this.inlineConfig;
3276
+ if (key === "pluginConfig") return this.pluginConfig;
3277
+ }
3278
+ return Reflect.get(target, key, receiver);
3279
+ },
3280
+ /**
3281
+ * A trap for the `in` operator.
3282
+ * @param target - The original object which is being proxied.
3283
+ * @param key - The name or `Symbol` of the property to check for existence.
3284
+ */
3285
+ has: (target, key) => {
3286
+ return Reflect.has(target, key) || UNRESOLVED_CONFIG_NAMES.includes(key.toString());
3287
+ },
3288
+ /**
3289
+ * A trap for `Reflect.ownKeys()`.
3290
+ * @param target - The original object which is being proxied.
3291
+ */
3292
+ ownKeys: (target) => {
3293
+ return getUnique([...Reflect.ownKeys(target), ...UNRESOLVED_CONFIG_NAMES]);
3294
+ },
3295
+ /**
3296
+ * A trap for setting a property value.
3297
+ * @param target - The original object which is being proxied.
3298
+ * @param key - The name or `Symbol` of the property to set.
3299
+ * @param newValue - The new value to assign to the property.
3300
+ * @param receiver - The object to which the assignment was originally directed.
3301
+ * @returns A `boolean` indicating whether or not the property was set.
3302
+ */
3303
+ set: (target, key, newValue, receiver) => {
3304
+ if (UNRESOLVED_CONFIG_NAMES.includes(key.toString())) throw new Error(`Cannot change property ${key.toString()} from config - it is only intended to be used as a reference.`);
3305
+ Reflect.set(this.overriddenConfig, key, newValue, receiver);
3306
+ return Reflect.set(target, key, newValue, receiver);
3307
+ }
3308
+ });
3293
3309
  }
3294
3310
  };
3295
3311
 
@@ -3434,7 +3450,7 @@ function createPluginContext(pluginId, plugin, environment) {
3434
3450
  return {
3435
3451
  meta: {
3436
3452
  ...isSetObject(message) ? message.meta : {},
3437
- environment: environment.environment?.name,
3453
+ environment: environment.config.environment.name,
3438
3454
  plugin: plugin.name
3439
3455
  },
3440
3456
  message: isString(message) ? message : message.message
@@ -3457,6 +3473,8 @@ function createPluginContext(pluginId, plugin, environment) {
3457
3473
  callHook: callHookFn,
3458
3474
  meta
3459
3475
  };
3476
+ if (prop === "api") return environment.$$internal.api;
3477
+ if (prop === "environment") return environment;
3460
3478
  if (prop === "id") return pluginId;
3461
3479
  if (prop === "logger") return logger;
3462
3480
  if (prop === "log") return (type, message) => {
@@ -3509,6 +3527,15 @@ function createPluginContext(pluginId, plugin, environment) {
3509
3527
  //#endregion
3510
3528
  //#region src/context/environment-context.ts
3511
3529
  var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends PowerlinesContext {
3530
+ /**
3531
+ * Internal references storage
3532
+ *
3533
+ * @danger
3534
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3535
+ *
3536
+ * @internal
3537
+ */
3538
+ #internal = {};
3512
3539
  /**
3513
3540
  * The hooks registered by plugins in this environment
3514
3541
  */
@@ -3518,37 +3545,72 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3518
3545
  *
3519
3546
  * @param options - The resolved execution options.
3520
3547
  * @param config - The user configuration options.
3521
- * @returns A promise that resolves to the new context.
3548
+ * @param overriddenConfig - The configuration options that should override all other configuration sources, such as CLI flags or environment variables. This is used to ensure that certain configuration values take precedence over any other settings defined in the user configuration or environment configuration, allowing for dynamic overrides based on the execution context.
3549
+ * @param environment - The resolved environment configuration, which may include additional properties or modifications made during the configuration loading process. This is used to provide context about the environment in which the command is being executed, allowing for environment-specific behavior and configuration resolution.
3550
+ * @returns A promise that resolves to an instance of the PowerlinesEnvironmentContext class, initialized with the provided configuration and environment data.
3522
3551
  */
3523
- static async fromConfig(options, config, environment) {
3524
- const context = new PowerlinesEnvironmentContext(options, config, environment);
3525
- await context.setup();
3526
- const powerlinesPath = await resolvePackage("powerlines");
3527
- if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
3528
- context.powerlinesPath = powerlinesPath;
3552
+ static async createEnvironment(options, config, overriddenConfig, environment) {
3553
+ const context = new PowerlinesEnvironmentContext(options, config, overriddenConfig);
3554
+ await context.setEnvironmentConfig(environment);
3529
3555
  return context;
3530
3556
  }
3531
3557
  /**
3532
- * The resolved environment configuration
3558
+ * The configuration options provided by plugins added by the user (and other plugins)
3533
3559
  */
3534
- environment;
3560
+ environmentConfig = {};
3535
3561
  /**
3536
3562
  * The list of plugins applied to this environment
3537
3563
  */
3538
3564
  plugins = [];
3539
3565
  /**
3540
- * The unique identifier of the environment associated with this context, which can be used for logging and other purposes to distinguish between different environments in the same process.
3566
+ * Internal context fields and methods
3567
+ *
3568
+ * @danger
3569
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3570
+ *
3571
+ * @internal
3541
3572
  */
3542
- get id() {
3543
- return this.environment.environmentId;
3573
+ get $$internal() {
3574
+ return this.#internal;
3544
3575
  }
3545
3576
  /**
3546
- * The hooks registered by plugins in this environment
3577
+ * Internal context fields and methods
3578
+ *
3579
+ * @danger
3580
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3581
+ *
3582
+ * @internal
3583
+ */
3584
+ set $$internal(value) {
3585
+ this.#internal = value;
3586
+ }
3587
+ /**
3588
+ * The unique identifier of the environment associated with this context, which can be used for logging and other purposes to distinguish between different environments in the same process.
3589
+ */
3590
+ get id() {
3591
+ return this.config.environment.id;
3592
+ }
3593
+ /**
3594
+ * The hooks registered by plugins in this environment
3547
3595
  */
3548
3596
  get hooks() {
3549
3597
  return this.#hooks;
3550
3598
  }
3551
3599
  /**
3600
+ * A setter function to populate the environment config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the environment configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any environment configuration provided during execution.
3601
+ *
3602
+ * @param config - The environment configuration values to set.
3603
+ * @returns A promise that resolves when the environment configuration values have been set.
3604
+ */
3605
+ async setEnvironmentConfig(config) {
3606
+ this.logger.debug({
3607
+ meta: { category: "config" },
3608
+ message: `Updating environment configuration object: \n${this.logConfig(config)}`
3609
+ });
3610
+ this.environmentConfig = config;
3611
+ await this.resolveConfig();
3612
+ }
3613
+ /**
3552
3614
  * Create a new logger instance
3553
3615
  *
3554
3616
  * @param options - The configuration options to use for the logger instance, which can be used to customize the appearance and behavior of the log messages generated by the logger. This is typically the name of the plugin or module that is creating the logger instance.
@@ -3558,7 +3620,7 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3558
3620
  createLogger(options, logFn) {
3559
3621
  return super.createLogger({
3560
3622
  ...options,
3561
- environment: this.environment?.name
3623
+ environment: this.config.environment?.name
3562
3624
  }, logFn);
3563
3625
  }
3564
3626
  /**
@@ -3570,39 +3632,13 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3570
3632
  extendLogger(options) {
3571
3633
  return super.extendLogger({
3572
3634
  ...options,
3573
- environment: this.environment?.name
3635
+ environment: this.config.environment?.name
3574
3636
  });
3575
3637
  }
3576
- /**
3577
- * Creates a clone of the current context with the same configuration and workspace settings. This can be useful for running multiple builds in parallel or for creating isolated contexts for different parts of the build process.
3578
- *
3579
- * @remarks
3580
- * The cloned context will have the same configuration and workspace settings as the original context, but will have a different build ID, release ID, and timestamp. The virtual file system and caches will also be separate between the original and cloned contexts.
3581
- *
3582
- * @returns A promise that resolves to the cloned context.
3583
- */
3584
- async clone() {
3585
- const context = await PowerlinesEnvironmentContext.fromConfig(deepClone(this.options), deepClone(this.config), deepClone(this.environment));
3586
- return this.copyTo(context);
3587
- }
3588
- /**
3589
- * Initialize the context with the provided configuration options
3590
- */
3591
- async setup() {
3592
- this.resolvedConfig = mergeConfig({
3593
- name: this.config.name,
3594
- title: this.config.title
3595
- }, getConfigProps({
3596
- ...this.environment,
3597
- root: this.options.root,
3598
- cwd: this.options.cwd
3599
- }), this.config);
3600
- await this.innerSetup();
3601
- }
3602
3638
  async addPlugin(plugin) {
3603
3639
  let resolvedPlugin = plugin;
3604
3640
  if (isFunction(plugin.applyToEnvironment)) {
3605
- const result = await Promise.resolve(plugin.applyToEnvironment(this.environment));
3641
+ const result = await Promise.resolve(plugin.applyToEnvironment(this.config.environment));
3606
3642
  if (!result || isObject(result) && Object.keys(result).length === 0) return;
3607
3643
  if (isPluginConfig(result)) return this.$$internal.addPlugin(result);
3608
3644
  resolvedPlugin = isPlugin(result) ? result : plugin;
@@ -3653,29 +3689,51 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3653
3689
  }
3654
3690
  return result;
3655
3691
  }
3656
- constructor(options, config, environment) {
3657
- super(options);
3658
- this.resolvedConfig = config;
3659
- this.environment = environment;
3692
+ constructor(options, config, overriddenConfig) {
3693
+ super(options, config.initialConfig);
3694
+ this.userConfig = config.userConfig ?? {};
3695
+ this.inlineConfig = config.inlineConfig ?? {};
3696
+ this.pluginConfig = config.pluginConfig ?? {};
3697
+ this.overriddenConfig = overriddenConfig;
3660
3698
  }
3661
3699
  /**
3662
- * Creates a clone of the current context with the same configuration and workspace settings. This can be useful for running multiple builds in parallel or for creating isolated contexts for different parts of the build process.
3700
+ * A function to merge the various configuration objects (initial, user, inline, and plugin) into a single resolved configuration object that can be used throughout the Powerlines process. This function takes into account the different sources of configuration and their respective priorities, ensuring that the final configuration reflects the intended settings for the project. The merged configuration is then returned as a new object that can be accessed through the `config` property of the context.
3663
3701
  *
3664
- * @remarks
3665
- * The cloned context will have the same configuration and workspace settings as the original context, but will have a different build ID, release ID, and timestamp. The virtual file system and caches will also be separate between the original and cloned contexts.
3666
- *
3667
- * @returns The cloned context.
3702
+ * @returns The merged configuration object that combines the initial, user, inline, and plugin configurations.
3668
3703
  */
3669
- copyTo(context) {
3670
- context.plugins = this.plugins;
3671
- return super.copyTo(context);
3704
+ mergeConfig() {
3705
+ return mergeConfig({
3706
+ ...omit(this.environmentConfig ?? {}, [
3707
+ "ssr",
3708
+ "preview",
3709
+ "consumer",
3710
+ "runtime"
3711
+ ]),
3712
+ environment: { name: this.environmentConfig?.name || DEFAULT_ENVIRONMENT },
3713
+ environmentConfig: this.environmentConfig ?? {}
3714
+ }, super.mergeConfig());
3672
3715
  }
3673
3716
  };
3674
3717
 
3675
3718
  //#endregion
3676
3719
  //#region src/context/execution-context.ts
3677
3720
  var PowerlinesExecutionContext = class PowerlinesExecutionContext extends PowerlinesContext {
3721
+ /**
3722
+ * Internal references storage
3723
+ *
3724
+ * @danger
3725
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3726
+ *
3727
+ * @internal
3728
+ */
3729
+ #internal = {};
3730
+ /**
3731
+ * A record of all environments by name
3732
+ */
3678
3733
  #environments = {};
3734
+ /**
3735
+ * The plugins added to this execution context, which may be used to track the plugins that have been added to the context and ensure that they are properly registered and executed during the build process. This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3736
+ */
3679
3737
  #plugins = [];
3680
3738
  /**
3681
3739
  * Create a new Storm context from the workspace root and user config.
@@ -3683,13 +3741,9 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3683
3741
  * @param options - The options for resolving the context.
3684
3742
  * @returns A promise that resolves to the new context.
3685
3743
  */
3686
- static async init(options, initialConfig) {
3687
- const context = new PowerlinesExecutionContext(options);
3688
- await context.init(options, initialConfig);
3689
- const powerlinesPath = await resolvePackage("powerlines");
3690
- if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
3691
- context.powerlinesPath = powerlinesPath;
3692
- await context.setup();
3744
+ static async fromInitialConfig(options, initialConfig) {
3745
+ const context = new PowerlinesExecutionContext(options, initialConfig);
3746
+ await context.init();
3693
3747
  return context;
3694
3748
  }
3695
3749
  /**
@@ -3698,19 +3752,10 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3698
3752
  * @param options - The options for resolving the context.
3699
3753
  * @returns A promise that resolves to the new context.
3700
3754
  */
3701
- static async inline(options, initialConfig, inlineConfig) {
3702
- const context = new PowerlinesExecutionContext(options);
3703
- await context.init(options, initialConfig);
3704
- context.config.inlineConfig = inlineConfig;
3705
- if (context.config.inlineConfig.command === "new") {
3706
- const workspacePackageJsonPath = joinPaths$1(context.config.cwd, "package.json");
3707
- if (!existsSync(workspacePackageJsonPath)) throw new Error(`The workspace package.json file could not be found at ${workspacePackageJsonPath}`);
3708
- context.packageJson = await readJsonFile(workspacePackageJsonPath);
3709
- }
3710
- await context.setup();
3711
- const powerlinesPath = await resolvePackage("powerlines");
3712
- if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
3713
- context.powerlinesPath = powerlinesPath;
3755
+ static async fromInlineConfig(options, initialConfig, inlineConfig) {
3756
+ const context = new PowerlinesExecutionContext(options, initialConfig);
3757
+ await context.init();
3758
+ await context.setInlineConfig(inlineConfig);
3714
3759
  return context;
3715
3760
  }
3716
3761
  /**
@@ -3722,7 +3767,7 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3722
3767
  * @internal
3723
3768
  */
3724
3769
  get $$internal() {
3725
- return super.$$internal;
3770
+ return this.#internal;
3726
3771
  }
3727
3772
  /**
3728
3773
  * Internal context fields and methods
@@ -3733,8 +3778,8 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3733
3778
  * @internal
3734
3779
  */
3735
3780
  set $$internal(value) {
3736
- super.$$internal = value;
3737
- for (const environment of Object.values(this.environments)) environment.$$internal = super.$$internal;
3781
+ this.#internal = value;
3782
+ for (const environment of Object.values(this.environments)) environment.$$internal = value;
3738
3783
  }
3739
3784
  /**
3740
3785
  * The unique identifier of the execution context, which can be used for logging and other purposes to distinguish between different executions in the same process.
@@ -3755,9 +3800,26 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3755
3800
  * Creates a new instance.
3756
3801
  *
3757
3802
  * @param options - The options to use for creating the context, including the resolved configuration and workspace settings.
3803
+ * @param initialConfig - The initial configuration options to use for the context, which can be used to provide default values for configuration options that may be overridden by user configuration or inline configuration. This is typically the configuration options provided by the user in a configuration file on disk, and can include any relevant settings such as environment definitions, plugin configurations, and other parameters that may be relevant to the execution of a Powerlines command.
3804
+ */
3805
+ constructor(options, initialConfig = {}) {
3806
+ super(options, initialConfig);
3807
+ this.initialOptions = options;
3808
+ this.initialConfig = initialConfig;
3809
+ }
3810
+ /**
3811
+ * A setter function to populate the inline config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the inline configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any inline configuration provided during execution.
3812
+ *
3813
+ * @param config - The inline configuration values to set.
3814
+ * @returns A promise that resolves when the inline configuration values have been set.
3758
3815
  */
3759
- constructor(options) {
3760
- super(options);
3816
+ async setInlineConfig(config) {
3817
+ await super.setInlineConfig(config);
3818
+ if (this.inlineConfig.command === "new") {
3819
+ const workspacePackageJsonPath = joinPaths$1(this.config.cwd, "package.json");
3820
+ if (!existsSync(workspacePackageJsonPath)) throw new Error(`The workspace package.json file could not be found at ${workspacePackageJsonPath}`);
3821
+ this.packageJson = await readJsonFile(workspacePackageJsonPath);
3822
+ }
3761
3823
  }
3762
3824
  /**
3763
3825
  * Create a new logger instance
@@ -3787,31 +3849,20 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3787
3849
  });
3788
3850
  }
3789
3851
  /**
3790
- * Creates a clone of the current context with the same configuration and workspace settings. This can be useful for running multiple builds in parallel or for creating isolated contexts for different parts of the build process.
3791
- *
3792
- * @remarks
3793
- * The cloned context will have the same configuration and workspace settings as the original context, but will have a different build ID, release ID, and timestamp. The virtual file system and caches will also be separate between the original and cloned contexts.
3794
- *
3795
- * @returns A promise that resolves to the cloned context.
3796
- */
3797
- async clone() {
3798
- const clone = await PowerlinesExecutionContext.init(this.options, this.initialConfig);
3799
- clone.config.userConfig = deepClone(this.config.userConfig);
3800
- clone.config.initialConfig = deepClone(this.config.initialConfig);
3801
- clone.config.inlineConfig = deepClone(this.config.inlineConfig);
3802
- clone.config.pluginConfig = deepClone(this.config.pluginConfig);
3803
- await clone.setup();
3804
- clone.$$internal = this.$$internal;
3805
- return this.copyTo(clone);
3806
- }
3807
- /**
3808
3852
  * A function to copy the context and update the fields for a specific environment
3809
3853
  *
3810
3854
  * @param environment - The environment configuration to use.
3811
3855
  * @returns A new context instance with the updated environment.
3812
3856
  */
3813
- async in(environment) {
3814
- const context = this.copyTo(await PowerlinesEnvironmentContext.fromConfig(deepClone(this.options), deepClone(this.config), deepClone(environment)));
3857
+ async createEnvironment(environment) {
3858
+ const context = await PowerlinesEnvironmentContext.createEnvironment(deepClone(this.options), deepClone(this.config), deepClone(this.overriddenConfig), deepClone(environment));
3859
+ context.dependencies = deepClone(this.dependencies);
3860
+ context.devDependencies = deepClone(this.devDependencies);
3861
+ context.persistedMeta = deepClone(this.persistedMeta);
3862
+ context.resolvePatterns = deepClone(this.resolvePatterns);
3863
+ context.powerlinesPath ??= this.powerlinesPath;
3864
+ context.resolver ??= this.resolver;
3865
+ context.$$internal = this.$$internal;
3815
3866
  context.plugins = [];
3816
3867
  for (const plugin of this.plugins) await context.addPlugin(plugin);
3817
3868
  return context;
@@ -3819,10 +3870,10 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3819
3870
  /**
3820
3871
  * Update the context using a new inline configuration options
3821
3872
  */
3822
- async setup() {
3823
- await super.setup();
3873
+ async resolveConfig() {
3874
+ await super.resolveConfig();
3824
3875
  await Promise.all(toArray(this.config.environments && Object.keys(this.config.environments).length > 0 ? Object.keys(this.config.environments).map((name) => createEnvironment(name, this.config)) : createDefaultEnvironment(this.config)).map(async (env) => {
3825
- this.#environments[env.name] = await this.in(env);
3876
+ this.#environments[env.name] = await this.createEnvironment(env);
3826
3877
  }));
3827
3878
  }
3828
3879
  /**
@@ -3847,12 +3898,20 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3847
3898
  if (name) environment = this.environments[name];
3848
3899
  if (Object.keys(this.environments).length === 1) {
3849
3900
  environment = this.environments[Object.keys(this.environments)[0]];
3850
- this.debug(`Applying the only configured environment: ${chalk.bold.cyanBright(environment?.environment.name)}`);
3901
+ this.trace({
3902
+ meta: { category: "plugins" },
3903
+ message: `Applying the only configured environment: ${chalk.bold.cyanBright(environment?.config.environment?.name)}`
3904
+ });
3851
3905
  }
3852
3906
  if (!environment) {
3853
3907
  if (name) throw new Error(`Environment "${name}" not found.`);
3854
- environment = await this.in(createDefaultEnvironment(this.config));
3855
- this.warn(`No environment specified, and no default environment found. Using a temporary default environment: ${chalk.bold.cyanBright(environment?.environment.name)}`);
3908
+ environment = await PowerlinesEnvironmentContext.createEnvironment(deepClone(this.options), deepClone(this.config), deepClone(this.overriddenConfig), deepClone(createDefaultEnvironment(this.config)));
3909
+ environment.plugins = [];
3910
+ for (const plugin of this.plugins) await environment.addPlugin(plugin);
3911
+ this.warn({
3912
+ meta: { category: "plugins" },
3913
+ message: `No environment specified, and no default environment found. Using a temporary default environment: ${chalk.bold.cyanBright(environment.config.environment?.name)}`
3914
+ });
3856
3915
  }
3857
3916
  return environment;
3858
3917
  }
@@ -3880,8 +3939,11 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3880
3939
  async toEnvironment() {
3881
3940
  let environment;
3882
3941
  if (Object.keys(this.environments).length > 1) {
3883
- environment = await this.in(createEnvironment(GLOBAL_ENVIRONMENT, this.config));
3884
- this.debug(`Combined all ${Object.keys(this.environments).length} environments into a single global context.`);
3942
+ environment = await this.createEnvironment(createEnvironment(GLOBAL_ENVIRONMENT, this.config));
3943
+ this.debug({
3944
+ meta: { category: "plugins" },
3945
+ message: `Combined all ${Object.keys(this.environments).length} environments into a single global context.`
3946
+ });
3885
3947
  } else environment = await this.getEnvironment();
3886
3948
  return environment;
3887
3949
  }
@@ -4301,11 +4363,11 @@ async function installDependencies(context) {
4301
4363
  //#endregion
4302
4364
  //#region src/_internal/helpers/resolve-tsconfig.ts
4303
4365
  function getTsconfigDtsPath(context) {
4304
- return joinPaths(relativePath(joinPaths(context.options.cwd, context.options.root), findFilePath(context.typesPath)), findFileName(context.typesPath));
4366
+ return joinPaths(relativePath(joinPaths(context.config.cwd, context.config.root), findFilePath(context.typesPath)), findFileName(context.typesPath));
4305
4367
  }
4306
4368
  async function resolveTsconfigChanges(context) {
4307
- const tsconfig = getParsedTypeScriptConfig(context.options.cwd, context.options.root, context.config.tsconfig, context.config.tsconfigRaw);
4308
- const tsconfigJson = await readJsonFile(getTsconfigFilePath(context.options.cwd, context.options.root, context.config.tsconfig));
4369
+ const tsconfig = getParsedTypeScriptConfig(context.config.cwd, context.config.root, context.config.tsconfig, context.config.tsconfigRaw);
4370
+ const tsconfigJson = await readJsonFile(getTsconfigFilePath(context.config.cwd, context.config.root, context.config.tsconfig));
4309
4371
  tsconfigJson.compilerOptions ??= {};
4310
4372
  if (context.config.output.dts !== false) {
4311
4373
  const dtsRelativePath = getTsconfigDtsPath(context);
@@ -4395,342 +4457,76 @@ var PowerlinesExecution = class PowerlinesExecution {
4395
4457
  * The Powerlines context
4396
4458
  */
4397
4459
  #context;
4398
- async #handleBuild(context) {
4399
- await this.callHook("build", {
4400
- environment: context,
4401
- order: "pre"
4402
- });
4403
- context.debug("Formatting the generated entry files before the build process starts.");
4404
- await formatFolder(context, context.entryPath);
4405
- await this.callHook("build", {
4406
- environment: context,
4407
- order: "normal"
4408
- });
4409
- if (context.config.output.copy) {
4410
- context.debug("Copying project's files from build output directory.");
4411
- 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");
4412
- const sourcePath = context.config.output.path;
4413
- if (existsSync(sourcePath) && sourcePath !== destinationPath) {
4414
- context.debug(`Copying files from project's build output directory (${context.config.output.path}) to the project's copy/publish directory (${destinationPath}).`);
4415
- await copyFiles(sourcePath, destinationPath);
4416
- } 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.`);
4417
- if (context.config.output.copy.assets && Array.isArray(context.config.output.copy.assets)) await Promise.all(context.config.output.copy.assets.map(async (asset) => {
4418
- 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(", ")})` : ""}`);
4419
- await context.fs.copy(asset, asset.output);
4420
- }));
4421
- } else context.debug("No copy configuration found for the project output. Skipping the copying of build output files.");
4422
- await this.callHook("build", {
4423
- environment: context,
4424
- order: "post"
4425
- });
4426
- }
4427
4460
  /**
4428
- * Get the configured environments
4461
+ * Initialize a Powerlines API instance
4429
4462
  *
4430
- * @returns The configured environments
4463
+ * @param options - The options to initialize the API with
4464
+ * @returns A new instance of the Powerlines API
4431
4465
  */
4432
- async #getEnvironments() {
4433
- if (!this.context.config.environments || Object.keys(this.context.config.environments).length <= 1) {
4434
- this.context.debug("No environments are configured for this Powerlines project. Using the default environment.");
4435
- return [await this.context.getEnvironment()];
4436
- }
4437
- this.context.debug(`Found ${Object.keys(this.context.config.environments).length} configured environment(s) for this Powerlines project.`);
4438
- return (await Promise.all(Object.entries(this.context.config.environments).map(async ([name, config]) => {
4439
- if (!await this.context.getEnvironmentSafe(name)) {
4440
- const resolvedEnvironment = await this.callHook("configEnvironment", { environment: name }, name, config);
4441
- if (resolvedEnvironment) this.context.environments[name] = await this.context.in(resolvedEnvironment);
4442
- }
4443
- return this.context.environments[name];
4444
- }))).filter((context) => isSet(context));
4466
+ static async from(options, initialConfig) {
4467
+ const api = new PowerlinesExecution(await PowerlinesExecutionContext.fromInitialConfig(options, initialConfig ?? {}));
4468
+ await api.init();
4469
+ return api;
4445
4470
  }
4446
4471
  /**
4447
- * Execute a handler function for each environment
4448
- *
4449
- * @param handle - The handler function to execute for each environment
4472
+ * The Powerlines context
4450
4473
  */
4451
- async #executeEnvironments(handle) {
4452
- await Promise.all((await this.#getEnvironments()).map(async (context) => {
4453
- return Promise.resolve(handle(context));
4454
- }));
4474
+ get context() {
4475
+ return this.#context;
4455
4476
  }
4456
4477
  /**
4457
- * Initialize a Powerlines plugin
4478
+ * Generate the Powerlines typescript declaration file
4458
4479
  *
4459
- * @param config - The configuration for the plugin
4460
- * @returns The initialized plugin instance, or null if the plugin was a duplicate
4461
- * @throws Will throw an error if the plugin cannot be found or is invalid
4480
+ * @remarks
4481
+ * This method will only generate the typescript declaration file for the Powerlines project. It is generally recommended to run the full `prepare` command, which will run this method as part of its process.
4482
+ *
4483
+ * @param inlineConfig - The inline configuration for the types command
4462
4484
  */
4463
- async #initPlugin(config) {
4464
- let awaited = config;
4465
- if (isPromiseLike(config)) awaited = await Promise.resolve(config);
4466
- if (!isPluginConfig(awaited)) {
4467
- const invalid = findInvalidPluginConfig(awaited);
4468
- 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`);
4469
- }
4470
- let plugins;
4471
- if (isPlugin(awaited)) plugins = [awaited];
4472
- else if (isFunction(awaited)) plugins = toArray(await Promise.resolve(awaited()));
4473
- else if (isString(awaited)) {
4474
- const resolved = await this.#resolvePlugin(awaited);
4475
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(resolved()));
4476
- else plugins = toArray(resolved);
4477
- } else if (Array.isArray(awaited) && awaited.every(isPlugin)) plugins = awaited;
4478
- else if (Array.isArray(awaited) && awaited.every(isPluginConfig)) {
4479
- plugins = [];
4480
- for (const pluginConfig of awaited) {
4481
- const initialized = await this.#initPlugin(pluginConfig);
4482
- if (initialized) plugins.push(...initialized);
4483
- }
4484
- } else if (isPluginConfigTuple(awaited) || isPluginConfigObject(awaited)) {
4485
- let pluginConfig;
4486
- let pluginOptions;
4487
- if (isPluginConfigTuple(awaited)) {
4488
- pluginConfig = awaited[0];
4489
- pluginOptions = awaited?.length === 2 ? awaited[1] : void 0;
4490
- } else {
4491
- pluginConfig = awaited.plugin;
4492
- pluginOptions = awaited.options;
4493
- }
4494
- if (isSetString(pluginConfig)) {
4495
- const resolved = await this.#resolvePlugin(pluginConfig);
4496
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(pluginOptions ? resolved(pluginOptions) : resolved()));
4497
- else plugins = toArray(resolved);
4498
- } else if (isFunction(pluginConfig)) plugins = toArray(await Promise.resolve(pluginConfig(pluginOptions)));
4499
- else if (Array.isArray(pluginConfig) && pluginConfig.every(isPlugin)) plugins = pluginConfig;
4500
- else if (isPlugin(pluginConfig)) plugins = toArray(pluginConfig);
4501
- }
4502
- if (!plugins) throw new Error(`The plugin configuration ${JSON.stringify(awaited)} is invalid. This configuration must point to a valid Powerlines plugin module.`);
4503
- 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.`);
4504
- const result = [];
4505
- 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.`);
4506
- else {
4507
- result.push(plugin);
4508
- this.context.trace(`Initializing the ${chalk.bold.cyanBright(plugin.name)} plugin...`);
4509
- }
4510
- return result;
4511
- }
4512
- async #resolvePlugin(pluginPath) {
4513
- if (pluginPath.startsWith("@") && pluginPath.split("/").filter(Boolean).length > 2) {
4514
- const splits = pluginPath.split("/").filter(Boolean);
4515
- pluginPath = `${splits[0]}/${splits[1]}`;
4516
- }
4517
- const isInstalled = isPackageExists(pluginPath, { paths: [this.context.config.cwd, this.context.config.root] });
4518
- if (!isInstalled && this.context.config.autoInstall) {
4519
- this.#context.warn(`The plugin package "${pluginPath}" is not installed. It will be installed automatically.`);
4520
- const result = await install(pluginPath, { cwd: this.context.config.root });
4521
- if (isNumber(result.exitCode) && result.exitCode > 0) {
4522
- this.#context.error(result.stderr);
4523
- throw new Error(`An error occurred while installing the build plugin package "${pluginPath}" `);
4524
- }
4525
- }
4526
- try {
4527
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(joinPaths(pluginPath, "plugin")));
4528
- const result = module.plugin ?? module.default;
4529
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4530
- return result;
4531
- } catch (error) {
4532
- try {
4533
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(pluginPath));
4534
- const result = module.plugin ?? module.default;
4535
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4536
- return result;
4537
- } catch {
4538
- if (!isInstalled) throw new Error(`The plugin package "${pluginPath}" is not installed. Please install the package using the command: "npm install ${pluginPath} --save-dev"`);
4539
- else throw new Error(`An error occurred while importing the build plugin package "${pluginPath}":
4540
- ${isError(error) ? error.message : String(error)}
4541
-
4542
- 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\`.`);
4485
+ async types(inlineConfig = { command: "types" }) {
4486
+ this.context.debug(" Aggregating configuration options for the Powerlines project");
4487
+ inlineConfig.command ??= "types";
4488
+ await this.context.setInlineConfig(inlineConfig);
4489
+ await this.executeEnvironments(async (context) => {
4490
+ context.debug(`Initializing the processing options for the Powerlines project.`);
4491
+ await this.callHook("configResolved", {
4492
+ environment: context,
4493
+ order: "pre"
4494
+ });
4495
+ await initializeTsconfig(context);
4496
+ await this.callHook("configResolved", {
4497
+ environment: context,
4498
+ order: "normal"
4499
+ });
4500
+ if (context.entry.length > 0) context.debug(`The configuration provided ${isObject(context.config.input) ? Object.keys(context.config.input).length : toArray(context.config.input).length} entry point(s), Powerlines has found ${context.entry.length} entry files(s) for the ${context.config.title} project${context.entry.length > 0 && context.entry.length < 10 ? `: \n${context.entry.map((entry) => `- ${entry.file}${entry.output ? ` -> ${entry.output}` : ""}`).join(" \n")}` : ""}`);
4501
+ else context.warn(`No entry files were found for the ${context.config.title} project. Please ensure this is correct. Powerlines plugins generally require at least one entry point to function properly.`);
4502
+ await resolveTsconfig(context);
4503
+ await installDependencies(context);
4504
+ await this.callHook("configResolved", {
4505
+ environment: context,
4506
+ order: "post"
4507
+ });
4508
+ context.trace(`Powerlines configuration has been resolved: \n\n${formatLogMessage({
4509
+ ...context.config,
4510
+ userConfig: isSetObject(context.config.userConfig) ? omit(context.config.userConfig, ["plugins"]) : void 0,
4511
+ inlineConfig: isSetObject(context.config.inlineConfig) ? omit(context.config.inlineConfig, ["plugins"]) : void 0,
4512
+ plugins: context.plugins.map((plugin) => plugin.plugin.name)
4513
+ })}`);
4514
+ if (!context.fs.existsSync(context.cachePath)) await createDirectory(context.cachePath);
4515
+ if (!context.fs.existsSync(context.dataPath)) await createDirectory(context.dataPath);
4516
+ if (context.config.skipCache === true || context.persistedMeta?.checksum !== context.meta.checksum) context.debug(`Using previously prepared files as the meta checksum has not changed.`);
4517
+ else {
4518
+ context.info(`Running \`prepare\` command as the meta checksum has changed since the last run.`);
4519
+ await this.prepare(defu({ output: { types: false } }, inlineConfig));
4543
4520
  }
4544
- }
4521
+ await this.handleTypes(context);
4522
+ this.context.debug("Formatting files generated during the types step.");
4523
+ await format(context, context.typesPath, await context.fs.read(context.typesPath) ?? "");
4524
+ await writeMetaFile(context);
4525
+ context.persistedMeta = context.meta;
4526
+ });
4545
4527
  }
4546
4528
  /**
4547
- * Generate the Powerlines TypeScript declaration file
4548
- *
4549
- * @remarks
4550
- * This method will generate the TypeScript declaration file for the Powerlines project, including any types provided by plugins.
4551
- *
4552
- * @param context - The environment context to use for generating the TypeScript declaration file
4553
- * @returns A promise that resolves when the TypeScript declaration file has been generated
4554
- */
4555
- async #types(context) {
4556
- context.debug(`Preparing the TypeScript definitions for the Powerlines project.`);
4557
- if (context.fs.existsSync(context.typesPath)) await context.fs.remove(context.typesPath);
4558
- if (!await resolvePackage("typescript")) throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed.");
4559
- context.debug("Running TypeScript compiler for built-in runtime module files.");
4560
- let { code, directives } = await emitBuiltinTypes(context, (await context.getBuiltins()).reduce((ret, builtin) => {
4561
- const formatted = replacePath(builtin.path, context.config.cwd);
4562
- if (!ret.includes(formatted)) ret.push(formatted);
4563
- return ret;
4564
- }, []));
4565
- context.debug(`Generating TypeScript declaration file ${context.typesPath}.`);
4566
- const merge = async (currentResult, previousResult) => {
4567
- if (!isSetString(currentResult) && !isSetObject(currentResult) && !isSetString(previousResult) && !isSetObject(previousResult)) return {
4568
- code,
4569
- directives
4570
- };
4571
- const previous = (await format(context, context.typesPath, isSetString(previousResult) ? previousResult : isSetObject(previousResult) ? previousResult.code : "")).trim().replace(code, "").trim();
4572
- const current = (await format(context, context.typesPath, isSetString(currentResult) ? currentResult : isSetObject(currentResult) ? currentResult.code : "")).trim().replace(previous, "").trim().replace(code, "").trim();
4573
- return {
4574
- directives: [...isSetObject(currentResult) && currentResult.directives ? currentResult.directives : [], ...isSetObject(previousResult) && previousResult.directives ? previousResult.directives : []],
4575
- code: await format(context, context.typesPath, `${!previous.includes(getTypescriptFileHeader(context)) && !current.includes(getTypescriptFileHeader(context)) ? `${code}\n` : ""}${previous}\n${current}`.trim())
4576
- };
4577
- };
4578
- const asNextParam = (previousResult) => isObject(previousResult) ? previousResult.code : previousResult;
4579
- let result = await this.callHook("types", {
4580
- environment: context,
4581
- sequential: true,
4582
- order: "pre",
4583
- result: "merge",
4584
- merge,
4585
- asNextParam
4586
- }, code);
4587
- if (result) {
4588
- if (isSetObject(result)) {
4589
- code = result.code;
4590
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4591
- } else if (isSetString(result)) code = result;
4592
- }
4593
- result = await this.callHook("types", {
4594
- environment: context,
4595
- sequential: true,
4596
- order: "normal",
4597
- result: "merge",
4598
- merge,
4599
- asNextParam
4600
- }, code);
4601
- if (result) {
4602
- if (isSetObject(result)) {
4603
- code = result.code;
4604
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4605
- } else if (isSetString(result)) code = result;
4606
- }
4607
- result = await this.callHook("types", {
4608
- environment: context,
4609
- sequential: true,
4610
- order: "post",
4611
- result: "merge",
4612
- merge,
4613
- asNextParam
4614
- }, code);
4615
- if (result) {
4616
- if (isSetObject(result)) {
4617
- code = result.code;
4618
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4619
- } else if (isSetString(result)) code = result;
4620
- }
4621
- if (isSetString(code?.trim()) || directives.length > 0) await context.fs.write(context.typesPath, `${directives.length > 0 ? `${directives.map((directive) => `/// <reference types="${directive}" />`).join("\n")}
4622
-
4623
- ` : ""}${getTypescriptFileHeader(context, {
4624
- directive: null,
4625
- prettierIgnore: false
4626
- })}
4627
-
4628
- ${formatTypes(code)}
4629
- `);
4630
- }
4631
- /**
4632
- * Initialize a Powerlines API instance
4633
- *
4634
- * @param options - The options to initialize the API with
4635
- * @returns A new instance of the Powerlines API
4636
- */
4637
- static async init(options, initialConfig) {
4638
- const api = new PowerlinesExecution(await PowerlinesExecutionContext.init(options, initialConfig ?? {}));
4639
- api.#context.config.initialConfig = initialConfig ?? {};
4640
- await api.setup();
4641
- return api;
4642
- }
4643
- /**
4644
- * The Powerlines context
4645
- */
4646
- get context() {
4647
- return this.#context;
4648
- }
4649
- /**
4650
- * Initialize the execution API with the provided configuration options
4651
- */
4652
- async setup() {
4653
- await this.#context.setup();
4654
- this.#context.$$internal = {
4655
- api: this,
4656
- addPlugin: this.addPlugin.bind(this)
4657
- };
4658
- const timer = this.#context.timer("Initialization");
4659
- for (const plugin of this.#context.config.plugins.flatMap((p) => toArray(p)) ?? []) await this.addPlugin(plugin);
4660
- if (this.#context.plugins.length === 0) this.#context.warn({
4661
- meta: { category: "plugins" },
4662
- message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
4663
- });
4664
- else this.#context.info({
4665
- meta: { category: "plugins" },
4666
- 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")}`
4667
- });
4668
- const pluginConfig = await this.callHook("config", {
4669
- environment: await this.#context.getEnvironment(),
4670
- sequential: true,
4671
- result: "merge",
4672
- merge: mergeConfigs
4673
- });
4674
- if (pluginConfig) {
4675
- this.#context.config.pluginConfig = pluginConfig;
4676
- await this.#context.setup();
4677
- }
4678
- timer();
4679
- }
4680
- /**
4681
- * Generate the Powerlines typescript declaration file
4682
- *
4683
- * @remarks
4684
- * This method will only generate the typescript declaration file for the Powerlines project. It is generally recommended to run the full `prepare` command, which will run this method as part of its process.
4685
- *
4686
- * @param inlineConfig - The inline configuration for the types command
4687
- */
4688
- async types(inlineConfig = { command: "types" }) {
4689
- this.context.debug(" Aggregating configuration options for the Powerlines project");
4690
- inlineConfig.command ??= "types";
4691
- this.context.config.inlineConfig = inlineConfig;
4692
- await this.setup();
4693
- await this.#executeEnvironments(async (context) => {
4694
- context.debug(`Initializing the processing options for the Powerlines project.`);
4695
- await this.callHook("configResolved", {
4696
- environment: context,
4697
- order: "pre"
4698
- });
4699
- await initializeTsconfig(context);
4700
- await this.callHook("configResolved", {
4701
- environment: context,
4702
- order: "normal"
4703
- });
4704
- if (context.entry.length > 0) context.debug(`The configuration provided ${isObject(context.config.input) ? Object.keys(context.config.input).length : toArray(context.config.input).length} entry point(s), Powerlines has found ${context.entry.length} entry files(s) for the ${context.config.title} project${context.entry.length > 0 && context.entry.length < 10 ? `: \n${context.entry.map((entry) => `- ${entry.file}${entry.output ? ` -> ${entry.output}` : ""}`).join(" \n")}` : ""}`);
4705
- else context.warn(`No entry files were found for the ${context.config.title} project. Please ensure this is correct. Powerlines plugins generally require at least one entry point to function properly.`);
4706
- await resolveTsconfig(context);
4707
- await installDependencies(context);
4708
- await this.callHook("configResolved", {
4709
- environment: context,
4710
- order: "post"
4711
- });
4712
- context.trace(`Powerlines configuration has been resolved: \n\n${formatLogMessage({
4713
- ...context.config,
4714
- userConfig: isSetObject(context.config.userConfig) ? omit(context.config.userConfig, ["plugins"]) : void 0,
4715
- inlineConfig: isSetObject(context.config.inlineConfig) ? omit(context.config.inlineConfig, ["plugins"]) : void 0,
4716
- plugins: context.plugins.map((plugin) => plugin.plugin.name)
4717
- })}`);
4718
- if (!context.fs.existsSync(context.cachePath)) await createDirectory(context.cachePath);
4719
- if (!context.fs.existsSync(context.dataPath)) await createDirectory(context.dataPath);
4720
- if (context.config.skipCache === true || context.persistedMeta?.checksum !== context.meta.checksum) context.debug(`Using previously prepared files as the meta checksum has not changed.`);
4721
- else {
4722
- context.info(`Running \`prepare\` command as the meta checksum has changed since the last run.`);
4723
- await this.prepare(defu({ output: { types: false } }, inlineConfig));
4724
- }
4725
- await this.#types(context);
4726
- this.context.debug("Formatting files generated during the types step.");
4727
- await format(context, context.typesPath, await context.fs.read(context.typesPath) ?? "");
4728
- await writeMetaFile(context);
4729
- context.persistedMeta = context.meta;
4730
- });
4731
- }
4732
- /**
4733
- * Prepare the Powerlines API
4529
+ * Prepare the Powerlines API
4734
4530
  *
4735
4531
  * @remarks
4736
4532
  * This method will prepare the Powerlines API for use, initializing any necessary resources.
@@ -4739,9 +4535,8 @@ ${formatTypes(code)}
4739
4535
  */
4740
4536
  async prepare(inlineConfig = { command: "prepare" }) {
4741
4537
  inlineConfig.command ??= "prepare";
4742
- this.context.config.inlineConfig = inlineConfig;
4743
- await this.setup();
4744
- await this.#executeEnvironments(async (context) => {
4538
+ await this.context.setInlineConfig(inlineConfig);
4539
+ await this.executeEnvironments(async (context) => {
4745
4540
  context.debug(`Initializing the processing options for the Powerlines project.`);
4746
4541
  await this.callHook("configResolved", {
4747
4542
  environment: context,
@@ -4791,7 +4586,7 @@ ${formatTypes(code)}
4791
4586
  environment: context,
4792
4587
  order: "post"
4793
4588
  });
4794
- if (context.config.output.types !== false) await this.#types(context);
4589
+ if (context.config.output.types !== false) await this.handleTypes(context);
4795
4590
  this.context.debug("Formatting files generated during the prepare step.");
4796
4591
  await Promise.all([formatFolder(context, context.builtinsPath), formatFolder(context, context.entryPath)]);
4797
4592
  await writeMetaFile(context);
@@ -4810,7 +4605,7 @@ ${formatTypes(code)}
4810
4605
  async new(inlineConfig) {
4811
4606
  inlineConfig.command ??= "new";
4812
4607
  await this.prepare(inlineConfig);
4813
- await this.#executeEnvironments(async (context) => {
4608
+ await this.executeEnvironments(async (context) => {
4814
4609
  context.debug("Initializing the processing options for the Powerlines project.");
4815
4610
  await this.callHook("new", {
4816
4611
  environment: context,
@@ -4859,7 +4654,7 @@ ${formatTypes(code)}
4859
4654
  async clean(inlineConfig = { command: "clean" }) {
4860
4655
  inlineConfig.command ??= "clean";
4861
4656
  await this.prepare(inlineConfig);
4862
- await this.#executeEnvironments(async (context) => {
4657
+ await this.executeEnvironments(async (context) => {
4863
4658
  context.debug("Cleaning the project's dist and artifacts directories.");
4864
4659
  await context.fs.remove(joinPaths(context.config.cwd, context.config.output.path));
4865
4660
  await context.fs.remove(joinPaths(context.config.cwd, context.config.root, context.config.output.artifactsPath));
@@ -4878,7 +4673,7 @@ ${formatTypes(code)}
4878
4673
  async lint(inlineConfig = { command: "lint" }) {
4879
4674
  inlineConfig.command ??= "lint";
4880
4675
  await this.prepare(inlineConfig);
4881
- await this.#executeEnvironments(async (context) => {
4676
+ await this.executeEnvironments(async (context) => {
4882
4677
  await this.callHook("lint", {
4883
4678
  environment: context,
4884
4679
  sequential: false
@@ -4894,7 +4689,7 @@ ${formatTypes(code)}
4894
4689
  async test(inlineConfig = { command: "test" }) {
4895
4690
  inlineConfig.command ??= "test";
4896
4691
  await this.prepare(inlineConfig);
4897
- await this.#executeEnvironments(async (context) => {
4692
+ await this.executeEnvironments(async (context) => {
4898
4693
  await this.callHook("test", {
4899
4694
  environment: context,
4900
4695
  sequential: false
@@ -4912,17 +4707,15 @@ ${formatTypes(code)}
4912
4707
  */
4913
4708
  async build(inlineConfig = { command: "build" }) {
4914
4709
  inlineConfig.command ??= "build";
4710
+ await this.context.setInlineConfig(inlineConfig);
4915
4711
  await this.context.generateChecksum();
4916
4712
  if (this.context.meta.checksum !== this.context.persistedMeta?.checksum || this.context.config.skipCache) {
4917
4713
  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.");
4918
4714
  await this.prepare(inlineConfig);
4919
- } else {
4920
- this.context.config.inlineConfig = inlineConfig;
4921
- await this.context.setup();
4922
4715
  }
4923
- if (this.context.config.singleBuild) await this.#handleBuild(await this.#context.toEnvironment());
4924
- else await this.#executeEnvironments(async (context) => {
4925
- await this.#handleBuild(context);
4716
+ if (this.context.config.singleBuild) await this.handleBuild(await this.#context.toEnvironment());
4717
+ else await this.executeEnvironments(async (context) => {
4718
+ await this.handleBuild(context);
4926
4719
  });
4927
4720
  }
4928
4721
  /**
@@ -4933,8 +4726,9 @@ ${formatTypes(code)}
4933
4726
  */
4934
4727
  async docs(inlineConfig = { command: "docs" }) {
4935
4728
  inlineConfig.command ??= "docs";
4729
+ await this.context.setInlineConfig(inlineConfig);
4936
4730
  await this.prepare(inlineConfig);
4937
- await this.#executeEnvironments(async (context) => {
4731
+ await this.executeEnvironments(async (context) => {
4938
4732
  context.debug("Writing documentation for the Powerlines project artifacts.");
4939
4733
  await this.callHook("docs", { environment: context });
4940
4734
  });
@@ -4949,8 +4743,9 @@ ${formatTypes(code)}
4949
4743
  */
4950
4744
  async deploy(inlineConfig = { command: "deploy" }) {
4951
4745
  inlineConfig.command ??= "deploy";
4746
+ await this.context.setInlineConfig(inlineConfig);
4952
4747
  await this.prepare(inlineConfig);
4953
- await this.#executeEnvironments(async (context) => {
4748
+ await this.executeEnvironments(async (context) => {
4954
4749
  await this.callHook("deploy", { environment: context });
4955
4750
  });
4956
4751
  }
@@ -4963,7 +4758,7 @@ ${formatTypes(code)}
4963
4758
  * @returns A promise that resolves when the finalization process has completed
4964
4759
  */
4965
4760
  async finalize() {
4966
- await this.#executeEnvironments(async (context) => {
4761
+ await this.executeEnvironments(async (context) => {
4967
4762
  await this.callHook("finalize", { environment: context });
4968
4763
  await context.fs.dispose();
4969
4764
  if (existsSync(context.cachePath) && !(await listFiles(joinPaths(context.cachePath, "**/*")))?.length) await removeDirectory(context.cachePath);
@@ -4995,13 +4790,40 @@ ${formatTypes(code)}
4995
4790
  this.#context = context;
4996
4791
  }
4997
4792
  /**
4793
+ * Initialize the execution API with the provided configuration options
4794
+ */
4795
+ async init() {
4796
+ this.#context.$$internal = {
4797
+ api: this,
4798
+ addPlugin: this.addPlugin.bind(this)
4799
+ };
4800
+ const timer = this.#context.timer("Initialization");
4801
+ for (const plugin of this.#context.config.plugins.flatMap((p) => toArray(p)) ?? []) await this.addPlugin(plugin);
4802
+ if (this.#context.plugins.length === 0) this.#context.warn({
4803
+ meta: { category: "plugins" },
4804
+ message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
4805
+ });
4806
+ else this.#context.info({
4807
+ meta: { category: "plugins" },
4808
+ 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")}`
4809
+ });
4810
+ const pluginConfig = await this.callHook("config", {
4811
+ environment: await this.#context.getEnvironment(),
4812
+ sequential: true,
4813
+ result: "merge",
4814
+ merge: mergeConfigs
4815
+ });
4816
+ if (pluginConfig) await this.context.setPluginConfig(pluginConfig);
4817
+ timer();
4818
+ }
4819
+ /**
4998
4820
  * Add a Powerlines plugin used in the build process
4999
4821
  *
5000
4822
  * @param config - The import path of the plugin to add
5001
4823
  */
5002
4824
  async addPlugin(config) {
5003
4825
  if (config) {
5004
- const result = await this.#initPlugin(config);
4826
+ const result = await this.initPlugin(config);
5005
4827
  if (!result) return;
5006
4828
  for (const plugin of result) {
5007
4829
  this.context.debug({
@@ -5012,33 +4834,266 @@ ${formatTypes(code)}
5012
4834
  }
5013
4835
  }
5014
4836
  }
4837
+ /**
4838
+ * Get the configured environments
4839
+ *
4840
+ * @returns The configured environments
4841
+ */
4842
+ async getEnvironments() {
4843
+ if (!this.context.config.environments || Object.keys(this.context.config.environments).length <= 1) {
4844
+ this.context.debug("No environments are configured for this Powerlines project. Using the default environment.");
4845
+ return [await this.context.getEnvironment()];
4846
+ }
4847
+ this.context.debug(`Found ${Object.keys(this.context.config.environments).length} configured environment(s) for this Powerlines project.`);
4848
+ return (await Promise.all(Object.entries(this.context.config.environments).map(async ([name, config]) => {
4849
+ if (!await this.context.getEnvironmentSafe(name)) {
4850
+ const resolvedEnvironment = await this.callHook("configEnvironment", { environment: name }, name, config);
4851
+ if (resolvedEnvironment) this.context.environments[name] = await this.context.createEnvironment(resolvedEnvironment);
4852
+ }
4853
+ return this.context.environments[name];
4854
+ }))).filter((context) => isSet(context));
4855
+ }
4856
+ /**
4857
+ * Execute a handler function for each environment
4858
+ *
4859
+ * @param handle - The handler function to execute for each environment
4860
+ */
4861
+ async executeEnvironments(handle) {
4862
+ await Promise.all((await this.getEnvironments()).map(async (context) => {
4863
+ return Promise.resolve(handle(context));
4864
+ }));
4865
+ }
4866
+ /**
4867
+ * Initialize a Powerlines plugin
4868
+ *
4869
+ * @param config - The configuration for the plugin
4870
+ * @returns The initialized plugin instance, or null if the plugin was a duplicate
4871
+ * @throws Will throw an error if the plugin cannot be found or is invalid
4872
+ */
4873
+ async initPlugin(config) {
4874
+ let awaited = config;
4875
+ if (isPromiseLike(config)) awaited = await Promise.resolve(config);
4876
+ if (!isPluginConfig(awaited)) {
4877
+ const invalid = findInvalidPluginConfig(awaited);
4878
+ 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`);
4879
+ }
4880
+ let plugins;
4881
+ if (isPlugin(awaited)) plugins = [awaited];
4882
+ else if (isFunction(awaited)) plugins = toArray(await Promise.resolve(awaited()));
4883
+ else if (isString(awaited)) {
4884
+ const resolved = await this.resolvePlugin(awaited);
4885
+ if (isFunction(resolved)) plugins = toArray(await Promise.resolve(resolved()));
4886
+ else plugins = toArray(resolved);
4887
+ } else if (Array.isArray(awaited) && awaited.every(isPlugin)) plugins = awaited;
4888
+ else if (Array.isArray(awaited) && awaited.every(isPluginConfig)) {
4889
+ plugins = [];
4890
+ for (const pluginConfig of awaited) {
4891
+ const initialized = await this.initPlugin(pluginConfig);
4892
+ if (initialized) plugins.push(...initialized);
4893
+ }
4894
+ } else if (isPluginConfigTuple(awaited) || isPluginConfigObject(awaited)) {
4895
+ let pluginConfig;
4896
+ let pluginOptions;
4897
+ if (isPluginConfigTuple(awaited)) {
4898
+ pluginConfig = awaited[0];
4899
+ pluginOptions = awaited?.length === 2 ? awaited[1] : void 0;
4900
+ } else {
4901
+ pluginConfig = awaited.plugin;
4902
+ pluginOptions = awaited.options;
4903
+ }
4904
+ if (isSetString(pluginConfig)) {
4905
+ const resolved = await this.resolvePlugin(pluginConfig);
4906
+ if (isFunction(resolved)) plugins = toArray(await Promise.resolve(pluginOptions ? resolved(pluginOptions) : resolved()));
4907
+ else plugins = toArray(resolved);
4908
+ } else if (isFunction(pluginConfig)) plugins = toArray(await Promise.resolve(pluginConfig(pluginOptions)));
4909
+ else if (Array.isArray(pluginConfig) && pluginConfig.every(isPlugin)) plugins = pluginConfig;
4910
+ else if (isPlugin(pluginConfig)) plugins = toArray(pluginConfig);
4911
+ }
4912
+ if (!plugins) throw new Error(`The plugin configuration ${JSON.stringify(awaited)} is invalid. This configuration must point to a valid Powerlines plugin module.`);
4913
+ 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.`);
4914
+ const result = [];
4915
+ 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.`);
4916
+ else {
4917
+ result.push(plugin);
4918
+ this.context.trace(`Initializing the ${chalk.bold.cyanBright(plugin.name)} plugin...`);
4919
+ }
4920
+ return result;
4921
+ }
4922
+ async resolvePlugin(pluginPath) {
4923
+ if (pluginPath.startsWith("@") && pluginPath.split("/").filter(Boolean).length > 2) {
4924
+ const splits = pluginPath.split("/").filter(Boolean);
4925
+ pluginPath = `${splits[0]}/${splits[1]}`;
4926
+ }
4927
+ const isInstalled = isPackageExists(pluginPath, { paths: [this.context.config.cwd, this.context.config.root] });
4928
+ if (!isInstalled && this.context.config.autoInstall) {
4929
+ this.#context.warn(`The plugin package "${pluginPath}" is not installed. It will be installed automatically.`);
4930
+ const result = await install(pluginPath, { cwd: this.context.config.root });
4931
+ if (isNumber(result.exitCode) && result.exitCode > 0) {
4932
+ this.#context.error(result.stderr);
4933
+ throw new Error(`An error occurred while installing the build plugin package "${pluginPath}" `);
4934
+ }
4935
+ }
4936
+ try {
4937
+ const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(joinPaths(pluginPath, "plugin")));
4938
+ const result = module.plugin ?? module.default;
4939
+ if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4940
+ return result;
4941
+ } catch (error) {
4942
+ try {
4943
+ const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(pluginPath));
4944
+ const result = module.plugin ?? module.default;
4945
+ if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4946
+ return result;
4947
+ } catch {
4948
+ if (!isInstalled) throw new Error(`The plugin package "${pluginPath}" is not installed. Please install the package using the command: "npm install ${pluginPath} --save-dev"`);
4949
+ else throw new Error(`An error occurred while importing the build plugin package "${pluginPath}":
4950
+ ${isError(error) ? error.message : String(error)}
4951
+
4952
+ 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\`.`);
4953
+ }
4954
+ }
4955
+ }
4956
+ async handleBuild(context) {
4957
+ await this.callHook("build", {
4958
+ environment: context,
4959
+ order: "pre"
4960
+ });
4961
+ context.debug("Formatting the generated entry files before the build process starts.");
4962
+ await formatFolder(context, context.entryPath);
4963
+ await this.callHook("build", {
4964
+ environment: context,
4965
+ order: "normal"
4966
+ });
4967
+ if (context.config.output.copy) {
4968
+ context.debug("Copying project's files from build output directory.");
4969
+ 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");
4970
+ const sourcePath = context.config.output.path;
4971
+ if (existsSync(sourcePath) && sourcePath !== destinationPath) {
4972
+ context.debug(`Copying files from project's build output directory (${context.config.output.path}) to the project's copy/publish directory (${destinationPath}).`);
4973
+ await copyFiles(sourcePath, destinationPath);
4974
+ } 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.`);
4975
+ if (context.config.output.copy.assets && Array.isArray(context.config.output.copy.assets)) await Promise.all(context.config.output.copy.assets.map(async (asset) => {
4976
+ 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(", ")})` : ""}`);
4977
+ await context.fs.copy(asset, asset.output);
4978
+ }));
4979
+ } else context.debug("No copy configuration found for the project output. Skipping the copying of build output files.");
4980
+ await this.callHook("build", {
4981
+ environment: context,
4982
+ order: "post"
4983
+ });
4984
+ }
4985
+ /**
4986
+ * Generate the Powerlines TypeScript declaration file
4987
+ *
4988
+ * @remarks
4989
+ * This method will generate the TypeScript declaration file for the Powerlines project, including any types provided by plugins.
4990
+ *
4991
+ * @param context - The environment context to use for generating the TypeScript declaration file
4992
+ * @returns A promise that resolves when the TypeScript declaration file has been generated
4993
+ */
4994
+ async handleTypes(context) {
4995
+ context.debug(`Preparing the TypeScript definitions for the Powerlines project.`);
4996
+ if (context.fs.existsSync(context.typesPath)) await context.fs.remove(context.typesPath);
4997
+ if (!await resolvePackage("typescript")) throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed.");
4998
+ context.debug("Running TypeScript compiler for built-in runtime module files.");
4999
+ let { code, directives } = await emitBuiltinTypes(context, (await context.getBuiltins()).reduce((ret, builtin) => {
5000
+ const formatted = replacePath(builtin.path, context.config.cwd);
5001
+ if (!ret.includes(formatted)) ret.push(formatted);
5002
+ return ret;
5003
+ }, []));
5004
+ context.debug(`Generating TypeScript declaration file ${context.typesPath}.`);
5005
+ const merge = async (currentResult, previousResult) => {
5006
+ if (!isSetString(currentResult) && !isSetObject(currentResult) && !isSetString(previousResult) && !isSetObject(previousResult)) return {
5007
+ code,
5008
+ directives
5009
+ };
5010
+ const previous = (await format(context, context.typesPath, isSetString(previousResult) ? previousResult : isSetObject(previousResult) ? previousResult.code : "")).trim().replace(code, "").trim();
5011
+ const current = (await format(context, context.typesPath, isSetString(currentResult) ? currentResult : isSetObject(currentResult) ? currentResult.code : "")).trim().replace(previous, "").trim().replace(code, "").trim();
5012
+ return {
5013
+ directives: [...isSetObject(currentResult) && currentResult.directives ? currentResult.directives : [], ...isSetObject(previousResult) && previousResult.directives ? previousResult.directives : []],
5014
+ code: await format(context, context.typesPath, `${!previous.includes(getTypescriptFileHeader(context)) && !current.includes(getTypescriptFileHeader(context)) ? `${code}\n` : ""}${previous}\n${current}`.trim())
5015
+ };
5016
+ };
5017
+ const asNextParam = (previousResult) => isObject(previousResult) ? previousResult.code : previousResult;
5018
+ let result = await this.callHook("types", {
5019
+ environment: context,
5020
+ sequential: true,
5021
+ order: "pre",
5022
+ result: "merge",
5023
+ merge,
5024
+ asNextParam
5025
+ }, code);
5026
+ if (result) {
5027
+ if (isSetObject(result)) {
5028
+ code = result.code;
5029
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
5030
+ } else if (isSetString(result)) code = result;
5031
+ }
5032
+ result = await this.callHook("types", {
5033
+ environment: context,
5034
+ sequential: true,
5035
+ order: "normal",
5036
+ result: "merge",
5037
+ merge,
5038
+ asNextParam
5039
+ }, code);
5040
+ if (result) {
5041
+ if (isSetObject(result)) {
5042
+ code = result.code;
5043
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
5044
+ } else if (isSetString(result)) code = result;
5045
+ }
5046
+ result = await this.callHook("types", {
5047
+ environment: context,
5048
+ sequential: true,
5049
+ order: "post",
5050
+ result: "merge",
5051
+ merge,
5052
+ asNextParam
5053
+ }, code);
5054
+ if (result) {
5055
+ if (isSetObject(result)) {
5056
+ code = result.code;
5057
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
5058
+ } else if (isSetString(result)) code = result;
5059
+ }
5060
+ if (isSetString(code?.trim()) || directives.length > 0) await context.fs.write(context.typesPath, `${directives.length > 0 ? `${directives.map((directive) => `/// <reference types="${directive}" />`).join("\n")}
5061
+
5062
+ ` : ""}${getTypescriptFileHeader(context, {
5063
+ directive: null,
5064
+ prettierIgnore: false
5065
+ })}
5066
+
5067
+ ${formatTypes(code)}
5068
+ `);
5069
+ }
5015
5070
  };
5016
5071
 
5017
5072
  //#endregion
5018
5073
  //#region src/_internal/worker.ts
5019
5074
  async function clean({ options, initialConfig, inlineConfig }) {
5020
- await (await PowerlinesExecution.init(options, initialConfig)).clean(inlineConfig);
5075
+ await (await PowerlinesExecution.from(options, initialConfig)).clean(inlineConfig);
5021
5076
  }
5022
5077
  async function prepare({ options, initialConfig, inlineConfig }) {
5023
- await (await PowerlinesExecution.init(options, initialConfig)).prepare(inlineConfig);
5078
+ await (await PowerlinesExecution.from(options, initialConfig)).prepare(inlineConfig);
5024
5079
  }
5025
5080
  async function types({ options, initialConfig, inlineConfig }) {
5026
- await (await PowerlinesExecution.init(options, initialConfig)).types(inlineConfig);
5081
+ await (await PowerlinesExecution.from(options, initialConfig)).types(inlineConfig);
5027
5082
  }
5028
5083
  async function lint({ options, initialConfig, inlineConfig }) {
5029
- await (await PowerlinesExecution.init(options, initialConfig)).lint(inlineConfig);
5084
+ await (await PowerlinesExecution.from(options, initialConfig)).lint(inlineConfig);
5030
5085
  }
5031
5086
  async function test({ options, initialConfig, inlineConfig }) {
5032
- await (await PowerlinesExecution.init(options, initialConfig)).test(inlineConfig);
5087
+ await (await PowerlinesExecution.from(options, initialConfig)).test(inlineConfig);
5033
5088
  }
5034
5089
  async function build({ options, initialConfig, inlineConfig }) {
5035
- await (await PowerlinesExecution.init(options, initialConfig)).build(inlineConfig);
5090
+ await (await PowerlinesExecution.from(options, initialConfig)).build(inlineConfig);
5036
5091
  }
5037
5092
  async function docs({ options, initialConfig, inlineConfig }) {
5038
- await (await PowerlinesExecution.init(options, initialConfig)).docs(inlineConfig);
5093
+ await (await PowerlinesExecution.from(options, initialConfig)).docs(inlineConfig);
5039
5094
  }
5040
5095
  async function deploy({ options, initialConfig, inlineConfig }) {
5041
- await (await PowerlinesExecution.init(options, initialConfig)).deploy(inlineConfig);
5096
+ await (await PowerlinesExecution.from(options, initialConfig)).deploy(inlineConfig);
5042
5097
  }
5043
5098
 
5044
5099
  //#endregion