@aifabrix/builder 2.39.1 → 2.39.3
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/README.md +1 -1
- package/lib/app/readme.js +50 -8
- package/lib/app/register.js +2 -2
- package/lib/cli/setup-app.js +4 -1
- package/lib/commands/app-logs.js +157 -10
- package/lib/commands/up-common.js +4 -0
- package/lib/external-system/generator.js +14 -3
- package/lib/schema/deployment-rules.yaml +0 -4
- package/lib/schema/external-datasource.schema.json +1593 -1594
- package/lib/schema/external-system.schema.json +420 -420
- package/lib/utils/app-register-display.js +22 -3
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +8 -8
- package/templates/applications/dataplane/env.template +3 -1
- package/templates/applications/dataplane/variables.yaml +1 -1
- package/templates/applications/keycloak/variables.yaml +1 -1
- package/templates/applications/miso-controller/env.template +14 -0
- package/templates/applications/miso-controller/variables.yaml +1 -1
- package/templates/external-system/external-system.json.hbs +20 -7
- package/templates/applications/dataplane/README.md +0 -219
- package/templates/applications/keycloak/README.md +0 -203
- package/templates/applications/miso-controller/README.md +0 -367
- package/templates/external-system/deploy.ps1.hbs +0 -34
- package/templates/external-system/deploy.sh.hbs +0 -34
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ All guides and references are listed in **[docs/README.md](docs/README.md)** (ta
|
|
|
103
103
|
|
|
104
104
|
- [CLI Reference](docs/cli-reference.md) – All commands
|
|
105
105
|
- [Infrastructure](docs/infrastructure.md) – What runs and why
|
|
106
|
-
- [Configuration](docs/configuration.md) – Config files
|
|
106
|
+
- [Configuration reference](docs/configuration/README.md) – Config files (deployment key, variables.yaml, env.template, secrets)
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
package/lib/app/readme.js
CHANGED
|
@@ -12,6 +12,7 @@ const fs = require('fs').promises;
|
|
|
12
12
|
const fsSync = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const handlebars = require('handlebars');
|
|
15
|
+
const yaml = require('js-yaml');
|
|
15
16
|
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -158,28 +159,69 @@ function generateReadmeMd(appName, config) {
|
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
/**
|
|
161
|
-
* Generates README.md file
|
|
162
|
+
* Generates README.md file (optionally only if missing)
|
|
162
163
|
* @async
|
|
163
164
|
* @function generateReadmeMdFile
|
|
164
165
|
* @param {string} appPath - Path to application directory
|
|
165
166
|
* @param {string} appName - Application name
|
|
166
|
-
* @param {Object} config - Application configuration
|
|
167
|
+
* @param {Object} config - Application configuration (e.g. from variables.yaml)
|
|
168
|
+
* @param {Object} [options] - Options
|
|
169
|
+
* @param {boolean} [options.force] - If true, overwrite existing README.md (dynamic generation)
|
|
167
170
|
* @returns {Promise<void>} Resolves when README.md is generated or skipped
|
|
168
171
|
* @throws {Error} If file generation fails
|
|
169
172
|
*/
|
|
170
|
-
async function generateReadmeMdFile(appPath, appName, config) {
|
|
171
|
-
// Ensure directory exists
|
|
173
|
+
async function generateReadmeMdFile(appPath, appName, config, options = {}) {
|
|
172
174
|
await fs.mkdir(appPath, { recursive: true });
|
|
173
175
|
const readmePath = path.join(appPath, 'README.md');
|
|
174
|
-
if (!(await fileExists(readmePath))) {
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
if (!options.force && (await fileExists(readmePath))) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const readmeContent = generateReadmeMd(appName, config);
|
|
180
|
+
await fs.writeFile(readmePath, readmeContent, 'utf8');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Loads variables.yaml from app path and generates README.md (overwrites if present).
|
|
185
|
+
* Used when copying template apps or running up-miso / up-platform / up-dataplane.
|
|
186
|
+
* @async
|
|
187
|
+
* @function ensureReadmeForAppPath
|
|
188
|
+
* @param {string} appPath - Path to application directory (must contain variables.yaml)
|
|
189
|
+
* @param {string} appName - Application name
|
|
190
|
+
* @returns {Promise<void>} Resolves when README.md is written or skipped (no variables.yaml)
|
|
191
|
+
*/
|
|
192
|
+
async function ensureReadmeForAppPath(appPath, appName) {
|
|
193
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
194
|
+
if (!(await fileExists(variablesPath))) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const content = await fs.readFile(variablesPath, 'utf8');
|
|
198
|
+
const config = yaml.load(content) || {};
|
|
199
|
+
await generateReadmeMdFile(appPath, appName, config, { force: true });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Generates README.md for an app at builder path(s): primary and cwd/builder if different.
|
|
204
|
+
* Use after ensureAppFromTemplate in up-miso / up-dataplane so README reflects current config.
|
|
205
|
+
* @async
|
|
206
|
+
* @function ensureReadmeForApp
|
|
207
|
+
* @param {string} appName - Application name (e.g. keycloak, miso-controller, dataplane)
|
|
208
|
+
* @returns {Promise<void>}
|
|
209
|
+
*/
|
|
210
|
+
async function ensureReadmeForApp(appName) {
|
|
211
|
+
const pathsUtil = require('../utils/paths');
|
|
212
|
+
const primaryPath = pathsUtil.getBuilderPath(appName);
|
|
213
|
+
await ensureReadmeForAppPath(primaryPath, appName);
|
|
214
|
+
const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
|
|
215
|
+
if (path.resolve(cwdBuilderPath) !== path.resolve(primaryPath)) {
|
|
216
|
+
await ensureReadmeForAppPath(cwdBuilderPath, appName);
|
|
177
217
|
}
|
|
178
218
|
}
|
|
179
219
|
|
|
180
220
|
module.exports = {
|
|
181
221
|
generateReadmeMdFile,
|
|
182
222
|
generateReadmeMd,
|
|
183
|
-
formatAppDisplayName
|
|
223
|
+
formatAppDisplayName,
|
|
224
|
+
ensureReadmeForAppPath,
|
|
225
|
+
ensureReadmeForApp
|
|
184
226
|
};
|
|
185
227
|
|
package/lib/app/register.js
CHANGED
|
@@ -177,9 +177,9 @@ async function registerApplication(appKey, options = {}) {
|
|
|
177
177
|
registrationData
|
|
178
178
|
);
|
|
179
179
|
|
|
180
|
-
// Save credentials and display results
|
|
180
|
+
// Save credentials and display results (pass display name we sent so output shows it when API returns key as displayName)
|
|
181
181
|
await saveLocalCredentials(responseData, authConfig.apiUrl);
|
|
182
|
-
displayRegistrationResults(responseData, authConfig.apiUrl, environment);
|
|
182
|
+
displayRegistrationResults(responseData, authConfig.apiUrl, environment, registrationData.displayName);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
module.exports = { registerApplication, getEnvironmentPrefix };
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -187,13 +187,16 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
|
|
|
187
187
|
.description('Show application container logs (and optional env summary with secrets masked)')
|
|
188
188
|
.option('-f', 'Follow log stream')
|
|
189
189
|
.option('-t, --tail <lines>', 'Number of lines (default: 100); 0 = full list', '100')
|
|
190
|
+
.option('-l, --level <level>', 'Show only logs at this level or above (debug|info|warn|error)')
|
|
190
191
|
.action(async(appName, options) => {
|
|
191
192
|
try {
|
|
192
193
|
const { runAppLogs } = require('../commands/app-logs');
|
|
193
194
|
const tailNum = parseInt(options.tail, 10);
|
|
195
|
+
const level = options.level !== undefined && options.level !== null && options.level !== '' ? String(options.level).trim() : undefined;
|
|
194
196
|
await runAppLogs(appName, {
|
|
195
197
|
follow: options.f,
|
|
196
|
-
tail: Number.isNaN(tailNum) ? 100 : tailNum
|
|
198
|
+
tail: Number.isNaN(tailNum) ? 100 : tailNum,
|
|
199
|
+
level
|
|
197
200
|
});
|
|
198
201
|
} catch (error) {
|
|
199
202
|
handleCommandError(error, 'logs');
|
package/lib/commands/app-logs.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const chalk = require('chalk');
|
|
10
10
|
const { exec, spawn } = require('child_process');
|
|
11
|
+
const readline = require('readline');
|
|
11
12
|
const { promisify } = require('util');
|
|
12
13
|
const logger = require('../utils/logger');
|
|
13
14
|
const config = require('../core/config');
|
|
@@ -19,6 +20,24 @@ const execAsync = promisify(exec);
|
|
|
19
20
|
/** Default number of log lines */
|
|
20
21
|
const DEFAULT_TAIL_LINES = 100;
|
|
21
22
|
|
|
23
|
+
/** Allowed log levels for --level filter (lowest to highest severity) */
|
|
24
|
+
const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
|
|
25
|
+
|
|
26
|
+
/** Severity rank: higher = more severe. Used for "show this level and above". */
|
|
27
|
+
const LEVEL_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
28
|
+
|
|
29
|
+
/** Prefix pattern: INFO:, ERROR:, WARN:, WARNING:, DEBUG: at start of line */
|
|
30
|
+
const LEVEL_PREFIX_REGEX = /^(DEBUG|INFO|WARN|WARNING|ERROR)\s*[:-\s]/i;
|
|
31
|
+
|
|
32
|
+
/** Level after timestamp or space (e.g. "2026-02-11 08:51:01 error: msg" or " error: msg") */
|
|
33
|
+
const LEVEL_AFTER_PREFIX_REGEX = /(?:^|\s)(DEBUG|INFO|WARN|WARNING|ERROR)\s*:/i;
|
|
34
|
+
|
|
35
|
+
/** Level after word boundary (e.g. "[pino]error: msg" or BOM + "error:") so "error:" is detected anywhere */
|
|
36
|
+
const LEVEL_WORD_BOUNDARY_REGEX = /\b(DEBUG|INFO|WARN|WARNING|ERROR)\s*:/i;
|
|
37
|
+
|
|
38
|
+
/** JSON "level" field pattern */
|
|
39
|
+
const LEVEL_JSON_REGEX = /"level"\s*:\s*"(\w+)"/i;
|
|
40
|
+
|
|
22
41
|
/** Env key patterns that indicate a secret (mask value) */
|
|
23
42
|
const SECRET_KEY_PATTERN = /password|secret|token|credential|api[_-]?key/i;
|
|
24
43
|
|
|
@@ -50,6 +69,53 @@ function maskEnvLine(line) {
|
|
|
50
69
|
return line;
|
|
51
70
|
}
|
|
52
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Extract log level from a line (prefix like INFO:/error: or JSON "level":"info").
|
|
74
|
+
* Supports: INFO:, ERROR:, error:, info: (miso-controller/pino), WARN:, DEBUG:, and "level":"x" in JSON.
|
|
75
|
+
* @param {string} line - Single log line
|
|
76
|
+
* @returns {string|null} One of 'debug'|'info'|'warn'|'error', or null if not parseable
|
|
77
|
+
*/
|
|
78
|
+
function getLogLevel(line) {
|
|
79
|
+
if (!line || typeof line !== 'string') return null;
|
|
80
|
+
const prefixMatch = line.match(LEVEL_PREFIX_REGEX);
|
|
81
|
+
if (prefixMatch) {
|
|
82
|
+
const raw = prefixMatch[1].toLowerCase();
|
|
83
|
+
return raw === 'warning' ? 'warn' : raw;
|
|
84
|
+
}
|
|
85
|
+
const afterPrefixMatch = line.match(LEVEL_AFTER_PREFIX_REGEX);
|
|
86
|
+
if (afterPrefixMatch) {
|
|
87
|
+
const raw = afterPrefixMatch[1].toLowerCase();
|
|
88
|
+
return raw === 'warning' ? 'warn' : raw;
|
|
89
|
+
}
|
|
90
|
+
const wordBoundaryMatch = line.match(LEVEL_WORD_BOUNDARY_REGEX);
|
|
91
|
+
if (wordBoundaryMatch) {
|
|
92
|
+
const raw = wordBoundaryMatch[1].toLowerCase();
|
|
93
|
+
return raw === 'warning' ? 'warn' : raw;
|
|
94
|
+
}
|
|
95
|
+
const jsonMatch = line.match(LEVEL_JSON_REGEX);
|
|
96
|
+
if (jsonMatch) {
|
|
97
|
+
const raw = jsonMatch[1].toLowerCase();
|
|
98
|
+
return raw === 'warning' ? 'warn' : raw;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Whether a line's level passes the minimum level filter (show this level and above).
|
|
105
|
+
* @param {string|null} lineLevel - Level from getLogLevel (null treated as 'info')
|
|
106
|
+
* @param {string|undefined|null} minLevel - Minimum level (e.g. 'error', 'info')
|
|
107
|
+
* @returns {boolean} True if line should be shown
|
|
108
|
+
*/
|
|
109
|
+
function passesLevelFilter(lineLevel, minLevel) {
|
|
110
|
+
if (minLevel === null || minLevel === undefined || minLevel === '') return true;
|
|
111
|
+
const normalized = (minLevel || '').toString().trim().toLowerCase();
|
|
112
|
+
if (!LOG_LEVELS.includes(normalized)) return true;
|
|
113
|
+
const lineRank =
|
|
114
|
+
lineLevel === null || lineLevel === undefined ? LEVEL_RANK.info : (LEVEL_RANK[lineLevel] ?? LEVEL_RANK.info);
|
|
115
|
+
const minRank = LEVEL_RANK[normalized];
|
|
116
|
+
return lineRank >= minRank;
|
|
117
|
+
}
|
|
118
|
+
|
|
53
119
|
/**
|
|
54
120
|
* Dump container env (masked) and print to logger
|
|
55
121
|
* @async
|
|
@@ -75,33 +141,102 @@ async function dumpMaskedEnv(containerName) {
|
|
|
75
141
|
}
|
|
76
142
|
|
|
77
143
|
/**
|
|
78
|
-
* Run docker logs (non-follow): tail N lines or full (tail 0)
|
|
144
|
+
* Run docker logs (non-follow): tail N lines or full (tail 0). If options.level is set, stdout is piped and filtered.
|
|
79
145
|
* @async
|
|
80
146
|
* @param {string} containerName - Docker container name
|
|
81
|
-
* @param {Object} options - { tail: number
|
|
147
|
+
* @param {Object} options - { tail: number, level?: string }
|
|
82
148
|
* @returns {Promise<void>}
|
|
83
149
|
*/
|
|
84
150
|
async function runDockerLogs(containerName, options) {
|
|
85
151
|
const args = options.tail === 0 ? ['logs', containerName] : ['logs', '--tail', String(options.tail), containerName];
|
|
152
|
+
const minLevel =
|
|
153
|
+
options.level !== undefined && options.level !== null && options.level !== ''
|
|
154
|
+
? String(options.level).trim().toLowerCase()
|
|
155
|
+
: null;
|
|
156
|
+
|
|
157
|
+
if (minLevel === null || minLevel === undefined || !LOG_LEVELS.includes(minLevel)) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const proc = spawn('docker', args, { stdio: 'inherit' });
|
|
160
|
+
proc.on('error', reject);
|
|
161
|
+
proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`docker logs exited with ${code}`))));
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
86
165
|
return new Promise((resolve, reject) => {
|
|
87
|
-
const proc = spawn('docker', args, { stdio: 'inherit' });
|
|
166
|
+
const proc = spawn('docker', args, { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
88
167
|
proc.on('error', reject);
|
|
89
|
-
|
|
168
|
+
|
|
169
|
+
function onLine(line) {
|
|
170
|
+
if (passesLevelFilter(getLogLevel(line), minLevel)) {
|
|
171
|
+
process.stdout.write(line + '\n');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const rlOut = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
|
|
176
|
+
rlOut.on('line', onLine);
|
|
177
|
+
const rlErr = readline.createInterface({ input: proc.stderr, crlfDelay: Infinity });
|
|
178
|
+
rlErr.on('line', onLine);
|
|
179
|
+
|
|
180
|
+
let streamsClosed = 0;
|
|
181
|
+
let exitCode = null;
|
|
182
|
+
function tryFinish() {
|
|
183
|
+
if (streamsClosed < 2 || exitCode === null) return;
|
|
184
|
+
if (exitCode !== 0) reject(new Error(`docker logs exited with ${exitCode}`));
|
|
185
|
+
else resolve();
|
|
186
|
+
}
|
|
187
|
+
rlOut.on('close', () => {
|
|
188
|
+
streamsClosed += 1;
|
|
189
|
+
tryFinish();
|
|
190
|
+
});
|
|
191
|
+
rlErr.on('close', () => {
|
|
192
|
+
streamsClosed += 1;
|
|
193
|
+
tryFinish();
|
|
194
|
+
});
|
|
195
|
+
proc.on('close', (code) => {
|
|
196
|
+
exitCode = code;
|
|
197
|
+
tryFinish();
|
|
198
|
+
});
|
|
90
199
|
});
|
|
91
200
|
}
|
|
92
201
|
|
|
93
202
|
/**
|
|
94
|
-
* Run docker logs --follow (stream), optionally with tail
|
|
203
|
+
* Run docker logs --follow (stream), optionally with tail. If minLevel is set, stdout is piped and filtered.
|
|
95
204
|
* @param {string} containerName - Docker container name
|
|
96
205
|
* @param {number} [tail] - Lines to show (0 = full, omit --tail)
|
|
206
|
+
* @param {string|null} [minLevel] - Minimum log level to show (debug|info|warn|error)
|
|
97
207
|
*/
|
|
98
|
-
function runDockerLogsFollow(containerName, tail) {
|
|
208
|
+
function runDockerLogsFollow(containerName, tail, minLevel) {
|
|
99
209
|
const args = tail === 0 ? ['logs', '-f', containerName] : ['logs', '-f', '--tail', String(tail), containerName];
|
|
100
|
-
const
|
|
210
|
+
const level =
|
|
211
|
+
minLevel !== undefined && minLevel !== null && minLevel !== ''
|
|
212
|
+
? String(minLevel).trim().toLowerCase()
|
|
213
|
+
: null;
|
|
214
|
+
const useFilter = level !== null && level !== undefined && LOG_LEVELS.includes(level);
|
|
215
|
+
|
|
216
|
+
if (!useFilter) {
|
|
217
|
+
const proc = spawn('docker', args, { stdio: 'inherit' });
|
|
218
|
+
proc.on('error', (err) => {
|
|
219
|
+
logger.log(chalk.red(`Error: ${err.message}`));
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
|
222
|
+
proc.on('close', (code) => {
|
|
223
|
+
if (code !== 0 && code !== null) process.exit(code);
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const proc = spawn('docker', args, { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
101
229
|
proc.on('error', (err) => {
|
|
102
230
|
logger.log(chalk.red(`Error: ${err.message}`));
|
|
103
231
|
process.exit(1);
|
|
104
232
|
});
|
|
233
|
+
function onLine(line) {
|
|
234
|
+
if (passesLevelFilter(getLogLevel(line), level)) process.stdout.write(line + '\n');
|
|
235
|
+
}
|
|
236
|
+
const rlOut = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
|
|
237
|
+
rlOut.on('line', onLine);
|
|
238
|
+
const rlErr = readline.createInterface({ input: proc.stderr, crlfDelay: Infinity });
|
|
239
|
+
rlErr.on('line', onLine);
|
|
105
240
|
proc.on('close', (code) => {
|
|
106
241
|
if (code !== 0 && code !== null) process.exit(code);
|
|
107
242
|
});
|
|
@@ -114,10 +249,22 @@ function runDockerLogsFollow(containerName, tail) {
|
|
|
114
249
|
* @param {Object} options - CLI options
|
|
115
250
|
* @param {boolean} [options.follow] - Follow log stream (-f)
|
|
116
251
|
* @param {number} [options.tail] - Number of lines (default 100; 0 = full list)
|
|
252
|
+
* @param {string} [options.level] - Show only logs at this level or above (debug|info|warn|error)
|
|
117
253
|
* @returns {Promise<void>}
|
|
118
254
|
*/
|
|
119
255
|
async function runAppLogs(appKey, options = {}) {
|
|
120
256
|
validateAppName(appKey);
|
|
257
|
+
const rawLevel =
|
|
258
|
+
options.level !== undefined && options.level !== null && options.level !== ''
|
|
259
|
+
? String(options.level).trim()
|
|
260
|
+
: undefined;
|
|
261
|
+
const level = rawLevel ? rawLevel.toLowerCase() : undefined;
|
|
262
|
+
if (level !== undefined && level !== null && !LOG_LEVELS.includes(level)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`Invalid log level '${rawLevel}'; use one of: ${LOG_LEVELS.join(', ')}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
121
268
|
const developerId = await config.getDeveloperId();
|
|
122
269
|
const containerName = containerHelpers.getContainerName(appKey, developerId);
|
|
123
270
|
|
|
@@ -131,16 +278,16 @@ async function runAppLogs(appKey, options = {}) {
|
|
|
131
278
|
}
|
|
132
279
|
|
|
133
280
|
if (follow) {
|
|
134
|
-
runDockerLogsFollow(containerName, tail);
|
|
281
|
+
runDockerLogsFollow(containerName, tail, level);
|
|
135
282
|
return;
|
|
136
283
|
}
|
|
137
284
|
|
|
138
285
|
try {
|
|
139
|
-
await runDockerLogs(containerName, { tail });
|
|
286
|
+
await runDockerLogs(containerName, { tail, level });
|
|
140
287
|
} catch (err) {
|
|
141
288
|
logger.log(chalk.red(`Error: ${err.message}`));
|
|
142
289
|
throw new Error(`Failed to show logs: ${err.message}`);
|
|
143
290
|
}
|
|
144
291
|
}
|
|
145
292
|
|
|
146
|
-
module.exports = { runAppLogs, maskEnvLine };
|
|
293
|
+
module.exports = { runAppLogs, maskEnvLine, getLogLevel, passesLevelFilter };
|
|
@@ -15,9 +15,11 @@ const chalk = require('chalk');
|
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
16
|
const pathsUtil = require('../utils/paths');
|
|
17
17
|
const { copyTemplateFiles } = require('../validation/template');
|
|
18
|
+
const { ensureReadmeForAppPath, ensureReadmeForApp } = require('../app/readme');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Copy template to a target path if variables.yaml is missing there.
|
|
22
|
+
* After copy, generates README.md from templates/applications/README.md.hbs.
|
|
21
23
|
* @param {string} appName - Application name
|
|
22
24
|
* @param {string} targetAppPath - Target directory (e.g. builder/keycloak)
|
|
23
25
|
* @returns {Promise<boolean>} True if template was copied, false if already present
|
|
@@ -28,6 +30,7 @@ async function ensureTemplateAtPath(appName, targetAppPath) {
|
|
|
28
30
|
return false;
|
|
29
31
|
}
|
|
30
32
|
await copyTemplateFiles(appName, targetAppPath);
|
|
33
|
+
await ensureReadmeForAppPath(targetAppPath, appName);
|
|
31
34
|
return true;
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -160,6 +163,7 @@ async function ensureAppFromTemplate(appName) {
|
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
await ensureReadmeForApp(appName);
|
|
163
167
|
return primaryCopied;
|
|
164
168
|
}
|
|
165
169
|
|
|
@@ -35,13 +35,19 @@ async function generateExternalSystemTemplate(appPath, systemKey, config) {
|
|
|
35
35
|
const templateContent = await fs.readFile(templatePath, 'utf8');
|
|
36
36
|
const template = handlebars.compile(templateContent);
|
|
37
37
|
|
|
38
|
+
const roles = (config.roles || null) && config.roles.map((role) => ({
|
|
39
|
+
...role,
|
|
40
|
+
groups: role.groups || role.Groups || undefined
|
|
41
|
+
}));
|
|
42
|
+
|
|
38
43
|
const context = {
|
|
39
44
|
systemKey: systemKey,
|
|
40
45
|
systemDisplayName: config.systemDisplayName || systemKey.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
|
41
46
|
systemDescription: config.systemDescription || `External system integration for ${systemKey}`,
|
|
42
47
|
systemType: config.systemType || 'openapi',
|
|
43
48
|
authType: config.authType || 'apikey',
|
|
44
|
-
|
|
49
|
+
baseUrl: config.baseUrl || null,
|
|
50
|
+
roles: roles || null,
|
|
45
51
|
permissions: config.permissions || null
|
|
46
52
|
};
|
|
47
53
|
|
|
@@ -74,6 +80,8 @@ async function generateExternalDataSourceTemplate(appPath, datasourceKey, config
|
|
|
74
80
|
const templateContent = await fs.readFile(templatePath, 'utf8');
|
|
75
81
|
const template = handlebars.compile(templateContent);
|
|
76
82
|
|
|
83
|
+
const dimensions = config.dimensions || {};
|
|
84
|
+
const attributes = config.attributes || {};
|
|
77
85
|
const context = {
|
|
78
86
|
datasourceKey: datasourceKey,
|
|
79
87
|
datasourceDisplayName: config.datasourceDisplayName || datasourceKey.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
|
@@ -82,8 +90,11 @@ async function generateExternalDataSourceTemplate(appPath, datasourceKey, config
|
|
|
82
90
|
entityType: config.entityType || datasourceKey.split('-').pop(),
|
|
83
91
|
resourceType: config.resourceType || 'document',
|
|
84
92
|
systemType: config.systemType || 'openapi',
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
// Pass non-empty objects so template uses custom block; empty/null so template uses schema-valid defaults
|
|
94
|
+
dimensions: Object.keys(dimensions).length > 0 ? dimensions : null,
|
|
95
|
+
attributes: Object.keys(attributes).length > 0 ? attributes : null,
|
|
96
|
+
// Literal expression strings for default attribute block (schema: pipe-based DSL {{raw.path}})
|
|
97
|
+
raw: { id: '{{raw.id}}', name: '{{raw.name}}' }
|
|
87
98
|
};
|
|
88
99
|
|
|
89
100
|
const rendered = template(context);
|
|
@@ -49,7 +49,6 @@ application:
|
|
|
49
49
|
overridablePaths:
|
|
50
50
|
- configuration.items.value
|
|
51
51
|
- authentication.endpoints
|
|
52
|
-
- deployment.controllerUrl
|
|
53
52
|
- healthCheck.interval
|
|
54
53
|
- healthCheck.probeIntervalInSeconds
|
|
55
54
|
|
|
@@ -60,7 +59,6 @@ externalSystem:
|
|
|
60
59
|
- description
|
|
61
60
|
- type
|
|
62
61
|
- enabled
|
|
63
|
-
- environment
|
|
64
62
|
- authentication
|
|
65
63
|
- openapi
|
|
66
64
|
- mcp
|
|
@@ -75,8 +73,6 @@ externalSystem:
|
|
|
75
73
|
- generateMcpContract
|
|
76
74
|
- generateOpenApiContract
|
|
77
75
|
overridablePaths:
|
|
78
|
-
- environment.baseUrl
|
|
79
|
-
- environment.region
|
|
80
76
|
- authentication.oauth2
|
|
81
77
|
- authentication.apikey
|
|
82
78
|
- authentication.basic
|