@nu-art/build-and-install 0.401.9 → 0.500.6
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.
- package/BuildAndInstall.d.ts +2 -2
- package/BuildAndInstall.js +10 -2
- package/config/consts.d.ts +2 -2
- package/config/consts.js +1 -1
- package/config/package/consts.d.ts +1 -1
- package/config/types/package/runtime-package.d.ts +1 -1
- package/config/types/project-config.d.ts +17 -2
- package/core/FilesCache.d.ts +5 -1
- package/core/FilesCache.js +5 -1
- package/core/Unit_HelpPrinter.d.ts +4 -7
- package/core/Unit_HelpPrinter.js +3 -11
- package/core/params.d.ts +2 -1
- package/core/params.js +12 -3
- package/core/types.d.ts +2 -2
- package/dependencies/UnitsDependencyMapper.d.ts +8 -0
- package/dependencies/UnitsDependencyMapper.js +58 -4
- package/exceptions/UnitPhaseException.d.ts +1 -1
- package/exports/IndicesMcpServer.js +1 -1
- package/package.json +5 -5
- package/phases/PhaseManager.d.ts +2 -2
- package/phases/PhaseManager.js +4 -4
- package/phases/definitions/consts.d.ts +3 -0
- package/phases/definitions/consts.js +7 -0
- package/phases/definitions/types.d.ts +1 -1
- package/templates/backend/proxy/proxy._ts +36 -31
- package/templates/firebase/config/database.rules.json +6 -0
- package/units/base/BaseUnit.d.ts +1 -1
- package/units/discovery/UnitsMapper.d.ts +3 -2
- package/units/discovery/resolvers/UnitMapper_Base.d.ts +2 -2
- package/units/discovery/resolvers/UnitMapper_FirebaseFunction.d.ts +12 -1
- package/units/discovery/resolvers/UnitMapper_FirebaseFunction.js +48 -18
- package/units/discovery/resolvers/UnitMapper_FirebaseHosting.d.ts +5 -9
- package/units/discovery/resolvers/UnitMapper_FirebaseHosting.js +3 -1
- package/units/discovery/resolvers/UnitMapper_Node.d.ts +1 -1
- package/units/discovery/resolvers/UnitMapper_Node.js +8 -15
- package/units/discovery/resolvers/UnitMapper_ViteHosting.d.ts +46 -0
- package/units/discovery/resolvers/UnitMapper_ViteHosting.js +59 -0
- package/units/implementations/Unit_NodeProject.d.ts +3 -2
- package/units/implementations/Unit_NodeProject.js +30 -6
- package/units/implementations/Unit_PackageJson.d.ts +8 -2
- package/units/implementations/Unit_PackageJson.js +46 -11
- package/units/implementations/Unit_TypescriptLib.d.ts +8 -2
- package/units/implementations/Unit_TypescriptLib.js +155 -4
- package/units/implementations/firebase/Unit_FirebaseFunctionsApp.d.ts +12 -2
- package/units/implementations/firebase/Unit_FirebaseFunctionsApp.js +73 -17
- package/units/implementations/firebase/Unit_FirebaseHostingApp.d.ts +19 -97
- package/units/implementations/firebase/Unit_FirebaseHostingApp.js +28 -290
- package/units/implementations/firebase/Unit_HostingApp.d.ts +59 -0
- package/units/implementations/firebase/Unit_HostingApp.js +225 -0
- package/units/implementations/firebase/Unit_ViteHostingApp.d.ts +10 -0
- package/units/implementations/firebase/Unit_ViteHostingApp.js +28 -0
- package/workspace/Workspace.d.ts +3 -1
- package/workspace/Workspace.js +2 -1
- package/config/types/configs/index.d.ts +0 -3
- package/config/types/configs/index.js +0 -3
- package/config/types/index.d.ts +0 -4
- package/config/types/index.js +0 -4
- package/config/types/package/index.d.ts +0 -2
- package/config/types/package/index.js +0 -2
- package/phases/definitions/index.d.ts +0 -2
- package/phases/definitions/index.js +0 -2
- package/phases/index.d.ts +0 -2
- package/phases/index.js +0 -2
- package/units/discovery/resolvers/index.d.ts +0 -4
- package/units/discovery/resolvers/index.js +0 -4
- package/units/index.d.ts +0 -6
- package/units/index.js +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RecursivePartial, TypedMap } from '@nu-art/ts-common';
|
|
2
2
|
import { UnitPhaseImplementor } from '../../core/types.js';
|
|
3
3
|
import { Unit_PackageJson, Unit_PackageJson_Config } from './Unit_PackageJson.js';
|
|
4
|
-
import { Phase_CheckCyclicImports, Phase_Compile, Phase_ExtractDynamicDeps, Phase_Lint, Phase_MapExports, Phase_PreCompile, Phase_PrintDependencyTree, Phase_Publish, Phase_Test, Phase_ToESM } from '../../phases/definitions/
|
|
4
|
+
import { Phase_CheckCyclicImports, Phase_Compile, Phase_ExtractDynamicDeps, Phase_Lint, Phase_MapExports, Phase_PreCompile, Phase_PrintDependencyTree, Phase_Publish, Phase_Test, Phase_ToESM } from '../../phases/definitions/consts.js';
|
|
5
5
|
import { TsConfig } from '../base/types.js';
|
|
6
6
|
export type Unit_TypescriptLib_Config = Unit_PackageJson_Config & {
|
|
7
7
|
customESLintConfig: boolean;
|
|
@@ -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>;
|
|
@@ -7,7 +7,7 @@ import { resolve, resolve as pathResolve } from 'path';
|
|
|
7
7
|
import { Unit_PackageJson } from './Unit_PackageJson.js';
|
|
8
8
|
import { glob } from 'node:fs/promises';
|
|
9
9
|
import { TestTypes } from '../../core/params.js';
|
|
10
|
-
import {
|
|
10
|
+
import { DEFAULT_TEMPLATE_PATTERN, FileSystemUtils } from '@nu-art/ts-common/utils/FileSystemUtils';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { ExportMapper } from '../../exports/ExportMapper.js';
|
|
13
13
|
const assets = [
|
|
@@ -28,7 +28,8 @@ const defaultTestPatterns = {
|
|
|
28
28
|
pure: '**/*.test.ts',
|
|
29
29
|
firebase: '**/*.test.firebase.ts',
|
|
30
30
|
ui: '**/*.test.ui.ts',
|
|
31
|
-
mobile: '**/*.test.mobile.ts'
|
|
31
|
+
mobile: '**/*.test.mobile.ts',
|
|
32
|
+
playwright: '**/*.test.playwright.ts'
|
|
32
33
|
};
|
|
33
34
|
const CONST_ESM_PREFIX = 'export NODE_OPTIONS=\'--import data:text/javascript,import%20%7B%20register%20%7D%20from%20%22node%3Amodule%22%3B%20import%20%7B%20pathToFileURL%20%7D%20from%20%22node%3Aurl%22%3B%20register%28%22ts-node%2Fesm%22%2C%20pathToFileURL%28%22.%2F%22%29%29%3B\'';
|
|
34
35
|
const TestsCommandComposer = {
|
|
@@ -62,13 +63,35 @@ const TestsCommandComposer = {
|
|
|
62
63
|
mobile: async () => {
|
|
63
64
|
throw new NotImplementedYetException('Mobile tests not implemented yet');
|
|
64
65
|
},
|
|
66
|
+
playwright: async (config, runtimeContext) => {
|
|
67
|
+
const command = resolve(runtimeContext.parentUnit.config.fullPath, 'node_modules/.bin/playwright');
|
|
68
|
+
// Playwright accepts file paths as arguments (not glob patterns)
|
|
69
|
+
// If testFiles not explicitly provided, discover them via glob and convert to relative paths
|
|
70
|
+
let testFiles = runtimeContext.runtimeParams.testFiles;
|
|
71
|
+
if (!testFiles) {
|
|
72
|
+
const pattern = resolve(config.fullPath, 'src/test', defaultTestPatterns.playwright);
|
|
73
|
+
const fileIterator = glob(pattern, {});
|
|
74
|
+
const absoluteFiles = [];
|
|
75
|
+
for await (const file of fileIterator)
|
|
76
|
+
absoluteFiles.push(file);
|
|
77
|
+
testFiles = absoluteFiles.map(file => path.relative(config.fullPath, file));
|
|
78
|
+
}
|
|
79
|
+
const cli_testFiles = testFiles.length > 0 ? ` ${testFiles.join(' ')}` : '';
|
|
80
|
+
// Map test cases to Playwright's --grep option
|
|
81
|
+
const testCases = runtimeContext.runtimeParams.testCases;
|
|
82
|
+
const cli_testCases = testCases ? ` --grep '${testCases.join('|')}'` : '';
|
|
83
|
+
// Map debug port to Playwright's --debug option
|
|
84
|
+
const debugPort = runtimeContext.runtimeParams.testDebugPort;
|
|
85
|
+
const cli_debug = debugPort ? ` --debug=${debugPort}` : '';
|
|
86
|
+
return `${command} test${cli_testFiles}${cli_testCases}${cli_debug}`;
|
|
87
|
+
},
|
|
65
88
|
};
|
|
66
89
|
/**
|
|
67
90
|
* TypeScript library unit for building TypeScript packages.
|
|
68
91
|
*
|
|
69
92
|
* **Key Responsibilities**:
|
|
70
93
|
* - Compiles TypeScript to JavaScript
|
|
71
|
-
* - Runs tests (pure, firebase, ui, mobile)
|
|
94
|
+
* - Runs tests (pure, firebase, ui, mobile, playwright)
|
|
72
95
|
* - Lints code
|
|
73
96
|
* - Publishes packages
|
|
74
97
|
* - Manages assets (JSON, SCSS, SVG, images)
|
|
@@ -90,6 +113,11 @@ const TestsCommandComposer = {
|
|
|
90
113
|
* - **firebase**: Tests with Firebase emulators
|
|
91
114
|
* - **ui**: UI tests (not implemented)
|
|
92
115
|
* - **mobile**: Mobile tests (not implemented)
|
|
116
|
+
* - **playwright**: Playwright test runner (@playwright/test)
|
|
117
|
+
* - Automatic browser instance management (one per worker)
|
|
118
|
+
* - Better performance with shared browser instances across test files
|
|
119
|
+
* - Built-in fixtures and test isolation
|
|
120
|
+
* - Pattern: **\/*.test.playwright.ts
|
|
93
121
|
*
|
|
94
122
|
* **Asset Management**: Automatically copies non-TypeScript files (JSON, SCSS, SVG, images)
|
|
95
123
|
* to output directory during compilation.
|
|
@@ -128,6 +156,33 @@ export class Unit_TypescriptLib extends Unit_PackageJson {
|
|
|
128
156
|
FIREBASE_FIRESTORE_INDICES: resolve(pathToProjectRoot, firebaseConfigFiles.firestoreIndexesRules),
|
|
129
157
|
}, DEFAULT_TEMPLATE_PATTERN);
|
|
130
158
|
await this.releasePorts(ports);
|
|
159
|
+
const mongoPort = runtimeContext.baiConfig.files?.tests?.firebase?.mongoPort;
|
|
160
|
+
if (mongoPort) {
|
|
161
|
+
const containerName = `mongo-test-${config.key.replace(/[^a-z0-9-]/gi, '-')}`;
|
|
162
|
+
this.logInfo(`Starting MongoDB test container on port ${mongoPort} (container: ${containerName})`);
|
|
163
|
+
await this.releasePorts([`${mongoPort}`]);
|
|
164
|
+
const commando = this.allocateCommando();
|
|
165
|
+
await this.executeAsyncCommando(commando, `docker rm -f ${containerName} 2>/dev/null; docker run -d --name ${containerName} -p ${mongoPort}:${mongoPort} mongo:7 --replSet rs0 --port ${mongoPort}`, (stdout, stderr, exitCode) => {
|
|
166
|
+
if (exitCode !== 0)
|
|
167
|
+
throw new CommandoException('Failed to start MongoDB test container', stdout, stderr, exitCode);
|
|
168
|
+
});
|
|
169
|
+
const initCommando = this.allocateCommando();
|
|
170
|
+
await this.executeAsyncCommando(initCommando, `sleep 3 && docker exec ${containerName} mongosh --port ${mongoPort} --quiet --eval "rs.initiate({_id:'rs0',members:[{_id:0,host:'localhost:${mongoPort}'}]}); while(!rs.status().members.some(m=>m.stateStr==='PRIMARY')){sleep(200)} print('PRIMARY ready')"`, (stdout, stderr, exitCode) => {
|
|
171
|
+
if (exitCode !== 0)
|
|
172
|
+
throw new CommandoException('Failed to initiate MongoDB test replica set', stdout, stderr, exitCode);
|
|
173
|
+
});
|
|
174
|
+
this.logInfo(`MongoDB test container started as replica set on port ${mongoPort}`);
|
|
175
|
+
this.registerTerminatable(async () => {
|
|
176
|
+
try {
|
|
177
|
+
const stopCommando = this.allocateCommando();
|
|
178
|
+
await this.executeAsyncCommando(stopCommando, `docker rm -f ${containerName}`, () => {
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
this.logWarning(`Failed to stop MongoDB test container: ${e.message}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
131
186
|
},
|
|
132
187
|
ui: async () => {
|
|
133
188
|
throw new NotImplementedYetException('UI tests not implemented yet');
|
|
@@ -135,6 +190,48 @@ export class Unit_TypescriptLib extends Unit_PackageJson {
|
|
|
135
190
|
mobile: async () => {
|
|
136
191
|
throw new NotImplementedYetException('Mobile tests not implemented yet');
|
|
137
192
|
},
|
|
193
|
+
playwright: async (config, runtimeContext) => {
|
|
194
|
+
const playwrightConfigPath = resolve(config.fullPath, 'playwright.config.ts');
|
|
195
|
+
// Generate playwright.config.ts if missing
|
|
196
|
+
if (!await FileSystemUtils.file.exists(playwrightConfigPath)) {
|
|
197
|
+
const playwrightConfig = runtimeContext.baiConfig.files?.tests?.playwright;
|
|
198
|
+
const browsers = playwrightConfig?.browsers ?? ['chromium'];
|
|
199
|
+
const headless = playwrightConfig?.headless ?? true;
|
|
200
|
+
const baseURL = playwrightConfig?.baseURL;
|
|
201
|
+
const viewport = playwrightConfig?.viewport ?? { width: 1280, height: 720 };
|
|
202
|
+
// Vite config: from bai-config, or default to shared _thunderstorm/vite.config.ts when package is under _thunderstorm
|
|
203
|
+
const projectRoot = runtimeContext.parentUnit.config.fullPath;
|
|
204
|
+
const viteConfigPathFromConfig = playwrightConfig?.vite?.configPath;
|
|
205
|
+
const defaultThunderstormVitePath = '_thunderstorm/vite.config.ts';
|
|
206
|
+
const viteConfigPath = viteConfigPathFromConfig ?? (config.fullPath.includes('_thunderstorm') ? defaultThunderstormVitePath : undefined);
|
|
207
|
+
const vitePort = playwrightConfig?.vite?.port ?? 5173;
|
|
208
|
+
const viteOptions = viteConfigPath ? {
|
|
209
|
+
port: vitePort,
|
|
210
|
+
configPath: path.relative(config.fullPath, resolve(projectRoot, viteConfigPath))
|
|
211
|
+
} : undefined;
|
|
212
|
+
const configContent = this.generatePlaywrightConfig({
|
|
213
|
+
browsers,
|
|
214
|
+
headless,
|
|
215
|
+
baseURL,
|
|
216
|
+
viewport,
|
|
217
|
+
vite: viteOptions
|
|
218
|
+
});
|
|
219
|
+
await FileSystemUtils.file.write(playwrightConfigPath, configContent);
|
|
220
|
+
this.logInfo(`Generated playwright.config.ts for ${config.key}`);
|
|
221
|
+
}
|
|
222
|
+
// Ensure browser binaries are installed
|
|
223
|
+
const commando = this.allocateCommando(Commando_NVM)
|
|
224
|
+
.cd(config.fullPath);
|
|
225
|
+
const playwrightConfig = runtimeContext.baiConfig.files?.tests?.playwright;
|
|
226
|
+
const browsersToInstall = playwrightConfig?.browsers ?? ['chromium'];
|
|
227
|
+
for (const browser of browsersToInstall) {
|
|
228
|
+
await this.executeAsyncCommando(commando, `npx playwright install ${browser}`, (stdout, stderr, exitCode) => {
|
|
229
|
+
if (exitCode !== 0) {
|
|
230
|
+
this.logWarning(`Failed to install ${browser} browser, tests may fail`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
},
|
|
138
235
|
};
|
|
139
236
|
async runTests() {
|
|
140
237
|
const testsFolder = resolve(this.config.fullPath, 'src/test');
|
|
@@ -165,6 +262,60 @@ export class Unit_TypescriptLib extends Unit_PackageJson {
|
|
|
165
262
|
});
|
|
166
263
|
}
|
|
167
264
|
}
|
|
265
|
+
generatePlaywrightConfig(options) {
|
|
266
|
+
const browserNames = options.browsers.map(browser => {
|
|
267
|
+
const deviceName = browser === 'webkit' ? 'Safari' : browser.charAt(0).toUpperCase() + browser.slice(1);
|
|
268
|
+
return ` {
|
|
269
|
+
name: '${browser}',
|
|
270
|
+
use: { ...devices['Desktop ${deviceName}'] },
|
|
271
|
+
}`;
|
|
272
|
+
}).join(',\n');
|
|
273
|
+
// Build webServer block if vite config is provided
|
|
274
|
+
const viteConfig = options.vite;
|
|
275
|
+
const webServerBlock = viteConfig ? `
|
|
276
|
+
webServer: {
|
|
277
|
+
command: \`npx vite --config \${resolve(__dirname, '${viteConfig.configPath}')} --port \${vitePort} --host 127.0.0.1\`,
|
|
278
|
+
url: \`http://127.0.0.1:\${vitePort}/src/test/index.html\`,
|
|
279
|
+
reuseExistingServer: true,
|
|
280
|
+
timeout: 60000,
|
|
281
|
+
stdout: 'pipe',
|
|
282
|
+
stderr: 'pipe',
|
|
283
|
+
},
|
|
284
|
+
` : '';
|
|
285
|
+
// Use vite URL as baseURL when vite is configured, otherwise use provided baseURL
|
|
286
|
+
const baseURL = viteConfig
|
|
287
|
+
? '`http://127.0.0.1:${vitePort}`'
|
|
288
|
+
: options.baseURL ? `'${options.baseURL}'` : undefined;
|
|
289
|
+
const baseURLConfig = baseURL ? ` baseURL: ${baseURL},` : '';
|
|
290
|
+
// Add imports based on whether vite is used
|
|
291
|
+
const imports = viteConfig
|
|
292
|
+
? `import {defineConfig, devices} from '@playwright/test';
|
|
293
|
+
import {resolve} from 'path';
|
|
294
|
+
import {fileURLToPath} from 'url';
|
|
295
|
+
|
|
296
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
297
|
+
const vitePort = process.env.VITE_PORT || '${viteConfig.port}';
|
|
298
|
+
`
|
|
299
|
+
: `import {defineConfig, devices} from '@playwright/test';
|
|
300
|
+
`;
|
|
301
|
+
return `${imports}
|
|
302
|
+
export default defineConfig({
|
|
303
|
+
testDir: './src/test',
|
|
304
|
+
testMatch: '**/*.test.playwright.ts',
|
|
305
|
+
${webServerBlock}
|
|
306
|
+
use: {
|
|
307
|
+
headless: ${options.headless},
|
|
308
|
+
${baseURLConfig ? baseURLConfig + '\n' : ''} viewport: { width: ${options.viewport.width}, height: ${options.viewport.height} },
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
projects: [
|
|
312
|
+
${browserNames}
|
|
313
|
+
],
|
|
314
|
+
|
|
315
|
+
timeout: 30000,
|
|
316
|
+
});
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
168
319
|
dependencyUnits;
|
|
169
320
|
constructor(config) {
|
|
170
321
|
super(config);
|
|
@@ -401,7 +552,7 @@ export class Unit_TypescriptLib extends Unit_PackageJson {
|
|
|
401
552
|
const packageJson = FileSystemUtils.file.template.transform(__stringify(this.config.packageJson, true), params);
|
|
402
553
|
this.logVerbose('Compiling params: ', params);
|
|
403
554
|
this.logVerbose('Compiling from package.json: ', packageJson);
|
|
404
|
-
await FileSystemUtils.file.template.write(targetPath, packageJson, params,
|
|
555
|
+
await FileSystemUtils.file.template.write(targetPath, packageJson, params, DEFAULT_TEMPLATE_PATTERN);
|
|
405
556
|
}
|
|
406
557
|
async purge() {
|
|
407
558
|
await FileSystemUtils.folder.delete(this.config.output);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UnitPhaseImplementor } from '../../../core/types.js';
|
|
2
|
-
import { FirebasePackageConfig } from '../../../config/types/
|
|
2
|
+
import { FirebasePackageConfig } from '../../../config/types/package/package.js';
|
|
3
3
|
import { StringMap } from '@nu-art/ts-common';
|
|
4
|
-
import { Phase_BuildPushImage, Phase_Deploy, Phase_DeployImage, Phase_Launch } from '../../../phases/definitions/
|
|
4
|
+
import { Phase_BuildPushImage, Phase_Deploy, Phase_DeployImage, Phase_Launch } from '../../../phases/definitions/consts.js';
|
|
5
5
|
import { Unit_TypescriptLib, Unit_TypescriptLib_Config } from '../Unit_TypescriptLib.js';
|
|
6
6
|
export declare const firebaseFunctionEmulator_ErrorStrings: string[];
|
|
7
7
|
export declare const firebaseFunctionEmulator_WarningStrings: string[];
|
|
@@ -27,6 +27,10 @@ export type FunctionConfig = {
|
|
|
27
27
|
serviceAccountName?: string;
|
|
28
28
|
resources?: FunctionResourceConfig;
|
|
29
29
|
};
|
|
30
|
+
export type MongoEmulatorConfig = {
|
|
31
|
+
port?: number;
|
|
32
|
+
dbName?: string;
|
|
33
|
+
};
|
|
30
34
|
export type Unit_FirebaseFunctionsApp_Config = Unit_TypescriptLib_Config & {
|
|
31
35
|
firebaseConfig?: FirebasePackageConfig;
|
|
32
36
|
pathToFirebaseConfig: string;
|
|
@@ -39,6 +43,7 @@ export type Unit_FirebaseFunctionsApp_Config = Unit_TypescriptLib_Config & {
|
|
|
39
43
|
pathToEmulatorData: string;
|
|
40
44
|
sources?: string[];
|
|
41
45
|
functions: string[] | FunctionConfig[];
|
|
46
|
+
mongo?: MongoEmulatorConfig;
|
|
42
47
|
containerDeployment?: {
|
|
43
48
|
artifactRegistry: {
|
|
44
49
|
region: string;
|
|
@@ -98,6 +103,11 @@ export declare class Unit_FirebaseFunctionsApp<C extends Unit_FirebaseFunctionsA
|
|
|
98
103
|
postCompile(): Promise<void>;
|
|
99
104
|
launch(): Promise<void>;
|
|
100
105
|
releaseEmulatorPorts(): Promise<void>;
|
|
106
|
+
private resolveMongoPort;
|
|
107
|
+
private resolveMongoContainerName;
|
|
108
|
+
private resolveMongoDataPath;
|
|
109
|
+
private startMongoEmulator;
|
|
110
|
+
private stopMongoEmulator;
|
|
101
111
|
deploy(): Promise<void>;
|
|
102
112
|
/**
|
|
103
113
|
* Builds Docker container image using Google Cloud Build and pushes it to Artifact Registry.
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { CONST_BuildImageDir, CONST_FirebaseJSON, CONST_FirebaseRC, CONST_LatestTag, CONST_PackageJSON, CONST_TrashDir, CONST_VersionApp } from '../../../config/consts.js';
|
|
2
2
|
import { __stringify, _keys, _logger_logPrefixes, deepClone, ImplementationMissingException, LogLevel, Second, sleep } from '@nu-art/ts-common';
|
|
3
|
-
import { Const_FirebaseConfigKeys, Const_FirebaseDefaultsKeyToFile, FunctionBuildTemplateFiles } from '../../../templates/consts.js';
|
|
4
|
-
import { Commando_NVM } from '@nu-art/commando';
|
|
3
|
+
import { Const_FirebaseConfigKeys, Const_FirebaseDefaultsKeyToFile, Default_Files, FunctionBuildTemplateFiles } from '../../../templates/consts.js';
|
|
4
|
+
import { Commando_NVM, CommandoException } from '@nu-art/commando';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
|
-
import {
|
|
6
|
+
import { DEFAULT_TEMPLATE_PATTERN, FileSystemUtils } from '@nu-art/ts-common/utils/FileSystemUtils';
|
|
7
7
|
import { Unit_TypescriptLib } from '../Unit_TypescriptLib.js';
|
|
8
|
-
import { CommandoException } from '@nu-art/commando';
|
|
9
8
|
import { deployLogFilter, ensureArtifactRegistryRepository } from './common.js';
|
|
10
9
|
export const firebaseFunctionEmulator_ErrorStrings = [
|
|
11
10
|
'functions: Failed',
|
|
@@ -86,7 +85,7 @@ export class Unit_FirebaseFunctionsApp extends Unit_TypescriptLib {
|
|
|
86
85
|
return dependencies;
|
|
87
86
|
}, dependencies);
|
|
88
87
|
packageJson.dependencies = dependencies;
|
|
89
|
-
await FileSystemUtils.file.template.write(targetPath, __stringify(packageJson, true), this.deriveDistDependencies(),
|
|
88
|
+
await FileSystemUtils.file.template.write(targetPath, __stringify(packageJson, true), this.deriveDistDependencies(), DEFAULT_TEMPLATE_PATTERN);
|
|
90
89
|
}
|
|
91
90
|
async prepare() {
|
|
92
91
|
await super.prepare();
|
|
@@ -114,15 +113,63 @@ export class Unit_FirebaseFunctionsApp extends Unit_TypescriptLib {
|
|
|
114
113
|
async launch() {
|
|
115
114
|
await sleep(2 * Second * Unit_FirebaseFunctionsApp.staggerCount++);
|
|
116
115
|
await this.releaseEmulatorPorts();
|
|
117
|
-
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
if (this.config.mongo)
|
|
117
|
+
await this.startMongoEmulator();
|
|
118
|
+
let emulatorPromise;
|
|
119
|
+
const proxyPromise = this.runProxy(() => {
|
|
120
|
+
emulatorPromise = this.runEmulator();
|
|
121
|
+
});
|
|
122
|
+
await proxyPromise;
|
|
123
|
+
if (emulatorPromise)
|
|
124
|
+
await emulatorPromise;
|
|
121
125
|
}
|
|
122
126
|
async releaseEmulatorPorts() {
|
|
123
|
-
const allPorts = Array.from({ length:
|
|
127
|
+
const allPorts = Array.from({ length: 11 }, (_, i) => `${this.config.basePort + i}`);
|
|
128
|
+
if (this.config.mongo)
|
|
129
|
+
allPorts.push(`${this.resolveMongoPort()}`);
|
|
124
130
|
return this.releasePorts(allPorts);
|
|
125
131
|
}
|
|
132
|
+
//######################### MongoDB Emulator #########################
|
|
133
|
+
resolveMongoPort() {
|
|
134
|
+
return this.config.mongo?.port ?? this.config.basePort + 11;
|
|
135
|
+
}
|
|
136
|
+
resolveMongoContainerName() {
|
|
137
|
+
return `mongo-emu-${this.config.key.replace(/[^a-z0-9-]/gi, '-')}`;
|
|
138
|
+
}
|
|
139
|
+
resolveMongoDataPath() {
|
|
140
|
+
return resolve(this.config.fullPath, CONST_TrashDir, 'mongo-data');
|
|
141
|
+
}
|
|
142
|
+
async startMongoEmulator() {
|
|
143
|
+
const port = this.resolveMongoPort();
|
|
144
|
+
const containerName = this.resolveMongoContainerName();
|
|
145
|
+
const mongoDataPath = this.resolveMongoDataPath();
|
|
146
|
+
await FileSystemUtils.folder.create(mongoDataPath);
|
|
147
|
+
this.logInfo(`Starting MongoDB emulator on port ${port} (container: ${containerName}, data: ${mongoDataPath})`);
|
|
148
|
+
const stopAction = async () => this.stopMongoEmulator();
|
|
149
|
+
this.registerTerminatable(stopAction);
|
|
150
|
+
const commando = this.allocateCommando();
|
|
151
|
+
await this.executeAsyncCommando(commando, `docker rm -f ${containerName} 2>/dev/null; docker run -d --name ${containerName} -p ${port}:${port} -v ${mongoDataPath}:/data/db mongo:7 --replSet rs0 --port ${port}`, (stdout, stderr, exitCode) => {
|
|
152
|
+
if (exitCode !== 0)
|
|
153
|
+
throw new CommandoException(`Failed to start MongoDB emulator container`, stdout, stderr, exitCode);
|
|
154
|
+
});
|
|
155
|
+
const initCommando = this.allocateCommando();
|
|
156
|
+
await this.executeAsyncCommando(initCommando, `sleep 3 && docker exec ${containerName} mongosh --port ${port} --quiet --eval "try{rs.status()}catch(e){rs.initiate({_id:'rs0',members:[{_id:0,host:'localhost:${port}'}]})} while(!rs.status().members.some(m=>m.stateStr==='PRIMARY')){sleep(200)} print('PRIMARY ready')"`, (stdout, stderr, exitCode) => {
|
|
157
|
+
if (exitCode !== 0)
|
|
158
|
+
throw new CommandoException(`Failed to initiate MongoDB replica set`, stdout, stderr, exitCode);
|
|
159
|
+
});
|
|
160
|
+
this.logInfo(`MongoDB emulator started as replica set on port ${port}`);
|
|
161
|
+
}
|
|
162
|
+
async stopMongoEmulator() {
|
|
163
|
+
const containerName = this.resolveMongoContainerName();
|
|
164
|
+
this.logInfo(`Stopping MongoDB emulator (container: ${containerName})`);
|
|
165
|
+
try {
|
|
166
|
+
const commando = this.allocateCommando();
|
|
167
|
+
await this.executeAsyncCommando(commando, `docker rm -f ${containerName}`, () => { });
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
this.logWarning(`Failed to stop MongoDB emulator container: ${e.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
126
173
|
async deploy() {
|
|
127
174
|
const commando = this.allocateCommando(Commando_NVM).applyNVM()
|
|
128
175
|
.cd(this.config.output)
|
|
@@ -635,9 +682,8 @@ export class Unit_FirebaseFunctionsApp extends Unit_TypescriptLib {
|
|
|
635
682
|
async resolveProxyFile() {
|
|
636
683
|
const envConfig = this.getEnvConfig();
|
|
637
684
|
const targetPath = this.pathToProxy();
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
return;
|
|
685
|
+
const templatePath = this.runtimeContext.baiConfig.files?.backend?.proxy ?? Default_Files.backend.proxy;
|
|
686
|
+
await FileSystemUtils.folder.create(resolve(this.config.fullPath, '.trash/proxy'));
|
|
641
687
|
const params = {
|
|
642
688
|
PROJECT_ID: `${envConfig.projectId}`,
|
|
643
689
|
PROXY_PORT: `${this.config.basePort}`,
|
|
@@ -645,10 +691,10 @@ export class Unit_FirebaseFunctionsApp extends Unit_TypescriptLib {
|
|
|
645
691
|
PATH_TO_SSL_KEY: `${this.config.sslKey}`,
|
|
646
692
|
PATH_TO_SSL_CERTIFICATE: `${this.config.sslCert}`,
|
|
647
693
|
};
|
|
648
|
-
await FileSystemUtils.file.template.copy(
|
|
694
|
+
await FileSystemUtils.file.template.copy(templatePath, targetPath, params);
|
|
649
695
|
}
|
|
650
696
|
pathToProxy() {
|
|
651
|
-
return resolve(this.config.fullPath, '
|
|
697
|
+
return resolve(this.config.fullPath, '.trash/proxy/proxy.ts');
|
|
652
698
|
}
|
|
653
699
|
async resolveConfigDir() {
|
|
654
700
|
//Create the dir if it doesn't exist
|
|
@@ -779,11 +825,19 @@ export class Unit_FirebaseFunctionsApp extends Unit_TypescriptLib {
|
|
|
779
825
|
}));
|
|
780
826
|
}
|
|
781
827
|
//######################### Launch Logic #########################
|
|
782
|
-
async runProxy() {
|
|
828
|
+
async runProxy(onReady) {
|
|
783
829
|
await this.resolveProxyFile();
|
|
784
830
|
const commando = this.allocateCommando(Commando_NVM).applyNVM()
|
|
785
831
|
.cd(this.config.fullPath);
|
|
786
|
-
|
|
832
|
+
let readyFired = false;
|
|
833
|
+
commando.addLogProcessor((log) => {
|
|
834
|
+
if (!readyFired && log.includes('SSL proxy started at port')) {
|
|
835
|
+
readyFired = true;
|
|
836
|
+
onReady?.();
|
|
837
|
+
}
|
|
838
|
+
return true;
|
|
839
|
+
});
|
|
840
|
+
await this.executeAsyncCommando(commando, `${this.npmCommand('tsx')} .trash/proxy/proxy.ts`);
|
|
787
841
|
this.logWarning('PROXY TERMINATED');
|
|
788
842
|
}
|
|
789
843
|
async runEmulator() {
|
|
@@ -797,6 +851,8 @@ export class Unit_FirebaseFunctionsApp extends Unit_TypescriptLib {
|
|
|
797
851
|
return LogLevel.Warning;
|
|
798
852
|
})
|
|
799
853
|
.onLog(/.*Emulator Hub running.*/, () => this.setStatus('Launch Complete'));
|
|
854
|
+
if (this.config.mongo)
|
|
855
|
+
commando.custom(`export MONGODB_EMULATOR_HOST=localhost:${this.resolveMongoPort()}`);
|
|
800
856
|
await this.executeAsyncCommando(commando, `${this.npmCommand('firebase')} emulators:start --project ${this.config.envConfig.projectId} --export-on-exit --import=${this.config.pathToEmulatorData} ${this.runtimeContext.runtimeParams.debugBackend
|
|
801
857
|
? `--inspect-functions ${this.config.debugPort}` : ''}`);
|
|
802
858
|
this.logWarning('EMULATORS TERMINATED');
|
|
@@ -1,115 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { UnitPhaseImplementor } from '../../../core/types.js';
|
|
3
|
-
import { StringMap, TS_Object, TypedMap } from '@nu-art/ts-common';
|
|
1
|
+
import { TypedMap } from '@nu-art/ts-common';
|
|
4
2
|
import { UnitConfigJSON_Node } from '../../discovery/resolvers/UnitMapper_Node.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
export type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import { FirebaseHosting_EnvConfig, FirebaseHostingConfig, Unit_HostingApp, Unit_HostingApp_Config } from './Unit_HostingApp.js';
|
|
4
|
+
export type { FirebaseHostingConfig, FirebaseHosting_EnvConfig };
|
|
5
|
+
export type WebpackEnvConfig = {
|
|
6
|
+
minifyHtml?: boolean;
|
|
7
|
+
devServerSSL?: boolean;
|
|
8
|
+
devServerPort?: number;
|
|
9
|
+
sslKey?: string;
|
|
10
|
+
sslCert?: string;
|
|
13
11
|
};
|
|
14
|
-
export type
|
|
15
|
-
|
|
16
|
-
projectId: string;
|
|
17
|
-
isLocal?: boolean;
|
|
12
|
+
export type WebpackHosting_EnvConfig = FirebaseHosting_EnvConfig & {
|
|
13
|
+
webpackConfig?: WebpackEnvConfig;
|
|
18
14
|
};
|
|
19
15
|
export type UnitConfigJSON_FirebaseHosting = UnitConfigJSON_Node & {
|
|
20
16
|
servingPort?: number;
|
|
21
17
|
hostingConfig?: FirebaseHostingConfig;
|
|
22
|
-
envs: TypedMap<
|
|
18
|
+
envs: TypedMap<WebpackHosting_EnvConfig>;
|
|
23
19
|
};
|
|
24
|
-
export type Unit_FirebaseHostingApp_Config =
|
|
25
|
-
|
|
26
|
-
servingPort: number;
|
|
27
|
-
hostingConfig?: FirebaseHostingConfig;
|
|
28
|
-
envConfig: FirebaseHosting_EnvConfig;
|
|
29
|
-
sources?: string[];
|
|
30
|
-
hostingDeployment?: {
|
|
31
|
-
artifactRegistry: {
|
|
32
|
-
region: string;
|
|
33
|
-
repository: string;
|
|
34
|
-
projectId: string;
|
|
35
|
-
};
|
|
36
|
-
packageName: string;
|
|
37
|
-
};
|
|
20
|
+
export type Unit_FirebaseHostingApp_Config = Unit_HostingApp_Config & {
|
|
21
|
+
envConfig: WebpackHosting_EnvConfig;
|
|
38
22
|
};
|
|
39
23
|
/**
|
|
40
|
-
* Firebase Hosting application unit.
|
|
41
|
-
*
|
|
42
|
-
* **Key Features**:
|
|
43
|
-
* - Extends Unit_TypescriptLib (compiles TypeScript)
|
|
44
|
-
* - Manages Firebase hosting configuration (firebase.json, .firebaserc)
|
|
45
|
-
* - Supports multiple environments (local, staging, production)
|
|
46
|
-
* - Implements launch and deploy phases
|
|
47
|
-
*
|
|
48
|
-
* **Phases Implemented**:
|
|
49
|
-
* - `prepare()`: Resolves Firebase hosting config files
|
|
50
|
-
* - `compile()`: Compiles TypeScript (without declarations)
|
|
51
|
-
* - `launch()`: Starts Firebase hosting emulator
|
|
52
|
-
* - `deploy()`: Deploys to Firebase hosting
|
|
53
|
-
*
|
|
54
|
-
* **Configuration**:
|
|
55
|
-
* - `servingPort`: Port for local hosting server
|
|
56
|
-
* - `hostingConfig`: Firebase hosting configuration (public folder, rewrites)
|
|
57
|
-
* - `envConfig`: Environment-specific config (projectId, isLocal)
|
|
24
|
+
* Firebase Hosting application unit (webpack bundler).
|
|
25
|
+
* Extends Unit_HostingApp; implements compile via npm run build, launch via npm run start.
|
|
58
26
|
*/
|
|
59
|
-
export declare class Unit_FirebaseHostingApp<C extends Unit_FirebaseHostingApp_Config = Unit_FirebaseHostingApp_Config> extends
|
|
60
|
-
hosting: StringMap;
|
|
61
|
-
injectedMetadata: StringMap;
|
|
27
|
+
export declare class Unit_FirebaseHostingApp<C extends Unit_FirebaseHostingApp_Config = Unit_FirebaseHostingApp_Config> extends Unit_HostingApp<C> {
|
|
62
28
|
static DefaultConfig_FirebaseHosting: {
|
|
63
29
|
servingPort: number;
|
|
64
30
|
output: string;
|
|
65
31
|
};
|
|
66
32
|
constructor(config: Unit_FirebaseHostingApp<C>['config']);
|
|
67
33
|
prepare(): Promise<void>;
|
|
68
|
-
|
|
69
|
-
launch(): Promise<void>;
|
|
70
|
-
releaseWebpackPorts(): Promise<void>;
|
|
71
|
-
deploy(): Promise<void>;
|
|
72
|
-
private getEnvConfig;
|
|
73
|
-
private resolveHostingRC;
|
|
74
|
-
private resolveHostingJSON;
|
|
75
|
-
private resolveHostingRuntimeConfig;
|
|
34
|
+
private resolveWebpackEnvConfig;
|
|
76
35
|
protected compileImpl(): Promise<void>;
|
|
77
|
-
|
|
78
|
-
private runApp;
|
|
79
|
-
/**
|
|
80
|
-
* Builds hosting output and uploads it to Artifact Registry as a generic package.
|
|
81
|
-
*
|
|
82
|
-
* **Process**:
|
|
83
|
-
* 1. Validates hostingDeployment config exists
|
|
84
|
-
* 2. Creates deployment-metadata.json with build information
|
|
85
|
-
* 3. Creates tarball of hosting output directory
|
|
86
|
-
* 4. Uploads to Artifact Registry as generic package
|
|
87
|
-
*
|
|
88
|
-
* **Requirements**:
|
|
89
|
-
* - `--build-push-image <tag>` CLI flag with tag value
|
|
90
|
-
* - `hostingDeployment` config in unit config with `packageName` field
|
|
91
|
-
* - `packageName` must comply with Artifact Registry naming restrictions
|
|
92
|
-
* - gcloud CLI installed and authenticated
|
|
93
|
-
* - Artifact Registry API enabled in GCP project
|
|
94
|
-
*/
|
|
95
|
-
buildPushImage(): Promise<void>;
|
|
96
|
-
/**
|
|
97
|
-
* Deploys hosting build from Artifact Registry to Firebase Hosting.
|
|
98
|
-
*
|
|
99
|
-
* **Process**:
|
|
100
|
-
* 1. Validates hostingDeployment config exists
|
|
101
|
-
* 2. Downloads tarball from Artifact Registry
|
|
102
|
-
* 3. Extracts to temp directory
|
|
103
|
-
* 4. Sets up Firebase tools (package.json + npm install)
|
|
104
|
-
* 5. Deploys to Firebase Hosting
|
|
105
|
-
* 6. Validates deployment by fetching deployment-metadata.json
|
|
106
|
-
*
|
|
107
|
-
* **Requirements**:
|
|
108
|
-
* - `--deploy-image <tag>` CLI flag with tag value
|
|
109
|
-
* - `hostingDeployment` config in unit config with `packageName` field
|
|
110
|
-
* - Package must already exist in Artifact Registry (built via buildPushImage)
|
|
111
|
-
* - gcloud CLI installed and authenticated
|
|
112
|
-
* - Firebase CLI (installed via npm in temp directory)
|
|
113
|
-
*/
|
|
114
|
-
deployImage(): Promise<void>;
|
|
36
|
+
protected runApp(): Promise<void>;
|
|
115
37
|
}
|