@nu-art/build-and-install 0.401.9 → 0.500.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/BuildAndInstall.js +2 -1
  2. package/config/types/project-config.d.ts +14 -1
  3. package/core/params.js +2 -2
  4. package/dependencies/UnitsDependencyMapper.d.ts +8 -0
  5. package/dependencies/UnitsDependencyMapper.js +58 -4
  6. package/exports/IndicesMcpServer.js +1 -1
  7. package/package.json +5 -5
  8. package/phases/PhaseManager.js +4 -4
  9. package/phases/definitions/types.d.ts +1 -1
  10. package/templates/backend/proxy/proxy._ts +36 -31
  11. package/templates/firebase/config/database.rules.json +6 -0
  12. package/templates/firebase/functions/service.yaml +0 -2
  13. package/units/discovery/resolvers/UnitMapper_FirebaseHosting.d.ts +5 -9
  14. package/units/discovery/resolvers/UnitMapper_FirebaseHosting.js +3 -1
  15. package/units/discovery/resolvers/UnitMapper_Node.js +7 -3
  16. package/units/discovery/resolvers/UnitMapper_ViteHosting.d.ts +46 -0
  17. package/units/discovery/resolvers/UnitMapper_ViteHosting.js +59 -0
  18. package/units/discovery/resolvers/index.d.ts +1 -0
  19. package/units/discovery/resolvers/index.js +1 -0
  20. package/units/implementations/Unit_PackageJson.d.ts +2 -1
  21. package/units/implementations/Unit_PackageJson.js +9 -1
  22. package/units/implementations/Unit_TypescriptLib.d.ts +7 -1
  23. package/units/implementations/Unit_TypescriptLib.js +126 -2
  24. package/units/implementations/firebase/Unit_FirebaseFunctionsApp.js +24 -15
  25. package/units/implementations/firebase/Unit_FirebaseHostingApp.d.ts +19 -97
  26. package/units/implementations/firebase/Unit_FirebaseHostingApp.js +28 -290
  27. package/units/implementations/firebase/Unit_HostingApp.d.ts +59 -0
  28. package/units/implementations/firebase/Unit_HostingApp.js +225 -0
  29. package/units/implementations/firebase/Unit_ViteHostingApp.d.ts +10 -0
  30. package/units/implementations/firebase/Unit_ViteHostingApp.js +28 -0
@@ -8,7 +8,7 @@ import { PhaseManager } from './phases/PhaseManager.js';
8
8
  import { Workspace } from './workspace/Workspace.js';
9
9
  import { resolve } from 'path';
10
10
  import { CONST_BaiConfig, CONST_NodeModules, CONST_VersionApp } from './config/consts.js';
11
- import { UnitMapper_FirebaseFunction, UnitMapper_FirebaseHosting, UnitMapper_NodeLib, UnitMapper_NodeProject } from './units/discovery/resolvers/index.js';
11
+ import { UnitMapper_FirebaseFunction, UnitMapper_FirebaseHosting, UnitMapper_NodeLib, UnitMapper_NodeProject, UnitMapper_ViteHosting } from './units/discovery/resolvers/index.js';
12
12
  import { CLIParamsResolver } from '@nu-art/cli-params';
13
13
  import { RunningStatusHandler } from './runtime/RunningStatusHandler.js';
14
14
  import { FileSystemUtils } from '@nu-art/ts-common/utils/FileSystemUtils';
@@ -60,6 +60,7 @@ export class BuildAndInstall extends Logger {
60
60
  .addRules(UnitMapper_NodeLib)
61
61
  .addRules(UnitMapper_NodeProject)
62
62
  .addRules(UnitMapper_FirebaseHosting)
63
+ .addRules(UnitMapper_ViteHosting)
63
64
  .addRules(UnitMapper_FirebaseFunction)
64
65
  .setRuntimeParams(this.runtimeParams);
65
66
  }
@@ -29,6 +29,19 @@ export type BAI_Config = {
29
29
  '.firebaserc'?: string;
30
30
  baseEmulationPort?: number;
31
31
  };
32
+ playwright?: {
33
+ browsers?: ('chromium' | 'firefox' | 'webkit')[];
34
+ headless?: boolean;
35
+ baseURL?: string;
36
+ viewport?: {
37
+ width: number;
38
+ height: number;
39
+ };
40
+ vite?: {
41
+ port?: number;
42
+ configPath?: string;
43
+ };
44
+ };
32
45
  };
33
46
  firebase?: {
34
47
  databaseRules?: string;
@@ -41,7 +54,7 @@ export type BAI_Config = {
41
54
  eslintConfig?: string;
42
55
  };
43
56
  backend: {
44
- proxy: string;
57
+ proxy: string | null;
45
58
  };
46
59
  };
47
60
  };
package/core/params.js CHANGED
@@ -117,7 +117,7 @@ export const BaiParam_Test = {
117
117
  group: 'Test',
118
118
  description: 'Run the tests in all the project packages',
119
119
  };
