@powerlines/engine 0.45.2 → 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 -788
  2. package/dist/_internal/worker.mjs +847 -791
  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-BRzNYU9u.mjs → execution-context-Cp32TarF.mjs} +421 -363
  28. package/dist/execution-context-Cp32TarF.mjs.map +1 -0
  29. package/dist/{execution-context-BlWK2rTX.cjs → execution-context-DgqxcDDx.cjs} +419 -361
  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-BRzNYU9u.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
- this.config.plugins = (this.config.initialConfig.plugins ?? []).concat(this.config.userConfig.plugins ?? [], this.config.inlineConfig.plugins ?? []);
3127
- await this.innerSetup();
3128
3138
  }
3129
3139
  /**
3130
- * The resolved configuration for this context
3131
- */
3132
- resolvedConfig = {};
3133
- /**
3134
- * 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.
3135
- *
3136
- * @remarks
3137
- * 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.
3138
3141
  *
3139
- * @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.
3140
3144
  */
3141
- copyTo(context) {
3142
- for (const [key, value] of Object.entries(this)) if (!SKIP_CLONING_PROPS.includes(key)) if (isObject(value) || Array.isArray(value)) context[key] = deepClone(value);
3143
- else context[key] = value;
3144
- context.initialConfig = deepClone(this.initialConfig);
3145
- context.initialOptions = deepClone(this.initialOptions);
3146
- context.options = deepClone(this.options);
3147
- context.dependencies = deepClone(this.dependencies);
3148
- context.devDependencies = deepClone(this.devDependencies);
3149
- context.persistedMeta = this.persistedMeta ? deepClone(this.persistedMeta) : void 0;
3150
- context.packageJson = deepClone(this.packageJson);
3151
- context.projectJson = this.projectJson ? deepClone(this.projectJson) : void 0;
3152
- context.tsconfig ??= deepClone(this.tsconfig);
3153
- context.resolver ??= this.resolver;
3154
- context.fs ??= this.#fs;
3155
- context.$$internal = this.$$internal;
3156
- 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();
3157
3152
  }
3158
3153
  /**
3159
3154
  * Initialize the context with the provided configuration options
3160
- *
3161
- * @remarks
3162
- * 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.
3163
- *
3164
- * @param options - The configuration options to initialize the context with
3165
- */
3166
- async init(options, initialConfig) {
3167
- await super.init(options, initialConfig ?? {});
3168
- this.options.executionId = options.executionId ?? this.options.executionId;
3169
- this.options.executionIndex = options.executionIndex ?? this.options.executionIndex ?? 0;
3170
- const projectJsonPath = joinPaths$1(this.options.cwd, this.options.root, "project.json");
3171
- if (existsSync(projectJsonPath)) this.projectJson = await readJsonFile(projectJsonPath);
3172
- const packageJsonPath = joinPaths$1(this.options.cwd, this.options.root, "package.json");
3173
- if (existsSync(packageJsonPath)) {
3174
- this.packageJson = await readJsonFile(packageJsonPath);
3175
- this.options.organization ??= isSetObject(this.packageJson?.author) ? kebabCase(this.packageJson?.author?.name) : kebabCase(this.packageJson?.author);
3176
- }
3177
- this.#checksum = await this.generateChecksum(joinPaths$1(this.options.cwd, this.options.root));
3178
- 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 : {};
3179
- this.resolvedConfig = {
3180
- cwd: this.options.cwd,
3181
- root: this.options.root,
3182
- ...this.initialOptions,
3183
- ...initialConfig,
3184
- ...userConfig,
3185
- inlineConfig: {},
3186
- pluginConfig: {},
3187
- initialConfig,
3188
- userConfig
3189
- };
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);
3190
3164
  }
3191
3165
  /**
3192
3166
  * Initialize the context with the provided configuration options
3193
3167
  */
