@aifabrix/builder 2.37.0 → 2.37.9
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/.cursor/rules/project-rules.mdc +19 -0
- package/integration/hubspot/test.js +1 -1
- package/lib/api/wizard.api.js +24 -1
- package/lib/app/deploy.js +43 -7
- package/lib/app/list.js +3 -1
- package/lib/build/index.js +3 -4
- package/lib/cli/setup-app.js +1 -0
- package/lib/cli/setup-external-system.js +1 -0
- package/lib/cli/setup-utility.js +1 -1
- package/lib/commands/up-common.js +31 -1
- package/lib/commands/up-miso.js +7 -3
- package/lib/commands/wizard-core.js +44 -7
- package/lib/core/config.js +16 -1
- package/lib/core/secrets.js +42 -50
- package/lib/deployment/deployer-status.js +101 -0
- package/lib/deployment/deployer.js +62 -110
- package/lib/deployment/environment.js +146 -36
- package/lib/external-system/deploy.js +5 -1
- package/lib/external-system/test-auth.js +14 -7
- package/lib/generator/wizard.js +27 -16
- package/lib/schema/environment-deploy-request.schema.json +64 -0
- package/lib/utils/paths.js +28 -7
- package/lib/utils/secrets-generator.js +23 -8
- package/lib/utils/secrets-helpers.js +46 -21
- package/package.json +1 -1
- package/scripts/install-local.js +11 -2
- package/templates/external-system/deploy.js.hbs +11 -0
- package/templates/infra/environment-dev.json +10 -0
package/lib/generator/wizard.js
CHANGED
|
@@ -12,6 +12,19 @@ const chalk = require('chalk');
|
|
|
12
12
|
const logger = require('../utils/logger');
|
|
13
13
|
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Converts a string to a schema-valid key segment (lowercase letters, numbers, hyphens only).
|
|
17
|
+
* e.g. "recordStorage" -> "record-storage", "documentStorage" -> "document-storage"
|
|
18
|
+
* @param {string} str - Raw entity type or key segment (may be camelCase)
|
|
19
|
+
* @returns {string} Segment matching ^[a-z0-9-]+$
|
|
20
|
+
*/
|
|
21
|
+
function toKeySegment(str) {
|
|
22
|
+
if (!str || typeof str !== 'string') return 'default';
|
|
23
|
+
const withHyphens = str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
24
|
+
const sanitized = withHyphens.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
25
|
+
return sanitized || 'default';
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
/**
|
|
16
29
|
* Generate files from dataplane-generated wizard configurations
|
|
17
30
|
* @async
|
|
@@ -53,11 +66,12 @@ async function writeDatasourceJsonFiles(appPath, finalSystemKey, datasourceConfi
|
|
|
53
66
|
const datasourceFileNames = [];
|
|
54
67
|
for (const datasourceConfig of datasourceConfigs) {
|
|
55
68
|
const entityType = datasourceConfig.entityType || datasourceConfig.entityKey || datasourceConfig.key?.split('-').pop() || 'default';
|
|
56
|
-
const
|
|
57
|
-
|
|
69
|
+
const keySegment = toKeySegment(entityType);
|
|
70
|
+
const datasourceKey = datasourceConfig.key || `${finalSystemKey}-${keySegment}`;
|
|
71
|
+
// Extract datasource key (remove system key prefix if present); use normalized segment for filename
|
|
58
72
|
const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${finalSystemKey}-`)
|
|
59
73
|
? datasourceKey.substring(finalSystemKey.length + 1)
|
|
60
|
-
:
|
|
74
|
+
: keySegment;
|
|
61
75
|
const datasourceFileName = `${finalSystemKey}-datasource-${datasourceKeyOnly}.json`;
|
|
62
76
|
const datasourceFilePath = path.join(appPath, datasourceFileName);
|
|
63
77
|
await fs.writeFile(datasourceFilePath, JSON.stringify(datasourceConfig, null, 2), 'utf8');
|
|
@@ -143,13 +157,14 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
|
|
|
143
157
|
displayName: appDisplayName
|
|
144
158
|
};
|
|
145
159
|
|
|
146
|
-
// Update datasource configs to use appName-based keys and systemKey
|
|
160
|
+
// Update datasource configs to use appName-based keys and systemKey (key must match ^[a-z0-9-]+$)
|
|
147
161
|
const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
|
|
148
162
|
const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || 'default';
|
|
163
|
+
const keySegment = toKeySegment(entityType);
|
|
149
164
|
const entityDisplayName = entityType.charAt(0).toUpperCase() + entityType.slice(1).replace(/-/g, ' ');
|
|
150
165
|
return {
|
|
151
166
|
...ds,
|
|
152
|
-
key: `${finalSystemKey}-${
|
|
167
|
+
key: `${finalSystemKey}-${keySegment}`,
|
|
153
168
|
systemKey: finalSystemKey,
|
|
154
169
|
displayName: `${appDisplayName} ${entityDisplayName}`
|
|
155
170
|
};
|
|
@@ -360,23 +375,22 @@ async function generateEnvTemplate(appPath, systemConfig) {
|
|
|
360
375
|
}
|
|
361
376
|
|
|
362
377
|
/**
|
|
363
|
-
* Generate deployment
|
|
378
|
+
* Generate deployment script (deploy.js) from template
|
|
364
379
|
* @async
|
|
365
380
|
* @function generateDeployScripts
|
|
366
381
|
* @param {string} appPath - Application directory path
|
|
367
382
|
* @param {string} systemKey - System key
|
|
368
383
|
* @param {string} systemFileName - System file name
|
|
369
384
|
* @param {string[]} datasourceFileNames - Array of datasource file names
|
|
370
|
-
* @returns {Promise<Object>} Object with
|
|
385
|
+
* @returns {Promise<Object>} Object with deployJsPath
|
|
371
386
|
* @throws {Error} If generation fails
|
|
372
387
|
*/
|
|
373
388
|
const templatesExternalDir = path.join(__dirname, '..', '..', 'templates', 'external-system');
|
|
374
389
|
|
|
375
|
-
async function writeDeployScriptFromTemplate(templateName, outputPath, context
|
|
390
|
+
async function writeDeployScriptFromTemplate(templateName, outputPath, context) {
|
|
376
391
|
const templatePath = path.join(templatesExternalDir, templateName);
|
|
377
392
|
const content = Handlebars.compile(await fs.readFile(templatePath, 'utf8'))(context);
|
|
378
393
|
await fs.writeFile(outputPath, content, 'utf8');
|
|
379
|
-
if (executable) await fs.chmod(outputPath, 0o755);
|
|
380
394
|
logger.log(chalk.green(`✓ Generated ${path.basename(outputPath)}`));
|
|
381
395
|
}
|
|
382
396
|
|
|
@@ -385,13 +399,9 @@ async function generateDeployScripts(appPath, systemKey, systemFileName, datasou
|
|
|
385
399
|
const allJsonFiles = [systemFileName, ...datasourceFileNames];
|
|
386
400
|
const context = { systemKey, allJsonFiles, datasourceFileNames };
|
|
387
401
|
|
|
388
|
-
await writeDeployScriptFromTemplate('deploy.
|
|
389
|
-
await writeDeployScriptFromTemplate('deploy.ps1.hbs', path.join(appPath, 'deploy.ps1'), context, false);
|
|
390
|
-
await writeDeployScriptFromTemplate('deploy.js.hbs', path.join(appPath, 'deploy.js'), context, false);
|
|
402
|
+
await writeDeployScriptFromTemplate('deploy.js.hbs', path.join(appPath, 'deploy.js'), context);
|
|
391
403
|
|
|
392
404
|
return {
|
|
393
|
-
deployShPath: path.join(appPath, 'deploy.sh'),
|
|
394
|
-
deployPs1Path: path.join(appPath, 'deploy.ps1'),
|
|
395
405
|
deployJsPath: path.join(appPath, 'deploy.js')
|
|
396
406
|
};
|
|
397
407
|
} catch (error) {
|
|
@@ -424,10 +434,11 @@ async function generateReadme(appPath, appName, systemKey, systemConfig, datasou
|
|
|
424
434
|
|
|
425
435
|
const datasources = (Array.isArray(datasourceConfigs) ? datasourceConfigs : []).map((ds, index) => {
|
|
426
436
|
const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || `datasource${index + 1}`;
|
|
427
|
-
const
|
|
437
|
+
const keySegment = toKeySegment(entityType);
|
|
438
|
+
const datasourceKey = ds.key || `${systemKey}-${keySegment}`;
|
|
428
439
|
const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${systemKey}-`)
|
|
429
440
|
? datasourceKey.substring(systemKey.length + 1)
|
|
430
|
-
:
|
|
441
|
+
: keySegment;
|
|
431
442
|
return {
|
|
432
443
|
entityType,
|
|
433
444
|
displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/environment-deploy-request.schema.json",
|
|
4
|
+
"title": "Environment Deploy Request",
|
|
5
|
+
"description": "Request body for POST /api/v1/environments/{envKey}/deploy",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["environmentConfig"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"environmentConfig": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"description": "Environment infrastructure configuration",
|
|
12
|
+
"required": ["key", "environment", "preset", "serviceName", "location"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"key": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Environment key",
|
|
17
|
+
"pattern": "^[a-z0-9-]+$",
|
|
18
|
+
"minLength": 2,
|
|
19
|
+
"maxLength": 40
|
|
20
|
+
},
|
|
21
|
+
"environment": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Environment type",
|
|
24
|
+
"enum": ["miso", "dev", "tst", "pro"]
|
|
25
|
+
},
|
|
26
|
+
"preset": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Deployment preset size",
|
|
29
|
+
"enum": ["evaluation", "eval", "s", "m", "l", "xl"]
|
|
30
|
+
},
|
|
31
|
+
"serviceName": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Service name for resource naming",
|
|
34
|
+
"pattern": "^[a-z0-9-]{2,40}$"
|
|
35
|
+
},
|
|
36
|
+
"location": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Azure region (e.g. swedencentral)"
|
|
39
|
+
},
|
|
40
|
+
"resourceGroupName": { "type": "string" },
|
|
41
|
+
"subscriptionId": { "type": "string" },
|
|
42
|
+
"tenantId": { "type": "string" },
|
|
43
|
+
"customDomain": { "type": "object" },
|
|
44
|
+
"frontDoor": { "type": "object" },
|
|
45
|
+
"networking": { "type": "object" },
|
|
46
|
+
"allowedIPs": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": { "type": "string" }
|
|
49
|
+
},
|
|
50
|
+
"infrastructureAccess": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": { "type": "object" }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"additionalProperties": true
|
|
56
|
+
},
|
|
57
|
+
"dryRun": {
|
|
58
|
+
"type": "boolean",
|
|
59
|
+
"description": "If true, validate only without deploying",
|
|
60
|
+
"default": false
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"additionalProperties": false
|
|
64
|
+
}
|
package/lib/utils/paths.js
CHANGED
|
@@ -31,11 +31,15 @@ function safeHomedir() {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Returns the path to the config
|
|
35
|
-
*
|
|
34
|
+
* Returns the path to the config directory (same precedence as config.js so both read the same config).
|
|
35
|
+
* Priority: AIFABRIX_CONFIG (dirname) → AIFABRIX_HOME → ~/.aifabrix.
|
|
36
36
|
* @returns {string} Absolute path to config directory
|
|
37
37
|
*/
|
|
38
38
|
function getConfigDirForPaths() {
|
|
39
|
+
const configFile = process.env.AIFABRIX_CONFIG && typeof process.env.AIFABRIX_CONFIG === 'string';
|
|
40
|
+
if (configFile) {
|
|
41
|
+
return path.dirname(path.resolve(process.env.AIFABRIX_CONFIG.trim()));
|
|
42
|
+
}
|
|
39
43
|
if (process.env.AIFABRIX_HOME && typeof process.env.AIFABRIX_HOME === 'string') {
|
|
40
44
|
return path.resolve(process.env.AIFABRIX_HOME.trim());
|
|
41
45
|
}
|
|
@@ -281,7 +285,23 @@ function getAppPath(appName, appType) {
|
|
|
281
285
|
}
|
|
282
286
|
|
|
283
287
|
/**
|
|
284
|
-
*
|
|
288
|
+
* Base directory for integration/builder: project root when cwd is inside project, else cwd.
|
|
289
|
+
* So deploy works when run from integration/<app> (e.g. node deploy.js), and tests using temp dirs still work.
|
|
290
|
+
* @returns {string} Directory to resolve integration/ and builder/ from
|
|
291
|
+
*/
|
|
292
|
+
function getIntegrationBuilderBaseDir() {
|
|
293
|
+
const root = getProjectRoot();
|
|
294
|
+
const cwd = path.resolve(process.cwd());
|
|
295
|
+
const rootNorm = path.resolve(root);
|
|
296
|
+
if (cwd === rootNorm || cwd.startsWith(rootNorm + path.sep)) {
|
|
297
|
+
return rootNorm;
|
|
298
|
+
}
|
|
299
|
+
return cwd;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Gets the integration folder path for external systems.
|
|
304
|
+
* Uses project root when cwd is inside project so deploy works when run from integration/<app> (e.g. node deploy.js).
|
|
285
305
|
* @param {string} appName - Application name
|
|
286
306
|
* @returns {string} Absolute path to integration directory
|
|
287
307
|
*/
|
|
@@ -289,13 +309,14 @@ function getIntegrationPath(appName) {
|
|
|
289
309
|
if (!appName || typeof appName !== 'string') {
|
|
290
310
|
throw new Error('App name is required and must be a string');
|
|
291
311
|
}
|
|
292
|
-
|
|
312
|
+
const base = getIntegrationBuilderBaseDir();
|
|
313
|
+
return path.join(base, 'integration', appName);
|
|
293
314
|
}
|
|
294
315
|
|
|
295
316
|
/**
|
|
296
317
|
* Gets the builder folder path for regular applications.
|
|
297
318
|
* When AIFABRIX_BUILDER_DIR is set (e.g. by up-miso/up-dataplane from config aifabrix-env-config),
|
|
298
|
-
* uses that as builder root
|
|
319
|
+
* uses that as builder root; otherwise uses project root so deploy works when run from integration/<app>.
|
|
299
320
|
* @param {string} appName - Application name
|
|
300
321
|
* @returns {string} Absolute path to builder directory
|
|
301
322
|
*/
|
|
@@ -309,7 +330,8 @@ function getBuilderPath(appName) {
|
|
|
309
330
|
if (builderRoot) {
|
|
310
331
|
return path.join(builderRoot, appName);
|
|
311
332
|
}
|
|
312
|
-
|
|
333
|
+
const base = getIntegrationBuilderBaseDir();
|
|
334
|
+
return path.join(base, 'builder', appName);
|
|
313
335
|
}
|
|
314
336
|
|
|
315
337
|
/**
|
|
@@ -462,7 +484,6 @@ async function detectAppType(appName, options = {}) {
|
|
|
462
484
|
// Check builder folder (backward compatibility)
|
|
463
485
|
return checkBuilderFolder(appName);
|
|
464
486
|
}
|
|
465
|
-
|
|
466
487
|
module.exports = {
|
|
467
488
|
getAifabrixHome,
|
|
468
489
|
getConfigDirForPaths,
|
|
@@ -18,7 +18,17 @@ const logger = require('./logger');
|
|
|
18
18
|
const pathsUtil = require('./paths');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Skips commented or empty lines when scanning env.template
|
|
22
|
+
* @param {string} line - Single line
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
function isCommentOrEmptyLine(line) {
|
|
26
|
+
const t = line.trim();
|
|
27
|
+
return t === '' || t.startsWith('#');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Finds missing secret keys from template (skips commented and empty lines)
|
|
22
32
|
* @function findMissingSecretKeys
|
|
23
33
|
* @param {string} envTemplate - Environment template content
|
|
24
34
|
* @param {Object} existingSecrets - Existing secrets object
|
|
@@ -28,13 +38,18 @@ function findMissingSecretKeys(envTemplate, existingSecrets) {
|
|
|
28
38
|
const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
|
|
29
39
|
const missingKeys = [];
|
|
30
40
|
const seenKeys = new Set();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const lines = envTemplate.split('\n');
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (isCommentOrEmptyLine(line)) continue;
|
|
45
|
+
let match;
|
|
46
|
+
kvPattern.lastIndex = 0;
|
|
47
|
+
while ((match = kvPattern.exec(line)) !== null) {
|
|
48
|
+
const secretKey = match[1];
|
|
49
|
+
if (!seenKeys.has(secretKey) && !(secretKey in existingSecrets)) {
|
|
50
|
+
missingKeys.push(secretKey);
|
|
51
|
+
seenKeys.add(secretKey);
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
54
|
}
|
|
40
55
|
|
|
@@ -33,7 +33,17 @@ function interpolateEnvVars(content, envVars) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Returns true if the line is a comment or empty (should be skipped for kv:// resolution)
|
|
37
|
+
* @param {string} line - Single line
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
40
|
+
function isCommentOrEmptyLine(line) {
|
|
41
|
+
const t = line.trim();
|
|
42
|
+
return t === '' || t.startsWith('#');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Collect missing kv:// secrets referenced in content (skips commented and empty lines)
|
|
37
47
|
* @function collectMissingSecrets
|
|
38
48
|
* @param {string} content - Text content
|
|
39
49
|
* @param {Object} secrets - Available secrets
|
|
@@ -42,11 +52,16 @@ function interpolateEnvVars(content, envVars) {
|
|
|
42
52
|
function collectMissingSecrets(content, secrets) {
|
|
43
53
|
const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
|
|
44
54
|
const missing = [];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
if (isCommentOrEmptyLine(line)) continue;
|
|
58
|
+
let match;
|
|
59
|
+
kvPattern.lastIndex = 0;
|
|
60
|
+
while ((match = kvPattern.exec(line)) !== null) {
|
|
61
|
+
const secretKey = match[1];
|
|
62
|
+
if (!(secretKey in secrets)) {
|
|
63
|
+
missing.push(`kv://${secretKey}`);
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
}
|
|
52
67
|
return missing;
|
|
@@ -76,7 +91,7 @@ function formatMissingSecretsFileInfo(secretsFilePaths) {
|
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
/**
|
|
79
|
-
* Replace kv:// references with actual values
|
|
94
|
+
* Replace kv:// references with actual values (skips commented and empty lines)
|
|
80
95
|
* @function replaceKvInContent
|
|
81
96
|
* @param {string} content - Text content containing kv:// references
|
|
82
97
|
* @param {Object} secrets - Secrets map
|
|
@@ -85,15 +100,20 @@ function formatMissingSecretsFileInfo(secretsFilePaths) {
|
|
|
85
100
|
*/
|
|
86
101
|
function replaceKvInContent(content, secrets, envVars) {
|
|
87
102
|
const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
const lines = content.split('\n');
|
|
104
|
+
const result = lines.map(line => {
|
|
105
|
+
if (isCommentOrEmptyLine(line)) return line;
|
|
106
|
+
return line.replace(kvPattern, (match, secretKey) => {
|
|
107
|
+
let value = secrets[secretKey];
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
value = value.replace(/\$\{([A-Z_]+)\}/g, (m, envVar) => {
|
|
110
|
+
return envVars[envVar] || m;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
});
|
|
96
115
|
});
|
|
116
|
+
return result.join('\n');
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
/**
|
|
@@ -420,7 +440,7 @@ function ensureNonEmptySecrets(secrets) {
|
|
|
420
440
|
}
|
|
421
441
|
|
|
422
442
|
/**
|
|
423
|
-
* Validate secrets against the env template
|
|
443
|
+
* Validate secrets against the env template (skips commented and empty lines)
|
|
424
444
|
* @function validateSecrets
|
|
425
445
|
* @param {string} envTemplate - Environment template content
|
|
426
446
|
* @param {Object} secrets - Available secrets
|
|
@@ -429,11 +449,16 @@ function ensureNonEmptySecrets(secrets) {
|
|
|
429
449
|
function validateSecrets(envTemplate, secrets) {
|
|
430
450
|
const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
|
|
431
451
|
const missing = [];
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
452
|
+
const lines = envTemplate.split('\n');
|
|
453
|
+
for (const line of lines) {
|
|
454
|
+
if (isCommentOrEmptyLine(line)) continue;
|
|
455
|
+
let match;
|
|
456
|
+
kvPattern.lastIndex = 0;
|
|
457
|
+
while ((match = kvPattern.exec(line)) !== null) {
|
|
458
|
+
const secretKey = match[1];
|
|
459
|
+
if (!(secretKey in secrets)) {
|
|
460
|
+
missing.push(`kv://${secretKey}`);
|
|
461
|
+
}
|
|
437
462
|
}
|
|
438
463
|
}
|
|
439
464
|
return {
|
package/package.json
CHANGED
package/scripts/install-local.js
CHANGED
|
@@ -114,10 +114,19 @@ function installLocal() {
|
|
|
114
114
|
console.log('Linking @aifabrix/builder globally...\n');
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
|
+
const projectRoot = path.join(__dirname, '..');
|
|
117
118
|
if (pm === 'pnpm') {
|
|
118
|
-
|
|
119
|
+
// Update pnpm global.
|
|
120
|
+
execSync('pnpm link --global', { stdio: 'inherit', cwd: projectRoot });
|
|
121
|
+
// Also run npm link so npm's global bin points here; often PATH has
|
|
122
|
+
// npm's global bin before pnpm's, so "aifabrix" would otherwise stay old.
|
|
123
|
+
try {
|
|
124
|
+
execSync('npm link', { stdio: 'inherit', cwd: projectRoot });
|
|
125
|
+
} catch {
|
|
126
|
+
// npm may not be available or may fail; pnpm link already ran
|
|
127
|
+
}
|
|
119
128
|
} else {
|
|
120
|
-
execSync('npm link', { stdio: 'inherit' });
|
|
129
|
+
execSync('npm link', { stdio: 'inherit', cwd: projectRoot });
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
// Get new version after linking
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
2
3
|
/**
|
|
3
4
|
* Deploy {{systemKey}} external system and datasources using aifabrix CLI.
|
|
4
5
|
* Run: node deploy.js
|
|
@@ -13,6 +14,12 @@ const appKey = '{{systemKey}}';
|
|
|
13
14
|
const env = process.env.ENVIRONMENT || 'dev';
|
|
14
15
|
// Controller URL: from config (aifabrix auth config) or set CONTROLLER env before running
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Run a shell command with optional options.
|
|
19
|
+
* @param {string} cmd - Command to run
|
|
20
|
+
* @param {Object} [options={}] - Options (cwd, stdio, ignoreExit)
|
|
21
|
+
* @returns {boolean} True if command succeeded
|
|
22
|
+
*/
|
|
16
23
|
function run(cmd, options = {}) {
|
|
17
24
|
const opts = { cwd: scriptDir, stdio: 'inherit', ...options };
|
|
18
25
|
try {
|
|
@@ -24,6 +31,10 @@ function run(cmd, options = {}) {
|
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Check if aifabrix auth is logged in.
|
|
36
|
+
* @returns {boolean} True if logged in
|
|
37
|
+
*/
|
|
27
38
|
function isLoggedIn() {
|
|
28
39
|
try {
|
|
29
40
|
execSync('aifabrix auth status', { cwd: scriptDir, stdio: 'pipe' });
|