@aifabrix/builder 2.1.5 → 2.1.7

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.
@@ -0,0 +1,206 @@
1
+ /**
2
+ * AI Fabrix Builder Secrets Utilities
3
+ *
4
+ * This module provides utility functions for loading and processing secrets.
5
+ * Helper functions for secrets.js module.
6
+ *
7
+ * @fileoverview Secrets utility functions for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const yaml = require('js-yaml');
15
+ const os = require('os');
16
+ const logger = require('./logger');
17
+
18
+ /**
19
+ * Loads secrets from file with cascading lookup support
20
+ * First checks ~/.aifabrix/secrets.local.yaml, then build.secrets from variables.yaml
21
+ *
22
+ * @async
23
+ * @function loadSecretsFromFile
24
+ * @param {string} filePath - Path to secrets file
25
+ * @returns {Promise<Object>} Loaded secrets object or empty object if file doesn't exist
26
+ */
27
+ async function loadSecretsFromFile(filePath) {
28
+ if (!fs.existsSync(filePath)) {
29
+ return {};
30
+ }
31
+
32
+ try {
33
+ const content = fs.readFileSync(filePath, 'utf8');
34
+ const secrets = yaml.load(content);
35
+
36
+ if (!secrets || typeof secrets !== 'object') {
37
+ return {};
38
+ }
39
+
40
+ return secrets;
41
+ } catch (error) {
42
+ logger.warn(`Warning: Could not read secrets file ${filePath}: ${error.message}`);
43
+ return {};
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Loads user secrets from ~/.aifabrix/secrets.local.yaml
49
+ * @function loadUserSecrets
50
+ * @returns {Object} Loaded secrets object or empty object
51
+ */
52
+ function loadUserSecrets() {
53
+ const userSecretsPath = path.join(os.homedir(), '.aifabrix', 'secrets.local.yaml');
54
+ if (!fs.existsSync(userSecretsPath)) {
55
+ return {};
56
+ }
57
+
58
+ try {
59
+ const content = fs.readFileSync(userSecretsPath, 'utf8');
60
+ const secrets = yaml.load(content);
61
+ if (!secrets || typeof secrets !== 'object') {
62
+ throw new Error(`Invalid secrets file format: ${userSecretsPath}`);
63
+ }
64
+ return secrets;
65
+ } catch (error) {
66
+ if (error.message.includes('Invalid secrets file format')) {
67
+ throw error;
68
+ }
69
+ logger.warn(`Warning: Could not read secrets file ${userSecretsPath}: ${error.message}`);
70
+ return {};
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Loads build secrets from variables.yaml and merges with existing secrets
76
+ * @async
77
+ * @function loadBuildSecrets
78
+ * @param {Object} mergedSecrets - Existing secrets to merge with
79
+ * @param {string} appName - Application name
80
+ * @returns {Promise<Object>} Merged secrets object
81
+ */
82
+ async function loadBuildSecrets(mergedSecrets, appName) {
83
+ const variablesPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
84
+ if (!fs.existsSync(variablesPath)) {
85
+ return mergedSecrets;
86
+ }
87
+
88
+ try {
89
+ const variablesContent = fs.readFileSync(variablesPath, 'utf8');
90
+ const variables = yaml.load(variablesContent);
91
+
92
+ if (variables?.build?.secrets) {
93
+ const buildSecretsPath = path.resolve(
94
+ path.dirname(variablesPath),
95
+ variables.build.secrets
96
+ );
97
+
98
+ const buildSecrets = await loadSecretsFromFile(buildSecretsPath);
99
+
100
+ // Merge: user's file takes priority, but use build.secrets for missing/empty values
101
+ for (const [key, value] of Object.entries(buildSecrets)) {
102
+ if (!(key in mergedSecrets) || !mergedSecrets[key] || mergedSecrets[key] === '') {
103
+ mergedSecrets[key] = value;
104
+ }
105
+ }
106
+ }
107
+ } catch (error) {
108
+ logger.warn(`Warning: Could not load build.secrets from variables.yaml: ${error.message}`);
109
+ }
110
+
111
+ return mergedSecrets;
112
+ }
113
+
114
+ /**
115
+ * Loads default secrets from ~/.aifabrix/secrets.yaml
116
+ * @function loadDefaultSecrets
117
+ * @returns {Object} Loaded secrets object or empty object
118
+ */
119
+ function loadDefaultSecrets() {
120
+ const defaultPath = path.join(os.homedir(), '.aifabrix', 'secrets.yaml');
121
+ if (!fs.existsSync(defaultPath)) {
122
+ return {};
123
+ }
124
+
125
+ try {
126
+ const content = fs.readFileSync(defaultPath, 'utf8');
127
+ const secrets = yaml.load(content);
128
+ if (!secrets || typeof secrets !== 'object') {
129
+ throw new Error(`Invalid secrets file format: ${defaultPath}`);
130
+ }
131
+ return secrets;
132
+ } catch (error) {
133
+ if (error.message.includes('Invalid secrets file format')) {
134
+ throw error;
135
+ }
136
+ logger.warn(`Warning: Could not read secrets file ${defaultPath}: ${error.message}`);
137
+ return {};
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Builds a map of hostname to service name from environment config
143
+ * @function buildHostnameToServiceMap
144
+ * @param {Object} dockerHosts - Docker environment hosts configuration
145
+ * @returns {Object} Map of hostname to service name
146
+ */
147
+ function buildHostnameToServiceMap(dockerHosts) {
148
+ const hostnameToService = {};
149
+ for (const [key, hostname] of Object.entries(dockerHosts)) {
150
+ if (key.endsWith('_HOST')) {
151
+ // Use hostname directly as service name (e.g., 'keycloak', 'miso-controller')
152
+ hostnameToService[hostname] = hostname;
153
+ }
154
+ }
155
+ return hostnameToService;
156
+ }
157
+
158
+ /**
159
+ * Resolves port for a single URL by looking up service's variables.yaml
160
+ * @function resolveUrlPort
161
+ * @param {string} protocol - URL protocol (http:// or https://)
162
+ * @param {string} hostname - Service hostname
163
+ * @param {string} port - Current port
164
+ * @param {string} urlPath - URL path and query string
165
+ * @param {Object} hostnameToService - Map of hostname to service name
166
+ * @returns {string} URL with resolved port
167
+ */
168
+ function resolveUrlPort(protocol, hostname, port, urlPath, hostnameToService) {
169
+ const serviceName = hostnameToService[hostname];
170
+ if (!serviceName) {
171
+ // Not a service hostname, keep original
172
+ return `${protocol}${hostname}:${port}${urlPath}`;
173
+ }
174
+
175
+ // Try to load service's variables.yaml
176
+ const serviceVariablesPath = path.join(process.cwd(), 'builder', serviceName, 'variables.yaml');
177
+ if (!fs.existsSync(serviceVariablesPath)) {
178
+ // Service variables.yaml not found, keep original port
179
+ return `${protocol}${hostname}:${port}${urlPath}`;
180
+ }
181
+
182
+ try {
183
+ const variablesContent = fs.readFileSync(serviceVariablesPath, 'utf8');
184
+ const variables = yaml.load(variablesContent);
185
+
186
+ // Get containerPort or fall back to port
187
+ const containerPort = variables?.build?.containerPort || variables?.port || port;
188
+
189
+ // Replace port in URL
190
+ return `${protocol}${hostname}:${containerPort}${urlPath}`;
191
+ } catch (error) {
192
+ // Error loading variables.yaml, keep original port
193
+ logger.warn(`Warning: Could not load variables.yaml for service ${serviceName}: ${error.message}`);
194
+ return `${protocol}${hostname}:${port}${urlPath}`;
195
+ }
196
+ }
197
+
198
+ module.exports = {
199
+ loadSecretsFromFile,
200
+ loadUserSecrets,
201
+ loadBuildSecrets,
202
+ loadDefaultSecrets,
203
+ buildHostnameToServiceMap,
204
+ resolveUrlPort
205
+ };
206
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -21,9 +21,10 @@ RUN if [ -d "/tmp/build-context/themes" ] && [ "$(ls -A /tmp/build-context/theme
21
21
  fi && \
22
22
  rm -rf /tmp/build-context
23
23
 
24
- # Build Keycloak with health checks enabled
24
+ # Build Keycloak with health checks enabled (similar to Keycloak 24.0)
25
25
  # This enables the /health, /health/live, /health/ready, and /health/started endpoints
26
- RUN /opt/keycloak/bin/kc.sh build --health-enabled=true
26
+ # Expose health endpoints on main HTTP port (like 24.0) instead of management port
27
+ RUN /opt/keycloak/bin/kc.sh build --health-enabled=true --http-management-health-enabled=false
27
28
 
28
29
  # Switch back to keycloak user
29
30
  USER keycloak
@@ -15,8 +15,13 @@ KC_HTTP_ENABLED=true
15
15
  # HEALTH CHECK CONFIGURATION
16
16
  # =============================================================================
17
17
  # Enable health check endpoints: /health, /health/live, /health/ready, /health/started
18
+ # Keycloak 26.4.0+: Health endpoints are exposed on main HTTP(S) port (8080) by default
19
+ # Set http-management-health-enabled=false to expose on main port instead of management port
18
20
 
19
21
  KC_HEALTH_ENABLED=true
22
+ # Expose health endpoints on main HTTP port (like Keycloak 24.0)
23
+ # Set to false to expose on main port instead of management port (9000)
24
+ KC_HTTP_MANAGEMENT_HEALTH_ENABLED=false
20
25
 
21
26
  # =============================================================================
22
27
  # DATABASE CONFIGURATION
@@ -25,9 +25,9 @@ requires:
25
25
 
26
26
  # Health Check
27
27
  healthCheck:
28
- path: /health
28
+ path: /health/ready
29
29
  interval: 30
30
- probePath: /health
30
+ probePath: /health/ready
31
31
  probeRequestType: GET
32
32
  probeProtocol: Https
33
33
  probeIntervalInSeconds: 120
@@ -49,4 +49,4 @@ build:
49
49
  # Docker Compose
50
50
  compose:
51
51
  file: docker-compose.yaml
52
- service: keycloak
52
+ service: keycloak
@@ -62,6 +62,9 @@ RUN pnpm exec tsc -p tsconfig.docker.json || true
62
62
  # Copy sensitive-fields.config.json to dist folder
63
63
  RUN mkdir -p dist/src/services/logging && \
64
64
  cp src/services/logging/sensitive-fields.config.json dist/src/services/logging/ || true
65
+ # Copy generated Prisma logs client to dist folder (needed at runtime)
66
+ RUN mkdir -p dist/database/generated && \
67
+ cp -r src/database/generated/logs-client dist/database/generated/ || true
65
68
 
66
69
  # Return to root to prune correctly (needed to keep workspace dependencies)
67
70
  WORKDIR /app
@@ -121,14 +121,14 @@ MISO_CONTROLLER_URL=kv://miso-controller-url
121
121
 
122
122
  # Web Server URL (for OpenAPI documentation server URLs)
123
123
  # Used to generate correct server URLs in OpenAPI spec
124
- WEB_SERVER_URL=kv://miso-web-server-url
124
+ WEB_SERVER_URL=kv://miso-controller-web-server-url
125
125
 
126
126
  # MISO Environment Configuration (miso, dev, tst, pro)
127
127
  MISO_ENVIRONMENT=miso
128
128
 
129
129
  # MISO Application Client Credentials (per application)
130
- MISO_CLIENTID=kv://miso-client-idKeyVault
131
- MISO_CLIENTSECRET=kv://miso-client-secretKeyVault
130
+ MISO_CLIENTID=kv://miso-controller-client-idKeyVault
131
+ MISO_CLIENTSECRET=kv://miso-controller-client-secretKeyVault
132
132
 
133
133
  # =============================================================================
134
134
  # MORI SERVICE CONFIGURATION