@aifabrix/builder 2.39.2 → 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/register.js +2 -2
- package/lib/cli/setup-app.js +4 -1
- package/lib/commands/app-logs.js +157 -10
- package/lib/schema/deployment-rules.yaml +0 -4
- package/lib/utils/app-register-display.js +22 -3
- package/package.json +1 -1
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/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 };
|
|
@@ -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
|
|
@@ -37,18 +37,37 @@ function getEnvironmentPrefix(environment) {
|
|
|
37
37
|
return upper.length <= 4 ? upper : upper.substring(0, 4);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Resolve display name for output: use API value only when it is a real display name (not the key).
|
|
42
|
+
* @param {Object} application - Application from API response
|
|
43
|
+
* @param {string} [requestedDisplayName] - Display name sent in registration payload
|
|
44
|
+
* @returns {string} Display name to show
|
|
45
|
+
*/
|
|
46
|
+
function resolveDisplayName(application, requestedDisplayName) {
|
|
47
|
+
const apiDisplayName = application.displayName;
|
|
48
|
+
const key = application.key;
|
|
49
|
+
// If API returned a display name and it differs from key, use it
|
|
50
|
+
if (apiDisplayName && apiDisplayName !== key) {
|
|
51
|
+
return apiDisplayName;
|
|
52
|
+
}
|
|
53
|
+
// Otherwise use the display name we sent, or fall back to key
|
|
54
|
+
return requestedDisplayName || key;
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
/**
|
|
41
58
|
* Display registration success and credentials
|
|
42
59
|
* @param {Object} data - Registration response data
|
|
43
60
|
* @param {string} apiUrl - API URL
|
|
44
61
|
* @param {string} environment - Environment key
|
|
62
|
+
* @param {string} [requestedDisplayName] - Display name sent in registration (used when API echoes key as displayName)
|
|
45
63
|
*/
|
|
46
|
-
function displayRegistrationResults(data, apiUrl, environment) {
|
|
64
|
+
function displayRegistrationResults(data, apiUrl, environment, requestedDisplayName) {
|
|
65
|
+
const displayName = resolveDisplayName(data.application, requestedDisplayName);
|
|
47
66
|
logger.log(chalk.green('✅ Application registered successfully!\n'));
|
|
48
67
|
logger.log(chalk.bold('📋 Application Details:'));
|
|
49
68
|
logger.log(` ID: ${data.application.id}`);
|
|
50
69
|
logger.log(` Key: ${data.application.key}`);
|
|
51
|
-
logger.log(` Display Name: ${
|
|
70
|
+
logger.log(` Display Name: ${displayName}`);
|
|
52
71
|
logger.log(` Controller: ${apiUrl}\n`);
|
|
53
72
|
|
|
54
73
|
logger.log(chalk.bold.yellow('🔑 CREDENTIALS (save these immediately):'));
|
|
@@ -66,5 +85,5 @@ function displayRegistrationResults(data, apiUrl, environment) {
|
|
|
66
85
|
logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTSECRET = ${data.credentials.clientSecret}\n`));
|
|
67
86
|
}
|
|
68
87
|
|
|
69
|
-
module.exports = { displayRegistrationResults, getEnvironmentPrefix };
|
|
88
|
+
module.exports = { displayRegistrationResults, getEnvironmentPrefix, resolveDisplayName };
|
|
70
89
|
|