@aifabrix/builder 2.2.0 → 2.3.2
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/lib/app-run-helpers.js +381 -0
- package/lib/app-run.js +17 -392
- package/lib/build.js +102 -8
- package/lib/cli.js +27 -10
- package/lib/commands/secure.js +242 -0
- package/lib/config.js +106 -19
- package/lib/infra.js +14 -7
- package/lib/push.js +34 -7
- package/lib/secrets.js +75 -24
- package/lib/templates.js +1 -1
- package/lib/utils/build-copy.js +25 -5
- package/lib/utils/cli-utils.js +28 -3
- package/lib/utils/compose-generator.js +17 -4
- package/lib/utils/dev-config.js +8 -7
- package/lib/utils/docker-build.js +24 -0
- package/lib/utils/infra-containers.js +3 -2
- package/lib/utils/secrets-encryption.js +203 -0
- package/lib/utils/secrets-path.js +22 -3
- package/lib/utils/yaml-preserve.js +214 -0
- package/package.json +2 -2
- package/test-output.txt +0 -5431
|
@@ -13,6 +13,7 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const yaml = require('js-yaml');
|
|
16
|
+
const config = require('../config');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Resolves secrets file path (backward compatibility)
|
|
@@ -56,14 +57,17 @@ function resolveSecretsPath(secretsPath) {
|
|
|
56
57
|
/**
|
|
57
58
|
* Determines the actual secrets file paths that loadSecrets would use
|
|
58
59
|
* Mirrors the cascading lookup logic from loadSecrets
|
|
60
|
+
* Checks config.yaml for general secrets-path as fallback
|
|
61
|
+
*
|
|
62
|
+
* @async
|
|
59
63
|
* @function getActualSecretsPath
|
|
60
64
|
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
61
65
|
* @param {string} [appName] - Application name (optional, for variables.yaml lookup)
|
|
62
|
-
* @returns {Object} Object with userPath and buildPath (if configured)
|
|
66
|
+
* @returns {Promise<Object>} Object with userPath and buildPath (if configured)
|
|
63
67
|
* @returns {string} returns.userPath - User's secrets file path (~/.aifabrix/secrets.local.yaml)
|
|
64
|
-
* @returns {string|null} returns.buildPath - App's build.secrets file path (if configured in variables.yaml)
|
|
68
|
+
* @returns {string|null} returns.buildPath - App's build.secrets file path (if configured in variables.yaml or config.yaml)
|
|
65
69
|
*/
|
|
66
|
-
function getActualSecretsPath(secretsPath, appName) {
|
|
70
|
+
async function getActualSecretsPath(secretsPath, appName) {
|
|
67
71
|
// If explicit path provided, use it (backward compatibility)
|
|
68
72
|
if (secretsPath) {
|
|
69
73
|
const resolvedPath = resolveSecretsPath(secretsPath);
|
|
@@ -97,6 +101,21 @@ function getActualSecretsPath(secretsPath, appName) {
|
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
|
|
104
|
+
// If no build.secrets found in variables.yaml, check config.yaml for general secrets-path
|
|
105
|
+
if (!buildSecretsPath) {
|
|
106
|
+
try {
|
|
107
|
+
const generalSecretsPath = await config.getSecretsPath();
|
|
108
|
+
if (generalSecretsPath) {
|
|
109
|
+
// Resolve relative paths from current working directory
|
|
110
|
+
buildSecretsPath = path.isAbsolute(generalSecretsPath)
|
|
111
|
+
? generalSecretsPath
|
|
112
|
+
: path.resolve(process.cwd(), generalSecretsPath);
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
// Ignore errors, continue
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
// Return both paths (even if files don't exist) for error messages
|
|
101
120
|
return {
|
|
102
121
|
userPath: userSecretsPath,
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder YAML Preservation Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides line-by-line YAML parsing that preserves comments,
|
|
5
|
+
* formatting, and structure while encrypting values.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview YAML preservation utilities for secure command
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { encryptSecret, isEncrypted } = require('./secrets-encryption');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if a string value represents a YAML primitive (number, boolean, null)
|
|
16
|
+
* When parsing line-by-line, these appear as strings but should not be encrypted
|
|
17
|
+
*
|
|
18
|
+
* @function isYamlPrimitive
|
|
19
|
+
* @param {string} value - Value to check
|
|
20
|
+
* @returns {boolean} True if value is a YAML primitive
|
|
21
|
+
*/
|
|
22
|
+
function isYamlPrimitive(value) {
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
|
|
25
|
+
// Check for null
|
|
26
|
+
if (trimmed === 'null' || trimmed === '~' || trimmed === '') {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check for boolean
|
|
31
|
+
if (trimmed === 'true' || trimmed === 'false' || trimmed === 'True' || trimmed === 'False' ||
|
|
32
|
+
trimmed === 'TRUE' || trimmed === 'FALSE' || trimmed === 'yes' || trimmed === 'no' ||
|
|
33
|
+
trimmed === 'Yes' || trimmed === 'No' || trimmed === 'YES' || trimmed === 'NO' ||
|
|
34
|
+
trimmed === 'on' || trimmed === 'off' || trimmed === 'On' || trimmed === 'Off' ||
|
|
35
|
+
trimmed === 'ON' || trimmed === 'OFF') {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for number (integer or float, with optional sign)
|
|
40
|
+
if (/^[+-]?\d+$/.test(trimmed) || /^[+-]?\d*\.\d+([eE][+-]?\d+)?$/.test(trimmed) ||
|
|
41
|
+
/^[+-]?\.\d+([eE][+-]?\d+)?$/.test(trimmed) || /^0x[0-9a-fA-F]+$/.test(trimmed) ||
|
|
42
|
+
/^0o[0-7]+$/.test(trimmed) || /^0b[01]+$/.test(trimmed)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for YAML special values
|
|
47
|
+
if (trimmed === '.inf' || trimmed === '.Inf' || trimmed === '.INF' ||
|
|
48
|
+
trimmed === '-.inf' || trimmed === '-.Inf' || trimmed === '-.INF' ||
|
|
49
|
+
trimmed === '.nan' || trimmed === '.NaN' || trimmed === '.NAN') {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a value should be encrypted
|
|
58
|
+
* URLs (http:// and https://) are not encrypted as they are not secrets
|
|
59
|
+
* YAML primitives (numbers, booleans, null) are not encrypted
|
|
60
|
+
*
|
|
61
|
+
* @function shouldEncryptValue
|
|
62
|
+
* @param {string} value - Value to check
|
|
63
|
+
* @returns {boolean} True if value should be encrypted
|
|
64
|
+
*/
|
|
65
|
+
function shouldEncryptValue(value) {
|
|
66
|
+
if (typeof value !== 'string') {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
|
|
72
|
+
// Skip empty or whitespace-only values
|
|
73
|
+
if (trimmed.length === 0) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Skip YAML primitives (numbers, booleans, null)
|
|
78
|
+
if (isYamlPrimitive(trimmed)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Skip already encrypted values
|
|
83
|
+
if (isEncrypted(trimmed)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Skip URLs - they are not secrets
|
|
88
|
+
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extracts value from YAML line, handling quotes
|
|
97
|
+
* Removes quotes but remembers if they were present
|
|
98
|
+
*
|
|
99
|
+
* @function extractValue
|
|
100
|
+
* @param {string} valuePart - The value portion of a YAML line
|
|
101
|
+
* @returns {Object} Object with value and quote info
|
|
102
|
+
*/
|
|
103
|
+
function extractValue(valuePart) {
|
|
104
|
+
const trimmed = valuePart.trim();
|
|
105
|
+
|
|
106
|
+
// Check for quoted strings
|
|
107
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
108
|
+
(trimmed.startsWith('\'') && trimmed.endsWith('\''))) {
|
|
109
|
+
const quote = trimmed[0];
|
|
110
|
+
const unquoted = trimmed.slice(1, -1);
|
|
111
|
+
return { value: unquoted, quoted: true, quoteChar: quote };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { value: trimmed, quoted: false, quoteChar: null };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Formats value back with quotes if needed
|
|
119
|
+
*
|
|
120
|
+
* @function formatValue
|
|
121
|
+
* @param {string} value - Value to format
|
|
122
|
+
* @param {boolean} quoted - Whether value was originally quoted
|
|
123
|
+
* @param {string|null} quoteChar - Original quote character
|
|
124
|
+
* @returns {string} Formatted value
|
|
125
|
+
*/
|
|
126
|
+
function formatValue(value, quoted, quoteChar) {
|
|
127
|
+
if (quoted && quoteChar) {
|
|
128
|
+
return `${quoteChar}${value}${quoteChar}`;
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Encrypts YAML values while preserving comments and formatting
|
|
135
|
+
* Processes file line-by-line to maintain structure
|
|
136
|
+
*
|
|
137
|
+
* @function encryptYamlValues
|
|
138
|
+
* @param {string} content - Original YAML file content
|
|
139
|
+
* @param {string} encryptionKey - Encryption key
|
|
140
|
+
* @returns {Object} Object with encrypted content and statistics
|
|
141
|
+
* @returns {string} returns.content - Encrypted YAML content
|
|
142
|
+
* @returns {number} returns.encrypted - Count of encrypted values
|
|
143
|
+
* @returns {number} returns.total - Total count of values processed
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* const result = encryptYamlValues(yamlContent, encryptionKey);
|
|
147
|
+
* // Returns: { content: '...', encrypted: 5, total: 10 }
|
|
148
|
+
*/
|
|
149
|
+
function encryptYamlValues(content, encryptionKey) {
|
|
150
|
+
const lines = content.split(/\r?\n/);
|
|
151
|
+
const encryptedLines = [];
|
|
152
|
+
let encryptedCount = 0;
|
|
153
|
+
let totalCount = 0;
|
|
154
|
+
|
|
155
|
+
// Pattern to match key-value pairs with optional comments
|
|
156
|
+
// Matches: indentation, key, colon, value, optional whitespace, optional comment
|
|
157
|
+
// Handles: key: value, key: "value", key: value # comment, etc.
|
|
158
|
+
const kvPattern = /^(\s*)([^#:\n]+?):\s*(.+?)(\s*)(#.*)?$/;
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < lines.length; i++) {
|
|
161
|
+
const line = lines[i];
|
|
162
|
+
const trimmed = line.trim();
|
|
163
|
+
|
|
164
|
+
// Preserve empty lines and comment-only lines
|
|
165
|
+
if (trimmed === '' || trimmed.startsWith('#')) {
|
|
166
|
+
encryptedLines.push(line);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Try to match key-value pattern
|
|
171
|
+
const match = line.match(kvPattern);
|
|
172
|
+
if (match) {
|
|
173
|
+
totalCount++;
|
|
174
|
+
const [, indent, key, valuePart, trailingWhitespace, comment] = match;
|
|
175
|
+
|
|
176
|
+
// Extract value (handle quotes)
|
|
177
|
+
const { value, quoted, quoteChar } = extractValue(valuePart);
|
|
178
|
+
|
|
179
|
+
// Check if value should be encrypted
|
|
180
|
+
if (shouldEncryptValue(value)) {
|
|
181
|
+
// Encrypt the value
|
|
182
|
+
const encryptedValue = encryptSecret(value, encryptionKey);
|
|
183
|
+
const formattedValue = formatValue(encryptedValue, quoted, quoteChar);
|
|
184
|
+
|
|
185
|
+
// Reconstruct line with encrypted value
|
|
186
|
+
const encryptedLine = `${indent}${key}: ${formattedValue}${trailingWhitespace}${comment || ''}`;
|
|
187
|
+
encryptedLines.push(encryptedLine);
|
|
188
|
+
encryptedCount++;
|
|
189
|
+
} else {
|
|
190
|
+
// Keep original line (already encrypted, URL, or non-string)
|
|
191
|
+
encryptedLines.push(line);
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// Line doesn't match pattern (multiline value, complex structure, etc.)
|
|
195
|
+
// Preserve as-is
|
|
196
|
+
encryptedLines.push(line);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
content: encryptedLines.join('\n'),
|
|
202
|
+
encrypted: encryptedCount,
|
|
203
|
+
total: totalCount
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
encryptYamlValues,
|
|
209
|
+
shouldEncryptValue,
|
|
210
|
+
isYamlPrimitive,
|
|
211
|
+
extractValue,
|
|
212
|
+
formatValue
|
|
213
|
+
};
|
|
214
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.2
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "AI Fabrix Local Fabric & Deployment SDK",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"container"
|
|
33
33
|
],
|
|
34
34
|
"author": "eSystems Nordic Ltd",
|
|
35
|
-
"license": "
|
|
35
|
+
"license": "MIT",
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18.0.0"
|
|
38
38
|
},
|