3194
- async innerSetup() {
3195
- const logger = this.extendLogger({ category: "config" });
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,12 +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;
3744
+ static async fromInitialConfig(options, initialConfig) {
3745
+ const context = new PowerlinesExecutionContext(options, initialConfig);
3746
+ await context.init();
3692
3747
  return context;
3693
3748
  }
3694
3749
  /**
@@ -3697,19 +3752,10 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3697
3752
  * @param options - The options for resolving the context.
3698
3753
  * @returns A promise that resolves to the new context.
3699
3754
  */
3700
- static async inline(options, initialConfig, inlineConfig) {
3701
- const context = new PowerlinesExecutionContext(options);
3702
- await context.init(options, initialConfig);
3703
- context.config.inlineConfig = inlineConfig;
3704
- if (context.config.inlineConfig.command === "new") {
3705
- const workspacePackageJsonPath = joinPaths$1(context.config.cwd, "package.json");
3706
- if (!existsSync(workspacePackageJsonPath)) throw new Error(`The workspace package.json file could not be found at ${workspacePackageJsonPath}`);
3707
- context.packageJson = await readJsonFile(workspacePackageJsonPath);
3708
- }
3709
- await context.setup();
3710
- const powerlinesPath = await resolvePackage("powerlines");
3711
- if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
3712
- 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);
3713
3759
  return context;
3714
3760
  }
3715
3761
  /**
@@ -3721,7 +3767,7 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3721
3767
  * @internal
3722
3768
  */
3723
3769
  get $$internal() {
3724
- return super.$$internal;
3770
+ return this.#internal;
3725
3771
  }
3726
3772
  /**
3727
3773
  * Internal context fields and methods
@@ -3732,8 +3778,8 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3732
3778
  * @internal
3733
3779
  */
3734
3780
  set $$internal(value) {
3735
- super.$$internal = value;
3736
- 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;
3737
3783
  }
3738
3784
  /**
3739
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.
@@ -3754,9 +3800,26 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3754
3800
  * Creates a new instance.
3755
3801
  *
3756
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.
3757
3815
  */
3758
- constructor(options) {
3759
- 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
+ }
3760
3823
  }
3761
3824
  /**
3762
3825
  * Create a new logger instance
@@ -3786,31 +3849,20 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3786
3849
  });
3787
3850
  }
3788
3851
  /**
3789
- * 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.
3790
- *
3791
- * @remarks
3792
- * 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.
3793
- *
3794
- * @returns A promise that resolves to the cloned context.
3795
- */
3796
- async clone() {
3797
- const clone = await PowerlinesExecutionContext.init(this.options, this.initialConfig);
3798
- clone.config.userConfig = deepClone(this.config.userConfig);
3799
- clone.config.initialConfig = deepClone(this.config.initialConfig);
3800
- clone.config.inlineConfig = deepClone(this.config.inlineConfig);
3801
- clone.config.pluginConfig = deepClone(this.config.pluginConfig);
3802
- await clone.setup();
3803
- clone.$$internal = this.$$internal;
3804
- return this.copyTo(clone);
3805
- }
3806
- /**
3807
3852
  * A function to copy the context and update the fields for a specific environment
3808
3853
  *
3809
3854
  * @param environment - The environment configuration to use.
3810
3855
  * @returns A new context instance with the updated environment.
3811
3856
  */
3812
- async in(environment) {
3813
- 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;
3814
3866
  context.plugins = [];
3815
3867
  for (const plugin of this.plugins) await context.addPlugin(plugin);
3816
3868
  return context;
@@ -3818,10 +3870,10 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3818
3870
  /**
3819
3871
  * Update the context using a new inline configuration options
3820
3872
  */
3821
- async setup() {
3822
- await super.setup();
3873
+ async resolveConfig() {
3874
+ await super.resolveConfig();
3823
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) => {
3824
- this.#environments[env.name] = await this.in(env);
3876
+ this.#environments[env.name] = await this.createEnvironment(env);
3825
3877
  }));
3826
3878
  }
3827
3879
  /**
@@ -3846,12 +3898,20 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3846
3898
  if (name) environment = this.environments[name];
3847
3899
  if (Object.keys(this.environments).length === 1) {
3848
3900
  environment = this.environments[Object.keys(this.environments)[0]];
3849
- 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
+ });
3850
3905
  }
3851
3906
  if (!environment) {
3852
3907
  if (name) throw new Error(`Environment "${name}" not found.`);
3853
- environment = await this.in(createDefaultEnvironment(this.config));
3854
- 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
+ });
3855
3915
  }
3856
3916
  return environment;
3857
3917
  }
@@ -3879,8 +3939,11 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3879
3939
  async toEnvironment() {
3880
3940
  let environment;
3881
3941
  if (Object.keys(this.environments).length > 1) {
3882
- environment = await this.in(createEnvironment(GLOBAL_ENVIRONMENT, this.config));
3883
- 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
+ });
3884
3947
  } else environment = await this.getEnvironment();
3885
3948
  return environment;
3886
3949
  }
@@ -4300,11 +4363,11 @@ async function installDependencies(context) {
4300
4363
  //#endregion
4301
4364
  //#region src/_internal/helpers/resolve-tsconfig.ts
4302
4365
  function getTsconfigDtsPath(context) {
4303
- 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));
4304
4367
  }
4305
4368
  async function resolveTsconfigChanges(context) {
4306
- const tsconfig = getParsedTypeScriptConfig(context.options.cwd, context.options.root, context.config.tsconfig, context.config.tsconfigRaw);
4307
- 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));
4308
4371
  tsconfigJson.compilerOptions ??= {};
4309
4372
  if (context.config.output.dts !== false) {
4310
4373
  const dtsRelativePath = getTsconfigDtsPath(context);
@@ -4394,342 +4457,76 @@ var PowerlinesExecution = class PowerlinesExecution {
4394
4457
  * The Powerlines context
4395
4458
  */
