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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/BuildAndInstall.d.ts +40 -0
  2. package/BuildAndInstall.js +155 -0
  3. package/build-and-install-v3.d.ts +1 -44
  4. package/build-and-install-v3.js +1 -157
  5. package/build-and-install.js +11 -11
  6. package/config/consts.d.ts +43 -0
  7. package/config/consts.js +42 -0
  8. package/{core → config}/package/consts.d.ts +1 -1
  9. package/{core → config}/types/project-config.d.ts +3 -0
  10. package/core/FilesCache.d.ts +50 -0
  11. package/core/FilesCache.js +76 -0
  12. package/core/Unit_HelpPrinter.d.ts +16 -0
  13. package/core/Unit_HelpPrinter.js +47 -0
  14. package/core/params/params.d.ts +1 -41
  15. package/core/params/params.js +1 -332
  16. package/core/params.d.ts +50 -0
  17. package/core/params.js +441 -0
  18. package/{v3/core → core}/types.d.ts +1 -1
  19. package/{v3/UnitsDependencyMapper → dependencies}/UnitsDependencyMapper.d.ts +21 -1
  20. package/{v3/UnitsDependencyMapper → dependencies}/UnitsDependencyMapper.js +26 -3
  21. package/dependencies/types.d.ts +1 -0
  22. package/dependencies/types.js +1 -0
  23. package/exceptions/PhaseAggregatedException.d.ts +34 -0
  24. package/{core/exceptions → exceptions}/PhaseAggregatedException.js +26 -0
  25. package/exceptions/UnitPhaseException.d.ts +20 -0
  26. package/exceptions/UnitPhaseException.js +21 -0
  27. package/exports/ExportIndexCache.d.ts +25 -0
  28. package/exports/ExportIndexCache.js +115 -0
  29. package/exports/ExportMapper.d.ts +43 -0
  30. package/exports/ExportMapper.js +519 -0
  31. package/exports/IndicesMcpServer.d.ts +22 -0
  32. package/exports/IndicesMcpServer.js +220 -0
  33. package/exports/types.js +3 -0
  34. package/package.json +20 -9
  35. package/phases/PhaseManager.d.ts +130 -0
  36. package/{v3 → phases}/PhaseManager.js +99 -2
  37. package/{v3/phase → phases/definitions}/consts.d.ts +36 -0
  38. package/{v3/phase → phases/definitions}/consts.js +44 -2
  39. package/phases/definitions/types.d.ts +40 -0
  40. package/phases/index.d.ts +2 -0
  41. package/phases/index.js +2 -0
  42. package/run.js +10 -0
  43. package/runtime/RunningStatusHandler.d.ts +104 -0
  44. package/runtime/RunningStatusHandler.js +153 -0
  45. package/runtime/types.d.ts +1 -0
  46. package/runtime/types.js +2 -0
  47. package/{defaults → templates}/consts.d.ts +9 -0
  48. package/{defaults → templates}/consts.js +12 -2
  49. package/templates/firebase/functions/cloudbuild.yaml +17 -0
  50. package/templates/firebase/functions/dockerfile +19 -0
  51. package/templates/firebase/functions/service.yaml +49 -0
  52. package/{v3/units → units/base}/BaseUnit.d.ts +34 -4
  53. package/{v3/units → units/base}/BaseUnit.js +22 -2
  54. package/units/base/ProjectUnit.d.ts +32 -0
  55. package/units/base/ProjectUnit.js +25 -0
  56. package/units/base/types.js +1 -0
  57. package/units/discovery/UnitsMapper.d.ts +69 -0
  58. package/{v3/UnitsMapper → units/discovery}/UnitsMapper.js +50 -2
  59. package/units/discovery/resolvers/UnitMapper_Base.d.ts +65 -0
  60. package/units/discovery/resolvers/UnitMapper_Base.js +46 -0
  61. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_FirebaseFunction.d.ts +5 -3
  62. package/units/discovery/resolvers/UnitMapper_FirebaseFunction.js +105 -0
  63. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_FirebaseHosting.d.ts +3 -2
  64. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_FirebaseHosting.js +14 -10
  65. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_Node.d.ts +1 -1
  66. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_Node.js +2 -2
  67. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_NodeLib.d.ts +24 -1
  68. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_NodeLib.js +24 -1
  69. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_NodeProject.d.ts +22 -1
  70. package/{v3/UnitsMapper → units/discovery}/resolvers/UnitMapper_NodeProject.js +22 -1
  71. package/units/discovery/types.js +1 -0
  72. package/units/implementations/Unit_NodeProject.d.ts +59 -0
  73. package/{v3/units → units/implementations}/Unit_NodeProject.js +65 -5
  74. package/units/implementations/Unit_PackageJson.d.ts +56 -0
  75. package/{v3/units → units/implementations}/Unit_PackageJson.js +39 -3
  76. package/{v3/units → units/implementations}/Unit_TypescriptLib.d.ts +40 -4
  77. package/{v3/units → units/implementations}/Unit_TypescriptLib.js +167 -17
  78. package/units/implementations/firebase/Unit_FirebaseFunctionsApp.d.ts +233 -0
  79. package/units/implementations/firebase/Unit_FirebaseFunctionsApp.js +804 -0
  80. package/units/implementations/firebase/Unit_FirebaseHostingApp.d.ts +113 -0
  81. package/units/implementations/firebase/Unit_FirebaseHostingApp.js +320 -0
  82. package/units/implementations/firebase/common.d.ts +26 -0
  83. package/units/implementations/firebase/common.js +65 -0
  84. package/units/index.d.ts +6 -0
  85. package/units/index.js +6 -0
  86. package/v3/core/Unit_HelpPrinter.d.ts +1 -16
  87. package/v3/core/Unit_HelpPrinter.js +1 -47
  88. package/{v3 → workspace}/Workspace.d.ts +30 -15
  89. package/{v3 → workspace}/Workspace.js +48 -35
  90. package/core/consts.d.ts +0 -13
  91. package/core/consts.js +0 -12
  92. package/core/exceptions/PhaseAggregatedException.d.ts +0 -8
  93. package/core/exceptions/UnitPhaseException.d.ts +0 -5
  94. package/core/exceptions/UnitPhaseException.js +0 -6
  95. package/old/PhaseRunnerDispatcher.d.ts +0 -24
  96. package/old/PhaseRunnerDispatcher.js +0 -32
  97. package/old/runner-dispatchers.d.ts +0 -10
  98. package/old/runner-dispatchers.js +0 -3
  99. package/v3/PhaseManager.d.ts +0 -27
  100. package/v3/RunningStatusHandler.d.ts +0 -18
  101. package/v3/RunningStatusHandler.js +0 -67
  102. package/v3/UnitsMapper/UnitsMapper.d.ts +0 -21
  103. package/v3/UnitsMapper/resolvers/UnitMapper_Base.d.ts +0 -23
  104. package/v3/UnitsMapper/resolvers/UnitMapper_Base.js +0 -16
  105. package/v3/UnitsMapper/resolvers/UnitMapper_FirebaseFunction.js +0 -66
  106. package/v3/core/FilesCache.d.ts +0 -7
  107. package/v3/core/FilesCache.js +0 -33
  108. package/v3/phase/types.d.ts +0 -10
  109. package/v3/units/ProjectUnit.d.ts +0 -18
  110. package/v3/units/ProjectUnit.js +0 -11
  111. package/v3/units/Unit_NodeProject.d.ts +0 -30
  112. package/v3/units/Unit_PackageJson.d.ts +0 -17
  113. package/v3/units/firebase/Unit_FirebaseFunctionsApp.d.ts +0 -64
  114. package/v3/units/firebase/Unit_FirebaseFunctionsApp.js +0 -306
  115. package/v3/units/firebase/Unit_FirebaseHostingApp.d.ts +0 -49
  116. package/v3/units/firebase/Unit_FirebaseHostingApp.js +0 -118
  117. package/v3/units/firebase/common.d.ts +0 -3
  118. package/v3/units/firebase/common.js +0 -13
  119. package/v3/units/index.d.ts +0 -6
  120. package/v3/units/index.js +0 -6
  121. /package/{core → config}/package/consts.js +0 -0
  122. /package/{core → config}/types/configs/firebasejson.d.ts +0 -0
  123. /package/{core → config}/types/configs/firebasejson.js +0 -0
  124. /package/{core → config}/types/configs/firebaserc.d.ts +0 -0
  125. /package/{core → config}/types/configs/firebaserc.js +0 -0
  126. /package/{core → config}/types/configs/index.d.ts +0 -0
  127. /package/{core → config}/types/configs/index.js +0 -0
  128. /package/{core → config}/types/configs/package-json.d.ts +0 -0
  129. /package/{core → config}/types/configs/package-json.js +0 -0
  130. /package/{core → config}/types/core.d.ts +0 -0
  131. /package/{core → config}/types/core.js +0 -0
  132. /package/{core → config}/types/index.d.ts +0 -0
  133. /package/{core → config}/types/index.js +0 -0
  134. /package/{core → config}/types/package/index.d.ts +0 -0
  135. /package/{core → config}/types/package/index.js +0 -0
  136. /package/{core → config}/types/package/package.d.ts +0 -0
  137. /package/{core → config}/types/package/package.js +0 -0
  138. /package/{core → config}/types/package/runtime-package.d.ts +0 -0
  139. /package/{core → config}/types/package/runtime-package.js +0 -0
  140. /package/{core → config}/types/project-config.js +0 -0
  141. /package/{v3/core → core}/types.js +0 -0
  142. /package/{v3/UnitsMapper/types.js → exports/types.d.ts} +0 -0
  143. /package/{v3/phase → phases/definitions}/index.d.ts +0 -0
  144. /package/{v3/phase → phases/definitions}/index.js +0 -0
  145. /package/{v3/phase → phases/definitions}/types.js +0 -0
  146. /package/{v3/units/types.js → run.d.ts} +0 -0
  147. /package/{defaults/backend-proxy → templates/backend/proxy}/proxy._ts +0 -0
  148. /package/{defaults/.firebase_config → templates/firebase/config}/database.rules.json +0 -0
  149. /package/{defaults/.firebase_config → templates/firebase/config}/firestore.indexes.json +0 -0
  150. /package/{defaults/.firebase_config → templates/firebase/config}/firestore.rules +0 -0
  151. /package/{defaults/.firebase_config → templates/firebase/config}/storage.rules +0 -0
  152. /package/{v3/units → units/base}/types.d.ts +0 -0
  153. /package/{v3/UnitsMapper → units/discovery}/resolvers/index.d.ts +0 -0
  154. /package/{v3/UnitsMapper → units/discovery}/resolvers/index.js +0 -0
  155. /package/{v3/UnitsMapper → units/discovery}/types.d.ts +0 -0