120
- export const TestTypes = ['pure', 'firebase', 'ui', 'mobile'];
120
+ export const TestTypes = ['pure', 'firebase', 'ui', 'mobile', 'playwright'];
121
121
  export const BaiParam_TestType = {
122
122
  keys: ['--test-type', '-tt'],
123
123
  keyName: 'testType',
@@ -296,7 +296,7 @@ export const BaiParam_Publish = {
296
296
  process: (part) => part ?? 'patch'
297
297
  };
298
298
  export const BaiParam_UsePackage = {
299
- keys: ['-up', '--use-packages'],
299
+ keys: ['-up', '--use-package'],
300
300
  keyName: 'usePackage',
301
301
  type: 'string[]',
302
302
  group: 'Other',
@@ -69,4 +69,12 @@ export declare class UnitsDependencyMapper extends Logger {
69
69
  * Returns all leaf units (those with no dependencies).
70
70
  */
71
71
  getLeaves(): string[];
72
+ /**
73
+ * Detects cycles in a given dependency map (used for error reporting).
74
+ */
75
+ private detectCyclesInMap;
76
+ /**
77
+ * Formats the dependency graph as a readable string for error messages.
78
+ */
79
+ private formatDependencyGraph;
72
80
  }
@@ -99,10 +99,23 @@ export class UnitsDependencyMapper extends Logger {
99
99
  }
100
100
  }
101
101
  if (nextLayer.length === 0) {
102
- this.logWarning(participatingKeys);
103
- this.logWarning(map);
104
- this.logWarning(`Cyclic or disconnected dependency detected: ${lastNode?.key ?? '??'} and [${nextLayer.join(', ')}]`);
105
- throw new Error('Cyclic or disconnected dependency detected');
102
+ // Detect and report cycles explicitly
103
+ const cycles = this.detectCyclesInMap(map);
104
+ const unresolved = _keys(map).filter(key => !resolved.has(key) && !topLayer.includes(key));
105
+ let errorMessage = '';
106
+ errorMessage += `Last processed node: ${lastNode?.key ?? '??'}\n`;
107
+ errorMessage += `Unresolved nodes: [${unresolved.join(', ')}]\n`;
108
+ errorMessage += 'Cyclic or disconnected dependency detected!\n\n';
109
+ if (cycles.length > 0) {
110
+ errorMessage += `Found ${cycles.length} cycle(s):\n`;
111
+ for (const cycle of cycles) {
112
+ errorMessage += ` Cycle: ${cycle.join(' -> ')}\n`;
113
+ }
114
+ errorMessage += '\n';
115
+ }
116
+ this.logVerbose(`Full dependency graph:`, this.formatDependencyGraph(map));
117
+ this.logWarning(errorMessage);
118
+ throw new Error(errorMessage);
106
119
  }
107
120
  nextLayer.sort();
108
121
  layers.push(sortArray(nextLayer));
@@ -306,4 +319,45 @@ export class UnitsDependencyMapper extends Logger {
306
319
  getLeaves() {
307
320
  return _values(this.map).filter(node => node.dependsOn.length === 0).map(node => node.key).sort();
308
321
  }
322
+ /**
323
+ * Detects cycles in a given dependency map (used for error reporting).
324
+ */
325
+ detectCyclesInMap(map) {
326
+ const visited = new Set();
327
+ const inStack = new Set();
328
+ const cycles = [];
329
+ const visit = (key, path) => {
330
+ if (inStack.has(key)) {
331
+ const cycleStart = path.indexOf(key);
332
+ cycles.push(path.slice(cycleStart).concat(key));
333
+ return;
334
+ }
335
+ if (visited.has(key))
336
+ return;
337
+ visited.add(key);
338
+ inStack.add(key);
339
+ const node = map[key];
340
+ if (node)
341
+ for (const dep of node.dependsOn)
342
+ visit(dep, path.concat(key));
343
+ inStack.delete(key);
344
+ };
345
+ for (const key of _keys(map))
346
+ visit(key, []);
347
+ return cycles;
348
+ }
349
+ /**
350
+ * Formats the dependency graph as a readable string for error messages.
351
+ */
352
+ formatDependencyGraph(map) {
353
+ const lines = [];
354
+ for (const key of _keys(map)) {
355
+ const node = map[key];
356
+ const deps = node.dependsOn.length > 0
357
+ ? ` -> [${node.dependsOn.join(', ')}]`
358
+ : ' (no dependencies)';
359
+ lines.push(` ${key}${deps}`);
360
+ }
361
+ return lines.join('\n');
362
+ }
309
363
  }
@@ -201,7 +201,7 @@ export class IndicesMcpServer extends Logger {
201
201
  package: packageName,
202
202
  stale,
203
203
  message: stale
204
- ? "Source files are newer than index files. Run 'bai --map-exports' to regenerate."
204
+ ? 'Source files are newer than index files. Run \'bai --map-exports\' to regenerate.'
205
205
  : 'Indices are up to date.'
206
206
  });