4396
4459
  #context;
4397
- async #handleBuild(context) {
4398
- await this.callHook("build", {
4399
- environment: context,
4400
- order: "pre"
4401
- });
4402
- context.debug("Formatting the generated entry files before the build process starts.");
4403
- await formatFolder(context, context.entryPath);
4404
- await this.callHook("build", {
4405
- environment: context,
4406
- order: "normal"
4407
- });
4408
- if (context.config.output.copy) {
4409
- context.debug("Copying project's files from build output directory.");
4410
- 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");
4411
- const sourcePath = context.config.output.path;
4412
- if (existsSync(sourcePath) && sourcePath !== destinationPath) {
4413
- context.debug(`Copying files from project's build output directory (${context.config.output.path}) to the project's copy/publish directory (${destinationPath}).`);
4414
- await copyFiles(sourcePath, destinationPath);
4415
- } 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.`);
4416
- if (context.config.output.copy.assets && Array.isArray(context.config.output.copy.assets)) await Promise.all(context.config.output.copy.assets.map(async (asset) => {
4417
- 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(", ")})` : ""}`);
4418
- await context.fs.copy(asset, asset.output);
4419
- }));
4420
- } else context.debug("No copy configuration found for the project output. Skipping the copying of build output files.");
4421
- await this.callHook("build", {
4422
- environment: context,
4423
- order: "post"
4424
- });
4425
- }
4426
4460
  /**
4427
- * Get the configured environments
4461
+ * Initialize a Powerlines API instance
4428
4462
  *
4429
- * @returns The configured environments
4463
+ * @param options - The options to initialize the API with
4464
+ * @returns A new instance of the Powerlines API
4430
4465
  */
4431
- async #getEnvironments() {
4432
- if (!this.context.config.environments || Object.keys(this.context.config.environments).length <= 1) {
4433
- this.context.debug("No environments are configured for this Powerlines project. Using the default environment.");
4434
- return [await this.context.getEnvironment()];
4435
- }
4436
- this.context.debug(`Found ${Object.keys(this.context.config.environments).length} configured environment(s) for this Powerlines project.`);
4437
- return (await Promise.all(Object.entries(this.context.config.environments).map(async ([name, config]) => {
4438
- if (!await this.context.getEnvironmentSafe(name)) {
4439
- const resolvedEnvironment = await this.callHook("configEnvironment", { environment: name }, name, config);
4440
- if (resolvedEnvironment) this.context.environments[name] = await this.context.in(resolvedEnvironment);
4441
- }
4442
- return this.context.environments[name];
4443
- }))).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;
4444
4470
  }
4445
4471
  /**
4446
- * Execute a handler function for each environment
4447
- *
4448
- * @param handle - The handler function to execute for each environment
4472
+ * The Powerlines context
4449
4473
  */
4450
- async #executeEnvironments(handle) {
4451
- await Promise.all((await this.#getEnvironments()).map(async (context) => {
4452
- return Promise.resolve(handle(context));
4453
- }));
4474
+ get context() {
4475
+ return this.#context;
4454
4476
  }
4455
4477
  /**
4456
- * Initialize a Powerlines plugin
4478
+ * Generate the Powerlines typescript declaration file
4457
4479
  *
4458
- * @param config - The configuration for the plugin
4459
- * @returns The initialized plugin instance, or null if the plugin was a duplicate
4460
- * @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
4461
4484
  */
4462
- async #initPlugin(config) {
4463
- let awaited = config;
4464
- if (isPromiseLike(config)) awaited = await Promise.resolve(config);
4465
- if (!isPluginConfig(awaited)) {
4466
- const invalid = findInvalidPluginConfig(awaited);
4467
- 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`);
4468
- }
4469
- let plugins;
4470
- if (isPlugin(awaited)) plugins = [awaited];
4471
- else if (isFunction(awaited)) plugins = toArray(await Promise.resolve(awaited()));
4472
- else if (isString(awaited)) {
4473
- const resolved = await this.#resolvePlugin(awaited);
4474
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(resolved()));
4475
- else plugins = toArray(resolved);
4476
- } else if (Array.isArray(awaited) && awaited.every(isPlugin)) plugins = awaited;
4477
- else if (Array.isArray(awaited) && awaited.every(isPluginConfig)) {
4478
- plugins = [];
4479
- for (const pluginConfig of awaited) {
4480
- const initialized = await this.#initPlugin(pluginConfig);
4481
- if (initialized) plugins.push(...initialized);
4482
- }
4483
- } else if (isPluginConfigTuple(awaited) || isPluginConfigObject(awaited)) {
4484
- let pluginConfig;
4485
- let pluginOptions;
4486
- if (isPluginConfigTuple(awaited)) {
4487
- pluginConfig = awaited[0];
4488
- pluginOptions = awaited?.length === 2 ? awaited[1] : void 0;
4489
- } else {
4490
- pluginConfig = awaited.plugin;
4491
- pluginOptions = awaited.options;
4492
- }
4493
- if (isSetString(pluginConfig)) {
4494
- const resolved = await this.#resolvePlugin(pluginConfig);
4495
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(pluginOptions ? resolved(pluginOptions) : resolved()));
4496
- else plugins = toArray(resolved);
4497
- } else if (isFunction(pluginConfig)) plugins = toArray(await Promise.resolve(pluginConfig(pluginOptions)));
4498
- else if (Array.isArray(pluginConfig) && pluginConfig.every(isPlugin)) plugins = pluginConfig;
4499
- else if (isPlugin(pluginConfig)) plugins = toArray(pluginConfig);
4500
- }
4501
- if (!plugins) throw new Error(`The plugin configuration ${JSON.stringify(awaited)} is invalid. This configuration must point to a valid Powerlines plugin module.`);
4502
- 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.`);
4503
- const result = [];
4504
- 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.`);
4505
- else {
4506
- result.push(plugin);
4507
- this.context.trace(`Initializing the ${chalk.bold.cyanBright(plugin.name)} plugin...`);
4508
- }
4509
- return result;
4510
- }
4511
- async #resolvePlugin(pluginPath) {
4512
- if (pluginPath.startsWith("@") && pluginPath.split("/").filter(Boolean).length > 2) {
4513
- const splits = pluginPath.split("/").filter(Boolean);
4514
- pluginPath = `${splits[0]}/${splits[1]}`;
4515
- }
4516
- const isInstalled = isPackageExists(pluginPath, { paths: [this.context.config.cwd, this.context.config.root] });
4517
- if (!isInstalled && this.context.config.autoInstall) {
4518
- this.#context.warn(`The plugin package "${pluginPath}" is not installed. It will be installed automatically.`);
4519
- const result = await install(pluginPath, { cwd: this.context.config.root });
4520
- if (isNumber(result.exitCode) && result.exitCode > 0) {
4521
- this.#context.error(result.stderr);
4522
- throw new Error(`An error occurred while installing the build plugin package "${pluginPath}" `);
4523
- }
4524
- }
4525
- try {
4526
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(joinPaths(pluginPath, "plugin")));
4527
- const result = module.plugin ?? module.default;
4528
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4529
- return result;
4530
- } catch (error) {
4531
- try {
4532
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(pluginPath));
4533
- const result = module.plugin ?? module.default;
4534
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4535
- return result;
4536
- } catch {
4537
- if (!isInstalled) throw new Error(`The plugin package "${pluginPath}" is not installed. Please install the package using the command: "npm install ${pluginPath} --save-dev"`);
4538
- else throw new Error(`An error occurred while importing the build plugin package "${pluginPath}":
4539
- ${isError(error) ? error.message : String(error)}
4540
-
4541
- 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));
4542
4520
  }
4543
- }
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
+ });
4544
4527
  }
4545
4528
  /**
4546
- * Generate the Powerlines TypeScript declaration file
4547
- *
4548
- * @remarks
4549
- * This method will generate the TypeScript declaration file for the Powerlines project, including any types provided by plugins.
4550
- *
4551
- * @param context - The environment context to use for generating the TypeScript declaration file
4552
- * @returns A promise that resolves when the TypeScript declaration file has been generated
4553
- */
4554
- async #types(context) {
4555
- context.debug(`Preparing the TypeScript definitions for the Powerlines project.`);
4556
- if (context.fs.existsSync(context.typesPath)) await context.fs.remove(context.typesPath);
4557
- if (!await resolvePackage("typescript")) throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed.");
4558
- context.debug("Running TypeScript compiler for built-in runtime module files.");
4559
- let { code, directives } = await emitBuiltinTypes(context, (await context.getBuiltins()).reduce((ret, builtin) => {
4560
- const formatted = replacePath(builtin.path, context.config.cwd);
4561
- if (!ret.includes(formatted)) ret.push(formatted);
4562
- return ret;
4563
- }, []));
4564
- context.debug(`Generating TypeScript declaration file ${context.typesPath}.`);
4565
- const merge = async (currentResult, previousResult) => {
4566
- if (!isSetString(currentResult) && !isSetObject(currentResult) && !isSetString(previousResult) && !isSetObject(previousResult)) return {
4567
- code,
4568
- directives
4569
- };
4570
- const previous = (await format(context, context.typesPath, isSetString(previousResult) ? previousResult : isSetObject(previousResult) ? previousResult.code : "")).trim().replace(code, "").trim();
4571
- const current = (await format(context, context.typesPath, isSetString(currentResult) ? currentResult : isSetObject(currentResult) ? currentResult.code : "")).trim().replace(previous, "").trim().replace(code, "").trim();
4572
- return {
4573
- directives: [...isSetObject(currentResult) && currentResult.directives ? currentResult.directives : [], ...isSetObject(previousResult) && previousResult.directives ? previousResult.directives : []],
4574
- code: await format(context, context.typesPath, `${!previous.includes(getTypescriptFileHeader(context)) && !current.includes(getTypescriptFileHeader(context)) ? `${code}\n` : ""}${previous}\n${current}`.trim())
4575
- };
4576
- };
4577
- const asNextParam = (previousResult) => isObject(previousResult) ? previousResult.code : previousResult;
4578
- let result = await this.callHook("types", {
4579
- environment: context,
4580
- sequential: true,
4581
- order: "pre",
4582
- result: "merge",
4583
- merge,
4584
- asNextParam
4585
- }, code);
4586
- if (result) {
4587
- if (isSetObject(result)) {
4588
- code = result.code;
4589
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4590
- } else if (isSetString(result)) code = result;
4591
- }
4592
- result = await this.callHook("types", {
4593
- environment: context,
4594
- sequential: true,
4595
- order: "normal",
4596
- result: "merge",
4597
- merge,
4598
- asNextParam
4599
- }, code);
4600
- if (result) {
4601
- if (isSetObject(result)) {
4602
- code = result.code;
4603
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4604
- } else if (isSetString(result)) code = result;
4605
- }
4606
- result = await this.callHook("types", {
4607
- environment: context,
4608
- sequential: true,
4609
- order: "post",
4610
- result: "merge",
4611
- merge,
4612
- asNextParam
4613
- }, code);
4614
- if (result) {
4615
- if (isSetObject(result)) {
4616
- code = result.code;
4617
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4618
- } else if (isSetString(result)) code = result;
4619
- }
4620
- if (isSetString(code?.trim()) || directives.length > 0) await context.fs.write(context.typesPath, `${directives.length > 0 ? `${directives.map((directive) => `/// <reference types="${directive}" />`).join("\n")}
4621
-
4622
- ` : ""}${getTypescriptFileHeader(context, {
4623
- directive: null,
4624
- prettierIgnore: false
4625
- })}
4626
-
4627
- ${formatTypes(code)}
4628
- `);
4629
- }
4630
- /**
4631
- * Initialize a Powerlines API instance
4632
- *
4633
- * @param options - The options to initialize the API with
4634
- * @returns A new instance of the Powerlines API
4635
- */
4636
- static async init(options, initialConfig) {
4637
- const api = new PowerlinesExecution(await PowerlinesExecutionContext.init(options, initialConfig ?? {}));
4638
- api.#context.config.initialConfig = initialConfig ?? {};
4639
- await api.setup();
4640
- return api;
4641
- }
4642
- /**
4643
- * The Powerlines context
4644
- */
4645
- get context() {
4646
- return this.#context;
4647
- }
4648
- /**
4649
- * Initialize the execution API with the provided configuration options
4650
- */
4651
- async setup() {
4652
- await this.#context.setup();
4653
- this.#context.$$internal = {
4654
- api: this,
4655
- addPlugin: this.addPlugin.bind(this)
4656
- };
4657
- const timer = this.#context.timer("Initialization");
4658
- for (const plugin of this.#context.config.plugins.flatMap((p) => toArray(p)) ?? []) await this.addPlugin(plugin);
4659
- if (this.#context.plugins.length === 0) this.#context.warn({
4660
- meta: { category: "plugins" },
4661
- message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
4662
- });
4663
- else this.#context.info({
4664
- meta: { category: "plugins" },
4665
- 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")}`
4666
- });
4667
- const pluginConfig = await this.callHook("config", {
4668
- environment: await this.#context.getEnvironment(),
4669
- sequential: true,
4670
- result: "merge",
4671
- merge: mergeConfigs
4672
- });
4673
- if (pluginConfig) {
4674
- this.#context.config.pluginConfig = pluginConfig;
4675
- await this.#context.setup();
4676
- }
4677
- timer();
4678
- }
4679
- /**
4680
- * Generate the Powerlines typescript declaration file
4681
- *
4682
- * @remarks
4683
- * 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.
4684
- *
4685
- * @param inlineConfig - The inline configuration for the types command
4686
- */
4687
- async types(inlineConfig = { command: "types" }) {
4688
- this.context.debug(" Aggregating configuration options for the Powerlines project");
4689
- inlineConfig.command ??= "types";
4690
- this.context.config.inlineConfig = inlineConfig;
4691
- await this.setup();
4692
- await this.#executeEnvironments(async (context) => {
4693
- context.debug(`Initializing the processing options for the Powerlines project.`);
4694
- await this.callHook("configResolved", {
4695
- environment: context,
4696
- order: "pre"
4697
- });
4698
- await initializeTsconfig(context);
4699
- await this.callHook("configResolved", {
4700
- environment: context,
4701
- order: "normal"
4702
- });
4703
- 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")}` : ""}`);
4704
- 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.`);
4705
- await resolveTsconfig(context);
4706
- await installDependencies(context);
4707
- await this.callHook("configResolved", {
4708
- environment: context,
4709
- order: "post"
4710
- });
4711
- context.trace(`Powerlines configuration has been resolved: \n\n${formatLogMessage({
4712
- ...context.config,
4713
- userConfig: isSetObject(context.config.userConfig) ? omit(context.config.userConfig, ["plugins"]) : void 0,
4714
- inlineConfig: isSetObject(context.config.inlineConfig) ? omit(context.config.inlineConfig, ["plugins"]) : void 0,
4715
- plugins: context.plugins.map((plugin) => plugin.plugin.name)
4716
- })}`);
4717
- if (!context.fs.existsSync(context.cachePath)) await createDirectory(context.cachePath);
4718
- if (!context.fs.existsSync(context.dataPath)) await createDirectory(context.dataPath);
4719
- if (context.config.skipCache === true || context.persistedMeta?.checksum !== context.meta.checksum) context.debug(`Using previously prepared files as the meta checksum has not changed.`);
4720
- else {
4721
- context.info(`Running \`prepare\` command as the meta checksum has changed since the last run.`);
4722
- await this.prepare(defu({ output: { types: false } }, inlineConfig));
4723
- }
4724
- await this.#types(context);
4725
- this.context.debug("Formatting files generated during the types step.");
4726
- await format(context, context.typesPath, await context.fs.read(context.typesPath) ?? "");
4727
- await writeMetaFile(context);
4728
- context.persistedMeta = context.meta;
4729
- });
4730
- }
4731
- /**
4732
- * Prepare the Powerlines API
4529
+ * Prepare the Powerlines API
4733
4530
  *
4734
4531
  * @remarks
4735
4532
  * This method will prepare the Powerlines API for use, initializing any necessary resources.
@@ -4738,9 +4535,8 @@ ${formatTypes(code)}
4738
4535
  */
