@powerlines/engine 0.45.3 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/_internal/worker.cjs +886 -803
  2. package/dist/_internal/worker.mjs +889 -806
  3. package/dist/_internal/worker.mjs.map +1 -1
  4. package/dist/api.cjs +292 -323
  5. package/dist/api.d.cts +44 -11
  6. package/dist/api.d.cts.map +1 -1
  7. package/dist/api.d.mts +44 -11
  8. package/dist/api.d.mts.map +1 -1
  9. package/dist/api.mjs +292 -323
  10. package/dist/api.mjs.map +1 -1
  11. package/dist/{base-context-Byizvf4F.cjs → base-context-D7G_24-i.cjs} +82 -76
  12. package/dist/{base-context-BSAC5sO9.mjs → base-context-DU0NRHDt.mjs} +85 -79
  13. package/dist/base-context-DU0NRHDt.mjs.map +1 -0
  14. package/dist/context/index.cjs +3 -3
  15. package/dist/context/index.d.cts +44 -617
  16. package/dist/context/index.d.cts.map +1 -1
  17. package/dist/context/index.d.mts +44 -617
  18. package/dist/context/index.d.mts.map +1 -1
  19. package/dist/context/index.mjs +3 -3
  20. package/dist/engine-context-BuJQY312.cjs +91 -0
  21. package/dist/engine-context-BvDfqfY7.mjs +86 -0
  22. package/dist/engine-context-BvDfqfY7.mjs.map +1 -0
  23. package/dist/execution-context-BpRfsnkE.d.mts +644 -0
  24. package/dist/execution-context-BpRfsnkE.d.mts.map +1 -0
  25. package/dist/{execution-context-BYGFYty0.cjs → execution-context-BrX9i_L8.cjs} +449 -364
  26. package/dist/{execution-context-Bkxp1fML.mjs → execution-context-CgDuoi8o.mjs} +451 -366
  27. package/dist/execution-context-CgDuoi8o.mjs.map +1 -0
  28. package/dist/execution-context-CodQucFX.d.cts +644 -0
  29. package/dist/execution-context-CodQucFX.d.cts.map +1 -0
  30. package/dist/index.cjs +15 -16
  31. package/dist/index.d.cts +3 -3
  32. package/dist/index.d.cts.map +1 -1
  33. package/dist/index.d.mts +3 -3
  34. package/dist/index.d.mts.map +1 -1
  35. package/dist/index.mjs +15 -16
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/{tsconfig-QMSxSwBD.cjs → tsconfig-BUDqmOaT.cjs} +13 -13
  38. package/dist/{tsconfig-CI6bla4E.mjs → tsconfig-MeFEs21S.mjs} +14 -14
  39. package/dist/tsconfig-MeFEs21S.mjs.map +1 -0
  40. package/dist/typescript/index.cjs +1 -1
  41. package/dist/typescript/index.d.cts +6 -6
  42. package/dist/typescript/index.d.cts.map +1 -1
  43. package/dist/typescript/index.d.mts +6 -6
  44. package/dist/typescript/index.d.mts.map +1 -1
  45. package/dist/typescript/index.mjs +1 -1
  46. package/package.json +17 -17
  47. package/dist/base-context-BSAC5sO9.mjs.map +0 -1
  48. package/dist/engine-context-CI_0NWIk.cjs +0 -73
  49. package/dist/engine-context-_RMFwG4J.mjs +0 -68
  50. package/dist/engine-context-_RMFwG4J.mjs.map +0 -1
  51. package/dist/execution-context-Bkxp1fML.mjs.map +0 -1
  52. package/dist/tsconfig-CI6bla4E.mjs.map +0 -1
@@ -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
  }
@@ -2156,7 +2170,7 @@ function createResolver(options) {
2156
2170
 
2157
2171
  //#endregion
2158
2172
  //#region src/context/base-context.ts
2159
- var PowerlinesBaseContext = class PowerlinesBaseContext {
2173
+ var PowerlinesBaseContext = class {
2160
2174
  #timestamp = Date.now();
2161
2175
  /**
2162
2176
  * The path to the Powerlines package
@@ -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
  */
@@ -2208,19 +2222,6 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2208
2222
  });
2209
2223
  }
2210
2224
  /**
2211
- * 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.
2212
- *
2213
- * @remarks
2214
- * 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.
2215
- *
2216
- * @returns A promise that resolves to the cloned context.
2217
- */
2218
- async clone() {
2219
- const clone = new PowerlinesBaseContext();
2220
- await clone.init(this.options, this.initialConfig);
2221
- return clone;
2222
- }
2223
- /**
2224
2225
  * A logging function for fatal messages
2225
2226
  *
2226
2227
  * @param message - The message to log.
@@ -2300,7 +2301,7 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2300
2301
  * @returns A logger client instance that can be used to generate log messages with consistent formatting and metadata.
2301
2302
  */