@@ -0,0 +1,113 @@
1
+ import { FirebasePackageConfig } from '../../../config/types/index.js';
2
+ import { UnitPhaseImplementor } from '../../../core/types.js';
3
+ import { StringMap, TS_Object, TypedMap } from '@nu-art/ts-common';
4
+ import { UnitConfigJSON_Node } from '../../discovery/resolvers/UnitMapper_Node.js';
5
+ import { Phase_BuildPushImage, Phase_Deploy, Phase_DeployImage, Phase_Launch } from '../../../phases/definitions/index.js';
6
+ import { Unit_TypescriptLib, Unit_TypescriptLib_Config } from '../Unit_TypescriptLib.js';
7
+ export type FirebaseHostingConfig = {
8
+ public: string;
9
+ rewrites: {
10
+ source: string;
11
+ destination: string;
12
+ }[];
13
+ };
14
+ export type FirebaseHosting_EnvConfig = {
15
+ config: TS_Object;
16
+ projectId: string;
17
+ isLocal?: boolean;
18
+ };
19
+ export type UnitConfigJSON_FirebaseHosting = UnitConfigJSON_Node & {
20
+ servingPort?: number;
21
+ hostingConfig?: FirebaseHostingConfig;
22
+ envs: TypedMap<FirebaseHosting_EnvConfig>;
23
+ };
24
+ export type Unit_FirebaseHostingApp_Config = Unit_TypescriptLib_Config & {
25
+ firebaseConfig?: FirebasePackageConfig;
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
+ };
37
+ };
38
+ /**
39
+ * Firebase Hosting application unit.
40
+ *
41
+ * **Key Features**:
42
+ * - Extends Unit_TypescriptLib (compiles TypeScript)
43
+ * - Manages Firebase hosting configuration (firebase.json, .firebaserc)
44
+ * - Supports multiple environments (local, staging, production)
45
+ * - Implements launch and deploy phases
46
+ *
47
+ * **Phases Implemented**:
48
+ * - `prepare()`: Resolves Firebase hosting config files
49
+ * - `compile()`: Compiles TypeScript (without declarations)
50
+ * - `launch()`: Starts Firebase hosting emulator
51
+ * - `deploy()`: Deploys to Firebase hosting
52
+ *
53
+ * **Configuration**:
54
+ * - `servingPort`: Port for local hosting server
55
+ * - `hostingConfig`: Firebase hosting configuration (public folder, rewrites)
56
+ * - `envConfig`: Environment-specific config (projectId, isLocal)
57
+ */
58
+ export declare class Unit_FirebaseHostingApp<C extends Unit_FirebaseHostingApp_Config = Unit_FirebaseHostingApp_Config> extends Unit_TypescriptLib<C> implements UnitPhaseImplementor<[Phase_Launch, Phase_Deploy, Phase_BuildPushImage, Phase_DeployImage]> {
59
+ hosting: StringMap;
60
+ injectedMetadata: StringMap;
61
+ static DefaultConfig_FirebaseHosting: {
62
+ servingPort: number;
63
+ output: string;
64
+ };
65
+ constructor(config: Unit_FirebaseHostingApp<C>['config']);
66
+ prepare(): Promise<void>;
67
+ compile(): Promise<void>;
68
+ launch(): Promise<void>;
69
+ releaseWebpackPorts(): Promise<void>;
70
+ deploy(): Promise<void>;
71
+ private getEnvConfig;
72
+ private resolveHostingRC;
73
+ private resolveHostingJSON;
74
+ private resolveHostingRuntimeConfig;
75
+ protected compileImpl(): Promise<void>;
76
+ private createAppVersionFile;
77
+ private runApp;
78
+ /**
79
+ * Builds hosting output and uploads it to Artifact Registry as a generic package.
80
+ *
81
+ * **Process**:
82
+ * 1. Validates hostingDeployment config exists
83
+ * 2. Creates deployment-metadata.json with build information
84
+ * 3. Creates tarball of hosting output directory
85
+ * 4. Uploads to Artifact Registry as generic package
86
+ *
87
+ * **Requirements**:
88
+ * - `--build-push-image <tag>` CLI flag with tag value
89
+ * - `hostingDeployment` config in unit config
90
+ * - gcloud CLI installed and authenticated
91
+ * - Artifact Registry API enabled in GCP project
92
+ */
93
+ buildPushImage(): Promise<void>;
94
+ /**
95
+ * Deploys hosting build from Artifact Registry to Firebase Hosting.
96
+ *
97
+ * **Process**:
98
+ * 1. Validates hostingDeployment config exists
99
+ * 2. Downloads tarball from Artifact Registry
100
+ * 3. Extracts to temp directory
101
+ * 4. Sets up Firebase tools (package.json + npm install)
102
+ * 5. Deploys to Firebase Hosting
103
+ * 6. Validates deployment by fetching deployment-metadata.json
104
+ *
105
+ * **Requirements**:
106
+ * - `--deploy-image <tag>` CLI flag with tag value
107
+ * - `hostingDeployment` config in unit config
108
+ * - Package must already exist in Artifact Registry (built via buildPushImage)
109
+ * - gcloud CLI installed and authenticated
110
+ * - Firebase CLI (installed via npm in temp directory)
111
+ */
112
+ deployImage(): Promise<void>;
113
+ }
@@ -0,0 +1,320 @@
1
+ import { ImplementationMissingException, LogLevel } from '@nu-art/ts-common';
2
+ import { CONST_DeployHostingDir, CONST_DeploymentMetadata, CONST_FirebaseJSON, CONST_FirebaseRC, CONST_HostingBuildTarball, CONST_StagingDir, CONST_TrashDir, CONST_VersionApp } from '../../../config/consts.js';
3
+ import { Commando_NVM } from '@nu-art/commando/shell/plugins/nvm';
4
+ import { Commando_Basic } from '@nu-art/commando/shell/plugins/basic';
5
+ import { resolve } from 'path';
6
+ import { Unit_TypescriptLib } from '../Unit_TypescriptLib.js';
7
+ import { CommandoException } from '@nu-art/commando/shell/core/CliError';
8
+ import { deployLogFilter, ensureArtifactRegistryRepository } from './common.js';
9
+ import { FileSystemUtils } from '@nu-art/ts-common/utils/FileSystemUtils';
10
+ /**
11
+ * Firebase Hosting application unit.
12
+ *
13
+ * **Key Features**:
14
+ * - Extends Unit_TypescriptLib (compiles TypeScript)
15
+ * - Manages Firebase hosting configuration (firebase.json, .firebaserc)
16
+ * - Supports multiple environments (local, staging, production)
17
+ * - Implements launch and deploy phases
18
+ *
19
+ * **Phases Implemented**:
20
+ * - `prepare()`: Resolves Firebase hosting config files
21
+ * - `compile()`: Compiles TypeScript (without declarations)
22
+ * - `launch()`: Starts Firebase hosting emulator
23
+ * - `deploy()`: Deploys to Firebase hosting
24
+ *
25
+ * **Configuration**:
26
+ * - `servingPort`: Port for local hosting server
27
+ * - `hostingConfig`: Firebase hosting configuration (public folder, rewrites)
28
+ * - `envConfig`: Environment-specific config (projectId, isLocal)
29
+ */
30
+ export class Unit_FirebaseHostingApp extends Unit_TypescriptLib {
31
+ hosting = {};
32
+ injectedMetadata = {};
33
+ static DefaultConfig_FirebaseHosting = {
34
+ servingPort: 8100,
35
+ output: 'dist',
36
+ };
37
+ constructor(config) {
38
+ super(config);
39
+ this.addToClassStack(Unit_FirebaseHostingApp);
40
+ }
41
+ //######################### Phase Implementations #########################
42
+ async prepare() {
43
+ await super.prepare();
44
+ await this.resolveHostingRC();
45
+ await this.resolveHostingJSON();
46
+ await this.resolveHostingRuntimeConfig();
47
+ }
48
+ async compile() {
49
+ await this.resolveTSConfig(resolve(this.config.fullPath, './src'), 'main', { compilerOptions: { declaration: false } });
50
+ await this.clearOutputDir();
51
+ await this.createAppVersionFile();
52
+ await this.compileImpl();
53
+ }
54
+ async launch() {
55
+ this.setStatus('Launching');
56
+ await this.releaseWebpackPorts();
57
+ await this.runApp();
58
+ }
59
+ async releaseWebpackPorts() {
60
+ return this.releasePorts([`${this.config.servingPort}`]);
61
+ }
62
+ async deploy() {
63
+ const commando = this.allocateCommando(Commando_NVM).applyNVM()
64
+ .cd(this.config.fullPath)
65
+ .setLogLevelFilter(deployLogFilter)
66
+ // example: Function URL (hello(us-central1)): https://hello-kv65k7yylq-uc.a.run.app
67
+ .onLog(/.*Hosting URL.*(https:\/\/.*?)$/, match => {
68
+ this.hosting[match[1]] = match[2];
69
+ });
70
+ const debug = this.runtimeContext.runtimeParams.verbose ? ' --debug' : '';
71
+ await this.executeAsyncCommando(commando, `${this.npmCommand('firebase')}${debug} deploy --only hosting`);
72
+ }
73
+ //######################### ResolveConfig Logic #########################
74
+ getEnvConfig() {
75
+ const envConfig = this.config.envConfig;
76
+ if (!envConfig)
77
+ throw new ImplementationMissingException(`Missing EnvConfig in unit ${this.config.label}`);
78
+ return envConfig;
79
+ }
80
+ async resolveHostingRC() {
81
+ const envConfig = this.getEnvConfig();
82
+ const rcConfig = { projects: { default: envConfig.projectId } };
83
+ const targetPath = `${this.config.fullPath}/${CONST_FirebaseRC}`;
84
+ await FileSystemUtils.file.write.json(targetPath, rcConfig);
85
+ }
86
+ async resolveHostingJSON() {
87
+ const envConfig = this.getEnvConfig();
88
+ const targetPath = `${this.config.fullPath}/${CONST_FirebaseJSON}`;
89
+ let fileContent;
90
+ if (envConfig.isLocal)
91
+ fileContent = {};
92
+ else
93
+ fileContent = {
94
+ hosting: this.config.hostingConfig ?? {
95
+ 'public': 'dist',
96
+ 'rewrites': [
97
+ { 'source': '**', 'destination': '/index.html' }
98
+ ]
99
+ }
100
+ };
101
+ await FileSystemUtils.file.write.json(targetPath, fileContent);
102
+ }
103
+ async resolveHostingRuntimeConfig() {
104
+ const envConfig = this.getEnvConfig().config;
105
+ const targetPath = resolve(this.config.fullPath, `./src/main/config.ts`);
106
+ const fileContent = `export const config = ${JSON.stringify(envConfig, null, 2)};`;
107
+ await FileSystemUtils.file.write(targetPath, fileContent);
108
+ }
109
+ //######################### Compile Logic #########################
110
+ async compileImpl() {
111
+ const commando = this.allocateCommando(Commando_NVM, Commando_Basic).applyNVM()
112
+ .cd(this.config.fullPath);
113
+ await this.executeAsyncCommando(commando, `ENV=${this.runtimeContext.runtimeParams.environment} npm run build`, (stdout, stderr, exitCode) => {
114
+ if (exitCode > 0)
115
+ throw new CommandoException(`Error compiling`, stdout, stderr, exitCode);
116
+ });
117
+ }
118
+ async createAppVersionFile() {
119
+ //Writing the file to the package source instead of the output is fine,
120
+ //Webpack bundles files into the output automatically!
121
+ const targetPath = `${this.config.fullPath}/src/main/${CONST_VersionApp}`;
122
+ const appVersion = this.runtimeContext.version;
123
+ await FileSystemUtils.file.write.json(targetPath, { version: appVersion });
124
+ }
125
+ //######################### Launch Logic #########################
126
+ async runApp() {
127
+ const commando = this.allocateCommando(Commando_NVM).applyNVM()
128
+ .setUID(this.config.key)
129
+ .cd(this.config.fullPath)
130
+ .setLogLevelFilter((log, type) => {
131
+ if (log.toLowerCase().includes('<i>'))
132
+ return LogLevel.Info;
133
+ });
134
+ await this.executeAsyncCommando(commando, 'npm run start');
135
+ this.logWarning('HOSTING TERMINATED');
136
+ }
137
+ //######################### Build Push Image Phase #########################
138
+ /**
139
+ * Builds hosting output and uploads it to Artifact Registry as a generic package.
140
+ *
141
+ * **Process**:
142
+ * 1. Validates hostingDeployment config exists
143
+ * 2. Creates deployment-metadata.json with build information
144
+ * 3. Creates tarball of hosting output directory
145
+ * 4. Uploads to Artifact Registry as generic package
146
+ *
147
+ * **Requirements**:
148
+ * - `--build-push-image <tag>` CLI flag with tag value
149
+ * - `hostingDeployment` config in unit config
150
+ * - gcloud CLI installed and authenticated
151
+ * - Artifact Registry API enabled in GCP project
152
+ */
153
+ async buildPushImage() {
154
+ const hostingDeployment = this.config.hostingDeployment;
155
+ if (!hostingDeployment) {
156
+ throw new ImplementationMissingException(`Missing hostingDeployment config in unit ${this.config.key}`);
157
+ }
158
+ const buildTag = this.runtimeContext.runtimeParams.buildPushImage;
159
+ if (!buildTag) {
160
+ throw new ImplementationMissingException(`Missing buildPushImage runtime param in unit ${this.config.key}`);
161
+ }
162
+ const artifactRegistry = hostingDeployment.artifactRegistry;
163
+ const packageName = this.config.packageJson.name;
164
+ this.logInfo(`Building and uploading hosting package to Artifact Registry:`);
165
+ this.logInfo(` Package: ${packageName}`);
166
+ this.logInfo(` Tag: ${buildTag}`);
167
+ // Check for dry run mode
168
+ if (this.runtimeContext.runtimeParams.dryRun) {
169
+ this.logInfo(`[DRY RUN] Would build and upload hosting package: ${packageName}:${buildTag}`);
170
+ return;
171
+ }
172
+ // Ensure Artifact Registry repository exists
173
+ const commando = this.allocateCommando();
174
+ await ensureArtifactRegistryRepository(commando, artifactRegistry, 'generic', this);
175
+ // Create deployment-metadata.json in output directory
176
+ const metadata = {
177
+ ...this.injectedMetadata,
178
+ 'build.timestamp': new Date().toISOString(),
179
+ 'build.tag': buildTag,
180
+ 'build.project': artifactRegistry.projectId,
181
+ 'build.package-name': packageName,
182
+ 'version': this.runtimeContext.version,
183
+ 'git.commit': process.env.GIT_COMMIT || '',
184
+ 'git.branch': process.env.GIT_BRANCH || '',
185
+ 'build.user': process.env.USER || '',
186
+ };
187
+ this.logDebug(`Metadata: `, metadata);
188
+ // Create staging directory for tarball contents
189
+ const buildOutputDir = resolve(this.config.fullPath, CONST_TrashDir);
190
+ const stagingDir = resolve(buildOutputDir, CONST_StagingDir);
191
+ await FileSystemUtils.folder.delete(stagingDir);
192
+ await FileSystemUtils.folder.create(stagingDir);
193
+ const tarballPath = resolve(buildOutputDir, CONST_HostingBuildTarball);
194
+ // Ensure firebase.json and .firebaserc exist (they should from prepare phase)
195
+ await this.resolveHostingRC();
196
+ await this.resolveHostingJSON();
197
+ // Copy all files to staging directory
198
+ const firebaseJsonPath = resolve(this.config.fullPath, CONST_FirebaseJSON);
199
+ const firebaseRcPath = resolve(this.config.fullPath, CONST_FirebaseRC);
200
+ const outputDirName = resolve(this.config.output).split('/').pop() || 'dist';
201
+ // Copy firebase.json, .firebaserc, dist folder, and create deployment-metadata.json
202
+ await FileSystemUtils.file.copy(firebaseJsonPath, resolve(stagingDir, CONST_FirebaseJSON));
203
+ await FileSystemUtils.file.copy(firebaseRcPath, resolve(stagingDir, CONST_FirebaseRC));
204
+ await FileSystemUtils.folder.copy(this.config.output, resolve(stagingDir, outputDirName));
205
+ await FileSystemUtils.file.write.json(resolve(stagingDir, CONST_DeploymentMetadata), metadata);
206
+ // Create tarball from staging directory contents
207
+ // Note: Use explicit file list to include hidden files (.*) which * wildcard doesn't match
208
+ commando.cd(stagingDir);
209
+ await this.executeAsyncCommando(commando, `tar -czf ${tarballPath} ${CONST_FirebaseJSON} ${CONST_FirebaseRC} ${CONST_DeploymentMetadata} ${outputDirName}`, (stdout, stderr, exitCode) => {
210
+ if (exitCode !== 0)
211
+ throw new CommandoException(`Failed to create tarball (exit code ${exitCode})`, stdout, stderr, exitCode);
212
+ });
213
+ this.logInfo(`Created tarball: ${tarballPath}`);
214
+ // Upload to Artifact Registry
215
+ const region = artifactRegistry.region;
216
+ const repository = artifactRegistry.repository;
217
+ const projectId = artifactRegistry.projectId;
218
+ // Upload file - package and version are created automatically if they don't exist
219
+ // Note: Generic packages don't support "latest" version like Docker images
220
+ // The build tag should be used for deployment (defaults to 'latest' if not specified)
221
+ await this.executeAsyncCommando(commando, `gcloud artifacts generic upload --package=${packageName} --version=${buildTag} --source=${tarballPath} --location=${region} --repository=${repository} --project=${projectId}`, (stdout, stderr, exitCode) => {
222
+ if (exitCode !== 0)
223
+ throw new CommandoException(`Failed to upload hosting package to Artifact Registry (exit code ${exitCode})`, stdout, stderr, exitCode);
224
+ });
225
+ this.logInfo(`Successfully uploaded hosting package: ${packageName}:${buildTag} and ${packageName}:latest`);
226
+ }
227
+ //######################### Deploy Image Phase #########################
228
+ /**
229
+ * Deploys hosting build from Artifact Registry to Firebase Hosting.
230
+ *
231
+ * **Process**:
232
+ * 1. Validates hostingDeployment config exists
233
+ * 2. Downloads tarball from Artifact Registry
234
+ * 3. Extracts to temp directory
235
+ * 4. Sets up Firebase tools (package.json + npm install)
236
+ * 5. Deploys to Firebase Hosting
237
+ * 6. Validates deployment by fetching deployment-metadata.json
238
+ *
239
+ * **Requirements**:
240
+ * - `--deploy-image <tag>` CLI flag with tag value
241
+ * - `hostingDeployment` config in unit config
242
+ * - Package must already exist in Artifact Registry (built via buildPushImage)
243
+ * - gcloud CLI installed and authenticated
244
+ * - Firebase CLI (installed via npm in temp directory)
245
+ */
246
+ async deployImage() {
247
+ const hostingDeployment = this.config.hostingDeployment;
248
+ if (!hostingDeployment) {
249
+ throw new ImplementationMissingException(`Missing hostingDeployment config in unit ${this.config.key}`);
250
+ }
251
+ const deployTag = this.runtimeContext.runtimeParams.deployImage;
252
+ if (!deployTag) {
253
+ throw new ImplementationMissingException(`Missing deployImage runtime param in unit ${this.config.key}. Generic packages require a specific version tag (cannot use 'latest')`);
254
+ }
255
+ const artifactRegistry = hostingDeployment.artifactRegistry;
256
+ const packageName = this.config.packageJson.name;
257
+ const region = artifactRegistry.region;
258
+ const repository = artifactRegistry.repository;
259
+ const projectId = artifactRegistry.projectId;
260
+ this.logInfo(`Deploying hosting package: ${packageName}:${deployTag}`);
261
+ // Check for dry run mode
262
+ if (this.runtimeContext.runtimeParams.dryRun) {
263
+ this.logInfo(`[DRY RUN] Would deploy hosting package: ${packageName}:${deployTag}`);
264
+ return;
265
+ }
266
+ // Setup temp directory for deployment
267
+ const deployTempDir = resolve(this.config.fullPath, `${CONST_TrashDir}/${CONST_DeployHostingDir}`);
268
+ this.logInfo(`Setting up deployment directory: ${deployTempDir}`);
269
+ await FileSystemUtils.folder.delete(deployTempDir);
270
+ await FileSystemUtils.folder.create(deployTempDir);
271
+ const commando = this.allocateCommando(Commando_NVM)
272
+ .applyNVM()
273
+ .cd(deployTempDir)
274
+ .mark();
275
+ // Download tarball from Artifact Registry
276
+ // Note: --destination must be a directory, not a file path
277
+ this.logInfo(`Downloading hosting package from Artifact Registry from: ${projectId}/${repository}/${packageName}/${deployTag}`);
278
+ await this.executeAsyncCommando(commando, `gcloud artifacts generic download --package=${packageName} --version=${deployTag} --destination=${deployTempDir} --location=${region} --repository=${repository} --project=${projectId}`, (stdout, stderr, exitCode) => {
279
+ if (exitCode !== 0)
280
+ throw new CommandoException(`Failed to download hosting package from Artifact Registry (exit code ${exitCode})`, stdout, stderr, exitCode);
281
+ });
282
+ // Find the downloaded file (gcloud downloads with auto-generated name based on package and version)
283
+ // The file will be named: {packageName}-{version}.tar.gz
284
+ this.logInfo(`Locating downloaded tarball...`);
285
+ const downloadedFiles = await FileSystemUtils.folder.list(deployTempDir);
286
+ const tarballFile = downloadedFiles.find(file => file.endsWith('.tar.gz'));
287
+ if (!tarballFile) {
288
+ throw new ImplementationMissingException(`Downloaded tarball not found in ${deployTempDir}. Files found: ${downloadedFiles.join(', ')}`);
289
+ }
290
+ const tarballPath = resolve(deployTempDir, tarballFile);
291
+ this.logDebug(`Downloaded tarball: ${tarballPath}`);
292
+ // Extract tarball directly to deployTempDir (contains firebase.json, .firebaserc, and dist/)
293
+ this.logInfo(`Extracting hosting package...`);
294
+ await this.executeAsyncCommando(commando, `tar -xzf ${tarballPath} -C ${deployTempDir}`, (stdout, stderr, exitCode) => {
295
+ if (exitCode !== 0) {
296
+ throw new CommandoException(`Failed to extract tarball (exit code ${exitCode})`, stdout, stderr, exitCode);
297
+ }
298
+ });
299
+ this.logInfo(`Extracted hosting package to: ${deployTempDir}`);
300
+ // firebase.json and .firebaserc are already in deployTempDir from tarball extraction
301
+ // Deploy using firebase CLI
302
+ const envConfig = this.getEnvConfig();
303
+ this.logInfo(`Deploying to Firebase Hosting: ${deployTempDir} => ${envConfig.projectId}`);
304
+ const deployCommando = this.allocateCommando(Commando_NVM).applyNVM()
305
+ .cd(deployTempDir)
306
+ .setLogLevelFilter(deployLogFilter)
307
+ .onLog(/.*Hosting URL.*(https:\/\/.*?)$/, match => {
308
+ this.hosting[match[1]] = match[2];
309
+ });
310
+ const debug = this.runtimeContext.runtimeParams.verbose ? ' --debug' : '';
311
+ await this.executeAsyncCommando(deployCommando, `npx firebase${debug} deploy --only hosting`, (stdout, stderr, exitCode) => {
312
+ if (exitCode !== 0) {
313
+ throw new CommandoException(`Failed to deploy hosting (exit code ${exitCode})`, stdout, stderr, exitCode);
314
+ }
315
+ });
316
+ this.logInfo(`Hosting deployed: `, this.hosting);
317
+ // Cleanup temp directory (optional - keep for debugging)
318
+ // await FileSystemUtils.folder.delete(deployTempDir);
319
+ }
320
+ }
@@ -0,0 +1,26 @@
1
+ import { LogTypes } from '@nu-art/commando/shell/types';
2
+ import { Logger, LogLevel } from '@nu-art/ts-common';
3
+ import { CommandoInteractive } from '@nu-art/commando/shell/index';
4
+ export declare const deployLogFilter: (log: string, std: LogTypes) => LogLevel.Debug | LogLevel.Info | LogLevel.Warning | LogLevel.Error;
5
+ /**
6
+ * Ensures an Artifact Registry repository exists, creating it if it doesn't.
7
+ *
8
+ * **Process**:
9
+ * 1. Attempts to describe the repository to check if it exists
10
+ * 2. If repository doesn't exist (NOT_FOUND), creates it with the specified format
11
+ * 3. Ignores "already exists" errors during creation
12
+ *
13
+ * **Repository Formats**:
14
+ * - `'docker'`: For Docker container images (uses `-docker.pkg.dev` domain)
15
+ * - `'generic'`: For generic packages like tarballs (uses `-pkg.dev` domain)
16
+ *
17
+ * @param commando - CommandoInteractive instance for executing gcloud commands
18
+ * @param artifactRegistry - Artifact Registry configuration
19
+ * @param repositoryFormat - Repository format ('docker' or 'generic')
20
+ * @param logger - Logger instance for logging messages
21
+ */
22
+ export declare function ensureArtifactRegistryRepository(commando: CommandoInteractive, artifactRegistry: {
23
+ region: string;
24
+ repository: string;
25
+ projectId: string;
26
+ }, repositoryFormat: 'docker' | 'generic', logger: Logger): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { LogLevel } from '@nu-art/ts-common';
2
+ import { CommandoException } from '@nu-art/commando/shell/core/CliError';
3
+ const warn = ['missing required API'];
4
+ const info = ['=== Deploying', 'functions: Successfully deployed function'];
5
+ const infoStartsWith = ['✔ ', 'i '];
6
+ export const deployLogFilter = (log, std) => {
7
+ if (log.startsWith('⚠ ') || warn.find(str => log.includes(str)))
8
+ return LogLevel.Warning;
9
+ if (infoStartsWith.find(str => log.startsWith(str)) || info.find(str => log.includes(str)))
10
+ return LogLevel.Info;
11
+ if (log.includes('Error:'))
12
+ return LogLevel.Error;
13
+ return LogLevel.Debug;
14
+ };
15
+ /**
16
+ * Ensures an Artifact Registry repository exists, creating it if it doesn't.
17
+ *
18
+ * **Process**:
19
+ * 1. Attempts to describe the repository to check if it exists
20
+ * 2. If repository doesn't exist (NOT_FOUND), creates it with the specified format
21
+ * 3. Ignores "already exists" errors during creation
22
+ *
23
+ * **Repository Formats**:
24
+ * - `'docker'`: For Docker container images (uses `-docker.pkg.dev` domain)
25
+ * - `'generic'`: For generic packages like tarballs (uses `-pkg.dev` domain)
26
+ *
27
+ * @param commando - CommandoInteractive instance for executing gcloud commands
28
+ * @param artifactRegistry - Artifact Registry configuration
29
+ * @param repositoryFormat - Repository format ('docker' or 'generic')
30
+ * @param logger - Logger instance for logging messages
31
+ */
32
+ export async function ensureArtifactRegistryRepository(commando, artifactRegistry, repositoryFormat, logger) {
33
+ const { region, repository, projectId } = artifactRegistry;
34
+ // Check if repository exists
35
+ logger.logDebug(`Checking if Artifact Registry repository exists: ${repository} in ${region}`);
36
+ const repositoryExists = await commando
37
+ .append(`gcloud artifacts repositories describe ${repository} --location=${region} --project=${projectId}`)
38
+ .execute((stdout, stderr, exitCode) => {
39
+ if (exitCode === 0)
40
+ return true;
41
+ // Check if error is NOT_FOUND
42
+ if (stderr.includes('NOT_FOUND') || stderr.includes('not found'))
43
+ return false;
44
+ // Other errors should be thrown
45
+ throw new CommandoException(`Failed to check repository existence (exit code ${exitCode})`, stdout, stderr, exitCode);
46
+ });
47
+ if (repositoryExists)
48
+ return logger.logDebug(`Repository ${repository} already exists`);
49
+ // Repository doesn't exist, create it
50
+ logger.logInfo(`Creating Artifact Registry repository: ${repository} (format: ${repositoryFormat})`);
51
+ await commando
52
+ .append(`gcloud artifacts repositories create ${repository} --repository-format=${repositoryFormat} --location=${region} --project=${projectId} --description="Repository for ${repositoryFormat} artifacts"`)
53
+ .execute((stdout, stderr, exitCode) => {
54
+ if (exitCode === 0) {
55
+ logger.logInfo(`Successfully created repository: ${repository}`);
56
+ return;
57
+ }
58
+ // Check if error is "already exists" (might have been created between check and create)
59
+ if (stderr.includes('already exists') || stderr.includes('ALREADY_EXISTS')) {
60
+ logger.logDebug(`Repository ${repository} already exists (created concurrently)`);
61
+ return;
62
+ }
63
+ throw new CommandoException(`Failed to create repository (exit code ${exitCode})`, stdout, stderr, exitCode);
64
+ });
65
+ }
@@ -0,0 +1,6 @@
1
+ export * from './base/BaseUnit.js';
2
+ export * from './base/ProjectUnit.js';
3
+ export * from './implementations/Unit_TypescriptLib.js';
4
+ export * from './implementations/Unit_PackageJson.js';
5
+ export * from './implementations/Unit_NodeProject.js';
6
+ export * from './base/types.js';
package/units/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './base/BaseUnit.js';
2
+ export * from './base/ProjectUnit.js';
3
+ export * from './implementations/Unit_TypescriptLib.js';
4
+ export * from './implementations/Unit_PackageJson.js';
5
+ export * from './implementations/Unit_NodeProject.js';
6
+ export * from './base/types.js';
@@ -1,16 +1 @@
1
- import { BaseCliParam, CliParams } from '@nu-art/commando/cli-params/types';
2
- import { UnitPhaseImplementor } from './types.js';
3
- import { Phase } from '../phase/index.js';
4
- import { ProjectUnit } from '../units/index.js';
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<"buildTree", boolean> | BaseCliParam<"includeApps", string[]> | BaseCliParam<"toESM", boolean> | BaseCliParam<"simulation", boolean> | BaseCliParam<"checkCyclicImports", boolean> | BaseCliParam<"help", boolean>)[];
7
- export type Help_BaiParams = CliParams<typeof AllBaiParams_Help>;
8
- export type Phase_Help = typeof phase_Help;
9
- export declare const phaseKey_Help = "help";
10
- export declare const phase_Help: Phase<'printHelp'>;
11
- declare class _Unit_HelpPrinter extends ProjectUnit implements UnitPhaseImplementor<[Phase_Help]> {
12
- constructor();
13
- printHelp(): Promise<void>;
14
- }
15
- export declare const Unit_HelpPrinter: _Unit_HelpPrinter;
16
- export {};
1
+ export * from '../../core/Unit_HelpPrinter.js';
@@ -1,47 +1 @@
1
- import { _keys, LogLevel, reduceToMap } from '@nu-art/ts-common';
2
- import { AllBaiParams, BaiParam_AllUnits, BaiParam_NoBuild, BaiParam_Prepare } from '../../core/params/params.js';
3
- import { ProjectUnit } from '../units/index.js';
4
- export const BaiParam_Help = {
5
- keys: ['--help', '-h'],
6
- keyName: 'help',
7
- type: 'boolean',
8
- group: 'General',
9
- description: 'This help menu',
10
- dependencies: [{ param: BaiParam_NoBuild, value: true }, { param: BaiParam_Prepare, value: false }, { param: BaiParam_AllUnits, value: true }],
11
- };
12
- const AllBaiParams_Help = [...AllBaiParams, BaiParam_Help];
13
- export const phaseKey_Help = 'help';
14
- export const phase_Help = {
15
- key: phaseKey_Help,
16
- name: 'Help',
17
- method: 'printHelp',
18
- filter: (params) => params.help,
19
- };
20
- const config = { key: 'help-printer', fullPath: process.cwd(), relativePath: '.', dependencies: {}, label: 'Help Printer' };
21
- class _Unit_HelpPrinter extends ProjectUnit {
22
- constructor() {
23
- super(config);
24
- this.setMinLevel(LogLevel.Verbose);
25
- }
26
- async printHelp() {
27
- this.logInfo('Build and install parameters:');
28
- const noGroupConst = 'No Group';
29
- //Resolve all params by group
30
- const paramsByGroup = reduceToMap(AllBaiParams, param => param.group ?? noGroupConst, (item, index, mapper) => {
31
- mapper[item.group ?? noGroupConst] = [...mapper[item.group ?? noGroupConst] ?? [], item];
32
- return mapper[item.group ?? noGroupConst];
33
- });
34
- _keys(paramsByGroup).map(paramGroup => {
35
- this.logErrorBold(`${paramGroup}: \n`);
36
- // commando.append(`echo "${paramGroup}:" \n`);
37
- paramsByGroup[paramGroup].map(param => {
38
- const paramKeys = param.keys.join(' | ');
39
- const paramDescription = param.description.trim().split('\n').join('\n\t\t');
40
- this.logDebug(`${paramKeys}`);
41
- this.logVerbose(`\t${paramDescription}\n`);
42
- // commando.append(`echo "\n ${param.keys.join(' | ')} \n \t\t${param.description.trim().split('\n').join('\n\t\t')} \n"`);
43
- });
44
- });
45
- }
46
- }
47
- export const Unit_HelpPrinter = new _Unit_HelpPrinter();
1
+ export * from '../../core/Unit_HelpPrinter.js';