4739
4536
  async prepare(inlineConfig = { command: "prepare" }) {
4740
4537
  inlineConfig.command ??= "prepare";
4741
- this.context.config.inlineConfig = inlineConfig;
4742
- await this.setup();
4743
- await this.#executeEnvironments(async (context) => {
4538
+ await this.context.setInlineConfig(inlineConfig);
4539
+ await this.executeEnvironments(async (context) => {
4744
4540
  context.debug(`Initializing the processing options for the Powerlines project.`);
4745
4541
  await this.callHook("configResolved", {
4746
4542
  environment: context,
@@ -4790,7 +4586,7 @@ ${formatTypes(code)}
4790
4586
  environment: context,
4791
4587
  order: "post"
4792
4588
  });
4793
- if (context.config.output.types !== false) await this.#types(context);
4589
+ if (context.config.output.types !== false) await this.handleTypes(context);
4794
4590
  this.context.debug("Formatting files generated during the prepare step.");
4795
4591
  await Promise.all([formatFolder(context, context.builtinsPath), formatFolder(context, context.entryPath)]);
4796
4592
  await writeMetaFile(context);
@@ -4809,7 +4605,7 @@ ${formatTypes(code)}
4809
4605
  async new(inlineConfig) {
4810
4606
  inlineConfig.command ??= "new";
4811
4607
  await this.prepare(inlineConfig);
4812
- await this.#executeEnvironments(async (context) => {
4608
+ await this.executeEnvironments(async (context) => {
4813
4609
  context.debug("Initializing the processing options for the Powerlines project.");
4814
4610
  await this.callHook("new", {
4815
4611
  environment: context,
@@ -4858,7 +4654,7 @@ ${formatTypes(code)}
4858
4654
  async clean(inlineConfig = { command: "clean" }) {
4859
4655
  inlineConfig.command ??= "clean";
4860
4656
  await this.prepare(inlineConfig);
4861
- await this.#executeEnvironments(async (context) => {
4657
+ await this.executeEnvironments(async (context) => {
4862
4658
  context.debug("Cleaning the project's dist and artifacts directories.");
4863
4659
  await context.fs.remove(joinPaths(context.config.cwd, context.config.output.path));
4864
4660
  await context.fs.remove(joinPaths(context.config.cwd, context.config.root, context.config.output.artifactsPath));
@@ -4877,7 +4673,7 @@ ${formatTypes(code)}
4877
4673
  async lint(inlineConfig = { command: "lint" }) {
4878
4674
  inlineConfig.command ??= "lint";
4879
4675
  await this.prepare(inlineConfig);
4880
- await this.#executeEnvironments(async (context) => {
4676
+ await this.executeEnvironments(async (context) => {
4881
4677
  await this.callHook("lint", {
4882
4678
  environment: context,
4883
4679
  sequential: false
@@ -4893,7 +4689,7 @@ ${formatTypes(code)}
4893
4689
  async test(inlineConfig = { command: "test" }) {
4894
4690
  inlineConfig.command ??= "test";
4895
4691
  await this.prepare(inlineConfig);
4896
- await this.#executeEnvironments(async (context) => {
4692
+ await this.executeEnvironments(async (context) => {
4897
4693
  await this.callHook("test", {
4898
4694
  environment: context,
4899
4695
  sequential: false
@@ -4911,17 +4707,15 @@ ${formatTypes(code)}
4911
4707
  */
4912
4708
  async build(inlineConfig = { command: "build" }) {
4913
4709
  inlineConfig.command ??= "build";
4710
+ await this.context.setInlineConfig(inlineConfig);
4914
4711
  await this.context.generateChecksum();
4915
4712
  if (this.context.meta.checksum !== this.context.persistedMeta?.checksum || this.context.config.skipCache) {
4916
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.");
4917
4714
  await this.prepare(inlineConfig);
4918
- } else {
4919
- this.context.config.inlineConfig = inlineConfig;
4920
- await this.context.setup();
4921
4715
  }
4922
- if (this.context.config.singleBuild) await this.#handleBuild(await this.#context.toEnvironment());
4923
- else await this.#executeEnvironments(async (context) => {
4924
- 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);
4925
4719
  });
4926
4720
  }
4927
4721
  /**
@@ -4932,8 +4726,9 @@ ${formatTypes(code)}
4932
4726
  */
4933
4727
  async docs(inlineConfig = { command: "docs" }) {
4934
4728
  inlineConfig.command ??= "docs";
4729
+ await this.context.setInlineConfig(inlineConfig);
4935
4730
  await this.prepare(inlineConfig);
4936
- await this.#executeEnvironments(async (context) => {
4731
+ await this.executeEnvironments(async (context) => {
4937
4732
  context.debug("Writing documentation for the Powerlines project artifacts.");
4938
4733
  await this.callHook("docs", { environment: context });
4939
4734
  });
@@ -4948,8 +4743,9 @@ ${formatTypes(code)}
4948
4743
  */
4949
4744
  async deploy(inlineConfig = { command: "deploy" }) {
4950
4745
  inlineConfig.command ??= "deploy";
4746
+ await this.context.setInlineConfig(inlineConfig);
4951
4747
  await this.prepare(inlineConfig);
4952
- await this.#executeEnvironments(async (context) => {
4748
+ await this.executeEnvironments(async (context) => {
4953
4749
  await this.callHook("deploy", { environment: context });
4954
4750
  });
4955
4751
  }
@@ -4962,7 +4758,7 @@ ${formatTypes(code)}
4962
4758
  * @returns A promise that resolves when the finalization process has completed
4963
4759
  */
4964
4760
  async finalize() {
4965
- await this.#executeEnvironments(async (context) => {
4761
+ await this.executeEnvironments(async (context) => {
4966
4762
  await this.callHook("finalize", { environment: context });
4967
4763
  await context.fs.dispose();
4968
4764
  if (existsSync(context.cachePath) && !(await listFiles(joinPaths(context.cachePath, "**/*")))?.length) await removeDirectory(context.cachePath);
@@ -4994,13 +4790,40 @@ ${formatTypes(code)}
4994
4790
  this.#context = context;
4995
4791
  }
4996
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
+ /**
4997
4820
  * Add a Powerlines plugin used in the build process
4998
4821
  *
4999
4822
  * @param config - The import path of the plugin to add
5000
4823
  */
5001
4824
  async addPlugin(config) {
5002
4825
  if (config) {
5003
- const result = await this.#initPlugin(config);
4826
+ const result = await this.initPlugin(config);
5004
4827
  if (!result) return;
5005
4828
  for (const plugin of result) {
5006
4829
  this.context.debug({
@@ -5011,33 +4834,266 @@ ${formatTypes(code)}
5011
4834
  }
5012
4835
  }
5013
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
+ }
5014
5070
  };
5015
5071
 
5016
5072
  //#endregion
5017
5073
  //#region src/_internal/worker.ts
5018
5074
  async function clean({ options, initialConfig, inlineConfig }) {
5019
- await (await PowerlinesExecution.init(options, initialConfig)).clean(inlineConfig);
5075
+ await (await PowerlinesExecution.from(options, initialConfig)).clean(inlineConfig);
5020
5076
  }
5021
5077
  async function prepare({ options, initialConfig, inlineConfig }) {
5022
- await (await PowerlinesExecution.init(options, initialConfig)).prepare(inlineConfig);
5078
+ await (await PowerlinesExecution.from(options, initialConfig)).prepare(inlineConfig);
5023
5079
  }
5024
5080
  async function types({ options, initialConfig, inlineConfig }) {
5025
- await (await PowerlinesExecution.init(options, initialConfig)).types(inlineConfig);
5081
+ await (await PowerlinesExecution.from(options, initialConfig)).types(inlineConfig);
5026
5082
  }
5027
5083
  async function lint({ options, initialConfig, inlineConfig }) {
5028
- await (await PowerlinesExecution.init(options, initialConfig)).lint(inlineConfig);
5084
+ await (await PowerlinesExecution.from(options, initialConfig)).lint(inlineConfig);
5029
5085
  }
5030
5086
  async function test({ options, initialConfig, inlineConfig }) {
5031
- await (await PowerlinesExecution.init(options, initialConfig)).test(inlineConfig);
5087
+ await (await PowerlinesExecution.from(options, initialConfig)).test(inlineConfig);
5032
5088
  }
5033
5089
  async function build({ options, initialConfig, inlineConfig }) {
5034
- await (await PowerlinesExecution.init(options, initialConfig)).build(inlineConfig);
5090
+ await (await PowerlinesExecution.from(options, initialConfig)).build(inlineConfig);
5035
5091
  }
5036
5092
  async function docs({ options, initialConfig, inlineConfig }) {
5037
- await (await PowerlinesExecution.init(options, initialConfig)).docs(inlineConfig);
5093
+ await (await PowerlinesExecution.from(options, initialConfig)).docs(inlineConfig);
5038
5094
  }
5039
5095
  async function deploy({ options, initialConfig, inlineConfig }) {
5040
- await (await PowerlinesExecution.init(options, initialConfig)).deploy(inlineConfig);
5096
+ await (await PowerlinesExecution.from(options, initialConfig)).deploy(inlineConfig);
5041
5097
  }
5042
5098
 
5043
5099
  //#endregion