2302
2303
  createLogger(options, logFn) {
2303
- return createLogger$1(this.options.name || this.options.root, {
2304
+ return createLogger$1(this.options.name || this.options.root || "powerlines", {
2304
2305
  ...this.configFile.config,
2305
2306
  ...this.options,
2306
2307
  ...options
@@ -2316,6 +2317,24 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2316
2317
  return extendLogger(this.logger, options);
2317
2318
  }
2318
2319
  /**
2320
+ * 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
2321
+ */
2322
+ initialOptions = {};
2323
+ /**
2324
+ * 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.
2325
+ */
2326
+ initialConfig = {};
2327
+ /**
2328
+ * 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.
2329
+ *
2330
+ * @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.
2331
+ * @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
2332
+ */
2333
+ constructor(options, initialConfig = {}) {
2334
+ this.initialOptions = options;
2335
+ this.initialConfig = initialConfig;
2336
+ }
2337
+ /**
2319
2338
  * 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
2339
  *
2321
2340
  * @returns A promise that resolves to the workspace configuration object, or undefined if no configuration file is found.
@@ -2327,64 +2346,41 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2327
2346
  } : void 0);
2328
2347
  }
2329
2348
  /**
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
2349
  * Initialize the context with the provided configuration options
2349
2350
  *
2350
2351
  * @remarks
2351
2352
  * 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
2353
  */
2356
- async init(options, initialConfig) {
2357
- this.initialOptions = { ...options };
2358
- this.initialConfig = { ...initialConfig };
2354
+ async init() {
2359
2355
  if (!this.powerlinesPath) {
2360
2356
  const powerlinesPath = await resolvePackage("powerlines");
2361
2357
  if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
2362
2358
  this.powerlinesPath = powerlinesPath;
2363
2359
  }
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 ?? {}, {
2360
+ this.options = defu(this.initialOptions, this.initialConfig, {
2361
+ cwd: process.cwd(),
2376
2362
  mode: await this.getDefaultMode(),
2377
- logLevel: await this.getDefaultLogLevel()
2363
+ logLevel: await this.getDefaultLogLevel(),
2364
+ framework: "powerlines"
2378
2365
  });
2366
+ if (!this.options.root) if (this.options.configFile) {
2367
+ const configFile = appendPath(this.options.configFile, this.options.cwd);
2368
+ 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.`);
2369
+ 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.`);
2370
+ this.options.root = relativePath$1(this.options.cwd, findFilePath$1(configFile));
2371
+ } else this.options.root = ".";
2372
+ else this.options.root = replacePath(this.options.root, this.options.cwd);
2379
2373
  this.resolver = createResolver({
2380
- workspaceRoot: cwd,
2381
- root,
2374
+ workspaceRoot: this.options.cwd,
2375
+ root: this.options.root,
2382
2376
  cacheDir: this.envPaths.cache,
2383
2377
  mode: this.options.mode
2384
2378
  });
2379
+ await this.resolvePackageConfigs();
2385
2380
  this.configFile = await loadUserConfigFile(this.options, this.resolver);
2386
- if (!this.options.name) {
2387
- if (this.configFile.config) {
2381
+ if (this.configFile.config) {
2382
+ if (isSetString(this.configFile.configFile)) this.options.configFile ??= replacePath(this.configFile.configFile, this.options.cwd);
2383
+ if (!this.options.name) {
2388
2384
  if (isSetObject(this.configFile.config) && isSetString(this.configFile.config.name)) this.options.name = this.configFile.config.name;
2389
2385
  else if (Array.isArray(this.configFile.config)) {
2390
2386
  for (const config of this.configFile.config) if (isSetObject(config) && isSetString(config.name)) {
@@ -2393,22 +2389,46 @@ var PowerlinesBaseContext = class PowerlinesBaseContext {
2393
2389
  }
2394
2390
  }
2395
2391
  }
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
- }
2392
+ if (!this.options.name) this.options.name = this.projectJson?.name || this.packageJson?.name;
2393
+ }
2394
+ }
2395
+ /**
2396
+ * Resolve the package configurations for the project by loading the `package.json` and `project.json` files, if they exist. This function will look for these files in the project root and parse their contents as JavaScript objects. The parsed contents will be stored in the context for later use by plugins and other parts of the build process.
2397
+ *
2398
+ * @remarks
2399
+ * The `package.json` file is typically used to store metadata about the project, such as its name, version, dependencies, and other information. The `project.json` file is an optional file that can be used to store additional configuration or metadata specific to the project, and is not required for all projects.
2400
+ *
2401
+ * @param cwd - The current working directory to look for the package configurations. Defaults to the `cwd` specified in the context configuration.
2402
+ * @param root - The root directory of the project to look for the package configurations. Defaults to the `root` specified in the context configuration.
2403
+ * @returns A promise that resolves when the package configurations have been loaded and stored in the context.
2404
+ */
2405
+ async resolvePackageConfigs(cwd = this.options.cwd, root = this.options.root) {
2406
+ const projectJsonPath = joinPaths$2(appendPath(root, cwd), "project.json");
2407
+ if (existsSync$1(projectJsonPath)) this.projectJson = await readJsonFile$1(projectJsonPath);
2408
+ const packageJsonPath = joinPaths$2(appendPath(root, cwd), "package.json");
2409
+ if (existsSync$1(packageJsonPath)) {
2410
+ this.packageJson = await readJsonFile$1(packageJsonPath);
2411
+ this.options.organization ??= isSetObject(this.packageJson?.author) ? kebabCase(this.packageJson?.author?.name) : kebabCase(this.packageJson?.author);
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,247 @@ var PowerlinesContext = class PowerlinesContext extends PowerlinesBaseContext {
3090
3079
  /**
3091
3080
  * Generates a checksum representing the current context state
3092
3081
  *
3093
- * @param root - The root directory of the project to generate the checksum for
3082
+ * @param path - The root directory of the project to generate the checksum for
3094
3083
  * @returns A promise that resolves to a string representing the checksum
3095
3084
  */
3096
- async generateChecksum(root = this.config.root) {
3097
- this.#checksum = await hashDirectory(root, { ignore: [
3098
- "node_modules",
3099
- ".git",
3100
- ".nx",
3101
- ".cache",
3102
- "tmp",
3103
- "dist"
3104
- ] });
3105
- return this.#checksum;
3085
+ async generateChecksum(path) {
3086
+ return hashDirectory(path || appendPath(this.options.root, this.options.cwd));
3106
3087
  }
3107
3088
  /**
3108
- * Initialize the context with the provided configuration options
3089
+ * A setter function to populate the inline config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the inline configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any inline configuration provided during execution.
3090
+ *
3091
+ * @param config - The inline configuration values to set.
3092
+ * @returns A promise that resolves when the inline configuration values have been set.
3109
3093
  */
3110
- async setup() {
3111
- this.resolvedConfig = mergeConfig({
3112
- root: this.options.root,
3113
- cwd: this.options.cwd,
3114
- inlineConfig: this.config.inlineConfig ?? {},
3115
- userConfig: this.config.userConfig ?? {},
3116
- initialConfig: this.config.initialConfig ?? {},
3117
- pluginConfig: this.config.pluginConfig ?? {}
3118
- }, getConfigProps(this.config.inlineConfig), getConfigProps(this.config.userConfig), getConfigProps(this.config.initialConfig), getConfigProps(this.config.pluginConfig), this.options, {
3119
- name: this.projectJson?.name || this.packageJson?.name,
3094
+ async setInlineConfig(config) {
3095
+ this.logger.debug({
3096
+ meta: { category: "config" },
3097
+ message: `Updating inline configuration object: \n${this.logConfig(config)}`
3098
+ });
3099
+ this.inlineConfig = config;
3100
+ await this.resolveConfig();
3101
+ }
3102
+ /**
3103
+ * A setter function to populate the plugin config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the plugin configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any plugin configuration provided during execution.
3104
+ *
3105
+ * @param config - The plugin configuration values to set.
3106
+ * @returns A promise that resolves when the plugin configuration values have been set.
3107
+ */
3108
+ async setPluginConfig(config) {
3109
+ this.logger.debug({
3110
+ meta: { category: "config" },
3111
+ message: `Updating plugin configuration object: \n${this.logConfig(config)}`
3112
+ });
3113
+ this.pluginConfig = config;
3114
+ await this.resolveConfig();
3115
+ }
3116
+ /**
3117
+ * A function to merge the various configuration objects (initial, user, inline, and plugin) into a single resolved configuration object that can be used throughout the Powerlines process. This function takes into account the different sources of configuration and their respective priorities, ensuring that the final configuration reflects the intended settings for the project. The merged configuration is then returned as a new object that can be accessed through the `config` property of the context.
3118
+ *
3119
+ * @returns The merged configuration object that combines the initial, user, inline, and plugin configurations.
3120
+ */
3121
+ mergeConfig() {
3122
+ return mergeConfig({
3123
+ mode: this.initialOptions.mode,
3124
+ framework: this.initialOptions.framework,
3125
+ initialOptions: this.initialOptions,
3126
+ options: this.options,
3127
+ inlineConfig: this.inlineConfig,
3128
+ userConfig: this.userConfig,
3129
+ initialConfig: this.initialConfig,
3130
+ pluginConfig: this.pluginConfig
3131
+ }, getConfigProps(this.overriddenConfig), omit(this.options, ["mode", "framework"]), getConfigProps(this.inlineConfig), getConfigProps(this.userConfig), getConfigProps(this.initialConfig), getConfigProps(this.pluginConfig), {
3120
3132
  version: this.packageJson?.version,
3121
3133
  description: this.packageJson?.description
3122
3134
  }, {
3123
3135
  environments: {},
3124
3136
  resolve: {}
3125
3137
  });
3126
- await this.innerSetup();
3127
3138
  }
3128
3139
  /**
3129
- * The resolved configuration for this context
3130
- */
3131
- resolvedConfig = {};
3132
- /**
3133
- * Creates a clone of the current context with the same configuration and workspace settings. This can be useful for running multiple builds in parallel or for creating isolated contexts for different parts of the build process.
3134
- *
3135
- * @remarks
3136
- * The cloned context will have the same configuration and workspace settings as the original context, but will have a different build ID, release ID, and timestamp. The virtual file system and caches will also be separate between the original and cloned contexts.
3140
+ * A setter function to populate the user config values provided during execution of the command, such as CLI flags or other parameters that may be relevant to the command being executed. This function can be used to update the context with the user configuration values, which may be used during the configuration resolution process to ensure that the final configuration reflects both the user configuration and any inline configuration provided during execution.
3137
3141
  *
3138
- * @returns The cloned context.
3142
+ * @param config - The user configuration values to set.
3143
+ * @returns A promise that resolves when the user configuration values have been set.
3139
3144
  */
3140
- copyTo(context) {
3141
- for (const [key, value] of Object.entries(this)) if (!SKIP_CLONING_PROPS.includes(key)) if (isObject(value) || Array.isArray(value)) context[key] = deepClone(value);
3142
- else context[key] = value;
3143
- context.initialConfig = deepClone(this.initialConfig);
3144
- context.initialOptions = deepClone(this.initialOptions);
3145
- context.options = deepClone(this.options);
3146
- context.dependencies = deepClone(this.dependencies);
3147
- context.devDependencies = deepClone(this.devDependencies);
3148
- context.persistedMeta = this.persistedMeta ? deepClone(this.persistedMeta) : void 0;
3149
- context.packageJson = deepClone(this.packageJson);
3150
- context.projectJson = this.projectJson ? deepClone(this.projectJson) : void 0;
3151
- context.tsconfig ??= deepClone(this.tsconfig);
3152
- context.resolver ??= this.resolver;
3153
- context.fs ??= this.#fs;
3154
- context.$$internal = this.$$internal;
3155
- return context;
3145
+ async setUserConfig(config) {
3146
+ this.logger.debug({
3147
+ meta: { category: "config" },
3148
+ message: `Updating user configuration object: \n${this.logConfig(config)}`
3149
+ });
3150
+ this.userConfig = config;
3151
+ await this.resolveConfig();
3156
3152
  }
3157
3153
  /**
3158
3154
  * Initialize the context with the provided configuration options
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);
3164
+ }
3165
+ /**
3166
+ * Resolve the package configurations for the project by loading the `package.json` and `project.json` files, if they exist. This function will look for these files in the project root and parse their contents as JavaScript objects. The parsed contents will be stored in the context for later use by plugins and other parts of the build process.
3159
3167
  *
3160
- * @remarks
3161
- * This method will set up the resolver and load the user configuration file based on the provided options. It is called during the construction of the context and can also be called when cloning the context to ensure that the new context has the same configuration and resolver setup.
3168
+ * @remarks
3169
+ * The `package.json` file is typically used to store metadata about the project, such as its name, version, dependencies, and other information. The `project.json` file is an optional file that can be used to store additional configuration or metadata specific to the project, and is not required for all projects.
3162
3170
  *
3163
- * @param options - The configuration options to initialize the context with
3164
- */
3165
- async init(options, initialConfig) {
3166
- await super.init(options, initialConfig ?? {});
3167
- this.options.executionId = options.executionId ?? this.options.executionId;
3168
- this.options.executionIndex = options.executionIndex ?? this.options.executionIndex ?? 0;
3169
- const projectJsonPath = joinPaths$1(this.options.cwd, this.options.root, "project.json");
3170
- if (existsSync(projectJsonPath)) this.projectJson = await readJsonFile(projectJsonPath);
3171
- const packageJsonPath = joinPaths$1(this.options.cwd, this.options.root, "package.json");
3172
- if (existsSync(packageJsonPath)) {
3173
- this.packageJson = await readJsonFile(packageJsonPath);
3174
- this.options.organization ??= isSetObject(this.packageJson?.author) ? kebabCase(this.packageJson?.author?.name) : kebabCase(this.packageJson?.author);
3175
- }
3176
- this.#checksum = await this.generateChecksum(joinPaths$1(this.options.cwd, this.options.root));
3177
- const userConfig = this.configFile.config ? Array.isArray(this.configFile.config) && this.configFile.config.length > this.options.executionIndex ? this.configFile.config[this.options.executionIndex] : this.configFile.config : {};
3178
- this.resolvedConfig = {
3179
- cwd: this.options.cwd,
3180
- root: this.options.root,
3181
- ...this.initialOptions,
3182
- ...initialConfig,
3183
- ...userConfig,
3184
- inlineConfig: {},
3185
- pluginConfig: {},
3186
- initialConfig,
3187
- userConfig
3188
- };
3171
+ * @param cwd - The current working directory to look for the package configurations. Defaults to the `cwd` specified in the context configuration.
3172
+ * @param root - The root directory of the project to look for the package configurations. Defaults to the `root` specified in the context configuration.
3173
+ * @returns A promise that resolves when the package configurations have been loaded and stored in the context.
3174
+ */
3175
+ async resolvePackageConfigs(cwd = this.config.cwd, root = this.config.root) {
3176
+ return super.resolvePackageConfigs(cwd, root);
3189
3177
  }
3190
3178
  /**
3191
3179
  * Initialize the context with the provided configuration options
3192
3180
  */
3193
- async innerSetup() {
3194
- const logger = this.extendLogger({ category: "config" });
3195
- this.config.plugins = (this.config.initialConfig.plugins ?? []).concat(this.config.userConfig.plugins ?? [], this.config.inlineConfig.plugins ?? []);
3196
- this.config.output = defu(this.config.output ?? {}, {
3181
+ async resolveConfig() {
3182
+ const mergedConfig = this.mergeConfig();
3183
+ this.logger.trace({
3184
+ meta: { category: "config" },
3185
+ 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)}`
3186
+ });
3187
+ mergedConfig.output = defu(mergedConfig.output ?? {}, {
3197
3188
  copy: { assets: [
3198
3189
  { glob: "LICENSE" },
3199
3190
  {
3200
- input: this.config.root,
3191
+ input: mergedConfig.root,
3201
3192
  glob: "*.md"
3202
3193
  },
3203
3194
  {
3204
- input: this.config.root,
3195
+ input: mergedConfig.root,
3205
3196
  glob: "package.json"
3206
3197
  }
3207
3198
  ] },
3208
3199
  dts: true
3209
3200
  });
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) => {
3201
+ if (!mergedConfig.mode) mergedConfig.mode = "production";
3202
+ if (!mergedConfig.framework) mergedConfig.framework = "powerlines";
3203
+ if (!mergedConfig.projectType) mergedConfig.projectType = "application";
3204
+ if (!mergedConfig.platform) mergedConfig.platform = "neutral";
3205
+ mergedConfig.compatibilityDate = resolveCompatibilityDates(mergedConfig.compatibilityDate, "latest");
3206
+ this.resolvedConfig = mergedConfig;
3207
+ this.#configProxy = this.createConfigProxy();
3208
+ if (!this.packageJson) await this.resolvePackageConfigs();
3209
+ mergedConfig.input = getUniqueInputs(mergedConfig.input);
3210
+ if (mergedConfig.name?.startsWith("@") && mergedConfig.name.split("/").filter(Boolean).length > 1) mergedConfig.name = mergedConfig.name.split("/").filter(Boolean)[1];
3211
+ mergedConfig.title ??= titleCase(mergedConfig.name);
3212
+ if (mergedConfig.resolve.external) mergedConfig.resolve.external = getUnique(mergedConfig.resolve.external);
3213
+ if (mergedConfig.resolve.noExternal) mergedConfig.resolve.noExternal = getUnique(mergedConfig.resolve.noExternal);
3214
+ mergedConfig.plugins = (mergedConfig.plugins ?? []).flatMap((plugin) => toArray(plugin)).filter(Boolean).reduce((ret, plugin) => {
3240
3215
  if (isPlugin(plugin) && isDuplicate(plugin, ret.filter((p) => isPlugin(p)))) return ret;
3241
3216
  ret.push(plugin);
3242
3217
  return ret;
3243
3218
  }, []);
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) => {
3219
+ if (!mergedConfig.logLevel) if (mergedConfig.mode === "development") mergedConfig.logLevel = DEFAULT_DEVELOPMENT_LOG_LEVEL;
3220
+ else if (mergedConfig.mode === "test") mergedConfig.logLevel = DEFAULT_TEST_LOG_LEVEL;
3221
+ else mergedConfig.logLevel = DEFAULT_PRODUCTION_LOG_LEVEL;
3222
+ if (mergedConfig.tsconfig) mergedConfig.tsconfig = replacePath(replacePathTokens(this, mergedConfig.tsconfig), mergedConfig.cwd);
3223
+ else mergedConfig.tsconfig = getTsconfigFilePath(mergedConfig.cwd, mergedConfig.root);
3224
+ mergedConfig.output.format = getUnique(toArray(mergedConfig.output?.format ?? (mergedConfig.projectType === "library" ? ["cjs", "esm"] : ["esm"])));
3225
+ if (mergedConfig.output.path) mergedConfig.output.path = appendPath(replacePathTokens(this, mergedConfig.output.path), mergedConfig.cwd);
3226
+ else mergedConfig.output.path = appendPath(joinPaths$1(mergedConfig.root, "dist"), mergedConfig.cwd);
3227
+ mergedConfig.output.copy ??= {};
3228
+ 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;
3229
+ else mergedConfig.output.copy.path = appendPath(replacePathTokens(this, mergedConfig.output.copy.path || joinPaths$1("dist", mergedConfig.root)), mergedConfig.cwd);
3230
+ 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);
3231
+ 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
3232
  return {
3260
3233
  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),
3234
+ 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),
3235
+ 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
3236
  ignore: isSetObject(asset) && asset.ignore ? toArray(asset.ignore) : void 0
3264
3237
  };
3265
3238
  }), (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) => ({
3239
+ if (!mergedConfig.output?.sourceMap) if (mergedConfig.mode === "development") mergedConfig.output.sourceMap = true;
3240
+ else mergedConfig.output.sourceMap = false;
3241
+ if (!mergedConfig.output.minify) if (mergedConfig.mode === "production") mergedConfig.output.minify = true;
3242
+ else mergedConfig.output.minify = false;
3243
+ if (!mergedConfig.output.artifactsPath) mergedConfig.output.artifactsPath = `.${mergedConfig.framework || "powerlines"}`;
3244
+ if (mergedConfig.output.copy && mergedConfig.output.copy.assets) mergedConfig.output.copy.assets = mergedConfig.output.copy.assets.map((asset) => ({
3272
3245
  ...asset,
3273
3246
  glob: replacePathTokens(this, asset.glob),
3274
3247
  ignore: asset.ignore ? asset.ignore.map((ignore) => replacePathTokens(this, ignore)) : void 0,
3275
3248
  input: replacePathTokens(this, asset.input),
3276
3249
  output: replacePathTokens(this, asset.output)
3277
3250
  }));
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;
3251
+ 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;
3252
+ this.resolvedConfig = mergedConfig;
3253
+ this.#configProxy = this.createConfigProxy();
3254
+ this.logger.debug({
3255
+ meta: { category: "config" },
3256
+ 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)}`
3257
+ });
3279
3258
  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
- })}`);
3259
+ }
3260
+ logConfig(config) {
3261
+ return formatLogMessage({
3262
+ ...omit(config, ["plugins"]),
3263
+ 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
3264
+ });
3265
+ }
3266
+ createConfigProxy() {
3267
+ return new Proxy(this.resolvedConfig, {
3268
+ /**
3269
+ * A trap for the `delete` operator.
3270
+ * @param target - The original object which is being proxied.
3271
+ * @param key - The name or `Symbol` of the property to delete.
3272
+ * @returns A `boolean` indicating whether or not the property was deleted.
3273
+ */
3274
+ deleteProperty: (target, key) => {
3275
+ 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.`);
3276
+ Reflect.deleteProperty(this.overriddenConfig, key);
3277
+ return Reflect.deleteProperty(target, key);
3278
+ },
3279
+ /**
3280
+ * A trap for getting a property value.
3281
+ * @param target - The original object which is being proxied.
3282
+ * @param key - The name or `Symbol` of the property to get.
3283
+ * @param receiver - The proxy or an object that inherits from the proxy.
3284
+ */
3285
+ get: (target, key, receiver) => {
3286
+ if (UNRESOLVED_CONFIG_NAMES.includes(key.toString())) {
3287
+ if (key === "initialConfig") return this.initialConfig;
3288
+ if (key === "userConfig") return this.userConfig;
3289
+ if (key === "inlineConfig") return this.inlineConfig;
3290
+ if (key === "pluginConfig") return this.pluginConfig;
3291
+ }
3292
+ return Reflect.get(target, key, receiver);
3293
+ },
3294
+ /**
3295
+ * A trap for the `in` operator.
3296
+ * @param target - The original object which is being proxied.
3297
+ * @param key - The name or `Symbol` of the property to check for existence.
3298
+ */
3299
+ has: (target, key) => {
3300
+ return Reflect.has(target, key) || UNRESOLVED_CONFIG_NAMES.includes(key.toString());
3301
+ },
3302
+ /**
3303
+ * A trap for `Reflect.ownKeys()`.
3304
+ * @param target - The original object which is being proxied.
3305
+ */
3306
+ ownKeys: (target) => {
3307
+ return getUnique([...Reflect.ownKeys(target), ...UNRESOLVED_CONFIG_NAMES]);
3308
+ },
3309
+ /**
3310
+ * A trap for setting a property value.
3311
+ * @param target - The original object which is being proxied.
3312
+ * @param key - The name or `Symbol` of the property to set.
3313
+ * @param newValue - The new value to assign to the property.
3314
+ * @param receiver - The object to which the assignment was originally directed.
3315
+ * @returns A `boolean` indicating whether or not the property was set.
3316
+ */
3317
+ set: (target, key, newValue, receiver) => {
3318
+ 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.`);
3319
+ Reflect.set(this.overriddenConfig, key, newValue, receiver);
3320
+ return Reflect.set(target, key, newValue, receiver);
3321
+ }
3322
+ });
3293
3323
  }
3294
3324
  };
3295
3325
 
@@ -3434,7 +3464,7 @@ function createPluginContext(pluginId, plugin, environment) {
3434
3464
  return {
3435
3465
  meta: {
3436
3466
  ...isSetObject(message) ? message.meta : {},
3437
- environment: environment.environment?.name,
3467
+ environment: environment.config.environment.name,
3438
3468
  plugin: plugin.name
3439
3469
  },
3440
3470
  message: isString(message) ? message : message.message
@@ -3457,6 +3487,8 @@ function createPluginContext(pluginId, plugin, environment) {
3457
3487
  callHook: callHookFn,
3458
3488
  meta
3459
3489
  };
3490
+ if (prop === "api") return environment.$$internal.api;
3491
+ if (prop === "environment") return environment;
3460
3492
  if (prop === "id") return pluginId;
3461
3493
  if (prop === "logger") return logger;
3462
3494
  if (prop === "log") return (type, message) => {
@@ -3509,6 +3541,15 @@ function createPluginContext(pluginId, plugin, environment) {
3509
3541
  //#endregion
3510
3542
  //#region src/context/environment-context.ts
3511
3543
  var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends PowerlinesContext {
3544
+ /**
3545
+ * Internal references storage
3546
+ *
3547
+ * @danger
3548
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3549
+ *
3550
+ * @internal
3551
+ */
3552
+ #internal = {};
3512
3553
  /**
3513
3554
  * The hooks registered by plugins in this environment
3514
3555
  */
@@ -3518,29 +3559,50 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3518
3559
  *
3519
3560
  * @param options - The resolved execution options.
3520
3561
  * @param config - The user configuration options.
3521
- * @returns A promise that resolves to the new context.
3562
+ * @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.
3563
+ * @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.
3564
+ * @returns A promise that resolves to an instance of the PowerlinesEnvironmentContext class, initialized with the provided configuration and environment data.
3522
3565
  */
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;
3566
+ static async createEnvironment(options, config, overriddenConfig, environment) {
3567
+ const context = new PowerlinesEnvironmentContext(options, config, overriddenConfig);
3568
+ await context.setEnvironmentConfig(environment);
3529
3569
  return context;
3530
3570
  }
3531
3571
  /**
3532
- * The resolved environment configuration
3572
+ * The configuration options provided by plugins added by the user (and other plugins)
3533
3573
  */
3534
- environment;
3574
+ environmentConfig = {};
3535
3575
  /**
3536
3576
  * The list of plugins applied to this environment
3537
3577
  */
3538
3578
  plugins = [];
3539
3579
  /**
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.
3580
+ * Internal context fields and methods
3581
+ *
3582
+ * @danger
3583
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3584
+ *
3585
+ * @internal
3541
3586
  */
3542
- get id() {
3543
- return this.environment.environmentId;
3587
+ get $$internal() {
3588
+ return this.#internal;
3589
+ }
3590
+ /**
3591
+ * Internal context fields and methods
3592
+ *
3593
+ * @danger
3594
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3595
+ *
3596
+ * @internal
3597
+ */
3598
+ set $$internal(value) {
3599
+ this.#internal = value;
3600
+ }
3601
+ /**
3602
+ * 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.
3603
+ */
3604
+ get id() {
3605
+ return this.config.environment.id;
3544
3606
  }
3545
3607
  /**
3546
3608
  * The hooks registered by plugins in this environment
@@ -3549,6 +3611,20 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3549
3611
  return this.#hooks;
3550
3612
  }
3551
3613
  /**
3614
+ * 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.
3615
+ *
3616
+ * @param config - The environment configuration values to set.
3617
+ * @returns A promise that resolves when the environment configuration values have been set.
3618
+ */
3619
+ async setEnvironmentConfig(config) {
3620
+ this.logger.debug({
3621
+ meta: { category: "config" },
3622
+ message: `Updating environment configuration object: \n${this.logConfig(config)}`
3623
+ });
3624
+ this.environmentConfig = config;
3625
+ await this.resolveConfig();
3626
+ }
3627
+ /**
3552
3628
  * Create a new logger instance
3553
3629
  *
3554
3630
  * @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 +3634,7 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3558
3634
  createLogger(options, logFn) {
3559
3635
  return super.createLogger({
3560
3636
  ...options,
3561
- environment: this.environment?.name
3637
+ environment: this.config.environment?.name
3562
3638
  }, logFn);
3563
3639
  }
3564
3640
  /**
@@ -3570,47 +3646,21 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3570
3646
  extendLogger(options) {
3571
3647
  return super.extendLogger({
3572
3648
  ...options,
3573
- environment: this.environment?.name
3649
+ environment: this.config.environment?.name
3574
3650
  });
3575
3651
  }
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
3652
  async addPlugin(plugin) {
3603
3653
  let resolvedPlugin = plugin;
3604
3654
  if (isFunction(plugin.applyToEnvironment)) {
3605
- const result = await Promise.resolve(plugin.applyToEnvironment(this.environment));
3655
+ const result = await Promise.resolve(plugin.applyToEnvironment(this.config.environment));
3606
3656
  if (!result || isObject(result) && Object.keys(result).length === 0) return;
3607
3657
  if (isPluginConfig(result)) return this.$$internal.addPlugin(result);
3608
3658
  resolvedPlugin = isPlugin(result) ? result : plugin;
3609
3659
  }
3610
- const pluginId = uuid();
3611
- const context = createPluginContext(pluginId, resolvedPlugin, this);
3660
+ const id = uuid();
3661
+ const context = createPluginContext(id, resolvedPlugin, this);
3612
3662
  this.plugins.push({
3613
- pluginId,
3663
+ id,
3614
3664
  plugin: resolvedPlugin,
3615
3665
  context
3616
3666
  });
@@ -3653,29 +3703,51 @@ var PowerlinesEnvironmentContext = class PowerlinesEnvironmentContext extends Po
3653
3703
  }
3654
3704
  return result;
3655
3705
  }
3656
- constructor(options, config, environment) {
3657
- super(options);
3658
- this.resolvedConfig = config;
3659
- this.environment = environment;
3706
+ constructor(options, config, overriddenConfig) {
3707
+ super(options, config.initialConfig);
3708
+ this.userConfig = config.userConfig ?? {};
3709
+ this.inlineConfig = config.inlineConfig ?? {};
3710
+ this.pluginConfig = config.pluginConfig ?? {};
3711
+ this.overriddenConfig = overriddenConfig;
3660
3712
  }
3661
3713
  /**
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.
3714
+ * 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
3715
  *
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.
3716
+ * @returns The merged configuration object that combines the initial, user, inline, and plugin configurations.
3668
3717
  */
3669
- copyTo(context) {
3670
- context.plugins = this.plugins;
3671
- return super.copyTo(context);
3718
+ mergeConfig() {
3719
+ return mergeConfig({
3720
+ ...omit(this.environmentConfig ?? {}, [
3721
+ "ssr",
3722
+ "preview",
3723
+ "consumer",
3724
+ "runtime"
3725
+ ]),
3726
+ environment: { name: this.environmentConfig?.name || DEFAULT_ENVIRONMENT },
3727
+ environmentConfig: this.environmentConfig ?? {}
3728
+ }, super.mergeConfig());
3672
3729
  }
3673
3730
  };
3674
3731
 
3675
3732
  //#endregion
3676
3733
  //#region src/context/execution-context.ts
3677
3734
  var PowerlinesExecutionContext = class PowerlinesExecutionContext extends PowerlinesContext {
3735
+ /**
3736
+ * Internal references storage
3737
+ *
3738
+ * @danger
3739
+ * This field is for internal use only and should not be accessed or modified directly. It is unstable and can be changed at anytime.
3740
+ *
3741
+ * @internal
3742
+ */
3743
+ #internal = {};
3744
+ /**
3745
+ * A record of all environments by name
3746
+ */
3678
3747
  #environments = {};
3748
+ /**
3749
+ * 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.
3750
+ */
3679
3751
  #plugins = [];
3680
3752
  /**
3681
3753
  * Create a new Storm context from the workspace root and user config.
@@ -3683,13 +3755,9 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3683
3755
  * @param options - The options for resolving the context.
3684
3756
  * @returns A promise that resolves to the new context.
3685
3757
  */
3686
- static async init(options, initialConfig) {
3687
- const context = new PowerlinesExecutionContext(options);
3688
- await context.init(options, initialConfig);
3689
- const powerlinesPath = await resolvePackage("powerlines");
3690
- if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
3691
- context.powerlinesPath = powerlinesPath;
3692
- await context.setup();
3758
+ static async fromInitialConfig(options, initialConfig) {
3759
+ const context = new PowerlinesExecutionContext(options, initialConfig);
3760
+ await context.init();
3693
3761
  return context;
3694
3762
  }
3695
3763
  /**
@@ -3698,19 +3766,10 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3698
3766
  * @param options - The options for resolving the context.
3699
3767
  * @returns A promise that resolves to the new context.
3700
3768
  */
3701
- static async inline(options, initialConfig, inlineConfig) {
3702
- const context = new PowerlinesExecutionContext(options);
3703
- await context.init(options, initialConfig);
3704
- context.config.inlineConfig = inlineConfig;
3705
- if (context.config.inlineConfig.command === "new") {
3706
- const workspacePackageJsonPath = joinPaths$1(context.config.cwd, "package.json");
3707
- if (!existsSync(workspacePackageJsonPath)) throw new Error(`The workspace package.json file could not be found at ${workspacePackageJsonPath}`);
3708
- context.packageJson = await readJsonFile(workspacePackageJsonPath);
3709
- }
3710
- await context.setup();
3711
- const powerlinesPath = await resolvePackage("powerlines");
3712
- if (!powerlinesPath) throw new Error("Could not resolve `powerlines` package location.");
3713
- context.powerlinesPath = powerlinesPath;
3769
+ static async fromInlineConfig(options, initialConfig, inlineConfig) {
3770
+ const context = new PowerlinesExecutionContext(options, initialConfig);
3771
+ await context.init();
3772
+ await context.setInlineConfig(inlineConfig);
3714
3773
  return context;
3715
3774
  }
3716
3775
  /**
@@ -3722,7 +3781,7 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3722
3781
  * @internal
3723
3782
  */
3724
3783
  get $$internal() {
3725
- return super.$$internal;
3784
+ return this.#internal;
3726
3785
  }
3727
3786
  /**
3728
3787
  * Internal context fields and methods
@@ -3733,8 +3792,8 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3733
3792
  * @internal
3734
3793
  */
3735
3794
  set $$internal(value) {
3736
- super.$$internal = value;
3737
- for (const environment of Object.values(this.environments)) environment.$$internal = super.$$internal;
3795
+ this.#internal = value;
3796
+ for (const environment of Object.values(this.environments)) environment.$$internal = value;
3738
3797
  }
3739
3798
  /**
3740
3799
  * The unique identifier of the execution context, which can be used for logging and other purposes to distinguish between different executions in the same process.
@@ -3755,9 +3814,26 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3755
3814
  * Creates a new instance.
3756
3815
  *
3757
3816
  * @param options - The options to use for creating the context, including the resolved configuration and workspace settings.
3817
+ * @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.
3818
+ */
3819
+ constructor(options, initialConfig = {}) {
3820
+ super(options, initialConfig);
3821
+ this.initialOptions = options;
3822
+ this.initialConfig = initialConfig;
3823
+ }
3824
+ /**
3825
+ * 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.
3826
+ *
3827
+ * @param config - The inline configuration values to set.
3828
+ * @returns A promise that resolves when the inline configuration values have been set.
3758
3829
  */
3759
- constructor(options) {
3760
- super(options);
3830
+ async setInlineConfig(config) {
3831
+ await super.setInlineConfig(config);
3832
+ if (this.inlineConfig.command === "new") {
3833
+ const workspacePackageJsonPath = joinPaths$1(this.config.cwd, "package.json");
3834
+ if (!existsSync(workspacePackageJsonPath)) throw new Error(`The workspace package.json file could not be found at ${workspacePackageJsonPath}`);
3835
+ this.packageJson = await readJsonFile(workspacePackageJsonPath);
3836
+ }
3761
3837
  }
3762
3838
  /**
3763
3839
  * Create a new logger instance
@@ -3787,42 +3863,45 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3787
3863
  });
3788
3864
  }
3789
3865
  /**
3790
- * Creates a clone of the current context with the same configuration and workspace settings. This can be useful for running multiple builds in parallel or for creating isolated contexts for different parts of the build process.
3791
- *
3792
- * @remarks
3793
- * The cloned context will have the same configuration and workspace settings as the original context, but will have a different build ID, release ID, and timestamp. The virtual file system and caches will also be separate between the original and cloned contexts.
3794
- *
3795
- * @returns A promise that resolves to the cloned context.
3796
- */
3797
- async clone() {
3798
- const clone = await PowerlinesExecutionContext.init(this.options, this.initialConfig);
3799
- clone.config.userConfig = deepClone(this.config.userConfig);
3800
- clone.config.initialConfig = deepClone(this.config.initialConfig);
3801
- clone.config.inlineConfig = deepClone(this.config.inlineConfig);
3802
- clone.config.pluginConfig = deepClone(this.config.pluginConfig);
3803
- await clone.setup();
3804
- clone.$$internal = this.$$internal;
3805
- return this.copyTo(clone);
3806
- }
3807
- /**
3808
3866
  * A function to copy the context and update the fields for a specific environment
3809
3867
  *
3810
3868
  * @param environment - The environment configuration to use.
3811
3869
  * @returns A new context instance with the updated environment.
3812
3870
  */
3813
- async in(environment) {
3814
- const context = this.copyTo(await PowerlinesEnvironmentContext.fromConfig(deepClone(this.options), deepClone(this.config), deepClone(environment)));
3871
+ async createEnvironment(environment) {
3872
+ const context = await PowerlinesEnvironmentContext.createEnvironment(deepClone(this.options), deepClone(this.config), deepClone(this.overriddenConfig), deepClone(environment));
3873
+ context.$$internal = this.$$internal;
3874
+ context.dependencies = deepClone(this.dependencies);
3875
+ context.devDependencies = deepClone(this.devDependencies);
3876
+ context.persistedMeta = deepClone(this.persistedMeta);
3877
+ context.resolvePatterns = deepClone(this.resolvePatterns);
3878
+ context.powerlinesPath ??= this.powerlinesPath;
3879
+ context.resolver ??= this.resolver;
3815
3880
  context.plugins = [];
3816
3881
  for (const plugin of this.plugins) await context.addPlugin(plugin);
3882
+ for (const [key, value] of Object.entries(this)) if (![
3883
+ "fs",
3884
+ "$$internal",
3885
+ "dependencies",
3886
+ "devDependencies",
3887
+ "persistedMeta",
3888
+ "packageJson",
3889
+ "projectJson",
3890
+ "tsconfig",
3891
+ "resolver",
3892
+ "plugins",
3893
+ "environments"
3894
+ ].includes(key)) if (isObject(value) || Array.isArray(value)) context[key] = deepClone(value);
3895
+ else context[key] = value;
3817
3896
  return context;
3818
3897
  }
3819
3898
  /**
3820
3899
  * Update the context using a new inline configuration options
3821
3900
  */
3822
- async setup() {
3823
- await super.setup();
3901
+ async resolveConfig() {
3902
+ await super.resolveConfig();
3824
3903
  await Promise.all(toArray(this.config.environments && Object.keys(this.config.environments).length > 0 ? Object.keys(this.config.environments).map((name) => createEnvironment(name, this.config)) : createDefaultEnvironment(this.config)).map(async (env) => {
3825
- this.#environments[env.name] = await this.in(env);
3904
+ this.#environments[env.name] = await this.createEnvironment(env);
3826
3905
  }));
3827
3906
  }
3828
3907
  /**
@@ -3847,12 +3926,20 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3847
3926
  if (name) environment = this.environments[name];
3848
3927
  if (Object.keys(this.environments).length === 1) {
3849
3928
  environment = this.environments[Object.keys(this.environments)[0]];
3850
- this.debug(`Applying the only configured environment: ${chalk.bold.cyanBright(environment?.environment.name)}`);
3929
+ this.trace({
3930
+ meta: { category: "plugins" },
3931
+ message: `Applying the only configured environment: ${chalk.bold.cyanBright(environment?.config.environment?.name)}`
3932
+ });
3851
3933
  }
3852
3934
  if (!environment) {
3853
3935
  if (name) throw new Error(`Environment "${name}" not found.`);
3854
- environment = await this.in(createDefaultEnvironment(this.config));
3855
- this.warn(`No environment specified, and no default environment found. Using a temporary default environment: ${chalk.bold.cyanBright(environment?.environment.name)}`);
3936
+ environment = await PowerlinesEnvironmentContext.createEnvironment(deepClone(this.options), deepClone(this.config), deepClone(this.overriddenConfig), deepClone(createDefaultEnvironment(this.config)));
3937
+ environment.plugins = [];
3938
+ for (const plugin of this.plugins) await environment.addPlugin(plugin);
3939
+ this.warn({
3940
+ meta: { category: "plugins" },
3941
+ message: `No environment specified, and no default environment found. Using a temporary default environment: ${chalk.bold.cyanBright(environment.config.environment?.name)}`
3942
+ });
3856
3943
  }
3857
3944
  return environment;
3858
3945
  }
@@ -3880,8 +3967,11 @@ var PowerlinesExecutionContext = class PowerlinesExecutionContext extends Powerl
3880
3967
  async toEnvironment() {
3881
3968
  let environment;
3882
3969
  if (Object.keys(this.environments).length > 1) {
3883
- environment = await this.in(createEnvironment(GLOBAL_ENVIRONMENT, this.config));
3884
- this.debug(`Combined all ${Object.keys(this.environments).length} environments into a single global context.`);
3970
+ environment = await this.createEnvironment(createEnvironment(GLOBAL_ENVIRONMENT, this.config));
3971
+ this.debug({
3972
+ meta: { category: "plugins" },
3973
+ message: `Combined all ${Object.keys(this.environments).length} environments into a single global context.`
3974
+ });
3885
3975
  } else environment = await this.getEnvironment();
3886
3976
  return environment;
3887
3977
  }
@@ -4301,11 +4391,11 @@ async function installDependencies(context) {
4301
4391
  //#endregion
4302
4392
  //#region src/_internal/helpers/resolve-tsconfig.ts
4303
4393
  function getTsconfigDtsPath(context) {
4304
- return joinPaths(relativePath(joinPaths(context.options.cwd, context.options.root), findFilePath(context.typesPath)), findFileName(context.typesPath));
4394
+ return joinPaths(relativePath(joinPaths(context.config.cwd, context.config.root), findFilePath(context.typesPath)), findFileName(context.typesPath));
4305
4395
  }
4306
4396
  async function resolveTsconfigChanges(context) {
4307
- const tsconfig = getParsedTypeScriptConfig(context.options.cwd, context.options.root, context.config.tsconfig, context.config.tsconfigRaw);
4308
- const tsconfigJson = await readJsonFile(getTsconfigFilePath(context.options.cwd, context.options.root, context.config.tsconfig));
4397
+ const tsconfig = getParsedTypeScriptConfig(context.config.cwd, context.config.root, context.config.tsconfig, context.config.tsconfigRaw);
4398
+ const tsconfigJson = await readJsonFile(getTsconfigFilePath(context.config.cwd, context.config.root, context.config.tsconfig));
4309
4399
  tsconfigJson.compilerOptions ??= {};
4310
4400
  if (context.config.output.dts !== false) {
4311
4401
  const dtsRelativePath = getTsconfigDtsPath(context);
@@ -4395,353 +4485,86 @@ var PowerlinesExecution = class PowerlinesExecution {
4395
4485
  * The Powerlines context
4396
4486
  */
4397
4487
  #context;
4398
- async #handleBuild(context) {
4399
- await this.callHook("build", {
4400
- environment: context,
4401
- order: "pre"
4402
- });
4403
- context.debug("Formatting the generated entry files before the build process starts.");
4404
- await formatFolder(context, context.entryPath);
4405
- await this.callHook("build", {
4406
- environment: context,
4407
- order: "normal"
4408
- });
4409
- if (context.config.output.copy) {
4410
- context.debug("Copying project's files from build output directory.");
4411
- const destinationPath = isParentPath(appendPath(context.config.output.path, context.config.cwd), appendPath(context.config.root, context.config.cwd)) ? joinPaths(context.config.output.copy.path, relativePath(appendPath(context.config.root, context.config.cwd), appendPath(context.config.output.path, context.config.cwd))) : joinPaths(context.config.output.copy.path, "dist");
4412
- const sourcePath = context.config.output.path;
4413
- if (existsSync(sourcePath) && sourcePath !== destinationPath) {
4414
- context.debug(`Copying files from project's build output directory (${context.config.output.path}) to the project's copy/publish directory (${destinationPath}).`);
4415
- await copyFiles(sourcePath, destinationPath);
4416
- } else context.warn(`The source path for the copy operation ${!existsSync(sourcePath) ? "does not exist" : "is the same as the destination path"}. Source: ${sourcePath}, Destination: ${destinationPath}. Skipping copying of build output files.`);
4417
- if (context.config.output.copy.assets && Array.isArray(context.config.output.copy.assets)) await Promise.all(context.config.output.copy.assets.map(async (asset) => {
4418
- context.trace(`Copying asset(s): ${chalk.redBright(context.config.cwd === asset.input ? asset.glob : appendPath(asset.glob, replacePath(asset.input, context.config.cwd)))} -> ${chalk.greenBright(appendPath(asset.glob, replacePath(asset.output, context.config.cwd)))} ${Array.isArray(asset.ignore) && asset.ignore.length > 0 ? ` (ignoring: ${asset.ignore.map((i) => chalk.yellowBright(i)).join(", ")})` : ""}`);
4419
- await context.fs.copy(asset, asset.output);
4420
- }));
4421
- } else context.debug("No copy configuration found for the project output. Skipping the copying of build output files.");
4422
- await this.callHook("build", {
4423
- environment: context,
4424
- order: "post"
4425
- });
4426
- }
4427
4488
  /**
4428
- * Get the configured environments
4489
+ * Initialize a Powerlines API instance
4429
4490
  *
4430
- * @returns The configured environments
4491
+ * @param options - The options to initialize the API with
4492
+ * @returns A new instance of the Powerlines API
4431
4493
  */
4432
- async #getEnvironments() {
4433
- if (!this.context.config.environments || Object.keys(this.context.config.environments).length <= 1) {
4434
- this.context.debug("No environments are configured for this Powerlines project. Using the default environment.");
4435
- return [await this.context.getEnvironment()];
4436
- }
4437
- this.context.debug(`Found ${Object.keys(this.context.config.environments).length} configured environment(s) for this Powerlines project.`);
4438
- return (await Promise.all(Object.entries(this.context.config.environments).map(async ([name, config]) => {
4439
- if (!await this.context.getEnvironmentSafe(name)) {
4440
- const resolvedEnvironment = await this.callHook("configEnvironment", { environment: name }, name, config);
4441
- if (resolvedEnvironment) this.context.environments[name] = await this.context.in(resolvedEnvironment);
4442
- }
4443
- return this.context.environments[name];
4444
- }))).filter((context) => isSet(context));
4494
+ static async from(options, initialConfig) {
4495
+ const api = new PowerlinesExecution(await PowerlinesExecutionContext.fromInitialConfig(options, initialConfig ?? {}));
4496
+ await api.init();
4497
+ return api;
4445
4498
  }
4446
4499
  /**
4447
- * Execute a handler function for each environment
4448
- *
4449
- * @param handle - The handler function to execute for each environment
4500
+ * The Powerlines context
4450
4501
  */
4451
- async #executeEnvironments(handle) {
4452
- await Promise.all((await this.#getEnvironments()).map(async (context) => {
4453
- return Promise.resolve(handle(context));
4454
- }));
4502
+ get context() {
4503
+ return this.#context;
4455
4504
  }
4456
4505
  /**
4457
- * Initialize a Powerlines plugin
4506
+ * Generate the Powerlines typescript declaration file
4458
4507
  *
4459
- * @param config - The configuration for the plugin
4460
- * @returns The initialized plugin instance, or null if the plugin was a duplicate
4461
- * @throws Will throw an error if the plugin cannot be found or is invalid
4508
+ * @remarks
4509
+ * 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.
4510
+ *
4511
+ * @param inlineConfig - The inline configuration for the types command
4462
4512
  */
4463
- async #initPlugin(config) {
4464
- let awaited = config;
4465
- if (isPromiseLike(config)) awaited = await Promise.resolve(config);
4466
- if (!isPluginConfig(awaited)) {
4467
- const invalid = findInvalidPluginConfig(awaited);
4468
- throw new Error(`Invalid ${invalid && invalid.length > 1 ? "plugins" : "plugin"} specified in the configuration - ${invalid && invalid.length > 0 ? JSON.stringify(awaited) : invalid?.join("\n\n")} \n\nPlease ensure the value is one of the following: \n - an instance of \`Plugin\` \n - a plugin name \n - an object with the \`plugin\` and \`options\` properties \n - a tuple array with the plugin and options \n - a factory function that returns a plugin or array of plugins \n - an array of plugins or plugin configurations`);
4469
- }
4470
- let plugins;
4471
- if (isPlugin(awaited)) plugins = [awaited];
4472
- else if (isFunction(awaited)) plugins = toArray(await Promise.resolve(awaited()));
4473
- else if (isString(awaited)) {
4474
- const resolved = await this.#resolvePlugin(awaited);
4475
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(resolved()));
4476
- else plugins = toArray(resolved);
4477
- } else if (Array.isArray(awaited) && awaited.every(isPlugin)) plugins = awaited;
4478
- else if (Array.isArray(awaited) && awaited.every(isPluginConfig)) {
4479
- plugins = [];
4480
- for (const pluginConfig of awaited) {
4481
- const initialized = await this.#initPlugin(pluginConfig);
4482
- if (initialized) plugins.push(...initialized);
4483
- }
4484
- } else if (isPluginConfigTuple(awaited) || isPluginConfigObject(awaited)) {
4485
- let pluginConfig;
4486
- let pluginOptions;
4487
- if (isPluginConfigTuple(awaited)) {
4488
- pluginConfig = awaited[0];
4489
- pluginOptions = awaited?.length === 2 ? awaited[1] : void 0;
4490
- } else {
4491
- pluginConfig = awaited.plugin;
4492
- pluginOptions = awaited.options;
4493
- }
4494
- if (isSetString(pluginConfig)) {
4495
- const resolved = await this.#resolvePlugin(pluginConfig);
4496
- if (isFunction(resolved)) plugins = toArray(await Promise.resolve(pluginOptions ? resolved(pluginOptions) : resolved()));
4497
- else plugins = toArray(resolved);
4498
- } else if (isFunction(pluginConfig)) plugins = toArray(await Promise.resolve(pluginConfig(pluginOptions)));
4499
- else if (Array.isArray(pluginConfig) && pluginConfig.every(isPlugin)) plugins = pluginConfig;
4500
- else if (isPlugin(pluginConfig)) plugins = toArray(pluginConfig);
4501
- }
4502
- if (!plugins) throw new Error(`The plugin configuration ${JSON.stringify(awaited)} is invalid. This configuration must point to a valid Powerlines plugin module.`);
4503
- if (plugins.length > 0 && !plugins.every(isPlugin)) throw new Error(`The plugin option ${JSON.stringify(plugins)} does not export a valid module. This configuration must point to a valid Powerlines plugin module.`);
4504
- const result = [];
4505
- for (const plugin of plugins) if (isDuplicate(plugin, this.context.plugins)) this.context.trace(`Duplicate ${chalk.bold.cyanBright(plugin.name)} plugin dependency detected - Skipping initialization.`);
4506
- else {
4507
- result.push(plugin);
4508
- this.context.trace(`Initializing the ${chalk.bold.cyanBright(plugin.name)} plugin...`);
4509
- }
4510
- return result;
4511
- }
4512
- async #resolvePlugin(pluginPath) {
4513
- if (pluginPath.startsWith("@") && pluginPath.split("/").filter(Boolean).length > 2) {
4514
- const splits = pluginPath.split("/").filter(Boolean);
4515
- pluginPath = `${splits[0]}/${splits[1]}`;
4516
- }
4517
- const isInstalled = isPackageExists(pluginPath, { paths: [this.context.config.cwd, this.context.config.root] });
4518
- if (!isInstalled && this.context.config.autoInstall) {
4519
- this.#context.warn(`The plugin package "${pluginPath}" is not installed. It will be installed automatically.`);
4520
- const result = await install(pluginPath, { cwd: this.context.config.root });
4521
- if (isNumber(result.exitCode) && result.exitCode > 0) {
4522
- this.#context.error(result.stderr);
4523
- throw new Error(`An error occurred while installing the build plugin package "${pluginPath}" `);
4524
- }
4525
- }
4526
- try {
4527
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(joinPaths(pluginPath, "plugin")));
4528
- const result = module.plugin ?? module.default;
4529
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4530
- return result;
4531
- } catch (error) {
4532
- try {
4533
- const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(pluginPath));
4534
- const result = module.plugin ?? module.default;
4535
- if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4536
- return result;
4537
- } catch {
4538
- if (!isInstalled) throw new Error(`The plugin package "${pluginPath}" is not installed. Please install the package using the command: "npm install ${pluginPath} --save-dev"`);
4539
- else throw new Error(`An error occurred while importing the build plugin package "${pluginPath}":
4540
- ${isError(error) ? error.message : String(error)}
4541
-
4542
- Note: Please ensure the plugin package's default export is a class that extends \`Plugin\` with a constructor that excepts a single arguments of type \`PluginOptions\`.`);
4513
+ async types(inlineConfig = { command: "types" }) {
4514
+ this.context.debug(" Aggregating configuration options for the Powerlines project");
4515
+ inlineConfig.command ??= "types";
4516
+ await this.context.setInlineConfig(inlineConfig);
4517
+ await this.executeEnvironments(async (context) => {
4518
+ context.debug(`Initializing the processing options for the Powerlines project.`);
4519
+ await this.callHook("configResolved", {
4520
+ environment: context,
4521
+ order: "pre"
4522
+ });
4523
+ await initializeTsconfig(context);
4524
+ await this.callHook("configResolved", {
4525
+ environment: context,
4526
+ order: "normal"
4527
+ });
4528
+ 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")}` : ""}`);
4529
+ 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.`);
4530
+ await resolveTsconfig(context);
4531
+ await installDependencies(context);
4532
+ await this.callHook("configResolved", {
4533
+ environment: context,
4534
+ order: "post"
4535
+ });
4536
+ context.trace(`Powerlines configuration has been resolved: \n\n${formatLogMessage({
4537
+ ...context.config,
4538
+ userConfig: isSetObject(context.config.userConfig) ? omit(context.config.userConfig, ["plugins"]) : void 0,
4539
+ inlineConfig: isSetObject(context.config.inlineConfig) ? omit(context.config.inlineConfig, ["plugins"]) : void 0,
4540
+ plugins: context.plugins.map((plugin) => plugin.plugin.name)
4541
+ })}`);
4542
+ if (!context.fs.existsSync(context.cachePath)) await createDirectory(context.cachePath);
4543
+ if (!context.fs.existsSync(context.dataPath)) await createDirectory(context.dataPath);
4544
+ if (context.config.skipCache === true || context.persistedMeta?.checksum !== context.meta.checksum) context.debug(`Using previously prepared files as the meta checksum has not changed.`);
4545
+ else {
4546
+ context.info(`Running \`prepare\` command as the meta checksum has changed since the last run.`);
4547
+ await this.prepare(defu({ output: { types: false } }, inlineConfig));
4543
4548
  }
4544
- }
4549
+ await this.handleTypes(context);
4550
+ this.context.debug("Formatting files generated during the types step.");
4551
+ await format(context, context.typesPath, await context.fs.read(context.typesPath) ?? "");
4552
+ await writeMetaFile(context);
4553
+ context.persistedMeta = context.meta;
4554
+ });
4545
4555
  }
4546
4556
  /**
4547
- * Generate the Powerlines TypeScript declaration file
4557
+ * Prepare the Powerlines API
4548
4558
  *
4549
4559
  * @remarks
4550
- * This method will generate the TypeScript declaration file for the Powerlines project, including any types provided by plugins.
4560
+ * This method will prepare the Powerlines API for use, initializing any necessary resources.
4551
4561
  *
4552
- * @param context - The environment context to use for generating the TypeScript declaration file
4553
- * @returns A promise that resolves when the TypeScript declaration file has been generated
4554
- */
4555
- async #types(context) {
4556
- context.debug(`Preparing the TypeScript definitions for the Powerlines project.`);
4557
- if (context.fs.existsSync(context.typesPath)) await context.fs.remove(context.typesPath);
4558
- if (!await resolvePackage("typescript")) throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed.");
4559
- context.debug("Running TypeScript compiler for built-in runtime module files.");
4560
- let { code, directives } = await emitBuiltinTypes(context, (await context.getBuiltins()).reduce((ret, builtin) => {
4561
- const formatted = replacePath(builtin.path, context.config.cwd);
4562
- if (!ret.includes(formatted)) ret.push(formatted);
4563
- return ret;
4564
- }, []));
4565
- context.debug(`Generating TypeScript declaration file ${context.typesPath}.`);
4566
- const merge = async (currentResult, previousResult) => {
4567
- if (!isSetString(currentResult) && !isSetObject(currentResult) && !isSetString(previousResult) && !isSetObject(previousResult)) return {
4568
- code,
4569
- directives
4570
- };
4571
- const previous = (await format(context, context.typesPath, isSetString(previousResult) ? previousResult : isSetObject(previousResult) ? previousResult.code : "")).trim().replace(code, "").trim();
4572
- const current = (await format(context, context.typesPath, isSetString(currentResult) ? currentResult : isSetObject(currentResult) ? currentResult.code : "")).trim().replace(previous, "").trim().replace(code, "").trim();
4573
- return {
4574
- directives: [...isSetObject(currentResult) && currentResult.directives ? currentResult.directives : [], ...isSetObject(previousResult) && previousResult.directives ? previousResult.directives : []],
4575
- code: await format(context, context.typesPath, `${!previous.includes(getTypescriptFileHeader(context)) && !current.includes(getTypescriptFileHeader(context)) ? `${code}\n` : ""}${previous}\n${current}`.trim())
4576
- };
4577
- };
4578
- const asNextParam = (previousResult) => isObject(previousResult) ? previousResult.code : previousResult;
4579
- let result = await this.callHook("types", {
4580
- environment: context,
4581
- sequential: true,
4582
- order: "pre",
4583
- result: "merge",
4584
- merge,
4585
- asNextParam
4586
- }, code);
4587
- if (result) {
4588
- if (isSetObject(result)) {
4589
- code = result.code;
4590
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4591
- } else if (isSetString(result)) code = result;
4592
- }
4593
- result = await this.callHook("types", {
4594
- environment: context,
4595
- sequential: true,
4596
- order: "normal",
4597
- result: "merge",
4598
- merge,
4599
- asNextParam
4600
- }, code);
4601
- if (result) {
4602
- if (isSetObject(result)) {
4603
- code = result.code;
4604
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4605
- } else if (isSetString(result)) code = result;
4606
- }
4607
- result = await this.callHook("types", {
4608
- environment: context,
4609
- sequential: true,
4610
- order: "post",
4611
- result: "merge",
4612
- merge,
4613
- asNextParam
4614
- }, code);
4615
- if (result) {
4616
- if (isSetObject(result)) {
4617
- code = result.code;
4618
- if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
4619
- } else if (isSetString(result)) code = result;
4620
- }
4621
- if (isSetString(code?.trim()) || directives.length > 0) await context.fs.write(context.typesPath, `${directives.length > 0 ? `${directives.map((directive) => `/// <reference types="${directive}" />`).join("\n")}
4622
-
4623
- ` : ""}${getTypescriptFileHeader(context, {
4624
- directive: null,
4625
- prettierIgnore: false
4626
- })}
4627
-
4628
- ${formatTypes(code)}
4629
- `);
4630
- }
4631
- /**
4632
- * Initialize a Powerlines API instance
4633
- *
4634
- * @param options - The options to initialize the API with
4635
- * @returns A new instance of the Powerlines API
4636
- */
4637
- static async init(options, initialConfig) {
4638
- const api = new PowerlinesExecution(await PowerlinesExecutionContext.init(options, initialConfig ?? {}));
4639
- api.#context.config.initialConfig = initialConfig ?? {};
4640
- await api.setup();
4641
- return api;
4642
- }
4643
- /**
4644
- * The Powerlines context
4645
- */
4646
- get context() {
4647
- return this.#context;
4648
- }
4649
- /**
4650
- * Initialize the execution API with the provided configuration options
4651
- */
4652
- async setup() {
4653
- await this.#context.setup();
4654
- this.#context.$$internal = {
4655
- api: this,
4656
- addPlugin: this.addPlugin.bind(this)
4657
- };
4658
- const timer = this.#context.timer("Initialization");
4659
- for (const plugin of this.#context.config.plugins.flatMap((p) => toArray(p)) ?? []) await this.addPlugin(plugin);
4660
- if (this.#context.plugins.length === 0) this.#context.warn({
4661
- meta: { category: "plugins" },
4662
- message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
4663
- });
4664
- else this.#context.info({
4665
- meta: { category: "plugins" },
4666
- message: `Loaded ${this.#context.plugins.length} ${titleCase(this.#context.config.framework)} plugin${this.#context.plugins.length > 1 ? "s" : ""}: \n${this.#context.plugins.map((plugin, index) => ` ${index + 1}. ${colorText(plugin.name)}`).join("\n")}`
4667
- });
4668
- const pluginConfig = await this.callHook("config", {
4669
- environment: await this.#context.getEnvironment(),
4670
- sequential: true,
4671
- result: "merge",
4672
- merge: mergeConfigs
4673
- });
4674
- if (pluginConfig) {
4675
- this.#context.config.pluginConfig = pluginConfig;
4676
- await this.#context.setup();
4677
- }
4678
- timer();
4679
- }
4680
- /**
4681
- * Generate the Powerlines typescript declaration file
4682
- *
4683
- * @remarks
4684
- * This method will only generate the typescript declaration file for the Powerlines project. It is generally recommended to run the full `prepare` command, which will run this method as part of its process.
4685
- *
4686
- * @param inlineConfig - The inline configuration for the types command
4687
- */
4688
- async types(inlineConfig = { command: "types" }) {
4689
- this.context.debug(" Aggregating configuration options for the Powerlines project");
4690
- inlineConfig.command ??= "types";
4691
- this.context.config.inlineConfig = inlineConfig;
4692
- await this.setup();
4693
- await this.#executeEnvironments(async (context) => {
4694
- context.debug(`Initializing the processing options for the Powerlines project.`);
4695
- await this.callHook("configResolved", {
4696
- environment: context,
4697
- order: "pre"
4698
- });
4699
- await initializeTsconfig(context);
4700
- await this.callHook("configResolved", {
4701
- environment: context,
4702
- order: "normal"
4703
- });
4704
- if (context.entry.length > 0) context.debug(`The configuration provided ${isObject(context.config.input) ? Object.keys(context.config.input).length : toArray(context.config.input).length} entry point(s), Powerlines has found ${context.entry.length} entry files(s) for the ${context.config.title} project${context.entry.length > 0 && context.entry.length < 10 ? `: \n${context.entry.map((entry) => `- ${entry.file}${entry.output ? ` -> ${entry.output}` : ""}`).join(" \n")}` : ""}`);
4705
- else context.warn(`No entry files were found for the ${context.config.title} project. Please ensure this is correct. Powerlines plugins generally require at least one entry point to function properly.`);
4706
- await resolveTsconfig(context);
4707
- await installDependencies(context);
4708
- await this.callHook("configResolved", {
4709
- environment: context,
4710
- order: "post"
4711
- });
4712
- context.trace(`Powerlines configuration has been resolved: \n\n${formatLogMessage({
4713
- ...context.config,
4714
- userConfig: isSetObject(context.config.userConfig) ? omit(context.config.userConfig, ["plugins"]) : void 0,
4715
- inlineConfig: isSetObject(context.config.inlineConfig) ? omit(context.config.inlineConfig, ["plugins"]) : void 0,
4716
- plugins: context.plugins.map((plugin) => plugin.plugin.name)
4717
- })}`);
4718
- if (!context.fs.existsSync(context.cachePath)) await createDirectory(context.cachePath);
4719
- if (!context.fs.existsSync(context.dataPath)) await createDirectory(context.dataPath);
4720
- if (context.config.skipCache === true || context.persistedMeta?.checksum !== context.meta.checksum) context.debug(`Using previously prepared files as the meta checksum has not changed.`);
4721
- else {
4722
- context.info(`Running \`prepare\` command as the meta checksum has changed since the last run.`);
4723
- await this.prepare(defu({ output: { types: false } }, inlineConfig));
4724
- }
4725
- await this.#types(context);
4726
- this.context.debug("Formatting files generated during the types step.");
4727
- await format(context, context.typesPath, await context.fs.read(context.typesPath) ?? "");
4728
- await writeMetaFile(context);
4729
- context.persistedMeta = context.meta;
4730
- });
4731
- }
4732
- /**
4733
- * Prepare the Powerlines API
4734
- *
4735
- * @remarks
4736
- * This method will prepare the Powerlines API for use, initializing any necessary resources.
4737
- *
4738
- * @param inlineConfig - The inline configuration for the prepare command
4562
+ * @param inlineConfig - The inline configuration for the prepare command
4739
4563
  */
