@nu-art/build-and-install 0.400.13 → 0.401.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,9 @@ import { BaiParams } from './core/params/params.js';
3
3
  import { Phase } from './v3/phase/index.js';
4
4
  import { UnitsMapper } from './v3/UnitsMapper/UnitsMapper.js';
5
5
  import { ProjectUnit } from './v3/units/ProjectUnit.js';
6
- import { Unit_NodeProject } from './v3/units/index.js';
6
+ import { PhaseManager } from './v3/PhaseManager.js';
7
+ import { BaseUnit, Unit_NodeProject } from './v3/units/index.js';
8
+ import { Workspace } from './v3/Workspace.js';
7
9
  import { BaseCliParam } from '@nu-art/commando/cli-params/types';
8
10
  import { RunningStatusHandler } from './v3/RunningStatusHandler.js';
9
11
  export declare const DefaultPhases: Phase<string>[][];
@@ -15,18 +17,28 @@ export declare class BuildAndInstall extends Logger {
15
17
  private unitsMapper;
16
18
  private phases;
17
19
  private pathToProject;
18
- private allUnits;
20
+ readonly workspace: Workspace;
19
21
  readonly nodeProjectUnit: Unit_NodeProject;
20
22
  readonly runtimeParams: BaiParams;
21
- readonly projectUnits: ProjectUnit[];
22
- private unitsDependencyMapper;
23
23
  readonly runningStatus: RunningStatusHandler;
24
+ readonly phaseManager: PhaseManager;
24
25
  constructor(config?: Partial<BAI_Options>);
25
26
  init(): Promise<void>;
26
27
  prepareUnitsMapper(unitsMapper: UnitsMapper): void;
27
28
  setApplicativeUnits(projectUnits: ProjectUnit[]): void;
28
29
  setPhases(phases: Phase<string>[][]): void;
29
- build(): Promise<ProjectUnit<import("./v3/units/ProjectUnit.js").Config_ProjectUnit>[]>;
30
+ /**
31
+ * @deprecated Use workspace.projectUnits instead
32
+ * Backward compatibility getter for existing tests
33
+ */
34
+ get projectUnits(): ReadonlyArray<ProjectUnit>;
35
+ /**
36
+ * @deprecated Use workspace.scannedUnits instead
37
+ * Backward compatibility getter for existing tests
38
+ */
39
+ get scannedUnits(): ReadonlyArray<BaseUnit<any>>;
30
40
  run(): Promise<void>;
41
+ build(): Promise<void>;
42
+ private loadVersion;
31
43
  }
32
44
  export {};
@@ -1,12 +1,10 @@
1
- import { _keys, arrayToMap, BeLogged, DebugFlag, filterDuplicates, ImplementationMissingException, LogClient_Terminal, Logger, LogLevel, merge } from '@nu-art/ts-common';
1
+ import { BeLogged, DebugFlag, filterDuplicates, ImplementationMissingException, LogClient_Terminal, Logger, LogLevel, merge } from '@nu-art/ts-common';
2
2
  import { AllBaiParams } from './core/params/params.js';
3
3
  import { phases_Build, phases_Deploy, phases_Launch, phases_Terminating } from './v3/phase/index.js';
4
4
  import { UnitsMapper } from './v3/UnitsMapper/UnitsMapper.js';
5
- import { UnitsDependencyMapper } from './v3/UnitsDependencyMapper/UnitsDependencyMapper.js';
6
5
  import { FilesCache } from './v3/core/FilesCache.js';
7
- import { ProjectUnit } from './v3/units/ProjectUnit.js';
8
6
  import { PhaseManager } from './v3/PhaseManager.js';
9
- import { Unit_NodeProject } from './v3/units/index.js';
7
+ import { Workspace } from './v3/Workspace.js';
10
8
  import { resolve } from 'path';
11
9
  import { CONST_BaiConfig, CONST_NodeModules, CONST_VersionApp } from './core/consts.js';
12
10
  import { UnitMapper_FirebaseFunction, UnitMapper_FirebaseHosting, UnitMapper_NodeLib, UnitMapper_NodeProject } from './v3/UnitsMapper/resolvers/index.js';