207
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nu-art/build-and-install",
3
- "version": "0.401.9",
3
+ "version": "0.500.0",
4
4
  "description": "A build system for monorepos that orchestrates building, testing, and deploying units with dependency-aware phase execution",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -39,12 +39,12 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "chokidar": "^3.6.0",
42
- "@nu-art/cli-params": "0.401.9",
43
- "@nu-art/ts-common": "0.401.9",
44
- "@nu-art/commando": "0.401.9"
42
+ "@nu-art/cli-params": "0.500.0",
43
+ "@nu-art/ts-common": "0.500.0",
44
+ "@nu-art/commando": "0.500.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@nu-art/testalot": "0.401.9"
47
+ "@nu-art/testalot": "0.500.0"
48
48
  },
49
49
  "unitConfig": {
50
50
  "type": "typescript-lib"
@@ -99,8 +99,8 @@ export class PhaseManager extends Logger {
99
99
  // A unit is eligible if it's eligible for at least one phase in the group
100
100
  const eligibleUnitKeys = new Set();
101
101
  for (const phase of phaseGroup) {
102
- const unitCategory = phase.unitCategory ?? "active";
103
- const phaseEligibleKeys = unitCategory === "project" ? this.projectUnitKeys : this.activeUnits;
102
+ const unitCategory = phase.unitCategory ?? 'active';
103
+ const phaseEligibleKeys = unitCategory === 'project' ? this.projectUnitKeys : this.activeUnits;
104
104
  phaseEligibleKeys.forEach(key => eligibleUnitKeys.add(key));
105
105
  }
106
106
  const layerUnits = layer.filter(u => eligibleUnitKeys.has(u.config.key));
@@ -113,8 +113,8 @@ export class PhaseManager extends Logger {
113
113
  if (!(phase.method in unit && typeof unit[phase.method] === 'function'))
114
114
  return false;
115
115
  // Check if unit is eligible for this specific phase
116
- const unitCategory = phase.unitCategory ?? "active";
117
- const phaseEligibleKeys = unitCategory === "project" ? this.projectUnitKeys : this.activeUnits;
116
+ const unitCategory = phase.unitCategory ?? 'active';
117
+ const phaseEligibleKeys = unitCategory === 'project' ? this.projectUnitKeys : this.activeUnits;
118
118
  return phaseEligibleKeys.includes(unit.config.key);
119
119
  });
120
120
  if (supportedPhases.length === 0)
@@ -36,5 +36,5 @@ export type Phase<PhaseMethod extends string> = {
36
36
  /** Phases that must complete before this phase runs */
37
37
  dependencyPhase?: Phase<string>[];
38
38
  /** Unit category: 'project' = all project units (active + dependencies), 'active' = only active units (default) */
39
- unitCategory?: "project" | "active";
39
+ unitCategory?: 'project' | 'active';
40
40
  };
@@ -1,56 +1,61 @@
1
1
  import express from 'express';
2
- import request from 'request';
3
2
  import * as fs from 'fs';
3
+ import {Readable} from 'node:stream';
4
+ import {createServer} from 'node:https';
4
5
 
5
6
 
6
7
  const _express: express.Express = express();
7
8
  let _counter = 0;
8
- _express.all('*', (req, res) => {
9
+ _express.all('*', async (req, res) => {
9
10
  const counter = ++_counter;
10
11
  const qMark = req.originalUrl.indexOf('?');
11
12
  const query = qMark > 1 ? req.originalUrl.substring(qMark) : '';
12
13
  const path = req.path;
13
- let url = `http://127.0.0.1:SERVER_PORT/PROJECT_ID/us-central1/api${path}${query}`;
14
- if (path.startsWith('/emulatorDownload') || path.startsWith('/emulatorUpload'))
15
- url = `http://127.0.0.1:SERVER_PORT/PROJECT_ID/us-central1${path}${query}`;
16
-
14
+ let url = `http://127.0.0.1:{{SERVER_PORT}}/{{PROJECT_ID}}/us-central1/api${path}${query}`;
17
15
  console.log(`PROXY ${counter} - [${req.method}] ${url}`);
18
16
 
19
- const handleError = (error: any) => {
20
- if(!error)
21
- return;
17
+ try {
18
+ const headers: Record<string, string | string[] | undefined> = {...req.headers};
19
+ delete headers['host'];
20
+ delete headers['content-length'];
22
21
 
23
- console.log(`ERROR calling: ${url}`, error);
24
- res.status(503).end();
25
- };
22
+ const fetchOptions: RequestInit = {
23
+ method: req.method,
24
+ headers: headers as HeadersInit
25
+ };
26
26
 
27
- try {
28
- let reqContent;
29
- if (req.method === 'POST') {
30
- reqContent = request.post({uri: url, body: req.body}, handleError);
31
- } else if (req.method === 'PUT') {
32
- reqContent = request.put({uri: url, body: req.body}, handleError);
33
- } else if (req.method === 'GET') {
34
- reqContent = request.get(url, handleError);
35
- } else if (req.method === 'DELETE') {
36
- reqContent = request.delete(url, handleError);
37
- } else if (req.method === 'HEAD') {
38
- reqContent = request.head(url, handleError);
27
+ // Stream request body for methods that can have a body (fetch forbids body on GET/HEAD)
28
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
29
+ fetchOptions.body = Readable.toWeb(req) as any;
30
+ (fetchOptions as RequestInit & { duplex: 'half' }).duplex = 'half';
31
+ }
32
+
33
+ const response = await fetch(url, fetchOptions);
34
+
35
+ res.status(response.status);
36
+ // Don't forward content-encoding/content-length: fetch already decompressed the body
37
+ response.headers.forEach((value, key) => {
38
+ const k = key.toLowerCase();
39
+ if (k === 'content-encoding' || k === 'content-length') return;
40
+ res.setHeader(key, value);
41
+ });
42
+
43
+ if (response.body) {
44
+ Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0]).pipe(res, {end: true});
39
45
  } else {
40
- reqContent = request(url, handleError);
46
+ res.end();
41
47
  }
42
48
 
43
- req.pipe(reqContent).pipe(res, {end: true});
44
49
  console.log(`PROXY ${counter} - END`);
45
50
  } catch (e) {
46
51
  console.log(`ERROR calling: ${url}`, e);
47
- res.status(500).end();
52
+ res.status(503).end();
48
53
  }
49
54
  });
50
55
 
51
56
  const ssl = {
52
- key: 'PATH_TO_SSL_KEY',
53
- cert: 'PATH_TO_SSL_CERTIFICATE'
57
+ key: '{{PATH_TO_SSL_KEY}}',
58
+ cert: '{{PATH_TO_SSL_CERTIFICATE}}'
54
59
  };
55
60
 
56
61
  let key = ssl.key;
@@ -68,5 +73,5 @@ const options = {
68
73
  requestCert: false,
69
74
  };
70
75
 
71
- require('https').createServer(options, _express).listen(PROXY_PORT);
72
- console.log('SSL proxy started!!!');
76
+ createServer(options, _express).listen(+'{{PROXY_PORT}}');
77
+ console.log(`SSL proxy started at port: {{PROXY_PORT}}`);
@@ -2,6 +2,12 @@
2
2
  "rules": {
3
3
  ".read": false,
4
4
  ".write": false,
5
+ "_config": {
6
+ "frontend": {
7
+ ".read": true,
8
+ ".write": false
9
+ }
10
+ },
5
11
  "state": {
6
12
  "ModuleBE_SyncManager": {
7
13
  "syncData": {
@@ -12,8 +12,6 @@ metadata:
12
12
  cloudfunctions.googleapis.com/function-id: {{FUNCTION_NAME}}
13
13
  run.googleapis.com/ingress: all
14
14
  run.googleapis.com/ingress-status: all
15
- run.googleapis.com/invoker-iam-disabled: 'true'
16
- run.googleapis.com/maxScale: '100'
17
15
  spec:
18
16
  template:
19
17
  metadata:
@@ -1,6 +1,6 @@
1
1
  import { TypedMap } from '@nu-art/ts-common';
2
2
  import { UnitMapper_Node, UnitMapper_NodeContext } from './UnitMapper_Node.js';
3
- import { FirebaseHosting_EnvConfig, Unit_FirebaseHostingApp, UnitConfigJSON_FirebaseHosting } from '../../implementations/firebase/Unit_FirebaseHostingApp.js';
3
+ import { Unit_FirebaseHostingApp, UnitConfigJSON_FirebaseHosting, WebpackHosting_EnvConfig } from '../../implementations/firebase/Unit_FirebaseHostingApp.js';
4
4
  export declare class UnitMapper_FirebaseHosting_Class extends UnitMapper_Node<Unit_FirebaseHostingApp, UnitConfigJSON_FirebaseHosting> {
5
5
  static tsValidator_FirebaseHosting: {
6
6
  label: import("@nu-art/ts-common").Validator<string>;
@@ -9,17 +9,13 @@ export declare class UnitMapper_FirebaseHosting_Class extends UnitMapper_Node<Un
9
9
  hasSelfHotReload: import("@nu-art/ts-common").Validator<boolean>;
10
10
  type: import("@nu-art/ts-common").Validator<any>;
11
11
  servingPort: import("@nu-art/ts-common").Validator<number>;
12
- envs: (import("@nu-art/ts-common").ValidatorImpl<any> | ((input?: TypedMap<FirebaseHosting_EnvConfig> | undefined) => import("@nu-art/ts-common").InvalidResultObject<TypedMap<FirebaseHosting_EnvConfig>> | undefined))[];
12
+ envs: (import("@nu-art/ts-common").ValidatorImpl<any> | ((input?: TypedMap<WebpackHosting_EnvConfig> | undefined) => import("@nu-art/ts-common").InvalidResultObject<TypedMap<WebpackHosting_EnvConfig>> | undefined))[];
13
13
  hostingConfig: import("@nu-art/ts-common").ValidatorImpl<any>;
14
14
  hostingDeployment: (import("@nu-art/ts-common").ValidatorImpl<any> | ((input?: object | undefined) => string | object | undefined))[];
15
15
  };
16
16
  constructor();
17
17
  protected resolveNodeUnit(context: UnitMapper_NodeContext<UnitConfigJSON_FirebaseHosting>): Promise<Unit_FirebaseHostingApp<{
18
- envConfig: {
19
- config: import("@nu-art/ts-common").TS_Object;
20
- projectId: string;
21
- isLocal: boolean;
22
- };
18
+ envConfig: WebpackHosting_EnvConfig;
23
19
  isTopLevelApp: true;
24
20
  hasSelfHotReload: boolean;
25
21
  customESLintConfig: boolean;
@@ -28,8 +24,8 @@ export declare class UnitMapper_FirebaseHosting_Class extends UnitMapper_Node<Un
28
24
  packageJson: import("../types.js").TS_PackageJSON<UnitConfigJSON_FirebaseHosting>;
29
25
  label: string;
30
26
  servingPort: number;
31
- hostingConfig?: import("../../implementations/firebase/Unit_FirebaseHostingApp.js").FirebaseHostingConfig | undefined;
32
- envs: TypedMap<FirebaseHosting_EnvConfig>;
27
+ hostingConfig?: import("../../implementations/firebase/Unit_HostingApp.js").FirebaseHostingConfig | undefined;
28
+ envs: TypedMap<WebpackHosting_EnvConfig>;
33
29
  fullPath: import("@nu-art/ts-common").AbsolutePath;
34
30
  relativePath: import("@nu-art/ts-common").RelativePath;
35
31
  key: string;
@@ -7,6 +7,7 @@ const valuesValidator = {
7
7
  config: tsValidateMustExist,
8
8
  projectId: tsValidateAnyString,
9
9
  isLocal: tsValidateBoolean(false),
10
+ webpackConfig: tsValidateOptional
10
11
  };
11
12
  // Artifact Registry generic package name validation: lowercase, alphanumeric with dots, underscores, hyphens
12
13
  // Cannot start/end with separators, no consecutive separators
@@ -41,7 +42,8 @@ export class UnitMapper_FirebaseHosting_Class extends UnitMapper_Node {
41
42
  const envConfig = {
42
43
  config: envUnitConfig?.config,
43
44
  projectId: envUnitConfig?.projectId,
44
- isLocal: envUnitConfig?.isLocal ?? env === 'local'
45
+ isLocal: envUnitConfig?.isLocal ?? env === 'local',
46
+ webpackConfig: envUnitConfig?.webpackConfig,
45
47
  };
46
48
  const { type, ...unitConfig } = context.packageJson.unitConfig;
47
49
  return new Unit_FirebaseHostingApp({
@@ -1,7 +1,8 @@
1
- import { _keys, deepClone, tsValidate, tsValidateBoolean, tsValidateOptionalAnyString, tsValidateResult } from '@nu-art/ts-common';
1
+ import { _keys, deepClone, tsValidateBoolean, tsValidateOptionalAnyString, tsValidateResult } from '@nu-art/ts-common';
2
2
  import { FilesCache } from '../../../core/FilesCache.js';
3
3
  import { UnitMapper_Base } from './UnitMapper_Base.js';
4
4
  import { FileSystemUtils } from '@nu-art/ts-common/utils/FileSystemUtils';
5
+ import { Unit_PackageJson } from '../../implementations/Unit_PackageJson.js';
5
6
  export class UnitMapper_Node extends UnitMapper_Base {
6
7
  static invalidPaths = [];
7
8
  static tsValidator_Node = {
@@ -32,7 +33,7 @@ export class UnitMapper_Node extends UnitMapper_Base {
32
33
  if (tsValidateResult(packageJson.unitConfig.type, this.validator.type))
33
34
  return; // not the expected type for this mapper
34
35
  packageJson = deepClone(packageJson);
35
- tsValidate(packageJson.unitConfig, this.validator);
36
+ const configValidationResult = tsValidateResult(packageJson.unitConfig, this.validator, undefined, false);
36
37
  const dependencies = packageJson.dependencies;
37
38
  if (dependencies)
38
39
  packageJson.dependencies = _keys(dependencies).reduce((acc, key) => {
@@ -55,7 +56,10 @@ export class UnitMapper_Node extends UnitMapper_Base {
55
56
  };
56
57
  const customESLintConfig = packageJson.unitConfig.customESLintConfig ?? false;
57
58
  const customTSConfig = packageJson.unitConfig.customTSConfig ?? false;
58
- return this.resolveNodeUnit({ path, root, packageJson, baseConfig, customESLintConfig, customTSConfig });
59
+ const unit = await this.resolveNodeUnit({ path, root, packageJson, baseConfig, customESLintConfig, customTSConfig });
60
+ if (unit instanceof Unit_PackageJson)
61
+ unit.configValidationResult = configValidationResult;
62
+ return unit;
59
63
  }
60
64
  catch (e) {
61
65
  this.logError(`Failed to load package.json at: ${pathToFile}`, e);
@@ -0,0 +1,46 @@
1
+ import { TypedMap } from '@nu-art/ts-common';
2
+ import { UnitMapper_Node, UnitMapper_NodeContext } from './UnitMapper_Node.js';
3
+ import { FirebaseHosting_EnvConfig } from '../../implementations/firebase/Unit_HostingApp.js';
4
+ import { Unit_ViteHostingApp } from '../../implementations/firebase/Unit_ViteHostingApp.js';
5
+ type UnitConfigJSON_ViteHosting_Node = import('../../discovery/resolvers/UnitMapper_Node.js').UnitConfigJSON_Node & {
6
+ servingPort?: number;
7
+ hostingConfig?: import('../../implementations/firebase/Unit_HostingApp.js').FirebaseHostingConfig;
8
+ envs: TypedMap<FirebaseHosting_EnvConfig>;
9
+ };
10
+ export declare class UnitMapper_ViteHosting_Class extends UnitMapper_Node<Unit_ViteHostingApp, UnitConfigJSON_ViteHosting_Node> {
11
+ static tsValidator_ViteHosting: {
12
+ label: import("@nu-art/ts-common").Validator<string>;
13
+ customESLintConfig: import("@nu-art/ts-common").Validator<boolean>;
14
+ customTSConfig: import("@nu-art/ts-common").Validator<boolean>;
15
+ hasSelfHotReload: import("@nu-art/ts-common").Validator<boolean>;
16
+ type: import("@nu-art/ts-common").Validator<any>;
17
+ servingPort: import("@nu-art/ts-common").Validator<number>;
18
+ envs: (import("@nu-art/ts-common").ValidatorImpl<any> | ((input?: TypedMap<FirebaseHosting_EnvConfig> | undefined) => import("@nu-art/ts-common").InvalidResultObject<TypedMap<FirebaseHosting_EnvConfig>> | undefined))[];
19
+ hostingConfig: import("@nu-art/ts-common").ValidatorImpl<any>;
20
+ hostingDeployment: (import("@nu-art/ts-common").ValidatorImpl<any> | ((input?: object | undefined) => string | object | undefined))[];
21
+ };
22
+ constructor();
23
+ protected resolveNodeUnit(context: UnitMapper_NodeContext<UnitConfigJSON_ViteHosting_Node>): Promise<Unit_ViteHostingApp<{
24
+ envConfig: {
25
+ config: import("@nu-art/ts-common").TS_Object;
26
+ projectId: string;
27
+ isLocal: boolean;
28
+ };
29
+ isTopLevelApp: true;
30
+ hasSelfHotReload: boolean;
31
+ customESLintConfig: boolean;
32
+ customTSConfig: boolean;
33
+ output: string;
34
+ packageJson: import("../types.js").TS_PackageJSON<UnitConfigJSON_ViteHosting_Node>;
35
+ label: string;
36
+ servingPort: number;
37
+ hostingConfig?: import("../../implementations/firebase/Unit_HostingApp.js").FirebaseHostingConfig | undefined;
38
+ envs: TypedMap<FirebaseHosting_EnvConfig>;
39
+ fullPath: import("@nu-art/ts-common").AbsolutePath;
40
+ relativePath: import("@nu-art/ts-common").RelativePath;
41
+ key: string;
42
+ dependencies: import("@nu-art/ts-common").StringMap;
43
+ }>>;
44
+ }
45
+ export declare const UnitMapper_ViteHosting: UnitMapper_ViteHosting_Class;
46
+ export {};
@@ -0,0 +1,59 @@
1
+ import { tsValidateAnyString, tsValidateBoolean, tsValidateDynamicObject, tsValidateMustExist, tsValidateOptional, tsValidateOptionalAnyNumber, tsValidateOptionalObject, tsValidateRegexp, tsValidateValue } from '@nu-art/ts-common';
2
+ import { UnitMapper_Node } from './UnitMapper_Node.js';
3
+ import { Unit_HostingApp } from '../../implementations/firebase/Unit_HostingApp.js';
4
+ import { Unit_ViteHostingApp } from '../../implementations/firebase/Unit_ViteHostingApp.js';
5
+ import { resolve } from 'path';
6
+ import { BaiParam_SetEnv } from '../../../core/params.js';
7
+ const valuesValidator = {
8
+ config: tsValidateMustExist,
9
+ projectId: tsValidateAnyString,
10
+ isLocal: tsValidateBoolean(false),
11
+ };
12
+ const packageNameRegex = /^[a-z0-9]+([._-][a-z0-9]+)*$/;
13
+ const hostingDeploymentValidator = {
14
+ artifactRegistry: {
15
+ region: tsValidateAnyString,
16
+ repository: tsValidateAnyString,
17
+ projectId: tsValidateAnyString,
18
+ },
19
+ packageName: tsValidateRegexp(packageNameRegex, true),
20
+ };
21
+ export class UnitMapper_ViteHosting_Class extends UnitMapper_Node {
22
+ static tsValidator_ViteHosting = {
23
+ type: tsValidateValue(['vite-hosting']),
24
+ servingPort: tsValidateOptionalAnyNumber,
25
+ envs: tsValidateDynamicObject(valuesValidator, tsValidateAnyString),
26
+ hostingConfig: tsValidateOptional,
27
+ hostingDeployment: tsValidateOptionalObject(hostingDeploymentValidator),
28
+ ...UnitMapper_Node.tsValidator_Node,
29
+ };
30
+ constructor() {
31
+ super(UnitMapper_ViteHosting_Class.tsValidator_ViteHosting);
32
+ }
33
+ async resolveNodeUnit(context) {
34
+ const outputDir = context.packageJson.publishConfig?.directory;
35
+ const env = this.runtimeParams[BaiParam_SetEnv.keyName];
36
+ const envUnitConfig = context.packageJson.unitConfig.envs[env];
37
+ if (!envUnitConfig)
38
+ this.logWarning('Package Json config:', context.packageJson.unitConfig);
39
+ const envConfig = {
40
+ config: envUnitConfig?.config,
41
+ projectId: envUnitConfig?.projectId,
42
+ isLocal: envUnitConfig?.isLocal ?? env === 'local'
43
+ };
44
+ const { type, ...unitConfig } = context.packageJson.unitConfig;
45
+ return new Unit_ViteHostingApp({
46
+ ...context.baseConfig,
47
+ ...Unit_HostingApp.DefaultConfig_Hosting,
48
+ ...unitConfig,
49
+ envConfig,
50
+ isTopLevelApp: true,
51
+ hasSelfHotReload: unitConfig.hasSelfHotReload ?? false,
52
+ customESLintConfig: context.customESLintConfig,
53
+ customTSConfig: context.customTSConfig,
54
+ output: resolve(context.baseConfig.fullPath, outputDir ?? Unit_HostingApp.DefaultConfig_Hosting.output),
55
+ packageJson: context.packageJson,
56
+ });
57
+ }
58
+ }
59
+ export const UnitMapper_ViteHosting = new UnitMapper_ViteHosting_Class();
@@ -2,3 +2,4 @@ export * from './UnitMapper_NodeLib.js';
2
2
  export * from './UnitMapper_NodeProject.js';
3
3
  export * from './UnitMapper_FirebaseFunction.js';
4
4
  export * from './UnitMapper_FirebaseHosting.js';
5
+ export * from './UnitMapper_ViteHosting.js';
@@ -2,3 +2,4 @@ export * from './UnitMapper_NodeLib.js';
2
2
  export * from './UnitMapper_NodeProject.js';
3
3
  export * from './UnitMapper_FirebaseFunction.js';
4
4
  export * from './UnitMapper_FirebaseHosting.js';
5
+ export * from './UnitMapper_ViteHosting.js';
@@ -1,4 +1,4 @@
1
- import { StringMap } from '@nu-art/ts-common';
1
+ import { InvalidResult, StringMap } from '@nu-art/ts-common';
2
2
  import { UnitPhaseImplementor } from '../../core/types.js';
3
3
  import { Config_ProjectUnit, ProjectUnit } from '../base/ProjectUnit.js';
4
4
  import { TS_PackageJSON } from '../discovery/types.js';
@@ -31,6 +31,7 @@ export type Unit_PackageJson_Config = Config_ProjectUnit & {
31
31
  * **Base For**: Unit_NodeProject, Unit_TypescriptLib, Unit_FirebaseHosting, etc.
32
32
  */
33
33
  export declare class Unit_PackageJson<C extends Unit_PackageJson_Config = Unit_PackageJson_Config> extends ProjectUnit<C> implements UnitPhaseImplementor<[Phase_Purge, Phase_Prepare, Phase_PrepareWatch]> {
34
+ configValidationResult?: InvalidResult<any>;
34
35
  constructor(config: C);
35
36
  protected npmCommand(command: string): string;
36
37
  protected deriveDistDependencies(): StringMap;
@@ -1,7 +1,8 @@
1
1
  import { CONST_NodeModules, CONST_PackageJSON } from '../../config/consts.js';
2
- import { __stringify } from '@nu-art/ts-common';
2
+ import { __stringify, ValidationException } from '@nu-art/ts-common';
3
3
  import { ProjectUnit } from '../base/ProjectUnit.js';
4
4
  import { resolve } from 'path';
5
+ import { existsSync } from 'fs';
5
6
  import { Commando_NVM } from '@nu-art/commando';
6
7
  import { DEFAULT_OLD_TEMPLATE_PATTERN, FileSystemUtils } from '@nu-art/ts-common/utils/FileSystemUtils';
7
8
  /**
@@ -26,12 +27,16 @@ import { DEFAULT_OLD_TEMPLATE_PATTERN, FileSystemUtils } from '@nu-art/ts-common
26
27
  * **Base For**: Unit_NodeProject, Unit_TypescriptLib, Unit_FirebaseHosting, etc.
27
28
  */
28
29
  export class Unit_PackageJson extends ProjectUnit {
30
+ configValidationResult;
29
31
  constructor(config) {
30
32
  super(config);
31
33
  this.addToClassStack(Unit_PackageJson);
32
34
  }
33
35
  //######################### Internal Logic #########################
34
36
  npmCommand(command) {
37
+ const packageBin = resolve(this.config.fullPath, './node_modules/.bin', command);
38
+ if (existsSync(packageBin))
39
+ return packageBin;
35
40
  return resolve(this.runtimeContext.parentUnit.config.fullPath, './node_modules/.bin', command);
36
41
  }
37
42
  deriveDistDependencies() {
@@ -64,6 +69,8 @@ export class Unit_PackageJson extends ProjectUnit {
64
69
  * **Template Params**: Includes THUNDERSTORM_VERSION, __ENV__, and child unit versions.
65
70
  */
66
71
  async prepare() {
72
+ if (this.configValidationResult)
73
+ throw new ValidationException(`Invalid unit config for '${this.config.key}'`, undefined, this.configValidationResult);
67
74
  await this._sharedPrepare();
68
75
  }
69
76
  async watchPrepare() {
@@ -86,6 +93,7 @@ export class Unit_PackageJson extends ProjectUnit {
86
93
  }
87
94
  async releasePorts(allPorts) {
88
95
  const commando = this.allocateCommando(Commando_NVM).applyNVM();
96
+ this.logInfo('Releasing ports: ', allPorts);
89
97
  await commando.setUID(this.config.key)
90
98
  .append(`array=($(lsof -ti:${allPorts.join(',')}))`)
91
99
  .append(`((\${#array[@]} > 0)) && kill -9 "\${array[@]}"`)
@@ -14,7 +14,7 @@ export type Unit_TypescriptLib_Config = Unit_PackageJson_Config & {
14
14
  *
15
15
  * **Key Responsibilities**:
16
16
  * - Compiles TypeScript to JavaScript
17
- * - Runs tests (pure, firebase, ui, mobile)
17
+ * - Runs tests (pure, firebase, ui, mobile, playwright)
18
18
  * - Lints code
19
19
  * - Publishes packages
20
20
  * - Manages assets (JSON, SCSS, SVG, images)
@@ -36,6 +36,11 @@ export type Unit_TypescriptLib_Config = Unit_PackageJson_Config & {
36
36
  * - **firebase**: Tests with Firebase emulators
37
37
  * - **ui**: UI tests (not implemented)
38
38
  * - **mobile**: Mobile tests (not implemented)
39
+ * - **playwright**: Playwright test runner (@playwright/test)
40
+ * - Automatic browser instance management (one per worker)
41
+ * - Better performance with shared browser instances across test files
42
+ * - Built-in fixtures and test isolation
43
+ * - Pattern: **\/*.test.playwright.ts
39
44
  *
40
45
  * **Asset Management**: Automatically copies non-TypeScript files (JSON, SCSS, SVG, images)
41
46
  * to output directory during compilation.
@@ -45,6 +50,7 @@ export type Unit_TypescriptLib_Config = Unit_PackageJson_Config & {
45
50
  export declare class Unit_TypescriptLib<C extends Unit_TypescriptLib_Config = Unit_TypescriptLib_Config> extends Unit_PackageJson<C> implements UnitPhaseImplementor<[Phase_PreCompile, Phase_Compile, Phase_PrintDependencyTree, Phase_CheckCyclicImports, Phase_Lint, Phase_Test, Phase_Publish, Phase_ToESM, Phase_ExtractDynamicDeps, Phase_MapExports]> {
46
51
  private TestTypeWorkspaceSetup;
47
52
  runTests(): Promise<void>;
53
+ private generatePlaywrightConfig;
48
54
  protected dependencyUnits: Unit_TypescriptLib[];
49
55
  constructor(config: Unit_TypescriptLib<C>['config']);
50
56
  protected clearOutputDir(): Promise<void>;