4740
4564
  async prepare(inlineConfig = { command: "prepare" }) {
4741
4565
  inlineConfig.command ??= "prepare";
4742
- this.context.config.inlineConfig = inlineConfig;
4743
- await this.setup();
4744
- await this.#executeEnvironments(async (context) => {
4566
+ await this.context.setInlineConfig(inlineConfig);
4567
+ await this.executeEnvironments(async (context) => {
4745
4568
  context.debug(`Initializing the processing options for the Powerlines project.`);
4746
4569
  await this.callHook("configResolved", {
4747
4570
  environment: context,
@@ -4791,7 +4614,7 @@ ${formatTypes(code)}
4791
4614
  environment: context,
4792
4615
  order: "post"
4793
4616
  });
4794
- if (context.config.output.types !== false) await this.#types(context);
4617
+ if (context.config.output.types !== false) await this.handleTypes(context);
4795
4618
  this.context.debug("Formatting files generated during the prepare step.");
4796
4619
  await Promise.all([formatFolder(context, context.builtinsPath), formatFolder(context, context.entryPath)]);
4797
4620
  await writeMetaFile(context);
@@ -4810,7 +4633,7 @@ ${formatTypes(code)}
4810
4633
  async new(inlineConfig) {
4811
4634
  inlineConfig.command ??= "new";
4812
4635
  await this.prepare(inlineConfig);
4813
- await this.#executeEnvironments(async (context) => {
4636
+ await this.executeEnvironments(async (context) => {
4814
4637
  context.debug("Initializing the processing options for the Powerlines project.");
4815
4638
  await this.callHook("new", {
4816
4639
  environment: context,
@@ -4859,7 +4682,7 @@ ${formatTypes(code)}
4859
4682
  async clean(inlineConfig = { command: "clean" }) {
4860
4683
  inlineConfig.command ??= "clean";
4861
4684
  await this.prepare(inlineConfig);
4862
- await this.#executeEnvironments(async (context) => {
4685
+ await this.executeEnvironments(async (context) => {
4863
4686
  context.debug("Cleaning the project's dist and artifacts directories.");
4864
4687
  await context.fs.remove(joinPaths(context.config.cwd, context.config.output.path));
4865
4688
  await context.fs.remove(joinPaths(context.config.cwd, context.config.root, context.config.output.artifactsPath));
@@ -4878,7 +4701,7 @@ ${formatTypes(code)}
4878
4701
  async lint(inlineConfig = { command: "lint" }) {
4879
4702
  inlineConfig.command ??= "lint";
4880
4703
  await this.prepare(inlineConfig);
4881
- await this.#executeEnvironments(async (context) => {
4704
+ await this.executeEnvironments(async (context) => {
4882
4705
  await this.callHook("lint", {
4883
4706
  environment: context,
4884
4707
  sequential: false
@@ -4894,7 +4717,7 @@ ${formatTypes(code)}
4894
4717
  async test(inlineConfig = { command: "test" }) {
4895
4718
  inlineConfig.command ??= "test";
4896
4719
  await this.prepare(inlineConfig);
4897
- await this.#executeEnvironments(async (context) => {
4720
+ await this.executeEnvironments(async (context) => {
4898
4721
  await this.callHook("test", {
4899
4722
  environment: context,
4900
4723
  sequential: false
@@ -4912,17 +4735,15 @@ ${formatTypes(code)}
4912
4735
  */
4913
4736
  async build(inlineConfig = { command: "build" }) {
4914
4737
  inlineConfig.command ??= "build";
4738
+ await this.context.setInlineConfig(inlineConfig);
4915
4739
  await this.context.generateChecksum();
4916
4740
  if (this.context.meta.checksum !== this.context.persistedMeta?.checksum || this.context.config.skipCache) {
4917
4741
  this.context.info(!this.context.persistedMeta?.checksum ? "No previous build cache found. Preparing the project for the initial build." : this.context.meta.checksum !== this.context.persistedMeta.checksum ? "The project has been modified since the last time `prepare` was ran. Re-preparing the project." : "The project is configured to skip cache. Re-preparing the project.");
4918
4742
  await this.prepare(inlineConfig);
4919
- } else {
4920
- this.context.config.inlineConfig = inlineConfig;
4921
- await this.context.setup();
4922
4743
  }
4923
- if (this.context.config.singleBuild) await this.#handleBuild(await this.#context.toEnvironment());
4924
- else await this.#executeEnvironments(async (context) => {
4925
- await this.#handleBuild(context);
4744
+ if (this.context.config.singleBuild) await this.handleBuild(await this.#context.toEnvironment());
4745
+ else await this.executeEnvironments(async (context) => {
4746
+ await this.handleBuild(context);
4926
4747
  });
4927
4748
  }
4928
4749
  /**
@@ -4933,8 +4754,9 @@ ${formatTypes(code)}
4933
4754
  */
4934
4755
  async docs(inlineConfig = { command: "docs" }) {
4935
4756
  inlineConfig.command ??= "docs";
4757
+ await this.context.setInlineConfig(inlineConfig);
4936
4758
  await this.prepare(inlineConfig);
4937
- await this.#executeEnvironments(async (context) => {
4759
+ await this.executeEnvironments(async (context) => {
4938
4760
  context.debug("Writing documentation for the Powerlines project artifacts.");
4939
4761
  await this.callHook("docs", { environment: context });
4940
4762
  });
@@ -4949,8 +4771,9 @@ ${formatTypes(code)}
4949
4771
  */
4950
4772
  async deploy(inlineConfig = { command: "deploy" }) {
4951
4773
  inlineConfig.command ??= "deploy";
4774
+ await this.context.setInlineConfig(inlineConfig);
4952
4775
  await this.prepare(inlineConfig);
4953
- await this.#executeEnvironments(async (context) => {
4776
+ await this.executeEnvironments(async (context) => {
4954
4777
  await this.callHook("deploy", { environment: context });
4955
4778
  });
4956
4779
  }
@@ -4963,7 +4786,7 @@ ${formatTypes(code)}
4963
4786
  * @returns A promise that resolves when the finalization process has completed
4964
4787
  */
4965
4788
  async finalize() {
4966
- await this.#executeEnvironments(async (context) => {
4789
+ await this.executeEnvironments(async (context) => {
4967
4790
  await this.callHook("finalize", { environment: context });
4968
4791
  await context.fs.dispose();
4969
4792
  if (existsSync(context.cachePath) && !(await listFiles(joinPaths(context.cachePath, "**/*")))?.length) await removeDirectory(context.cachePath);
@@ -4995,13 +4818,40 @@ ${formatTypes(code)}
4995
4818
  this.#context = context;
4996
4819
  }
4997
4820
  /**
4821
+ * Initialize the execution API with the provided configuration options
4822
+ */
4823
+ async init() {
4824
+ this.#context.$$internal = {
4825
+ api: this,
4826
+ addPlugin: this.addPlugin.bind(this)
4827
+ };
4828
+ const timer = this.#context.timer("Initialization");
4829
+ for (const plugin of this.#context.config.plugins.flatMap((p) => toArray(p)) ?? []) await this.addPlugin(plugin);
4830
+ if (this.#context.plugins.length === 0) this.#context.warn({
4831
+ meta: { category: "plugins" },
4832
+ message: "No Powerlines plugins were specified in the options. Please ensure this is correct, as it is generally not recommended."
4833
+ });
4834
+ else this.#context.info({
4835
+ meta: { category: "plugins" },
4836
+ 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")}`
4837
+ });
4838
+ const pluginConfig = await this.callHook("config", {
4839
+ environment: await this.#context.getEnvironment(),
4840
+ sequential: true,
4841
+ result: "merge",
4842
+ merge: mergeConfigs
4843
+ });
4844
+ if (pluginConfig) await this.context.setPluginConfig(pluginConfig);
4845
+ timer();
4846
+ }
4847
+ /**
4998
4848
  * Add a Powerlines plugin used in the build process
4999
4849
  *
5000
4850
  * @param config - The import path of the plugin to add
5001
4851
  */
5002
4852
  async addPlugin(config) {
5003
4853
  if (config) {
5004
- const result = await this.#initPlugin(config);
4854
+ const result = await this.initPlugin(config);
5005
4855
  if (!result) return;
5006
4856
  for (const plugin of result) {
5007
4857
  this.context.debug({
@@ -5012,33 +4862,266 @@ ${formatTypes(code)}
5012
4862
  }
5013
4863
  }
5014
4864
  }
4865
+ /**
4866
+ * Get the configured environments
4867
+ *
4868
+ * @returns The configured environments
4869
+ */
4870
+ async getEnvironments() {
4871
+ if (!this.context.config.environments || Object.keys(this.context.config.environments).length <= 1) {
4872
+ this.context.debug("No environments are configured for this Powerlines project. Using the default environment.");
4873
+ return [await this.context.getEnvironment()];
4874
+ }
4875
+ this.context.debug(`Found ${Object.keys(this.context.config.environments).length} configured environment(s) for this Powerlines project.`);
4876
+ return (await Promise.all(Object.entries(this.context.config.environments).map(async ([name, config]) => {
4877
+ if (!await this.context.getEnvironmentSafe(name)) {
4878
+ const resolvedEnvironment = await this.callHook("configEnvironment", { environment: name }, name, config);
4879
+ if (resolvedEnvironment) this.context.environments[name] = await this.context.createEnvironment(resolvedEnvironment);
4880
+ }
4881
+ return this.context.environments[name];
4882
+ }))).filter((context) => isSet(context));
4883
+ }
4884
+ /**
4885
+ * Execute a handler function for each environment
4886
+ *
4887
+ * @param handle - The handler function to execute for each environment
4888
+ */
4889
+ async executeEnvironments(handle) {
4890
+ await Promise.all((await this.getEnvironments()).map(async (context) => {
4891
+ return Promise.resolve(handle(context));
4892
+ }));
4893
+ }
4894
+ /**
4895
+ * Initialize a Powerlines plugin
4896
+ *
4897
+ * @param config - The configuration for the plugin
4898
+ * @returns The initialized plugin instance, or null if the plugin was a duplicate
4899
+ * @throws Will throw an error if the plugin cannot be found or is invalid
4900
+ */
4901
+ async initPlugin(config) {
4902
+ let awaited = config;
4903
+ if (isPromiseLike(config)) awaited = await Promise.resolve(config);
4904
+ if (!isPluginConfig(awaited)) {
4905
+ const invalid = findInvalidPluginConfig(awaited);
4906
+ 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`);
4907
+ }
4908
+ let plugins;
4909
+ if (isPlugin(awaited)) plugins = [awaited];
4910
+ else if (isFunction(awaited)) plugins = toArray(await Promise.resolve(awaited()));
4911
+ else if (isString(awaited)) {
4912
+ const resolved = await this.resolvePlugin(awaited);
4913
+ if (isFunction(resolved)) plugins = toArray(await Promise.resolve(resolved()));
4914
+ else plugins = toArray(resolved);
4915
+ } else if (Array.isArray(awaited) && awaited.every(isPlugin)) plugins = awaited;
4916
+ else if (Array.isArray(awaited) && awaited.every(isPluginConfig)) {
4917
+ plugins = [];
4918
+ for (const pluginConfig of awaited) {
4919
+ const initialized = await this.initPlugin(pluginConfig);
4920
+ if (initialized) plugins.push(...initialized);
4921
+ }
4922
+ } else if (isPluginConfigTuple(awaited) || isPluginConfigObject(awaited)) {
4923
+ let pluginConfig;
4924
+ let pluginOptions;
4925
+ if (isPluginConfigTuple(awaited)) {
4926
+ pluginConfig = awaited[0];
4927
+ pluginOptions = awaited?.length === 2 ? awaited[1] : void 0;
4928
+ } else {
4929
+ pluginConfig = awaited.plugin;
4930
+ pluginOptions = awaited.options;
4931
+ }
4932
+ if (isSetString(pluginConfig)) {
4933
+ const resolved = await this.resolvePlugin(pluginConfig);
4934
+ if (isFunction(resolved)) plugins = toArray(await Promise.resolve(pluginOptions ? resolved(pluginOptions) : resolved()));
4935
+ else plugins = toArray(resolved);
4936
+ } else if (isFunction(pluginConfig)) plugins = toArray(await Promise.resolve(pluginConfig(pluginOptions)));
4937
+ else if (Array.isArray(pluginConfig) && pluginConfig.every(isPlugin)) plugins = pluginConfig;
4938
+ else if (isPlugin(pluginConfig)) plugins = toArray(pluginConfig);
4939
+ }
4940
+ if (!plugins) throw new Error(`The plugin configuration ${JSON.stringify(awaited)} is invalid. This configuration must point to a valid Powerlines plugin module.`);
4941
+ 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.`);
4942
+ const result = [];
4943
+ 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.`);
4944
+ else {
4945
+ result.push(plugin);
4946
+ this.context.trace(`Initializing the ${chalk.bold.cyanBright(plugin.name)} plugin...`);
4947
+ }
4948
+ return result;
4949
+ }
4950
+ async resolvePlugin(pluginPath) {
4951
+ if (pluginPath.startsWith("@") && pluginPath.split("/").filter(Boolean).length > 2) {
4952
+ const splits = pluginPath.split("/").filter(Boolean);
4953
+ pluginPath = `${splits[0]}/${splits[1]}`;
4954
+ }
4955
+ const isInstalled = isPackageExists(pluginPath, { paths: [this.context.config.cwd, this.context.config.root] });
4956
+ if (!isInstalled && this.context.config.autoInstall) {
4957
+ this.#context.warn(`The plugin package "${pluginPath}" is not installed. It will be installed automatically.`);
4958
+ const result = await install(pluginPath, { cwd: this.context.config.root });
4959
+ if (isNumber(result.exitCode) && result.exitCode > 0) {
4960
+ this.#context.error(result.stderr);
4961
+ throw new Error(`An error occurred while installing the build plugin package "${pluginPath}" `);
4962
+ }
4963
+ }
4964
+ try {
4965
+ const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(joinPaths(pluginPath, "plugin")));
4966
+ const result = module.plugin ?? module.default;
4967
+ if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4968
+ return result;
4969
+ } catch (error) {
4970
+ try {
4971
+ const module = await this.context.resolver.plugin.import(this.context.resolver.plugin.esmResolve(pluginPath));
4972
+ const result = module.plugin ?? module.default;
4973
+ if (!result) throw new Error(`The plugin package "${pluginPath}" does not export a valid module.`);
4974
+ return result;
4975
+ } catch {
4976
+ if (!isInstalled) throw new Error(`The plugin package "${pluginPath}" is not installed. Please install the package using the command: "npm install ${pluginPath} --save-dev"`);
4977
+ else throw new Error(`An error occurred while importing the build plugin package "${pluginPath}":
4978
+ ${isError(error) ? error.message : String(error)}
4979
+
4980
+ 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\`.`);
4981
+ }
4982
+ }
4983
+ }
4984
+ async handleBuild(context) {
4985
+ await this.callHook("build", {
4986
+ environment: context,
4987
+ order: "pre"
4988
+ });
4989
+ context.debug("Formatting the generated entry files before the build process starts.");
4990
+ await formatFolder(context, context.entryPath);
4991
+ await this.callHook("build", {
4992
+ environment: context,
4993
+ order: "normal"
4994
+ });
4995
+ if (context.config.output.copy) {
4996
+ context.debug("Copying project's files from build output directory.");
4997
+ 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");
4998
+ const sourcePath = context.config.output.path;
4999
+ if (existsSync(sourcePath) && sourcePath !== destinationPath) {
5000
+ context.debug(`Copying files from project's build output directory (${context.config.output.path}) to the project's copy/publish directory (${destinationPath}).`);
5001
+ await copyFiles(sourcePath, destinationPath);
5002
+ } 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.`);
5003
+ if (context.config.output.copy.assets && Array.isArray(context.config.output.copy.assets)) await Promise.all(context.config.output.copy.assets.map(async (asset) => {
5004
+ 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(", ")})` : ""}`);
5005
+ await context.fs.copy(asset, asset.output);
5006
+ }));
5007
+ } else context.debug("No copy configuration found for the project output. Skipping the copying of build output files.");
5008
+ await this.callHook("build", {
5009
+ environment: context,
5010
+ order: "post"
5011
+ });
5012
+ }
5013
+ /**
5014
+ * Generate the Powerlines TypeScript declaration file
5015
+ *
5016
+ * @remarks
5017
+ * This method will generate the TypeScript declaration file for the Powerlines project, including any types provided by plugins.
5018
+ *
5019
+ * @param context - The environment context to use for generating the TypeScript declaration file
5020
+ * @returns A promise that resolves when the TypeScript declaration file has been generated
5021
+ */
5022
+ async handleTypes(context) {
5023
+ context.debug(`Preparing the TypeScript definitions for the Powerlines project.`);
5024
+ if (context.fs.existsSync(context.typesPath)) await context.fs.remove(context.typesPath);
5025
+ if (!await resolvePackage("typescript")) throw new Error("Could not resolve TypeScript package location. Please ensure TypeScript is installed.");
5026
+ context.debug("Running TypeScript compiler for built-in runtime module files.");
5027
+ let { code, directives } = await emitBuiltinTypes(context, (await context.getBuiltins()).reduce((ret, builtin) => {
5028
+ const formatted = replacePath(builtin.path, context.config.cwd);
5029
+ if (!ret.includes(formatted)) ret.push(formatted);
5030
+ return ret;
5031
+ }, []));
5032
+ context.debug(`Generating TypeScript declaration file ${context.typesPath}.`);
5033
+ const merge = async (currentResult, previousResult) => {
5034
+ if (!isSetString(currentResult) && !isSetObject(currentResult) && !isSetString(previousResult) && !isSetObject(previousResult)) return {
5035
+ code,
5036
+ directives
5037
+ };
5038
+ const previous = (await format(context, context.typesPath, isSetString(previousResult) ? previousResult : isSetObject(previousResult) ? previousResult.code : "")).trim().replace(code, "").trim();
5039
+ const current = (await format(context, context.typesPath, isSetString(currentResult) ? currentResult : isSetObject(currentResult) ? currentResult.code : "")).trim().replace(previous, "").trim().replace(code, "").trim();
5040
+ return {
5041
+ directives: [...isSetObject(currentResult) && currentResult.directives ? currentResult.directives : [], ...isSetObject(previousResult) && previousResult.directives ? previousResult.directives : []],
5042
+ code: await format(context, context.typesPath, `${!previous.includes(getTypescriptFileHeader(context)) && !current.includes(getTypescriptFileHeader(context)) ? `${code}\n` : ""}${previous}\n${current}`.trim())
5043
+ };
5044
+ };
5045
+ const asNextParam = (previousResult) => isObject(previousResult) ? previousResult.code : previousResult;
5046
+ let result = await this.callHook("types", {
5047
+ environment: context,
5048
+ sequential: true,
5049
+ order: "pre",
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
+ result = await this.callHook("types", {
5061
+ environment: context,
5062
+ sequential: true,
5063
+ order: "normal",
5064
+ result: "merge",
5065
+ merge,
5066
+ asNextParam
5067
+ }, code);
5068
+ if (result) {
5069
+ if (isSetObject(result)) {
5070
+ code = result.code;
5071
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
5072
+ } else if (isSetString(result)) code = result;
5073
+ }
5074
+ result = await this.callHook("types", {
5075
+ environment: context,
5076
+ sequential: true,
5077
+ order: "post",
5078
+ result: "merge",
5079
+ merge,
5080
+ asNextParam
5081
+ }, code);
5082
+ if (result) {
5083
+ if (isSetObject(result)) {
5084
+ code = result.code;
5085
+ if (Array.isArray(result.directives) && result.directives.length > 0) directives = getUnique([...directives, ...result.directives]).filter(Boolean);
5086
+ } else if (isSetString(result)) code = result;
5087
+ }
5088
+ if (isSetString(code?.trim()) || directives.length > 0) await context.fs.write(context.typesPath, `${directives.length > 0 ? `${directives.map((directive) => `/// <reference types="${directive}" />`).join("\n")}
5089
+
5090
+ ` : ""}${getTypescriptFileHeader(context, {
5091
+ directive: null,
5092
+ prettierIgnore: false
5093
+ })}
5094
+
5095
+ ${formatTypes(code)}
5096
+ `);
5097
+ }
5015
5098
  };
5016
5099
 
5017
5100
  //#endregion
5018
5101
  //#region src/_internal/worker.ts
5019
5102
  async function clean({ options, initialConfig, inlineConfig }) {
5020
- await (await PowerlinesExecution.init(options, initialConfig)).clean(inlineConfig);
5103
+ await (await PowerlinesExecution.from(options, initialConfig)).clean(inlineConfig);
5021
5104
  }
5022
5105
  async function prepare({ options, initialConfig, inlineConfig }) {
5023
- await (await PowerlinesExecution.init(options, initialConfig)).prepare(inlineConfig);
5106
+ await (await PowerlinesExecution.from(options, initialConfig)).prepare(inlineConfig);
5024
5107
  }
5025
5108
  async function types({ options, initialConfig, inlineConfig }) {
5026
- await (await PowerlinesExecution.init(options, initialConfig)).types(inlineConfig);
5109
+ await (await PowerlinesExecution.from(options, initialConfig)).types(inlineConfig);
5027
5110
  }
5028
5111
  async function lint({ options, initialConfig, inlineConfig }) {
5029
- await (await PowerlinesExecution.init(options, initialConfig)).lint(inlineConfig);
5112
+ await (await PowerlinesExecution.from(options, initialConfig)).lint(inlineConfig);
5030
5113
  }
5031
5114
  async function test({ options, initialConfig, inlineConfig }) {
5032
- await (await PowerlinesExecution.init(options, initialConfig)).test(inlineConfig);
5115
+ await (await PowerlinesExecution.from(options, initialConfig)).test(inlineConfig);
5033
5116
  }
5034
5117
  async function build({ options, initialConfig, inlineConfig }) {
5035
- await (await PowerlinesExecution.init(options, initialConfig)).build(inlineConfig);
5118
+ await (await PowerlinesExecution.from(options, initialConfig)).build(inlineConfig);
5036
5119
  }
5037
5120
  async function docs({ options, initialConfig, inlineConfig }) {
5038
- await (await PowerlinesExecution.init(options, initialConfig)).docs(inlineConfig);
5121
+ await (await PowerlinesExecution.from(options, initialConfig)).docs(inlineConfig);
5039
5122
  }
5040
5123
  async function deploy({ options, initialConfig, inlineConfig }) {
5041
- await (await PowerlinesExecution.init(options, initialConfig)).deploy(inlineConfig);
5124
+ await (await PowerlinesExecution.from(options, initialConfig)).deploy(inlineConfig);
5042
5125
  }
5043
5126
 
5044
5127
  //#endregion