@@ -23,12 +21,11 @@ export class BuildAndInstall extends Logger {
23
21
  unitsMapper;
24
22
  phases = DefaultPhases;
25
23
  pathToProject;
26
- allUnits = [];
24
+ workspace;
27
25
  nodeProjectUnit;
28
26
  runtimeParams;
29
- projectUnits = [];
30
- unitsDependencyMapper;
31
27
  runningStatus;
28
+ phaseManager;
32
29
  constructor(config = {}) {
33
30
  super();
34
31
  const defaultConfig = merge({
@@ -39,6 +36,8 @@ export class BuildAndInstall extends Logger {
39
36
  BeLogged.addClient(LogClient_Terminal);
40
37
  this.pathToProject = defaultConfig.pathToProject;
41
38
  this.runningStatus = new RunningStatusHandler(this.pathToProject, this.runtimeParams);
39
+ this.workspace = new Workspace();
40
+ this.workspace.setMinLevel(DebugFlag.DefaultLogLevel);
42
41
  }
43
42
  async init() {
44
43
  await this.runningStatus.init();
@@ -60,88 +59,37 @@ export class BuildAndInstall extends Logger {
60
59
  .setRuntimeParams(this.runtimeParams);
61
60
  }
62
61
  setApplicativeUnits(projectUnits) {
63
- this.projectUnits.push(...projectUnits);
62
+ this.workspace.addProjectUnits(projectUnits);
64
63
  }
65
64
  setPhases(phases) {
66
65
  this.phases = phases;
67
66
  }
68
- async build() {
69
- await this.init();
70
- this.logVerbose(`Resolving units from: ${this.pathToProject}`);
71
- this.allUnits = await this.unitsMapper.resolveUnits(this.pathToProject);
72
- Object.freeze(this.allUnits);
73
- this.logDebug('Units found:', this.allUnits.map(unit => `${unit.constructor?.['name']}: ${unit.config.key}`).join('\n'));
74
- const unitKeyToUnitMap = arrayToMap(this.allUnits, unit => unit.config.key);
75
- const allProjectUnits = this.allUnits.filter(unit => unit.isInstanceOf(ProjectUnit));
76
- const nodeProjectUnit = allProjectUnits.find(unit => unit.isInstanceOf(Unit_NodeProject));
77
- // @ts-ignore
78
- this['nodeProjectUnit'] = nodeProjectUnit;
79
- if (!this.nodeProjectUnit)
80
- throw new ImplementationMissingException('NodeProject unit not found. Make sure you have a Unit_NodeProject in your project.');
81
- this.nodeProjectUnit.assignUnit(allProjectUnits);
82
- this.logDebug(`Parent unit: ${this.nodeProjectUnit.config.key}`);
83
- this.logDebug(`Child units: ${allProjectUnits.map(unit => unit.config.key).join(', ')}`);
84
- const pathToBaiConfig = resolve(this.nodeProjectUnit.config.fullPath, CONST_BaiConfig);
85
- this.logVerbose(`Loading BAI-Config from: ${pathToBaiConfig}`);
86
- const baiConfig = await FilesCache.load.json(pathToBaiConfig);
87
- this.logDebug('Loaded BAI-Config', baiConfig);
88
- this.projectUnits.push(...allProjectUnits);
89
- Object.freeze(this.projectUnits);
90
- const unitsDependencies = this.projectUnits.map(unit => ({
91
- key: unit.config.key,
92
- dependsOn: _keys(unit.config.dependencies).filter(key => !!unitKeyToUnitMap[key])
93
- }));
94
- const globalOutputFolder = resolve(this.pathToProject, '.trash/output');
95
- this.unitsDependencyMapper = new UnitsDependencyMapper(unitsDependencies, globalOutputFolder);
96
- const versionFilePath = resolve(this.pathToProject, CONST_VersionApp);
97
- this.logInfo('loading version from: ', versionFilePath);
98
- const version = await FileSystemUtils.file.read.json(versionFilePath, { version: '1.0.0' });
99
- const runtimeContext = ({
100
- version: version.version,
101
- parentUnit: this.nodeProjectUnit,
102
- childUnits: allProjectUnits,
103
- baiConfig,
104
- runtimeParams: this.runtimeParams,
105
- unitsMapper: this.unitsDependencyMapper,
106
- unitsResolver: (keys, className) => {
107
- return keys.map(key => unitKeyToUnitMap[key]).filter(unit => unit.isInstanceOf(className));
108
- },
109
- globalOutputFolder,
110
- });
111
- this.projectUnits.forEach(unit => unit.setupRuntimeContext(runtimeContext));
112
- if (!(await FileSystemUtils.file.exists(resolve(this.nodeProjectUnit.config.fullPath, CONST_NodeModules)))) {
113
- this.logInfo(`root project is missing ${CONST_NodeModules} folder. Enabling install...`);
114
- this.runtimeParams.install = true;
115
- }
116
- return allProjectUnits;
67
+ /**
68
+ * @deprecated Use workspace.projectUnits instead
69
+ * Backward compatibility getter for existing tests
70
+ */
71
+ get projectUnits() {
72
+ return this.workspace.projectUnits;
73
+ }
74
+ /**
75
+ * @deprecated Use workspace.scannedUnits instead
76
+ * Backward compatibility getter for existing tests
77
+ */
78
+ get scannedUnits() {
79
+ return this.workspace.scannedUnits;
117
80
  }
118
81
  async run() {
119
- const keyToUnitMap = arrayToMap(this.projectUnits, u => u.config.key);
120
- let topLevelAppKeys = this.projectUnits.filter(unit => unit.config.isTopLevelApp).map(unit => unit.config.key);
121
- this.logWarning('this.runtimeParams.includeApps', this.runtimeParams.includeApps);
122
- if (this.runtimeParams.includeApps?.length) {
123
- this.logWarning('HEREHREHREHRHERHER');
124
- const regexMatchers = this.runtimeParams.includeApps.map(filter => new RegExp(`.*?${filter}.*?`, 'i'));
125
- topLevelAppKeys = topLevelAppKeys.filter(unitKey => regexMatchers.some(matcher => matcher.test(unitKey)));
126
- }
127
- this.logDebug('topLevelAppKeys: ', topLevelAppKeys);
128
- const participatingUnitKeys = this.runtimeParams.allUnits
129
- ? undefined
130
- : [...this.unitsDependencyMapper.getTransitiveDependencies(topLevelAppKeys), ...topLevelAppKeys];
131
- const unitDependencyTree = (await this.unitsDependencyMapper.buildDependencyTree(participatingUnitKeys))
132
- .map(units => units.map(unitKey => keyToUnitMap[unitKey]));
133
- const phaseManager = new PhaseManager(this.runningStatus, this.phases, unitDependencyTree);
134
- const executionPlan = await phaseManager.calculateExecutionSteps();
82
+ const executionPlan = await this.phaseManager.calculateExecutionSteps();
135
83
  let killCounter = 0;
136
84
  process.on('SIGINT', async () => {
137
85
  this.logWarning('\n\n\n---------------------------------- Process Interrupted ----------------------------------\n\n\n');
138
- await phaseManager.break();
86
+ await this.phaseManager.break();
139
87
  killCounter++;
140
88
  if (killCounter > 5)
141
89
  process.exit(1);
142
90
  });
143
91
  try {
144
- await phaseManager.execute(executionPlan);
92
+ await this.phaseManager.execute(executionPlan);
145
93
  this.logInfo('Completed successfully');
146
94
  this.logInfo('---------------------------------- Process Completed successfully ----------------------------------');
147
95
  }
@@ -151,4 +99,59 @@ export class BuildAndInstall extends Logger {
151
99
  throw e;
152
100
  }
153
101
  }
102
+ async build() {
103
+ await this.init();
104
+ // Scan units from workspace
105
+ await this.workspace.scanUnits(this.pathToProject, this.unitsMapper);
106
+ const nodeProjectUnit = this.workspace.getNodeProjectUnit();
107
+ if (!nodeProjectUnit)
108
+ throw new ImplementationMissingException('NodeProject unit not found. Make sure you have a Unit_NodeProject in your project.');
109
+ // @ts-ignore
110
+ this['nodeProjectUnit'] = nodeProjectUnit;
111
+ const pathToBaiConfig = resolve(nodeProjectUnit.config.fullPath, CONST_BaiConfig);
112
+ this.logDebug(`Loading BAI-Config from: ${pathToBaiConfig}`);
113
+ const baiConfig = await FilesCache.load.json(pathToBaiConfig);
114
+ this.logVerbose('Loaded BAI-Config', baiConfig);
115
+ const globalOutputFolder = resolve(this.pathToProject, '.trash/output');
116
+ this.workspace.initializeDependencyMapper(globalOutputFolder);
117
+ let version = await this.loadVersion();
118
+ if (!(await FileSystemUtils.file.exists(resolve(nodeProjectUnit.config.fullPath, CONST_NodeModules)))) {
119
+ this.logInfo(`root project is missing ${CONST_NodeModules} folder. Enabling install...`);
120
+ this.runtimeParams.install = true;
121
+ }
122
+ // Derive active and project units based on runtime params
123
+ const units = this.workspace.deriveActiveAndProjectUnits(this.runtimeParams);
124
+ const childProjectUnits = this.workspace.getUnitsByKeys(units.projectUnits);
125
+ nodeProjectUnit.assignUnit(childProjectUnits);
126
+ this.logDebug(`Parent unit: ${nodeProjectUnit.config.key}`);
127
+ const runtimeContext = ({
128
+ version: version.version,
129
+ parentUnit: nodeProjectUnit,
130
+ childUnits: childProjectUnits,
131
+ baiConfig,
132
+ runtimeParams: this.runtimeParams,
133
+ unitsMapper: this.workspace.getDependencyMapper(),
134
+ unitsResolver: (keys, className) => {
135
+ return this.workspace.getUnitsByKeys(keys, className);
136
+ },
137
+ globalOutputFolder,
138
+ workspace: this.workspace,
139
+ });
140
+ this.workspace.projectUnits.forEach(unit => unit.setupRuntimeContext(runtimeContext));
141
+ const unitDependencyTree = await this.workspace.buildDependencyTree(units.projectUnits);
142
+ const manager = new PhaseManager(this.runningStatus, this.phases, unitDependencyTree, units.activeUnits, units.projectUnits);
143
+ // @ts-ignore
144
+ this['phaseManager'] = manager;
145
+ }
146
+ async loadVersion() {
147
+ const versionFilePath = resolve(this.pathToProject, CONST_VersionApp);
148
+ if (await FileSystemUtils.file.exists(versionFilePath)) {
149
+ this.logDebug('Loading version from: ', versionFilePath);
150
+ return await FileSystemUtils.file.read.json(versionFilePath, { version: '1.0.0' });
151
+ }
152
+ else {
153
+ this.logWarning(`No version file found at '${versionFilePath}', using default version: '1.0.0'`);
154
+ return { version: '1.0.0' };
155
+ }
156
+ }
154
157
  }
@@ -31,11 +31,11 @@ export declare const BaiParam_QuickDeploy: BaseCliParam<'quickDeploy', boolean>;
31
31
  type PromoteType = 'patch' | 'minor' | 'major';
32
32
  export declare const BaiParam_Publish: BaseCliParam<'publish', PromoteType>;
33
33
  export declare const BaiParam_UsePackage: BaseCliParam<'usePackage', string[]>;
34
- export declare const BaiParam_includePackage: BaseCliParam<'includePackage', string[]>;
34
+ export declare const BaiParam_BuildTree: BaseCliParam<'buildTree', boolean>;
35
35
  export declare const BaiParam_Apps: BaseCliParam<'includeApps', string[]>;
36
36
  export declare const BaiParam_ToESM: BaseCliParam<'toESM', boolean>;
37
37
  export declare const BaiParam_Simulate: BaseCliParam<'simulation', boolean>;
38
38
  export declare const BaiParam_CheckCyclicImports: BaseCliParam<'checkCyclicImports', boolean>;
39
- export declare const AllBaiParams: (BaseCliParam<"allUnits", boolean> | BaseCliParam<"dependencyTree", boolean> | BaseCliParam<"environment", string> | BaseCliParam<"install", boolean> | BaseCliParam<"clean", boolean> | BaseCliParam<"purge", boolean> | BaseCliParam<"generate", boolean> | BaseCliParam<"generateDocs", boolean> | BaseCliParam<"noBuild", boolean> | BaseCliParam<"prepare", boolean> | BaseCliParam<"dryRun", boolean> | BaseCliParam<"lint", boolean> | BaseCliParam<"watch", boolean> | BaseCliParam<"watchBuildTree", boolean> | BaseCliParam<"continue", boolean> | BaseCliParam<"test", boolean> | BaseCliParam<"testType", string[]> | BaseCliParam<"testFiles", string[]> | BaseCliParam<"testCases", string[]> | BaseCliParam<"testDebugPort", number> | BaseCliParam<"launch", boolean> | BaseCliParam<"debugBackend", boolean> | BaseCliParam<"deploy", boolean> | BaseCliParam<"debug", boolean> | BaseCliParam<"debugLifecycle", boolean> | BaseCliParam<"verbose", boolean> | BaseCliParam<"publish", PromoteType> | BaseCliParam<"usePackage", string[]> | BaseCliParam<"includePackage", string[]> | BaseCliParam<"includeApps", string[]> | BaseCliParam<"toESM", boolean> | BaseCliParam<"simulation", boolean> | BaseCliParam<"checkCyclicImports", boolean>)[];
39
+ export declare const AllBaiParams: (BaseCliParam<"allUnits", boolean> | BaseCliParam<"dependencyTree", boolean> | BaseCliParam<"environment", string> | BaseCliParam<"install", boolean> | BaseCliParam<"clean", boolean> | BaseCliParam<"purge", boolean> | BaseCliParam<"generate", boolean> | BaseCliParam<"generateDocs", boolean> | BaseCliParam<"noBuild", boolean> | BaseCliParam<"prepare", boolean> | BaseCliParam<"dryRun", boolean> | BaseCliParam<"lint", boolean> | BaseCliParam<"watch", boolean> | BaseCliParam<"watchBuildTree", boolean> | BaseCliParam<"continue", boolean> | BaseCliParam<"test", boolean> | BaseCliParam<"testType", string[]> | BaseCliParam<"testFiles", string[]> | BaseCliParam<"testCases", string[]> | BaseCliParam<"testDebugPort", number> | BaseCliParam<"launch", boolean> | BaseCliParam<"debugBackend", boolean> | BaseCliParam<"deploy", boolean> | BaseCliParam<"debug", boolean> | BaseCliParam<"debugLifecycle", boolean> | BaseCliParam<"verbose", boolean> | BaseCliParam<"publish", PromoteType> | BaseCliParam<"usePackage", string[]> | BaseCliParam<"buildTree", boolean> | BaseCliParam<"includeApps", string[]> | BaseCliParam<"toESM", boolean> | BaseCliParam<"simulation", boolean> | BaseCliParam<"checkCyclicImports", boolean>)[];
40
40
  export type BaiParams = CliParams<typeof AllBaiParams>;
41
41
  export {};
@@ -239,18 +239,12 @@ export const BaiParam_UsePackage = {
239
239
  },
240
240
  dependencies: [{ param: BaiParam_AllUnits, value: true }]
241
241
  };
242
- export const BaiParam_includePackage = {
243
- keys: ['-in', '--include'],
244
- keyName: 'includePackage',
245
- type: 'string[]',
242
+ export const BaiParam_BuildTree = {
243
+ keys: ['--build-tree', '-bt'],
244
+ keyName: 'buildTree',
245
+ type: 'boolean',
246
246
  group: 'Build',
247
- description: 'Will include the units to process',
248
- process: (value) => {
249
- if (!value)
250
- return [];
251
- return value.split(',').map(str => str.trim());
252
- },
253
- dependencies: []
247
+ description: 'When used with -up, makes all transitive dependencies active (compile/test them too)',
254
248
  };
255
249
  export const BaiParam_Apps = {
256
250
  keys: ['-app', '--application'],
@@ -263,7 +257,11 @@ export const BaiParam_Apps = {
263
257
  return [];
264
258
  return value.split(',').map(str => str.trim());
265
259
  },
266
- dependencies: []
260
+ isArray: true,
261
+ dependencies: [
262
+ { param: BaiParam_UsePackage, value: (currentValue) => currentValue },
263
+ { param: BaiParam_BuildTree, value: true }
264
+ ]
267
265
  };
268
266
  export const BaiParam_ToESM = {
269
267
  keys: ['-tesm', '--to-esm'],
@@ -326,8 +324,8 @@ export const AllBaiParams = [
326
324
  BaiParam_Debug,
327
325
  BaiParam_Verbose,
328
326
  BaiParam_Publish,
329
- BaiParam_includePackage,
330
327
  BaiParam_UsePackage,
328
+ BaiParam_BuildTree,
331
329
  BaiParam_ToESM,
332
330
  BaiParam_Simulate,
333
331
  BaiParam_DebugLifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nu-art/build-and-install",
3
- "version": "0.400.13",
3
+ "version": "0.401.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "chokidar": "^3.6.0",
34
- "@nu-art/ts-common": "0.400.13",
35
- "@nu-art/commando": "0.400.13"
34
+ "@nu-art/ts-common": "0.401.0",
35
+ "@nu-art/commando": "0.401.0"
36
36
  },
37
37
  "unitConfig": {
38
38
  "type": "typescript-lib"
@@ -17,8 +17,9 @@ export declare class PhaseManager extends Logger {
17
17
  private killed;
18
18
  private runningStatus;
19
19
  private activeUnits;
20
+ private projectUnitKeys;
20
21
  private readonly keyToPhaseMap;
21
- constructor(runningStatus: RunningStatusHandler, phases: Phase<any>[][], units: BaseUnit[][]);
22
+ constructor(runningStatus: RunningStatusHandler, phases: Phase<any>[][], units: BaseUnit[][], activeUnits: string[], projectUnitKeys: string[]);
22
23
  calculateExecutionSteps(): Promise<ScheduledStep[]>;
23
24
  execute(_steps: ScheduledStep[]): Promise<void>;
24
25
  break(): Promise<void[]>;
@@ -1,4 +1,4 @@
1
- import { addItemToArray, asArray, exists, flatArray, Logger, removeItemFromArray, timeCounter } from '@nu-art/ts-common';
1
+ import { addItemToArray, BadImplementationException, exists, flatArray, Logger, removeItemFromArray, timeCounter } from '@nu-art/ts-common';
2
2
  import { PhaseAggregatedException } from '../core/exceptions/PhaseAggregatedException.js';
3
3
  import { UnitPhaseException } from '../core/exceptions/UnitPhaseException.js';
4
4
  export class PhaseManager extends Logger {
@@ -8,34 +8,29 @@ export class PhaseManager extends Logger {
8
8
  killed = false;
9
9
  runningStatus;
10
10
  activeUnits;
11
+ projectUnitKeys;
11
12
  keyToPhaseMap;
12
- constructor(runningStatus, phases, units) {
13
+ constructor(runningStatus, phases, units, activeUnits, projectUnitKeys) {
13
14
  super();
14
15
  this.phases = phases;
15
16
  this.units = units;
16
17
  this.runningStatus = runningStatus;
17
- const unitKeySet = new Set();
18
- const allUnits = [];
19
- for (const unit of flatArray(units)) {
20
- if (unitKeySet.has(unit.config.key))
21
- throw new Error(`Multiple units with same key: ${unit.config.key}`);
22
- unitKeySet.add(unit.config.key);
23
- allUnits.push(unit);
18
+ this.activeUnits = activeUnits;
19
+ this.projectUnitKeys = projectUnitKeys;
20
+ const unitsSet = new Set();
21
+ for (const unit of flatArray(this.units)) {
22
+ if (unitsSet.has(unit.config.key))
23
+ throw new BadImplementationException(`Found duplicate unit: '${unit.config.key}' in the project`);
24
+ unitsSet.add(unit.config.key);
24
25
  }
25
- const usePackageKeys = this.runningStatus.runtimeParams.usePackage;
26
- if (!usePackageKeys?.length)
27
- this.activeUnits = allUnits.map(unit => unit.config.key);
28
- else {
29
- const regexMatchers = usePackageKeys.map(filter => new RegExp(`.*?${filter}.*?`, 'i'));
30
- this.activeUnits = allUnits.filter(unit => regexMatchers.some(matcher => matcher.test(unit.config.key))).map(unit => unit.config.key);
26
+ const phasesSet = new Set();
27
+ const flatPhases = flatArray(this.phases);
28
+ for (const phase of flatPhases) {
29
+ if (phasesSet.has(phase.key))
30
+ throw new BadImplementationException(`Found duplicate phase '${phase.key}' in the project`);
31
+ phasesSet.add(phase.key);
31
32
  }
32
- const packagesToInclude = this.runningStatus.runtimeParams.includePackage;
33
- if (packagesToInclude?.length) {
34
- const regexMatchers = asArray(packagesToInclude).map(filter => new RegExp(`.*?${filter}.*?`, 'i'));
35
- this.activeUnits.push(...allUnits.filter(unit => regexMatchers.some(matcher => matcher.test(unit.config.key))).map(unit => unit.config.key));
36
- this.activeUnits = [...new Set(this.activeUnits)];
37
- }
38
- this.keyToPhaseMap = flatArray(phases).reduce((acc, phase) => {
33
+ this.keyToPhaseMap = flatPhases.reduce((acc, phase) => {
39
34
  acc[phase.key] = phase;
40
35
  return acc;
41
36
  }, {});
@@ -45,15 +40,32 @@ export class PhaseManager extends Logger {
45
40
  const steps = [];
46
41
  this.logDebug('Calculating execution steps for phases: ', this.phases.map(phases => phases.map(phase => phase.key)));
47
42
  this.logDebug('Active Units: ', this.activeUnits);
43
+ this.logDebug('Project Units: ', this.projectUnitKeys);
48
44
  for (let phaseGroup of this.phases) {
49
45
  phaseGroup = phaseGroup.filter(phase => !exists(phase.filter) || phase.filter(this.runningStatus.runtimeParams));
50
46
  for (const layer of this.units) {
51
- const layerUnits = layer.filter(u => this.activeUnits.includes(u.config.key));
47
+ // Determine eligible units for this phase group
48
+ // A unit is eligible if it's eligible for at least one phase in the group
49
+ const eligibleUnitKeys = new Set();
50
+ for (const phase of phaseGroup) {
51
+ const unitCategory = phase.unitCategory ?? "active";
52
+ const phaseEligibleKeys = unitCategory === "project" ? this.projectUnitKeys : this.activeUnits;
53
+ phaseEligibleKeys.forEach(key => eligibleUnitKeys.add(key));
54
+ }
55
+ const layerUnits = layer.filter(u => eligibleUnitKeys.has(u.config.key));
52
56
  if (layerUnits.length === 0)
53
57
  continue;
54
58
  const phaseMap = new Map();
55
59
  for (const unit of layerUnits) {
56
- const supportedPhases = phaseGroup.filter(phase => phase.method in unit && typeof unit[phase.method] === 'function');
60
+ // Find phases this unit supports and is eligible for
61
+ const supportedPhases = phaseGroup.filter(phase => {
62
+ if (!(phase.method in unit && typeof unit[phase.method] === 'function'))
63
+ return false;
64
+ // Check if unit is eligible for this specific phase
65
+ const unitCategory = phase.unitCategory ?? "active";
66
+ const phaseEligibleKeys = unitCategory === "project" ? this.projectUnitKeys : this.activeUnits;
67
+ return phaseEligibleKeys.includes(unit.config.key);
68
+ });
57
69
  if (supportedPhases.length === 0)
58
70
  continue;
59
71
  const key = phaseGroup
@@ -20,7 +20,7 @@ export class UnitsMapper extends Logger {
20
20
  unit = await rule.resolveUnit(path, projectRoot);
21
21
  if (!unit)
22
22
  continue;
23
- this.logDebug(`Found unit ${unit.config.key} at path ${path}`);
23
+ this.logVerbose(`Found unit ${unit.config.key} at path ${path}`);
24
24
  units.push(unit);
25
25
  if (!unit.config.isRoot)
26
26
  return units;
@@ -0,0 +1,80 @@
1
+ import { Constructor, Logger } from '@nu-art/ts-common';
2
+ import { BaiParams } from '../core/params/params.js';
3
+ import { UnitsMapper } from './UnitsMapper/UnitsMapper.js';
4
+ import { UnitsDependencyMapper } from './UnitsDependencyMapper/UnitsDependencyMapper.js';
5
+ import { BaseUnit, ProjectUnit, Unit_NodeProject } from './units/index.js';
6
+ /**
7
+ * Manages all units in the workspace, including:
8
+ * - Scanned Units: All units discovered from workspace scan
9
+ * - Active Units: Units selected for execution (compile/test/lint)
10
+ * - Project Units: Active units + their transitive dependencies
11
+ */
12
+ export declare class Workspace extends Logger {
13
+ private _scannedUnits;
14
+ private _projectUnits;
15
+ private _activeUnits;
16
+ private _projectUnitKeys;
17
+ private unitsDependencyMapper?;
18
+ private unitKeyToUnitMap;
19
+ /**
20
+ * All units discovered from the workspace scan
21
+ */
22
+ get scannedUnits(): ReadonlyArray<BaseUnit<any>>;
23
+ /**
24
+ * All project units (subset of scanned units that are ProjectUnits)
25
+ */
26
+ get projectUnits(): ReadonlyArray<ProjectUnit>;
27
+ /**
28
+ * Keys of units selected for execution
29
+ */
30
+ get activeUnits(): ReadonlyArray<string>;
31
+ /**
32
+ * Keys of project units (active units + their transitive dependencies)
33
+ */
34
+ get projectUnitKeys(): ReadonlyArray<string>;
35
+ /**
36
+ * Scan units from the workspace using the provided UnitsMapper
37
+ */
38
+ scanUnits(path: string, unitsMapper: UnitsMapper): Promise<void>;
39
+ /**
40
+ * Add additional project units (e.g., from applicative configuration)
41
+ */
42
+ addProjectUnits(projectUnits: ProjectUnit[]): void;
43
+ /**
44
+ * Initialize the dependency mapper with project units
45
+ */
46
+ initializeDependencyMapper(globalOutputFolder: string): void;
47
+ /**
48
+ * Derive active and project units based on runtime parameters
49
+ * This must be called after initializeDependencyMapper()
50
+ */
51
+ deriveActiveAndProjectUnits(runtimeParams: BaiParams): {
52
+ activeUnits: string[];
53
+ projectUnits: string[];
54
+ };
55
+ /**
56
+ * Get a unit by its key
57
+ */
58
+ getUnitByKey<T extends BaseUnit>(key: string): T | undefined;
59
+ /**
60
+ * Get multiple units by their keys, optionally filtered by class type
61
+ */
62
+ getUnitsByKeys<T extends BaseUnit>(keys: string[], className?: Constructor<T>): T[];
63
+ /**
64
+ * Get the root NodeProject unit
65
+ */
66
+ getNodeProjectUnit(): Unit_NodeProject | undefined;
67
+ /**
68
+ * Build dependency tree for the given project unit keys
69
+ * Returns layers of units ordered from bottom (no dependencies) to top
70
+ */
71
+ buildDependencyTree(projectUnitKeys: string[]): Promise<ProjectUnit[][]>;
72
+ /**
73
+ * Get transitive dependencies for given unit keys
74
+ */
75
+ getTransitiveDependencies(keys: string[]): string[];
76
+ /**
77
+ * Get the dependency mapper (for runtime context)
78
+ */
79
+ getDependencyMapper(): UnitsDependencyMapper;
80
+ }
@@ -0,0 +1,179 @@
1
+ import { _keys, arrayToMap, flatArray, Logger } from '@nu-art/ts-common';
2
+ import { UnitsDependencyMapper } from './UnitsDependencyMapper/UnitsDependencyMapper.js';
3
+ import { ProjectUnit, Unit_NodeProject } from './units/index.js';
4
+ /**
5
+ * Manages all units in the workspace, including:
6
+ * - Scanned Units: All units discovered from workspace scan
7
+ * - Active Units: Units selected for execution (compile/test/lint)
8
+ * - Project Units: Active units + their transitive dependencies
9
+ */
10
+ export class Workspace extends Logger {
11
+ _scannedUnits = [];
12
+ _projectUnits = [];
13
+ _activeUnits = [];
14
+ _projectUnitKeys = [];
15
+ unitsDependencyMapper;
16
+ unitKeyToUnitMap = {};
17
+ /**
18
+ * All units discovered from the workspace scan
19
+ */
20
+ get scannedUnits() {
21
+ return this._scannedUnits;
22
+ }
23
+ /**
24
+ * All project units (subset of scanned units that are ProjectUnits)
25
+ */
26
+ get projectUnits() {
27
+ return this._projectUnits;
28
+ }
29
+ /**
30
+ * Keys of units selected for execution
31
+ */
32
+ get activeUnits() {
33
+ return this._activeUnits;
34
+ }
35
+ /**
36
+ * Keys of project units (active units + their transitive dependencies)
37
+ */
38
+ get projectUnitKeys() {
39
+ return this._projectUnitKeys;
40
+ }
41
+ /**
42
+ * Scan units from the workspace using the provided UnitsMapper
43
+ */
44
+ async scanUnits(path, unitsMapper) {
45
+ this.logDebug(`Resolving units from: ${path}`);
46
+ this._scannedUnits = await unitsMapper.resolveUnits(path);
47
+ Object.freeze(this._scannedUnits);
48
+ this.unitKeyToUnitMap = arrayToMap(this._scannedUnits, unit => unit.config.key);
49
+ this._projectUnits = this._scannedUnits.filter(unit => unit.isInstanceOf(ProjectUnit));
50
+ Object.freeze(this._projectUnits);
51
+ }
52
+ /**
53
+ * Add additional project units (e.g., from applicative configuration)
54
+ */
55
+ addProjectUnits(projectUnits) {
56
+ // Check for duplicates
57
+ const existingKeys = new Set(this._projectUnits.map(u => u.config.key));
58
+ const newUnits = projectUnits.filter(unit => !existingKeys.has(unit.config.key));
59
+ if (newUnits.length > 0) {
60
+ // Unfreeze, add, then refreeze
61
+ const current = [...this._projectUnits, ...newUnits];
62
+ this._projectUnits = current;
63
+ Object.freeze(this._projectUnits);
64
+ // Update lookup map
65
+ for (const unit of newUnits) {
66
+ this.unitKeyToUnitMap[unit.config.key] = unit;
67
+ }
68
+ }
69
+ }
70
+ /**
71
+ * Initialize the dependency mapper with project units
72
+ */
73
+ initializeDependencyMapper(globalOutputFolder) {
74
+ const unitsDependencies = this._projectUnits.map(unit => ({
75
+ key: unit.config.key,
76
+ dependsOn: _keys(unit.config.dependencies).filter(key => !!this.unitKeyToUnitMap[key])
77
+ }));
78
+ this.unitsDependencyMapper = new UnitsDependencyMapper(unitsDependencies, globalOutputFolder);
79
+ }
80
+ /**
81
+ * Derive active and project units based on runtime parameters
82
+ * This must be called after initializeDependencyMapper()
83
+ */
84
+ deriveActiveAndProjectUnits(runtimeParams) {
85
+ if (!this.unitsDependencyMapper) {
86
+ throw new Error('Dependency mapper must be initialized before deriving units. Call initializeDependencyMapper() first.');
87
+ }
88
+ const unitKeySet = new Set();
89
+ const allUnits = [];
90
+ for (const unit of flatArray(this._scannedUnits)) {
91
+ if (unitKeySet.has(unit.config.key))
92
+ throw new Error(`Multiple units with same key: ${unit.config.key}`);
93
+ unitKeySet.add(unit.config.key);
94
+ allUnits.push(unit);
95
+ }
96
+ let activeUnits = [];
97
+ let projectUnits = [];
98
+ // 1. Handle usePackage: "Work on these units only" (Option A)
99
+ // - Matched units → active (compile/test/lint these)
100
+ // - Matched + transitive → project (prepare dependencies, but don't compile them)
101
+ // - If buildTree flag is set, transitive dependencies also become active
102
+ const usePackageKeys = runtimeParams.usePackage;
103
+ if (usePackageKeys?.length) {
104
+ const regexMatchers = usePackageKeys.map(filter => new RegExp(`.*?${filter}.*?`, 'i'));
105
+ const matched = allUnits.filter(unit => regexMatchers.some(matcher => matcher.test(unit.config.key))).map(unit => unit.config.key);
106
+ const transitive = this.unitsDependencyMapper.getTransitiveDependencies(matched);
107
+ activeUnits.push(...matched);
108
+ projectUnits.push(...matched, ...transitive);
109
+ // If buildTree flag is set, make transitive dependencies active too
110
+ if (runtimeParams.buildTree) {
111
+ activeUnits.push(...transitive);
112
+ }
113
+ }
114
+ else {
115
+ const allKeys = allUnits.map(unit => unit.config.key);
116
+ activeUnits = allKeys;
117
+ projectUnits = allKeys;
118
+ }
119
+ this._activeUnits = [...new Set(activeUnits)];
120
+ this._projectUnitKeys = [...new Set(projectUnits)];
121
+ this.logDebug(`Active units: ${this._activeUnits.join(', ')}`);
122
+ this.logDebug(`Project units: ${this._projectUnitKeys.join(', ')}`);
123
+ return {
124
+ activeUnits: this._activeUnits,
125
+ projectUnits: this._projectUnitKeys
126
+ };
127
+ }
128
+ /**
129
+ * Get a unit by its key
130
+ */
131
+ getUnitByKey(key) {
132
+ return this.unitKeyToUnitMap[key];
133
+ }
134
+ /**
135
+ * Get multiple units by their keys, optionally filtered by class type
136
+ */
137
+ getUnitsByKeys(keys, className) {
138
+ const units = keys.map(key => this.unitKeyToUnitMap[key]).filter(Boolean);
139
+ if (className) {
140
+ return units.filter(unit => unit.isInstanceOf(className));
141
+ }
142
+ return units;
143
+ }
144
+ /**
145
+ * Get the root NodeProject unit
146
+ */
147
+ getNodeProjectUnit() {
148
+ return this._projectUnits.find(unit => unit.isInstanceOf(Unit_NodeProject));
149
+ }
150
+ /**
151
+ * Build dependency tree for the given project unit keys
152
+ * Returns layers of units ordered from bottom (no dependencies) to top
153
+ */
154
+ async buildDependencyTree(projectUnitKeys) {
155
+ if (!this.unitsDependencyMapper) {
156
+ throw new Error('Dependency mapper must be initialized. Call initializeDependencyMapper() first.');
157
+ }
158
+ const unitKeyLayers = await this.unitsDependencyMapper.buildDependencyTree(projectUnitKeys);
159
+ return unitKeyLayers.map(keys => keys.map(key => this.getUnitByKey(key)).filter(Boolean));
160
+ }
161
+ /**
162
+ * Get transitive dependencies for given unit keys
163
+ */
164
+ getTransitiveDependencies(keys) {
165
+ if (!this.unitsDependencyMapper) {
166
+ throw new Error('Dependency mapper must be initialized. Call initializeDependencyMapper() first.');
167
+ }
168
+ return this.unitsDependencyMapper.getTransitiveDependencies(keys);
169
+ }
170
+ /**
171
+ * Get the dependency mapper (for runtime context)
172
+ */
173
+ getDependencyMapper() {
174
+ if (!this.unitsDependencyMapper) {
175
+ throw new Error('Dependency mapper must be initialized. Call initializeDependencyMapper() first.');
176
+ }
177
+ return this.unitsDependencyMapper;
178
+ }
179
+ }
@@ -3,7 +3,7 @@ import { UnitPhaseImplementor } from './types.js';
3
3
  import { Phase } from '../phase/index.js';
4
4
  import { ProjectUnit } from '../units/index.js';
5
5
  export declare const BaiParam_Help: BaseCliParam<'help', boolean>;
6
- declare const AllBaiParams_Help: (BaseCliParam<"allUnits", boolean> | BaseCliParam<"dependencyTree", boolean> | BaseCliParam<"environment", string> | BaseCliParam<"install", boolean> | BaseCliParam<"clean", boolean> | BaseCliParam<"purge", boolean> | BaseCliParam<"generate", boolean> | BaseCliParam<"generateDocs", boolean> | BaseCliParam<"noBuild", boolean> | BaseCliParam<"prepare", boolean> | BaseCliParam<"dryRun", boolean> | BaseCliParam<"lint", boolean> | BaseCliParam<"watch", boolean> | BaseCliParam<"watchBuildTree", boolean> | BaseCliParam<"continue", boolean> | BaseCliParam<"test", boolean> | BaseCliParam<"testType", string[]> | BaseCliParam<"testFiles", string[]> | BaseCliParam<"testCases", string[]> | BaseCliParam<"testDebugPort", number> | BaseCliParam<"launch", boolean> | BaseCliParam<"debugBackend", boolean> | BaseCliParam<"deploy", boolean> | BaseCliParam<"debug", boolean> | BaseCliParam<"debugLifecycle", boolean> | BaseCliParam<"verbose", boolean> | BaseCliParam<"publish", "patch" | "minor" | "major"> | BaseCliParam<"usePackage", string[]> | BaseCliParam<"includePackage", string[]> | BaseCliParam<"includeApps", string[]> | BaseCliParam<"toESM", boolean> | BaseCliParam<"simulation", boolean> | BaseCliParam<"checkCyclicImports", boolean> | BaseCliParam<"help", boolean>)[];
6
+ declare const AllBaiParams_Help: (BaseCliParam<"allUnits", boolean> | BaseCliParam<"dependencyTree", boolean> | BaseCliParam<"environment", string> | BaseCliParam<"install", boolean> | BaseCliParam<"clean", boolean> | BaseCliParam<"purge", boolean> | BaseCliParam<"generate", boolean> | BaseCliParam<"generateDocs", boolean> | BaseCliParam<"noBuild", boolean> | BaseCliParam<"prepare", boolean> | BaseCliParam<"dryRun", boolean> | BaseCliParam<"lint", boolean> | BaseCliParam<"watch", boolean> | BaseCliParam<"watchBuildTree", boolean> | BaseCliParam<"continue", boolean> | BaseCliParam<"test", boolean> | BaseCliParam<"testType", string[]> | BaseCliParam<"testFiles", string[]> | BaseCliParam<"testCases", string[]> | BaseCliParam<"testDebugPort", number> | BaseCliParam<"launch", boolean> | BaseCliParam<"debugBackend", boolean> | BaseCliParam<"deploy", boolean> | BaseCliParam<"debug", boolean> | BaseCliParam<"debugLifecycle", boolean> | BaseCliParam<"verbose", boolean> | BaseCliParam<"publish", "patch" | "minor" | "major"> | BaseCliParam<"usePackage", string[]> | BaseCliParam<"buildTree", boolean> | BaseCliParam<"includeApps", string[]> | BaseCliParam<"toESM", boolean> | BaseCliParam<"simulation", boolean> | BaseCliParam<"checkCyclicImports", boolean> | BaseCliParam<"help", boolean>)[];
7
7
  export type Help_BaiParams = CliParams<typeof AllBaiParams_Help>;
8
8
  export type Phase_Help = typeof phase_Help;
9
9
  export declare const phaseKey_Help = "help";
@@ -38,6 +38,7 @@ export const phase_Prepare = {
38
38
  name: 'Prepare',
39
39
  method: 'prepare',
40
40
  filter: (baiParams) => baiParams.prepare,
41
+ unitCategory: "project", // All project units need to be prepared
41
42
  };
42
43
  export const phaseKey_Install = 'install';
43
44
  export const phase_Install = {
@@ -6,4 +6,5 @@ export type Phase<PhaseMethod extends string> = {
6
6
  filter?: (params: BaiParams) => boolean;
7
7
  terminateAfterPhase?: boolean;
8
8
  dependencyPhase?: Phase<string>[];
9
+ unitCategory?: "project" | "active";
9
10
  };
@@ -6,6 +6,7 @@ import { Commando_Basic } from '@nu-art/commando/shell/plugins/basic';
6
6
  import { BAI_Config } from '../../core/types/index.js';
7
7
  import { UnitsDependencyMapper } from '../UnitsDependencyMapper/UnitsDependencyMapper.js';
8
8
  import { BaiParams } from '../../core/params/params.js';
9
+ import { Workspace } from '../Workspace.js';
9
10
  export type BaseUnit_Config = {
10
11
  key: string;
11
12
  label: string;
@@ -18,6 +19,7 @@ export type UnitRuntimeContext = {
18
19
  unitsResolver: <Class extends BaseUnit>(keys: string[], className: Constructor<Class>) => Class[];
19
20
  runtimeParams: BaiParams;
20
21
  globalOutputFolder: string;
22
+ workspace?: Workspace;
21
23
  };
22
24
  export declare abstract class BaseUnit<C extends BaseUnit_Config = BaseUnit_Config, RT_Context extends UnitRuntimeContext = UnitRuntimeContext> extends Logger {
23
25
  readonly config: Readonly<C>;
@@ -162,7 +162,8 @@ export class Unit_NodeProject extends Unit_PackageJson {
162
162
  noBuild: false,
163
163
  continue: false
164
164
  };
165
- const phaseManager = new PhaseManager(new RunningStatusHandler(this.config.fullPath, watchRuntimeParams).isolate(), [[phase_CompileWatch]], unitDependencyTree);
165
+ const activeUnitKeys = this.runtimeContext.childUnits.map(unit => unit.config.key);
166
+ const phaseManager = new PhaseManager(new RunningStatusHandler(this.config.fullPath, watchRuntimeParams).isolate(), [[phase_CompileWatch]], unitDependencyTree, activeUnitKeys, activeUnitKeys);
166
167
  // @ts-ignore
167
168
  phaseManager.setTag('PhaseManager-Watcher');
168
169
  const executionPlan = await phaseManager.calculateExecutionSteps();
@@ -280,16 +280,19 @@ export class Unit_FirebaseFunctionsApp extends Unit_TypescriptLib {
280
280
  this.logWarning('PROXY TERMINATED');
281
281
  }
282
282
  async runEmulator() {
283
- if (!this.config.envConfig.identityAccount)
284
- throw new ImplementationMissingException('Must provide an identity service account to launch emulator');
285
- const pathToIdentityAccount = resolve(this.config.fullPath, this.config.envConfig.identityAccount);
286
- if (!FileSystemUtils.file.exists(pathToIdentityAccount))
287
- throw new ImplementationMissingException(`Missing identity file at: ${pathToIdentityAccount}`);
283
+ let exportGoogleCredentials;
284
+ if (this.config.envConfig.identityAccount) {
285
+ const pathToIdentityAccount = resolve(this.config.fullPath, this.config.envConfig.identityAccount);
286
+ if (!FileSystemUtils.file.exists(pathToIdentityAccount))
287
+ throw new ImplementationMissingException(`Missing identity file at: ${pathToIdentityAccount}`);
288
+ exportGoogleCredentials = `export GOOGLE_APPLICATION_CREDENTIALS="${pathToIdentityAccount}"`;
289
+ }
288
290
  const commando = this.allocateCommando(Commando_NVM).applyNVM()
289
291
  .setUID(this.config.key)
290
- .cd(this.config.fullPath)
291
- .append(`export GOOGLE_APPLICATION_CREDENTIALS="${pathToIdentityAccount}"`)
292
- .setLogLevelFilter((log, type) => {
292
+ .cd(this.config.fullPath);
293
+ if (exportGoogleCredentials)
294
+ commando.append(exportGoogleCredentials);
295
+ commando.setLogLevelFilter((log, type) => {
293
296
  if (this.emulatorLogStrings.error.some(errStr => log.includes(errStr)))
294
297
  return LogLevel.Error;
295
298
  if (this.emulatorLogStrings.warning.some(warnStr => log.includes(warnStr)))
@@ -51,7 +51,11 @@ export class Unit_FirebaseHostingApp extends Unit_TypescriptLib {
51
51
  this.hosting[match[1]] = match[2];
52
52
  });
53
53
  const debug = this.runtimeContext.runtimeParams.verbose ? ' --debug' : '';
54
- await this.executeAsyncCommando(commando, `${this.npmCommand('firebase')}${debug} deploy --only hosting`);
54
+ await this.executeAsyncCommando(commando, `${this.npmCommand('firebase')}${debug} deploy --only hosting`, (stdout, stderr, exitCode) => {
55
+ if (exitCode === 0)
56
+ return;
57
+ throw new CommandoException(`Failed to deploy hosting with exit code ${exitCode}`, stdout, stderr, exitCode);
58
+ });
55
59
  }
56
60
  //######################### ResolveConfig Logic #########################
57
61
  async resolveHostingRC() {