@aifabrix/builder 2.37.9 → 2.38.0
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 +19 -0
- package/integration/hubspot/hubspot-deploy.json +1 -2
- package/lib/api/applications.api.js +23 -1
- package/lib/api/credentials.api.js +34 -0
- package/lib/api/deployments.api.js +27 -0
- package/lib/api/types/applications.types.js +1 -1
- package/lib/api/types/deployments.types.js +1 -1
- package/lib/api/types/pipeline.types.js +1 -1
- package/lib/api/wizard.api.js +21 -1
- package/lib/app/run-helpers.js +30 -2
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.js +32 -0
- package/lib/cli/setup-credential-deployment.js +72 -0
- package/lib/cli/setup-utility.js +1 -25
- package/lib/commands/app-down.js +80 -0
- package/lib/commands/app-logs.js +146 -0
- package/lib/commands/app.js +22 -0
- package/lib/commands/credential-list.js +104 -0
- package/lib/commands/deployment-list.js +184 -0
- package/lib/core/templates.js +2 -1
- package/lib/generator/builders.js +8 -3
- package/lib/generator/external-controller-manifest.js +5 -4
- package/lib/generator/index.js +16 -14
- package/lib/generator/split.js +1 -0
- package/lib/generator/wizard.js +4 -1
- package/lib/schema/application-schema.json +6 -2
- package/lib/schema/deployment-rules.yaml +121 -0
- package/lib/utils/app-run-containers.js +2 -1
- package/lib/utils/compose-generator.js +2 -1
- package/lib/utils/help-builder.js +0 -1
- package/lib/utils/image-version.js +209 -0
- package/lib/utils/schema-loader.js +1 -1
- package/lib/utils/variable-transformer.js +1 -19
- package/lib/validation/external-manifest-validator.js +1 -1
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +1 -3
- package/templates/applications/dataplane/Dockerfile +2 -2
- package/templates/applications/dataplane/README.md +1 -3
- package/templates/applications/dataplane/variables.yaml +5 -3
- package/templates/applications/keycloak/Dockerfile +3 -3
- package/templates/applications/keycloak/README.md +14 -4
- package/templates/applications/keycloak/env.template +14 -2
- package/templates/applications/keycloak/variables.yaml +1 -1
- package/templates/applications/miso-controller/README.md +1 -3
- package/templates/applications/miso-controller/env.template +64 -11
package/lib/commands/app.js
CHANGED
|
@@ -15,6 +15,7 @@ const { listApplications } = require('../app/list');
|
|
|
15
15
|
const { registerApplication } = require('../app/register');
|
|
16
16
|
const { rotateSecret } = require('../app/rotate-secret');
|
|
17
17
|
const { showApp } = require('../app/show');
|
|
18
|
+
const { runAppDeploymentList } = require('./deployment-list');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Setup application management commands
|
|
@@ -81,6 +82,27 @@ function setupAppCommands(program) {
|
|
|
81
82
|
process.exit(1);
|
|
82
83
|
}
|
|
83
84
|
});
|
|
85
|
+
|
|
86
|
+
// Deployment list for an application
|
|
87
|
+
app
|
|
88
|
+
.command('deployment <appKey>')
|
|
89
|
+
.description('List last N deployments for an application in current environment (default pageSize=50)')
|
|
90
|
+
.option('--controller <url>', 'Controller URL (default: from config)')
|
|
91
|
+
.option('--environment <env>', 'Environment key (default: from config)')
|
|
92
|
+
.option('--page-size <n>', 'Items per page', '50')
|
|
93
|
+
.action(async(appKey, options) => {
|
|
94
|
+
try {
|
|
95
|
+
const opts = {
|
|
96
|
+
controller: options.controller,
|
|
97
|
+
environment: options.environment,
|
|
98
|
+
pageSize: parseInt(options.pageSize, 10) || 50
|
|
99
|
+
};
|
|
100
|
+
await runAppDeploymentList(appKey, opts);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
module.exports = { setupAppCommands };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential list command – list credentials from controller/dataplane
|
|
3
|
+
* GET /api/v1/credential. Used by `aifabrix credential list`.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Credential list command implementation
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
13
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
14
|
+
const { normalizeControllerUrl } = require('../core/config');
|
|
15
|
+
const { listCredentials } = require('../api/credentials.api');
|
|
16
|
+
|
|
17
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get auth token for credential list (device token from config)
|
|
21
|
+
* @async
|
|
22
|
+
* @param {string} controllerUrl - Controller base URL
|
|
23
|
+
* @returns {Promise<{token: string, controllerUrl: string}|null>}
|
|
24
|
+
*/
|
|
25
|
+
async function getCredentialListAuth(controllerUrl) {
|
|
26
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
27
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
28
|
+
if (deviceToken && deviceToken.token) {
|
|
29
|
+
return {
|
|
30
|
+
token: deviceToken.token,
|
|
31
|
+
controllerUrl: deviceToken.controller || normalizedUrl
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extract credentials array from API response
|
|
39
|
+
* @param {Object} response - API response
|
|
40
|
+
* @returns {Array}
|
|
41
|
+
*/
|
|
42
|
+
function extractCredentials(response) {
|
|
43
|
+
const data = response?.data ?? response;
|
|
44
|
+
const items = data?.credentials ?? data?.items ?? (Array.isArray(data) ? data : []);
|
|
45
|
+
return Array.isArray(items) ? items : [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Display credential list to user
|
|
50
|
+
* @param {Array} list - Credentials array
|
|
51
|
+
* @param {string} controllerUrl - Controller URL for header
|
|
52
|
+
*/
|
|
53
|
+
function displayCredentialList(list, controllerUrl) {
|
|
54
|
+
logger.log(chalk.bold(`\n🔐 Credentials (${controllerUrl}):\n`));
|
|
55
|
+
if (list.length === 0) {
|
|
56
|
+
logger.log(chalk.gray(' No credentials found.\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
list.forEach((c) => {
|
|
60
|
+
const key = c.key ?? c.id ?? c.credentialKey ?? '-';
|
|
61
|
+
const name = c.displayName ?? c.name ?? key;
|
|
62
|
+
logger.log(` ${chalk.cyan(key)} - ${name}`);
|
|
63
|
+
});
|
|
64
|
+
logger.log('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Run credential list command: call GET /api/v1/credential and display results
|
|
69
|
+
* @async
|
|
70
|
+
* @param {Object} options - CLI options
|
|
71
|
+
* @param {string} [options.controller] - Controller URL override
|
|
72
|
+
* @param {boolean} [options.activeOnly] - List only active credentials
|
|
73
|
+
* @param {number} [options.pageSize] - Items per page
|
|
74
|
+
* @returns {Promise<void>}
|
|
75
|
+
*/
|
|
76
|
+
async function runCredentialList(options = {}) {
|
|
77
|
+
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
78
|
+
if (!controllerUrl) {
|
|
79
|
+
logger.error(chalk.red('❌ Controller URL is required. Run "aifabrix login" first.'));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const authResult = await getCredentialListAuth(controllerUrl);
|
|
84
|
+
if (!authResult || !authResult.token) {
|
|
85
|
+
logger.error(chalk.red(`❌ No authentication token for controller: ${controllerUrl}`));
|
|
86
|
+
logger.error(chalk.gray('Run: aifabrix login'));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const authConfig = { type: 'bearer', token: authResult.token };
|
|
91
|
+
const listOptions = {
|
|
92
|
+
pageSize: options.pageSize || DEFAULT_PAGE_SIZE,
|
|
93
|
+
activeOnly: options.activeOnly
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
const response = await listCredentials(authResult.controllerUrl, authConfig, listOptions);
|
|
97
|
+
displayCredentialList(extractCredentials(response), authResult.controllerUrl);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
logger.error(chalk.red(`❌ Failed to list credentials: ${error.message}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { runCredentialList };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment list commands – list deployments for environment or for an app
|
|
3
|
+
* Uses GET .../deployments and GET .../applications/{appKey}/deployments.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Deployment list command implementation
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
13
|
+
const { resolveEnvironment } = require('../core/config');
|
|
14
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
15
|
+
const { listDeployments, listApplicationDeployments } = require('../api/deployments.api');
|
|
16
|
+
const { normalizeControllerUrl } = require('../core/config');
|
|
17
|
+
|
|
18
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get auth token for deployment list (device token from config)
|
|
22
|
+
* @async
|
|
23
|
+
* @param {string} controllerUrl - Controller base URL
|
|
24
|
+
* @returns {Promise<{token: string, controllerUrl: string}|null>}
|
|
25
|
+
*/
|
|
26
|
+
async function getDeploymentListAuth(controllerUrl) {
|
|
27
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
28
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
29
|
+
if (deviceToken && deviceToken.token) {
|
|
30
|
+
return {
|
|
31
|
+
token: deviceToken.token,
|
|
32
|
+
controllerUrl: deviceToken.controller || normalizedUrl
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract deployments array from API response
|
|
40
|
+
* @param {Object} response - API response
|
|
41
|
+
* @returns {Array}
|
|
42
|
+
*/
|
|
43
|
+
function extractDeployments(response) {
|
|
44
|
+
const data = response?.data ?? response;
|
|
45
|
+
const items = data?.items ?? data?.deployments ?? (Array.isArray(data) ? data : []);
|
|
46
|
+
return Array.isArray(items) ? items : [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Display environment deployment list to user
|
|
51
|
+
* @param {Array} deployments - Deployments array
|
|
52
|
+
* @param {string} environment - Environment key
|
|
53
|
+
* @param {string} controllerUrl - Controller URL
|
|
54
|
+
*/
|
|
55
|
+
function displayDeploymentList(deployments, environment, controllerUrl) {
|
|
56
|
+
logger.log(chalk.bold(`\n📋 Deployments (${environment}) at ${controllerUrl}:\n`));
|
|
57
|
+
if (deployments.length === 0) {
|
|
58
|
+
logger.log(chalk.gray(' No deployments found.\n'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
deployments.forEach((d) => {
|
|
62
|
+
const id = d.id ?? d.deploymentId ?? '-';
|
|
63
|
+
const appKey = d.applicationKey ?? d.appKey ?? d.application?.key ?? '-';
|
|
64
|
+
const status = d.status ?? '-';
|
|
65
|
+
const createdAt = d.createdAt ?? d.created ?? '';
|
|
66
|
+
logger.log(` ${chalk.cyan(id)} ${appKey} ${status} ${chalk.gray(createdAt)}`);
|
|
67
|
+
});
|
|
68
|
+
logger.log('');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Run deployment list (environment): list last N deployments for current environment
|
|
73
|
+
* @async
|
|
74
|
+
* @param {Object} options - CLI options
|
|
75
|
+
* @param {string} [options.controller] - Controller URL override
|
|
76
|
+
* @param {string} [options.environment] - Environment key override
|
|
77
|
+
* @param {number} [options.pageSize] - Items per page (default 50)
|
|
78
|
+
* @returns {Promise<void>}
|
|
79
|
+
*/
|
|
80
|
+
async function runDeploymentList(options = {}) {
|
|
81
|
+
const { environment, authResult } = await resolveDeploymentListContext(options);
|
|
82
|
+
const authConfig = { type: 'bearer', token: authResult.token };
|
|
83
|
+
const listOptions = { pageSize: options.pageSize || DEFAULT_PAGE_SIZE };
|
|
84
|
+
try {
|
|
85
|
+
const response = await listDeployments(
|
|
86
|
+
authResult.controllerUrl,
|
|
87
|
+
environment,
|
|
88
|
+
authConfig,
|
|
89
|
+
listOptions
|
|
90
|
+
);
|
|
91
|
+
displayDeploymentList(extractDeployments(response), environment, authResult.controllerUrl);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logger.error(chalk.red(`❌ Failed to list deployments: ${error.message}`));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Display app deployment list to user
|
|
100
|
+
* @param {Array} deployments - Deployments array
|
|
101
|
+
* @param {string} appKey - Application key
|
|
102
|
+
* @param {string} environment - Environment key
|
|
103
|
+
* @param {string} controllerUrl - Controller URL
|
|
104
|
+
*/
|
|
105
|
+
function displayAppDeploymentList(deployments, appKey, environment, controllerUrl) {
|
|
106
|
+
logger.log(chalk.bold(`\n📋 Deployments for ${appKey} (${environment}) at ${controllerUrl}:\n`));
|
|
107
|
+
if (deployments.length === 0) {
|
|
108
|
+
logger.log(chalk.gray(' No deployments found for this application.\n'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
deployments.forEach((d) => {
|
|
112
|
+
const id = d.id ?? d.deploymentId ?? '-';
|
|
113
|
+
const status = d.status ?? '-';
|
|
114
|
+
const createdAt = d.createdAt ?? d.created ?? '';
|
|
115
|
+
logger.log(` ${chalk.cyan(id)} ${status} ${chalk.gray(createdAt)}`);
|
|
116
|
+
});
|
|
117
|
+
logger.log('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve controller URL, environment, and auth for deployment list commands
|
|
122
|
+
* @async
|
|
123
|
+
* @param {Object} options - Options with optional controller, environment
|
|
124
|
+
* @returns {Promise<{controllerUrl: string, environment: string, authResult: Object}>}
|
|
125
|
+
*/
|
|
126
|
+
async function resolveDeploymentListContext(options) {
|
|
127
|
+
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
128
|
+
if (!controllerUrl) {
|
|
129
|
+
logger.error(chalk.red('❌ Controller URL is required. Run "aifabrix login" first.'));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
const environment = options.environment || (await resolveEnvironment());
|
|
133
|
+
const authResult = await getDeploymentListAuth(controllerUrl);
|
|
134
|
+
if (!authResult || !authResult.token) {
|
|
135
|
+
logger.error(chalk.red(`❌ No authentication token for controller: ${controllerUrl}`));
|
|
136
|
+
logger.error(chalk.gray('Run: aifabrix login'));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
return { controllerUrl, environment, authResult };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Run app deployment list: list last N deployments for an application
|
|
144
|
+
* @async
|
|
145
|
+
* @param {string} appKey - Application key
|
|
146
|
+
* @param {Object} options - CLI options
|
|
147
|
+
* @param {string} [options.controller] - Controller URL override
|
|
148
|
+
* @param {string} [options.environment] - Environment key override
|
|
149
|
+
* @param {number} [options.pageSize] - Items per page (default 50)
|
|
150
|
+
* @returns {Promise<void>}
|
|
151
|
+
*/
|
|
152
|
+
async function runAppDeploymentList(appKey, options = {}) {
|
|
153
|
+
if (!appKey || typeof appKey !== 'string') {
|
|
154
|
+
logger.error(chalk.red('❌ Application key is required.'));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const { environment, authResult } = await resolveDeploymentListContext(options);
|
|
159
|
+
const authConfig = { type: 'bearer', token: authResult.token };
|
|
160
|
+
const listOptions = { pageSize: options.pageSize || DEFAULT_PAGE_SIZE };
|
|
161
|
+
try {
|
|
162
|
+
const response = await listApplicationDeployments(
|
|
163
|
+
authResult.controllerUrl,
|
|
164
|
+
environment,
|
|
165
|
+
appKey,
|
|
166
|
+
authConfig,
|
|
167
|
+
listOptions
|
|
168
|
+
);
|
|
169
|
+
displayAppDeploymentList(
|
|
170
|
+
extractDeployments(response),
|
|
171
|
+
appKey,
|
|
172
|
+
environment,
|
|
173
|
+
authResult.controllerUrl
|
|
174
|
+
);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
logger.error(chalk.red(`❌ Failed to list deployments for ${appKey}: ${error.message}`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
runDeploymentList,
|
|
183
|
+
runAppDeploymentList
|
|
184
|
+
};
|
package/lib/core/templates.js
CHANGED
|
@@ -83,7 +83,8 @@ function buildWebappVariables(appName, displayName, config, imageName, imageTag)
|
|
|
83
83
|
key: appName,
|
|
84
84
|
displayName: displayName,
|
|
85
85
|
description: `${appName.replace(/-/g, ' ')} application`,
|
|
86
|
-
type: appType
|
|
86
|
+
type: appType,
|
|
87
|
+
version: config.version || '1.0.0'
|
|
87
88
|
},
|
|
88
89
|
image: {
|
|
89
90
|
name: imageName,
|
|
@@ -126,11 +126,17 @@ function buildAuthentication(rbac) {
|
|
|
126
126
|
* @returns {Object} App metadata
|
|
127
127
|
*/
|
|
128
128
|
function buildAppMetadata(appName, variables) {
|
|
129
|
+
const rawVersion = variables.app?.version;
|
|
130
|
+
const version =
|
|
131
|
+
rawVersion !== undefined && rawVersion !== null && String(rawVersion).trim()
|
|
132
|
+
? String(rawVersion).trim()
|
|
133
|
+
: '1.0.0';
|
|
129
134
|
return {
|
|
130
135
|
key: variables.app?.key || appName,
|
|
131
136
|
displayName: variables.app?.displayName || appName,
|
|
132
137
|
description: variables.app?.description || '',
|
|
133
|
-
type: variables.app?.type || 'webapp'
|
|
138
|
+
type: variables.app?.type || 'webapp',
|
|
139
|
+
version
|
|
134
140
|
};
|
|
135
141
|
}
|
|
136
142
|
|
|
@@ -375,12 +381,11 @@ function buildOptionalFields(deployment, variables, rbac) {
|
|
|
375
381
|
* Builds deployment manifest structure
|
|
376
382
|
* @param {string} appName - Application name
|
|
377
383
|
* @param {Object} variables - Variables configuration
|
|
378
|
-
* @param {string} deploymentKey - Deployment key
|
|
379
384
|
* @param {Array} configuration - Environment configuration
|
|
380
385
|
* @param {Object|null} rbac - RBAC configuration
|
|
381
386
|
* @returns {Object} Deployment manifest
|
|
382
387
|
*/
|
|
383
|
-
function buildManifestStructure(appName, variables,
|
|
388
|
+
function buildManifestStructure(appName, variables, configuration, rbac) {
|
|
384
389
|
const registryMode = variables.image?.registryMode || 'external';
|
|
385
390
|
const filteredConfiguration = filterConfigurationByRegistryMode(configuration, registryMode);
|
|
386
391
|
const deployment = buildBaseDeployment(appName, variables, filteredConfiguration);
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const { detectAppType } = require('../utils/paths');
|
|
14
|
-
const { generateDeploymentKeyFromJson } = require('../core/key-generator');
|
|
15
14
|
const { loadSystemFile, loadDatasourceFiles } = require('./external');
|
|
16
15
|
const { loadVariables, loadRbac } = require('./helpers');
|
|
17
16
|
|
|
@@ -97,7 +96,7 @@ async function loadSystemWithRbac(appPath, schemaBasePath, systemFile) {
|
|
|
97
96
|
*
|
|
98
97
|
* @example
|
|
99
98
|
* const manifest = await generateControllerManifest('my-hubspot');
|
|
100
|
-
* // Returns: { key, displayName, description, type: "external", system: {...}, dataSources: [...]
|
|
99
|
+
* // Returns: { key, displayName, description, type: "external", system: {...}, dataSources: [...] }
|
|
101
100
|
*/
|
|
102
101
|
async function generateControllerManifest(appName, options = {}) {
|
|
103
102
|
if (!appName || typeof appName !== 'string') {
|
|
@@ -124,13 +123,15 @@ async function generateControllerManifest(appName, options = {}) {
|
|
|
124
123
|
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
125
124
|
const datasourceJsons = await loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles);
|
|
126
125
|
|
|
126
|
+
const appVersion = variables.app?.version || variables.externalIntegration?.version || '1.0.0';
|
|
127
|
+
|
|
127
128
|
// Build externalIntegration block (required by application schema for type: "external")
|
|
128
129
|
const externalIntegration = {
|
|
129
130
|
schemaBasePath: schemaBasePath,
|
|
130
131
|
systems: systemFiles,
|
|
131
132
|
dataSources: datasourceFiles,
|
|
132
133
|
autopublish: variables.externalIntegration.autopublish !== false, // default true
|
|
133
|
-
version:
|
|
134
|
+
version: appVersion
|
|
134
135
|
};
|
|
135
136
|
|
|
136
137
|
const manifest = {
|
|
@@ -138,6 +139,7 @@ async function generateControllerManifest(appName, options = {}) {
|
|
|
138
139
|
displayName: metadata.displayName,
|
|
139
140
|
description: metadata.description,
|
|
140
141
|
type: 'external',
|
|
142
|
+
version: appVersion,
|
|
141
143
|
externalIntegration: externalIntegration,
|
|
142
144
|
// Inline system and dataSources for atomic deployment (optional but recommended)
|
|
143
145
|
system: systemJson,
|
|
@@ -148,7 +150,6 @@ async function generateControllerManifest(appName, options = {}) {
|
|
|
148
150
|
requiresStorage: false
|
|
149
151
|
};
|
|
150
152
|
|
|
151
|
-
manifest.deploymentKey = generateDeploymentKeyFromJson(manifest);
|
|
152
153
|
return manifest;
|
|
153
154
|
}
|
|
154
155
|
|
package/lib/generator/index.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const _keyGenerator = require('../core/key-generator');
|
|
15
14
|
const _validator = require('../validation/validator');
|
|
16
15
|
const builders = require('./builders');
|
|
17
16
|
const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
@@ -19,6 +18,7 @@ const splitFunctions = require('./split');
|
|
|
19
18
|
const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./helpers');
|
|
20
19
|
const { generateExternalSystemApplicationSchema, splitExternalApplicationSchema } = require('./external');
|
|
21
20
|
const { generateControllerManifest } = require('./external-controller-manifest');
|
|
21
|
+
const { resolveVersionForApp } = require('../utils/image-version');
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Generates deployment JSON from application configuration files
|
|
@@ -65,21 +65,15 @@ function loadDeploymentConfigFiles(appPath, appType, appName) {
|
|
|
65
65
|
* @param {Object} variables - Variables configuration
|
|
66
66
|
* @param {Object} envTemplate - Environment template
|
|
67
67
|
* @param {Object} rbac - RBAC configuration
|
|
68
|
-
* @returns {Object} Deployment manifest
|
|
68
|
+
* @returns {Object} Deployment manifest
|
|
69
69
|
* @throws {Error} If validation fails
|
|
70
70
|
*/
|
|
71
71
|
function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
|
|
72
72
|
// Parse environment variables from template and merge portalInput from variables.yaml
|
|
73
73
|
const configuration = parseEnvironmentVariables(envTemplate, variables);
|
|
74
74
|
|
|
75
|
-
// Build deployment manifest
|
|
76
|
-
const deployment = builders.buildManifestStructure(appName, variables,
|
|
77
|
-
|
|
78
|
-
// Generate deploymentKey from the manifest object (excluding deploymentKey field)
|
|
79
|
-
const deploymentKey = _keyGenerator.generateDeploymentKeyFromJson(deployment);
|
|
80
|
-
|
|
81
|
-
// Add deploymentKey to manifest
|
|
82
|
-
deployment.deploymentKey = deploymentKey;
|
|
75
|
+
// Build deployment manifest (Controller computes deploymentKey from schema)
|
|
76
|
+
const deployment = builders.buildManifestStructure(appName, variables, configuration, rbac);
|
|
83
77
|
|
|
84
78
|
// Validate deployment JSON against schema
|
|
85
79
|
const validation = _validator.validateDeploymentJson(deployment);
|
|
@@ -114,10 +108,13 @@ async function buildDeploymentManifestInMemory(appName, options = {}) {
|
|
|
114
108
|
}
|
|
115
109
|
|
|
116
110
|
const { variables, envTemplate, rbac } = loadDeploymentConfigFiles(appPath, appType, appName);
|
|
111
|
+
const resolved = await resolveVersionForApp(appName, variables, { updateBuilder: false });
|
|
112
|
+
const variablesWithVersion = {
|
|
113
|
+
...variables,
|
|
114
|
+
app: { ...variables.app, version: resolved.version }
|
|
115
|
+
};
|
|
117
116
|
const configuration = parseEnvironmentVariables(envTemplate, variables);
|
|
118
|
-
const deployment = builders.buildManifestStructure(appName,
|
|
119
|
-
const deploymentKey = _keyGenerator.generateDeploymentKeyFromJson(deployment);
|
|
120
|
-
deployment.deploymentKey = deploymentKey;
|
|
117
|
+
const deployment = builders.buildManifestStructure(appName, variablesWithVersion, configuration, rbac);
|
|
121
118
|
|
|
122
119
|
return { deployment, appPath };
|
|
123
120
|
}
|
|
@@ -145,7 +142,12 @@ async function generateDeployJson(appName, options = {}) {
|
|
|
145
142
|
|
|
146
143
|
// Regular app: generate deployment manifest
|
|
147
144
|
const { variables, envTemplate, rbac, jsonPath } = loadDeploymentConfigFiles(appPath, appType, appName);
|
|
148
|
-
const
|
|
145
|
+
const resolved = await resolveVersionForApp(appName, variables, { updateBuilder: false });
|
|
146
|
+
const variablesWithVersion = {
|
|
147
|
+
...variables,
|
|
148
|
+
app: { ...variables.app, version: resolved.version }
|
|
149
|
+
};
|
|
150
|
+
const deployment = buildAndValidateDeployment(appName, variablesWithVersion, envTemplate, rbac);
|
|
149
151
|
|
|
150
152
|
// Write deployment JSON
|
|
151
153
|
const jsonContent = JSON.stringify(deployment, null, 2);
|
package/lib/generator/split.js
CHANGED
|
@@ -98,6 +98,7 @@ function extractAppSection(deployment) {
|
|
|
98
98
|
if (deployment.displayName) app.displayName = deployment.displayName;
|
|
99
99
|
if (deployment.description) app.description = deployment.description;
|
|
100
100
|
if (deployment.type) app.type = deployment.type;
|
|
101
|
+
if (deployment.version) app.version = deployment.version;
|
|
101
102
|
return app;
|
|
102
103
|
}
|
|
103
104
|
|
package/lib/generator/wizard.js
CHANGED
|
@@ -234,8 +234,11 @@ async function generateOrUpdateVariablesYaml(params) {
|
|
|
234
234
|
key: appName,
|
|
235
235
|
displayName: systemConfig.displayName || appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
|
236
236
|
description: systemConfig.description || `External system integration for ${appName}`,
|
|
237
|
-
type: 'external'
|
|
237
|
+
type: 'external',
|
|
238
|
+
version: '1.0.0'
|
|
238
239
|
};
|
|
240
|
+
} else {
|
|
241
|
+
variables.app.version = variables.app.version || '1.0.0';
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
// Set deployment config if not present
|
|
@@ -57,8 +57,7 @@
|
|
|
57
57
|
"key",
|
|
58
58
|
"displayName",
|
|
59
59
|
"description",
|
|
60
|
-
"type"
|
|
61
|
-
"deploymentKey"
|
|
60
|
+
"type"
|
|
62
61
|
],
|
|
63
62
|
"properties": {
|
|
64
63
|
"key": {
|
|
@@ -91,6 +90,11 @@
|
|
|
91
90
|
"external"
|
|
92
91
|
]
|
|
93
92
|
},
|
|
93
|
+
"version": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "Application version (semantic); default 1.0.0 when empty",
|
|
96
|
+
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$"
|
|
97
|
+
},
|
|
94
98
|
"image": {
|
|
95
99
|
"type": "string",
|
|
96
100
|
"description": "Container image reference",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Deployment rules – Central mapping for Controller
|
|
2
|
+
#
|
|
3
|
+
# Defines which manifest paths trigger deployment and which can differ per environment.
|
|
4
|
+
# Controller uses this file (or equivalent) for deployment key computation and value merge.
|
|
5
|
+
# Schemas (application, external-system, external-datasource) remain clean; no x-* annotations.
|
|
6
|
+
#
|
|
7
|
+
# Semantics:
|
|
8
|
+
# triggerPaths: Change affects deployment key / requires deploy
|
|
9
|
+
# overridablePaths: Value can differ per environment (preserve on promote)
|
|
10
|
+
# A path may appear in both (e.g. authentication.endpoints triggers deploy and is overridable).
|
|
11
|
+
#
|
|
12
|
+
# Path format: Dot notation. Child paths override parent when both match.
|
|
13
|
+
# Schema keys: application | externalSystem | externalDataSource
|
|
14
|
+
|
|
15
|
+
application:
|
|
16
|
+
triggerPaths:
|
|
17
|
+
- key
|
|
18
|
+
- displayName
|
|
19
|
+
- description
|
|
20
|
+
- type
|
|
21
|
+
- version
|
|
22
|
+
- image
|
|
23
|
+
- registryMode
|
|
24
|
+
- port
|
|
25
|
+
- requiresDatabase
|
|
26
|
+
- databases
|
|
27
|
+
- requiresRedis
|
|
28
|
+
- requiresStorage
|
|
29
|
+
- configuration
|
|
30
|
+
- configuration.items
|
|
31
|
+
- configuration.items.required
|
|
32
|
+
- configuration.items.portalInput
|
|
33
|
+
- healthCheck
|
|
34
|
+
- healthCheck.path
|
|
35
|
+
- healthCheck.probePath
|
|
36
|
+
- healthCheck.probeRequestType
|
|
37
|
+
- healthCheck.probeProtocol
|
|
38
|
+
- frontDoorRouting
|
|
39
|
+
- authentication
|
|
40
|
+
- roles
|
|
41
|
+
- permissions
|
|
42
|
+
- repository
|
|
43
|
+
- startupCommand
|
|
44
|
+
- runtimeVersion
|
|
45
|
+
- scaling
|
|
46
|
+
- build
|
|
47
|
+
- deployment
|
|
48
|
+
- externalIntegration
|
|
49
|
+
overridablePaths:
|
|
50
|
+
- configuration.items.value
|
|
51
|
+
- authentication.endpoints
|
|
52
|
+
- deployment.controllerUrl
|
|
53
|
+
- healthCheck.interval
|
|
54
|
+
- healthCheck.probeIntervalInSeconds
|
|
55
|
+
|
|
56
|
+
externalSystem:
|
|
57
|
+
triggerPaths:
|
|
58
|
+
- key
|
|
59
|
+
- displayName
|
|
60
|
+
- description
|
|
61
|
+
- type
|
|
62
|
+
- enabled
|
|
63
|
+
- environment
|
|
64
|
+
- authentication
|
|
65
|
+
- openapi
|
|
66
|
+
- mcp
|
|
67
|
+
- dataSources
|
|
68
|
+
- configuration
|
|
69
|
+
- configuration.items
|
|
70
|
+
- tags
|
|
71
|
+
- roles
|
|
72
|
+
- permissions
|
|
73
|
+
- endpoints
|
|
74
|
+
- endpointsActive
|
|
75
|
+
- generateMcpContract
|
|
76
|
+
- generateOpenApiContract
|
|
77
|
+
overridablePaths:
|
|
78
|
+
- environment.baseUrl
|
|
79
|
+
- environment.region
|
|
80
|
+
- authentication.oauth2
|
|
81
|
+
- authentication.apikey
|
|
82
|
+
- authentication.basic
|
|
83
|
+
- authentication.aad
|
|
84
|
+
- openapi.specUrl
|
|
85
|
+
- openapi.documentKey
|
|
86
|
+
- mcp.serverUrl
|
|
87
|
+
- mcp.toolPrefix
|
|
88
|
+
- configuration.items.value
|
|
89
|
+
- credentialIdOrKey
|
|
90
|
+
|
|
91
|
+
externalDataSource:
|
|
92
|
+
triggerPaths:
|
|
93
|
+
- key
|
|
94
|
+
- displayName
|
|
95
|
+
- description
|
|
96
|
+
- enabled
|
|
97
|
+
- systemKey
|
|
98
|
+
- entityType
|
|
99
|
+
- resourceType
|
|
100
|
+
- version
|
|
101
|
+
- metadataSchema
|
|
102
|
+
- fieldMappings
|
|
103
|
+
- exposed
|
|
104
|
+
- validation
|
|
105
|
+
- quality
|
|
106
|
+
- indexing
|
|
107
|
+
- context
|
|
108
|
+
- documentStorage
|
|
109
|
+
- portalInput
|
|
110
|
+
- capabilities
|
|
111
|
+
- execution
|
|
112
|
+
- config
|
|
113
|
+
- openapi
|
|
114
|
+
overridablePaths:
|
|
115
|
+
- sync
|
|
116
|
+
- sync.mode
|
|
117
|
+
- sync.schedule
|
|
118
|
+
- sync.batchSize
|
|
119
|
+
- sync.maxParallelRequests
|
|
120
|
+
- openapi.baseUrl
|
|
121
|
+
- openapi.resourcePath
|
|
@@ -458,7 +458,8 @@ async function generateDockerCompose(appName, appConfig, options) {
|
|
|
458
458
|
const language = appConfig.build?.language || appConfig.language || 'typescript';
|
|
459
459
|
const template = loadDockerComposeTemplate(language);
|
|
460
460
|
const port = options.port || appConfig.port || 3000;
|
|
461
|
-
const imageOverride = options.image || options.imageOverride
|
|
461
|
+
const imageOverride = options.image || options.imageOverride ||
|
|
462
|
+
(options.tag ? `${getImageName(appConfig, appName)}:${options.tag}` : null);
|
|
462
463
|
const { devId, idNum } = await getDeveloperIdAndNumeric();
|
|
463
464
|
const { networkName, containerName } = buildNetworkAndContainerNames(appName, devId, idNum);
|
|
464
465
|
const serviceConfig = buildServiceConfig(appName, appConfig, port, devId, imageOverride);
|
|
@@ -75,7 +75,6 @@ const CATEGORIES = [
|
|
|
75
75
|
{ name: 'resolve', term: 'resolve <app>' },
|
|
76
76
|
{ name: 'json', term: 'json <app>' },
|
|
77
77
|
{ name: 'split-json', term: 'split-json <app>' },
|
|
78
|
-
{ name: 'genkey', term: 'genkey <app>' },
|
|
79
78
|
{ name: 'validate', term: 'validate <appOrFile>' },
|
|
80
79
|
{ name: 'diff', term: 'diff <file1> <file2>' }
|
|
81
80
|
]
|