@aifabrix/builder 2.39.3 → 2.40.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/.cursor/rules/project-rules.mdc +6 -6
- package/README.md +3 -3
- package/babel.config.js +6 -0
- package/integration/hubspot/README.md +53 -141
- package/integration/hubspot/application.yaml +37 -0
- package/integration/hubspot/env.template +2 -11
- package/integration/hubspot/hubspot-deploy.json +1 -0
- package/integration/hubspot/test.js +5 -5
- package/jest.config.manual.js +29 -0
- package/lib/api/credentials.api.js +5 -5
- package/lib/api/deployments.api.js +2 -2
- package/lib/api/pipeline.api.js +17 -17
- package/lib/api/wizard.api.js +2 -2
- package/lib/app/config.js +11 -6
- package/lib/app/deploy-config.js +13 -16
- package/lib/app/deploy.js +29 -22
- package/lib/app/display.js +1 -1
- package/lib/app/dockerfile.js +11 -12
- package/lib/app/helpers.js +51 -13
- package/lib/app/index.js +14 -2
- package/lib/app/prompts.js +37 -45
- package/lib/app/push.js +8 -11
- package/lib/app/readme.js +16 -12
- package/lib/app/register.js +1 -1
- package/lib/app/run-helpers.js +31 -22
- package/lib/app/run.js +44 -5
- package/lib/app/show-display.js +104 -44
- package/lib/app/show.js +123 -43
- package/lib/build/index.js +11 -18
- package/lib/cli/setup-app.js +36 -29
- package/lib/cli/setup-auth.js +19 -15
- package/lib/cli/setup-credential-deployment.js +3 -1
- package/lib/cli/setup-external-system.js +35 -16
- package/lib/cli/setup-infra.js +45 -23
- package/lib/cli/setup-utility.js +85 -31
- package/lib/commands/app-logs.js +28 -20
- package/lib/commands/app.js +30 -26
- package/lib/commands/auth-status.js +36 -3
- package/lib/commands/convert.js +202 -0
- package/lib/commands/credential-list.js +78 -17
- package/lib/commands/datasource.js +24 -24
- package/lib/commands/deployment-list.js +13 -6
- package/lib/commands/up-common.js +80 -42
- package/lib/commands/up-dataplane.js +15 -14
- package/lib/commands/up-miso.js +15 -14
- package/lib/commands/upload.js +163 -0
- package/lib/commands/wizard-core.js +5 -4
- package/lib/core/diff.js +84 -9
- package/lib/core/key-generator.js +9 -12
- package/lib/core/secrets-docker-env.js +2 -2
- package/lib/core/secrets.js +3 -2
- package/lib/core/templates.js +2 -2
- package/lib/datasource/deploy.js +2 -1
- package/lib/deployment/deployer.js +76 -48
- package/lib/external-system/delete.js +0 -1
- package/lib/external-system/deploy-helpers.js +5 -6
- package/lib/external-system/deploy.js +7 -2
- package/lib/external-system/download-helpers.js +4 -4
- package/lib/external-system/download.js +11 -10
- package/lib/external-system/generator.js +19 -17
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +1 -1
- package/lib/generator/external-controller-manifest.js +26 -29
- package/lib/generator/external-schema-utils.js +6 -18
- package/lib/generator/external.js +32 -27
- package/lib/generator/github.js +1 -1
- package/lib/generator/helpers.js +12 -19
- package/lib/generator/index.js +15 -15
- package/lib/generator/parse-image.js +35 -0
- package/lib/generator/split-readme.js +105 -0
- package/lib/generator/split-variables.js +149 -0
- package/lib/generator/split.js +86 -246
- package/lib/generator/wizard.js +51 -70
- package/lib/schema/application-schema.json +4 -4
- package/lib/schema/external-datasource.schema.json +5 -0
- package/lib/schema/external-system.schema.json +10 -0
- package/lib/utils/app-config-resolver.js +52 -0
- package/lib/utils/app-register-api.js +1 -1
- package/lib/utils/app-register-auth.js +1 -1
- package/lib/utils/app-register-config.js +16 -23
- package/lib/utils/app-register-validator.js +2 -2
- package/lib/utils/cli-utils.js +47 -3
- package/lib/utils/config-format.js +154 -0
- package/lib/utils/config-paths.js +19 -52
- package/lib/utils/config-tokens.js +1 -0
- package/lib/utils/docker-build.js +71 -94
- package/lib/utils/dockerfile-utils.js +1 -1
- package/lib/utils/env-copy.js +4 -4
- package/lib/utils/env-ports.js +2 -2
- package/lib/utils/error-formatter.js +1 -1
- package/lib/utils/error-formatters/validation-errors.js +1 -1
- package/lib/utils/external-readme.js +12 -5
- package/lib/utils/external-system-test-helpers.js +2 -0
- package/lib/utils/health-check.js +55 -66
- package/lib/utils/image-version.js +12 -21
- package/lib/utils/paths.js +45 -66
- package/lib/utils/port-resolver.js +8 -8
- package/lib/utils/schema-loader.js +22 -0
- package/lib/utils/schema-resolver.js +23 -33
- package/lib/utils/secrets-helpers.js +7 -7
- package/lib/utils/secrets-utils.js +10 -12
- package/lib/utils/template-helpers.js +13 -13
- package/lib/utils/token-manager.js +20 -2
- package/lib/utils/variable-transformer.js +2 -2
- package/lib/validation/validate-display.js +3 -4
- package/lib/validation/validate.js +34 -28
- package/lib/validation/validator.js +50 -30
- package/package.json +4 -1
- package/templates/README.md +1 -1
- package/templates/applications/README.md.hbs +3 -3
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/external-system/README.md.hbs +4 -4
- package/templates/external-system/external-system.json.hbs +1 -16
- package/integration/hubspot/variables.yaml +0 -17
- /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
- /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
- /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
|
@@ -10,24 +10,27 @@
|
|
|
10
10
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
|
-
const yaml = require('js-yaml');
|
|
14
13
|
const chalk = require('chalk');
|
|
15
14
|
const logger = require('../utils/logger');
|
|
16
15
|
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
17
|
+
const { isYamlPath } = require('../utils/config-format');
|
|
17
18
|
const { copyTemplateFiles } = require('../validation/template');
|
|
18
19
|
const { ensureReadmeForAppPath, ensureReadmeForApp } = require('../app/readme');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Copy template to a target path if
|
|
22
|
+
* Copy template to a target path if application config is missing there.
|
|
22
23
|
* After copy, generates README.md from templates/applications/README.md.hbs.
|
|
23
24
|
* @param {string} appName - Application name
|
|
24
25
|
* @param {string} targetAppPath - Target directory (e.g. builder/keycloak)
|
|
25
26
|
* @returns {Promise<boolean>} True if template was copied, false if already present
|
|
26
27
|
*/
|
|
27
28
|
async function ensureTemplateAtPath(appName, targetAppPath) {
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
try {
|
|
30
|
+
pathsUtil.resolveApplicationConfigPath(targetAppPath);
|
|
30
31
|
return false;
|
|
32
|
+
} catch {
|
|
33
|
+
// No application config; copy template
|
|
31
34
|
}
|
|
32
35
|
await copyTemplateFiles(appName, targetAppPath);
|
|
33
36
|
await ensureReadmeForAppPath(targetAppPath, appName);
|
|
@@ -37,18 +40,55 @@ async function ensureTemplateAtPath(appName, targetAppPath) {
|
|
|
37
40
|
/**
|
|
38
41
|
* Resolve the directory (folder) that would contain the .env file for envOutputPath.
|
|
39
42
|
* @param {string} envOutputPath - Value from build.envOutputPath (e.g. ../../.env)
|
|
40
|
-
* @param {string}
|
|
43
|
+
* @param {string} configPath - Path to application config file
|
|
41
44
|
* @returns {string} Absolute path to the folder that would contain the output .env file
|
|
42
45
|
*/
|
|
43
|
-
function getEnvOutputPathFolder(envOutputPath,
|
|
44
|
-
const
|
|
45
|
-
const resolvedFile = path.resolve(
|
|
46
|
+
function getEnvOutputPathFolder(envOutputPath, configPath) {
|
|
47
|
+
const configDir = path.dirname(configPath);
|
|
48
|
+
const resolvedFile = path.resolve(configDir, envOutputPath);
|
|
46
49
|
return path.dirname(resolvedFile);
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
|
-
*
|
|
53
|
+
* Patches envOutputPath to null in raw YAML content so comments and formatting are preserved.
|
|
54
|
+
* Only touches the line that sets envOutputPath under build.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} content - Raw file content (YAML)
|
|
57
|
+
* @returns {string|null} Patched content, or null if no change
|
|
58
|
+
*/
|
|
59
|
+
function patchEnvOutputPathInYamlContent(content) {
|
|
60
|
+
const re = /^(\s*)envOutputPath\s*:\s*.+$/m;
|
|
61
|
+
const match = content.match(re);
|
|
62
|
+
if (!match) return null;
|
|
63
|
+
const indent = match[1];
|
|
64
|
+
return content.replace(re, `${indent}envOutputPath: null`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Patches config file to set build.envOutputPath to null, preserving comments (YAML only).
|
|
69
|
+
* For JSON or when in-place patch fails, falls back to full write.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} configPath - Path to application config file
|
|
72
|
+
* @returns {boolean} True if file was modified
|
|
73
|
+
*/
|
|
74
|
+
function patchEnvOutputPathInFile(configPath) {
|
|
75
|
+
if (!isYamlPath(configPath)) {
|
|
76
|
+
const variables = loadConfigFile(configPath);
|
|
77
|
+
const updated = { ...variables, build: { ...(variables.build || {}), envOutputPath: null } };
|
|
78
|
+
writeConfigFile(configPath, updated);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
82
|
+
const patched = patchEnvOutputPathInYamlContent(content);
|
|
83
|
+
if (patched === null) return false;
|
|
84
|
+
fs.writeFileSync(configPath, patched, 'utf8');
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validates envOutputPath: if the target folder does not exist, patches application config to set envOutputPath to null.
|
|
51
90
|
* Used by up-platform, up-miso, up-dataplane so we do not keep a path that points outside an existing tree.
|
|
91
|
+
* Patches in place for YAML to preserve comments.
|
|
52
92
|
*
|
|
53
93
|
* @param {string} appName - Application name (e.g. keycloak, miso-controller, dataplane)
|
|
54
94
|
*/
|
|
@@ -59,48 +99,44 @@ function validateEnvOutputPathFolderOrNull(appName) {
|
|
|
59
99
|
if (path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
|
|
60
100
|
pathsToPatch.push(cwdBuilderPath);
|
|
61
101
|
}
|
|
62
|
-
const envOutputPathLine = /^(\s*envOutputPath:)\s*.*$/m;
|
|
63
|
-
const replacement = '$1 null # deploy only, no copy';
|
|
64
102
|
for (const appPath of pathsToPatch) {
|
|
65
|
-
|
|
66
|
-
|
|
103
|
+
let configPath;
|
|
104
|
+
try {
|
|
105
|
+
configPath = pathsUtil.resolveApplicationConfigPath(appPath);
|
|
106
|
+
} catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
67
109
|
try {
|
|
68
|
-
const
|
|
69
|
-
const variables = yaml.load(content);
|
|
110
|
+
const variables = loadConfigFile(configPath);
|
|
70
111
|
const value = variables?.build?.envOutputPath;
|
|
71
112
|
if (value === null || value === undefined || value === '') continue;
|
|
72
|
-
const folder = getEnvOutputPathFolder(String(value).trim(),
|
|
113
|
+
const folder = getEnvOutputPathFolder(String(value).trim(), configPath);
|
|
73
114
|
if (fs.existsSync(folder)) continue;
|
|
74
|
-
|
|
75
|
-
fs.writeFileSync(variablesPath, newContent, 'utf8');
|
|
115
|
+
patchEnvOutputPathInFile(configPath);
|
|
76
116
|
} catch (err) {
|
|
77
|
-
logger.warn(chalk.yellow(`Could not validate envOutputPath in ${
|
|
117
|
+
logger.warn(chalk.yellow(`Could not validate envOutputPath in ${configPath}: ${err.message}`));
|
|
78
118
|
}
|
|
79
119
|
}
|
|
80
120
|
}
|
|
81
121
|
|
|
82
122
|
/**
|
|
83
|
-
* Patches a single
|
|
123
|
+
* Patches a single application config file to set build.envOutputPath to null for deploy-only.
|
|
124
|
+
* Only writes when a change is needed (value is set and target folder does not exist).
|
|
125
|
+
* Uses in-place YAML patch when possible to preserve comments.
|
|
84
126
|
*
|
|
85
|
-
* @param {string}
|
|
86
|
-
* @param {RegExp} envOutputPathLine - Regex for envOutputPath line
|
|
87
|
-
* @param {string} replacement - Replacement string
|
|
127
|
+
* @param {string} configPath - Path to application config file
|
|
88
128
|
*/
|
|
89
|
-
function patchOneVariablesFileForDeployOnly(
|
|
90
|
-
const
|
|
91
|
-
if (!envOutputPathLine.test(content)) return;
|
|
92
|
-
const variables = yaml.load(content);
|
|
129
|
+
function patchOneVariablesFileForDeployOnly(configPath) {
|
|
130
|
+
const variables = loadConfigFile(configPath);
|
|
93
131
|
const value = variables?.build?.envOutputPath;
|
|
94
|
-
if (value
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const newContent = content.replace(envOutputPathLine, replacement);
|
|
99
|
-
fs.writeFileSync(variablesPath, newContent, 'utf8');
|
|
132
|
+
if (value === null || value === undefined || value === '') return;
|
|
133
|
+
const folder = getEnvOutputPathFolder(String(value).trim(), configPath);
|
|
134
|
+
if (fs.existsSync(folder)) return;
|
|
135
|
+
patchEnvOutputPathInFile(configPath);
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
/**
|
|
103
|
-
* Patches
|
|
139
|
+
* Patches application config to set build.envOutputPath to null for deploy-only (no local code).
|
|
104
140
|
* Only patches when the target folder does NOT exist; when folder exists, keeps the value.
|
|
105
141
|
* Use when running up-miso/up-platform so we do not copy .env to repo paths or show that message.
|
|
106
142
|
* Patches both primary builder path and cwd/builder if different.
|
|
@@ -114,22 +150,24 @@ function patchEnvOutputPathForDeployOnly(appName) {
|
|
|
114
150
|
if (path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
|
|
115
151
|
pathsToPatch.push(cwdBuilderPath);
|
|
116
152
|
}
|
|
117
|
-
const envOutputPathLine = /^(\s*envOutputPath:)\s*.*$/m;
|
|
118
|
-
const replacement = '$1 null # deploy only, no copy';
|
|
119
153
|
for (const appPath of pathsToPatch) {
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
let configPath;
|
|
155
|
+
try {
|
|
156
|
+
configPath = pathsUtil.resolveApplicationConfigPath(appPath);
|
|
157
|
+
} catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
122
160
|
try {
|
|
123
|
-
patchOneVariablesFileForDeployOnly(
|
|
161
|
+
patchOneVariablesFileForDeployOnly(configPath);
|
|
124
162
|
} catch (err) {
|
|
125
|
-
logger.warn(chalk.yellow(`Could not patch envOutputPath in ${
|
|
163
|
+
logger.warn(chalk.yellow(`Could not patch envOutputPath in ${configPath}: ${err.message}`));
|
|
126
164
|
}
|
|
127
165
|
}
|
|
128
166
|
}
|
|
129
167
|
|
|
130
168
|
/**
|
|
131
|
-
* Ensures builder app directory exists from template if
|
|
132
|
-
* If builder/<appName>/
|
|
169
|
+
* Ensures builder app directory exists from template if application config is missing.
|
|
170
|
+
* If builder/<appName>/application config does not exist, copies from templates/applications/<appName>.
|
|
133
171
|
* Uses AIFABRIX_BUILDER_DIR when set (e.g. by up-miso/up-dataplane from config aifabrix-env-config).
|
|
134
172
|
* When using a custom builder dir, also populates cwd/builder/<appName> so the repo's builder/ is not empty.
|
|
135
173
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Always local deployment: registers or rotates dataplane app in dev, sends
|
|
5
5
|
* deployment manifest to Miso Controller, then runs the dataplane app locally
|
|
6
|
-
* (same as aifabrix deploy dataplane --
|
|
6
|
+
* (same as aifabrix deploy dataplane --local). If app is already
|
|
7
7
|
* registered, uses rotate-secret; otherwise registers.
|
|
8
8
|
*
|
|
9
9
|
* @fileoverview up-dataplane command implementation
|
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
* @version 2.0.0
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const yaml = require('js-yaml');
|
|
17
14
|
const chalk = require('chalk');
|
|
15
|
+
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
18
17
|
const logger = require('../utils/logger');
|
|
19
18
|
const config = require('../core/config');
|
|
20
19
|
const { checkAuthentication } = require('../utils/app-register-auth');
|
|
@@ -47,20 +46,22 @@ async function registerOrRotateDataplane(options, controllerUrl, environmentKey,
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
|
-
* Build full image ref from registry and dataplane
|
|
49
|
+
* Build full image ref from registry and dataplane config (registry/name:tag)
|
|
51
50
|
* @param {string} registry - Registry URL
|
|
52
51
|
* @returns {string|undefined} Full image reference or undefined
|
|
53
52
|
*/
|
|
54
53
|
function buildDataplaneImageRef(registry) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
try {
|
|
55
|
+
const builderPath = pathsUtil.getBuilderPath('dataplane');
|
|
56
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
57
|
+
const variables = loadConfigFile(configPath);
|
|
58
|
+
const name = variables?.image?.name || variables?.app?.key || 'dataplane';
|
|
59
|
+
const tag = variables?.image?.tag || 'latest';
|
|
60
|
+
const base = (registry || '').replace(/\/+$/, '');
|
|
61
|
+
return base ? `${base}/${name}:${tag}` : undefined;
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/**
|
package/lib/commands/up-miso.js
CHANGED
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
* @version 2.0.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
12
|
const chalk = require('chalk');
|
|
13
|
+
const pathsUtil = require('../utils/paths');
|
|
14
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
16
15
|
const logger = require('../utils/logger');
|
|
17
16
|
const config = require('../core/config');
|
|
18
17
|
const secrets = require('../core/secrets');
|
|
@@ -21,7 +20,7 @@ const app = require('../app');
|
|
|
21
20
|
const { saveLocalSecret } = require('../utils/local-secrets');
|
|
22
21
|
const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
23
22
|
|
|
24
|
-
/** Keycloak base port (from templates/applications/keycloak
|
|
23
|
+
/** Keycloak base port (from templates/applications/keycloak application config) */
|
|
25
24
|
const KEYCLOAK_BASE_PORT = 8082;
|
|
26
25
|
/** Miso controller base port (dev-config app base) */
|
|
27
26
|
const MISO_BASE_PORT = 3000;
|
|
@@ -46,21 +45,23 @@ function parseImageOptions(imageOpts) {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
/**
|
|
49
|
-
* Build full image ref from registry and app
|
|
48
|
+
* Build full image ref from registry and app config (registry/name:tag)
|
|
50
49
|
* @param {string} appName - keycloak or miso-controller
|
|
51
50
|
* @param {string} registry - Registry URL
|
|
52
51
|
* @returns {string} Full image reference
|
|
53
52
|
*/
|
|
54
53
|
function buildImageRefFromRegistry(appName, registry) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
try {
|
|
55
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
56
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
57
|
+
const variables = loadConfigFile(configPath);
|
|
58
|
+
const name = variables?.image?.name || variables?.app?.key || appName;
|
|
59
|
+
const tag = variables?.image?.tag || 'latest';
|
|
60
|
+
const base = (registry || '').replace(/\/+$/, '');
|
|
61
|
+
return base ? `${base}/${name}:${tag}` : undefined;
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/**
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload external system to dataplane (upload → validate → publish).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Upload command handler for aifabrix upload <system-key>
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
12
|
+
const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
|
|
13
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
14
|
+
const { validateExternalSystemComplete } = require('../validation/validate');
|
|
15
|
+
const { displayValidationResults } = require('../validation/validate-display');
|
|
16
|
+
const { generateControllerManifest } = require('../generator/external-controller-manifest');
|
|
17
|
+
const {
|
|
18
|
+
uploadApplicationViaPipeline,
|
|
19
|
+
validateUploadViaPipeline,
|
|
20
|
+
publishUploadViaPipeline
|
|
21
|
+
} = require('../api/pipeline.api');
|
|
22
|
+
const { formatApiError } = require('../utils/api-error-handler');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validates system-key format (same as download).
|
|
26
|
+
* @param {string} systemKey - System key
|
|
27
|
+
* @throws {Error} If invalid
|
|
28
|
+
*/
|
|
29
|
+
function validateSystemKeyFormat(systemKey) {
|
|
30
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
31
|
+
throw new Error('System key is required and must be a string');
|
|
32
|
+
}
|
|
33
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
34
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds pipeline upload payload from controller manifest.
|
|
40
|
+
* Payload: { version, application, dataSources }; application = system with RBAC.
|
|
41
|
+
* @param {Object} manifest - Controller manifest from generateControllerManifest
|
|
42
|
+
* @returns {Object} { version, application, dataSources }
|
|
43
|
+
*/
|
|
44
|
+
function buildUploadPayload(manifest) {
|
|
45
|
+
return {
|
|
46
|
+
version: manifest.version || '1.0.0',
|
|
47
|
+
application: manifest.system,
|
|
48
|
+
dataSources: manifest.dataSources || []
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolves dataplane URL and auth (same pattern as download).
|
|
54
|
+
* @param {string} systemKey - System key
|
|
55
|
+
* @param {Object} options - Options with optional dataplane override
|
|
56
|
+
* @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }>}
|
|
57
|
+
*/
|
|
58
|
+
async function resolveDataplaneAndAuth(systemKey, options) {
|
|
59
|
+
const { resolveEnvironment } = require('../core/config');
|
|
60
|
+
const environment = await resolveEnvironment();
|
|
61
|
+
const controllerUrl = await resolveControllerUrl();
|
|
62
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
63
|
+
|
|
64
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
65
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <system-key>" first.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let dataplaneUrl;
|
|
69
|
+
if (options.dataplane) {
|
|
70
|
+
dataplaneUrl = options.dataplane.replace(/\/$/, '');
|
|
71
|
+
} else {
|
|
72
|
+
logger.log(chalk.blue('Resolving dataplane URL...'));
|
|
73
|
+
dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { dataplaneUrl, authConfig, environment };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Runs upload → validate → publish on the dataplane.
|
|
81
|
+
* @param {string} dataplaneUrl - Dataplane base URL
|
|
82
|
+
* @param {Object} authConfig - Auth config
|
|
83
|
+
* @param {Object} payload - { version, application, dataSources }
|
|
84
|
+
* @returns {Promise<{ uploadId: string }>}
|
|
85
|
+
*/
|
|
86
|
+
async function runUploadValidatePublish(dataplaneUrl, authConfig, payload) {
|
|
87
|
+
const uploadRes = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, payload);
|
|
88
|
+
const uploadId = uploadRes?.data?.uploadId ?? uploadRes?.data?.id ?? uploadRes?.uploadId;
|
|
89
|
+
if (!uploadId) {
|
|
90
|
+
const msg = uploadRes?.success === false
|
|
91
|
+
? formatApiError(uploadRes, dataplaneUrl)
|
|
92
|
+
: 'Upload did not return an upload ID';
|
|
93
|
+
throw new Error(msg);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const validateRes = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
|
|
97
|
+
if (validateRes?.success === false) {
|
|
98
|
+
const msg = formatApiError(validateRes, dataplaneUrl);
|
|
99
|
+
throw new Error(`Upload validation failed: ${msg}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
|
|
103
|
+
return { uploadId };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Throws if validation result is invalid (displays results first).
|
|
108
|
+
* @param {Object} validationResult - Result from validateExternalSystemComplete
|
|
109
|
+
* @throws {Error} If validationResult.valid is false
|
|
110
|
+
*/
|
|
111
|
+
function throwIfValidationFailed(validationResult) {
|
|
112
|
+
if (!validationResult.valid) {
|
|
113
|
+
displayValidationResults(validationResult);
|
|
114
|
+
throw new Error('Validation failed. Fix errors before uploading.');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Uploads external system to dataplane (upload → validate → publish). No controller deploy.
|
|
120
|
+
* @param {string} systemKey - External system key (integration/<system-key>/)
|
|
121
|
+
* @param {Object} [options] - Options
|
|
122
|
+
* @param {boolean} [options.dryRun] - Validate and build payload only; no API calls
|
|
123
|
+
* @param {string} [options.dataplane] - Override dataplane URL
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
* @throws {Error} If validation or API calls fail
|
|
126
|
+
*/
|
|
127
|
+
async function uploadExternalSystem(systemKey, options = {}) {
|
|
128
|
+
validateSystemKeyFormat(systemKey);
|
|
129
|
+
|
|
130
|
+
logger.log(chalk.blue(`\nUploading external system to dataplane: ${systemKey}`));
|
|
131
|
+
|
|
132
|
+
const validationResult = await validateExternalSystemComplete(systemKey, { type: 'external' });
|
|
133
|
+
throwIfValidationFailed(validationResult);
|
|
134
|
+
logger.log(chalk.green('Validation passed.'));
|
|
135
|
+
|
|
136
|
+
const manifest = await generateControllerManifest(systemKey, { type: 'external' });
|
|
137
|
+
const payload = buildUploadPayload(manifest);
|
|
138
|
+
|
|
139
|
+
if (options.dryRun) {
|
|
140
|
+
logger.log(chalk.yellow('Dry run: would upload payload (no API calls).'));
|
|
141
|
+
logger.log(chalk.gray(` System: ${manifest.key}, version: ${payload.version}, datasources: ${payload.dataSources.length}`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey, options);
|
|
146
|
+
requireBearerForDataplanePipeline(authConfig);
|
|
147
|
+
logger.log(chalk.blue(`Dataplane: ${dataplaneUrl}`));
|
|
148
|
+
|
|
149
|
+
await runUploadValidatePublish(dataplaneUrl, authConfig, payload);
|
|
150
|
+
|
|
151
|
+
logger.log(chalk.green('\nUpload validated and published to dataplane.'));
|
|
152
|
+
logger.log(chalk.blue(`Environment: ${environment}`));
|
|
153
|
+
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
154
|
+
logger.log(chalk.blue(`Dataplane: ${dataplaneUrl}`));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = {
|
|
158
|
+
uploadExternalSystem,
|
|
159
|
+
buildUploadPayload,
|
|
160
|
+
resolveDataplaneAndAuth,
|
|
161
|
+
runUploadValidatePublish,
|
|
162
|
+
validateSystemKeyFormat
|
|
163
|
+
};
|
|
@@ -339,7 +339,7 @@ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfi
|
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
/**
|
|
342
|
-
* Fetches deployment docs and writes README.md when
|
|
342
|
+
* Fetches deployment docs and writes README.md when application config and deploy JSON are available.
|
|
343
343
|
* @async
|
|
344
344
|
* @param {string} appPath - Application path
|
|
345
345
|
* @param {string} appName - Application name
|
|
@@ -348,12 +348,13 @@ async function validateWizardConfiguration(dataplaneUrl, authConfig, systemConfi
|
|
|
348
348
|
* @param {string} systemKey - System key
|
|
349
349
|
*/
|
|
350
350
|
async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl, authConfig, systemKey) {
|
|
351
|
-
const
|
|
351
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
352
352
|
const deployPath = path.join(appPath, `${appName}-deploy.json`);
|
|
353
353
|
let variablesYaml = null;
|
|
354
354
|
let deployJson = null;
|
|
355
355
|
try {
|
|
356
|
-
|
|
356
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
357
|
+
variablesYaml = await fs.readFile(configPath, 'utf8');
|
|
357
358
|
} catch {
|
|
358
359
|
// optional
|
|
359
360
|
}
|
|
@@ -372,7 +373,7 @@ async function tryUpdateReadmeFromDeploymentDocs(appPath, appName, dataplaneUrl,
|
|
|
372
373
|
if (content && typeof content === 'string') {
|
|
373
374
|
const readmePath = path.join(appPath, 'README.md');
|
|
374
375
|
await fs.writeFile(readmePath, content, 'utf8');
|
|
375
|
-
logger.log(chalk.gray(' Updated README.md from deployment-docs API (
|
|
376
|
+
logger.log(chalk.gray(' Updated README.md from deployment-docs API (application config + deploy JSON).'));
|
|
376
377
|
}
|
|
377
378
|
}
|
|
378
379
|
|
package/lib/core/diff.js
CHANGED
|
@@ -13,6 +13,10 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
17
|
+
const { detectSchemaTypeFromParsed, loadExternalSystemSchema, loadExternalDataSourceSchema } = require('../utils/schema-loader');
|
|
18
|
+
const { validateObjectAgainstApplicationSchema } = require('../validation/validator');
|
|
19
|
+
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Handle added field in comparison
|
|
@@ -169,19 +173,22 @@ function identifyBreakingChanges(comparison) {
|
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
/**
|
|
172
|
-
* Compares two configuration files
|
|
173
|
-
*
|
|
176
|
+
* Compares two configuration files.
|
|
177
|
+
* Both files must be the same config type (app, system, or datasource).
|
|
178
|
+
* By default validates both against their schema; pass { validate: false } to skip.
|
|
174
179
|
*
|
|
175
180
|
* @async
|
|
176
181
|
* @function compareFiles
|
|
177
182
|
* @param {string} file1 - Path to first file
|
|
178
183
|
* @param {string} file2 - Path to second file
|
|
184
|
+
* @param {Object} [options] - Options
|
|
185
|
+
* @param {boolean} [options.validate=true] - If true, validate both files against their schema
|
|
179
186
|
* @returns {Promise<Object>} Comparison result with differences
|
|
180
|
-
* @throws {Error} If files cannot be read or
|
|
187
|
+
* @throws {Error} If files cannot be read, parsed, types differ, or validation fails
|
|
181
188
|
*
|
|
182
189
|
* @example
|
|
183
190
|
* const result = await compareFiles('./old.json', './new.json');
|
|
184
|
-
*
|
|
191
|
+
* const resultNoValidate = await compareFiles('./a.yaml', './b.yaml', { validate: false });
|
|
185
192
|
*/
|
|
186
193
|
/**
|
|
187
194
|
* Validates file paths
|
|
@@ -206,21 +213,70 @@ function validateFilePaths(file1, file2) {
|
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
/**
|
|
209
|
-
* Reads and parses a JSON
|
|
216
|
+
* Reads and parses a config file (JSON or YAML by extension: .json, .yaml, .yml).
|
|
210
217
|
* @function readAndParseFile
|
|
211
218
|
* @param {string} filePath - File path
|
|
212
|
-
* @returns {Object} Parsed
|
|
219
|
+
* @returns {Object} Parsed object
|
|
213
220
|
* @throws {Error} If file cannot be read or parsed
|
|
214
221
|
*/
|
|
215
222
|
function readAndParseFile(filePath) {
|
|
216
223
|
try {
|
|
217
|
-
|
|
218
|
-
return JSON.parse(content);
|
|
224
|
+
return loadConfigFile(filePath);
|
|
219
225
|
} catch (error) {
|
|
220
226
|
throw new Error(`Failed to parse ${filePath}: ${error.message}`);
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Maps schema type to user-facing label (app | system | datasource).
|
|
232
|
+
* @param {string} schemaType - 'application' | 'external-system' | 'external-datasource'
|
|
233
|
+
* @returns {string} 'app' | 'system' | 'datasource'
|
|
234
|
+
*/
|
|
235
|
+
function toUserFacingType(schemaType) {
|
|
236
|
+
const map = {
|
|
237
|
+
application: 'app',
|
|
238
|
+
'external-system': 'system',
|
|
239
|
+
'external-datasource': 'datasource'
|
|
240
|
+
};
|
|
241
|
+
return map[schemaType] || 'app';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Runs external schema validator and returns error messages or null.
|
|
246
|
+
* @param {Function} validateFn - AJV validate function
|
|
247
|
+
* @param {Object} parsed - Parsed config object
|
|
248
|
+
* @returns {string[]|null} Error messages or null if valid
|
|
249
|
+
*/
|
|
250
|
+
function getValidationErrors(validateFn, parsed) {
|
|
251
|
+
const valid = validateFn(parsed);
|
|
252
|
+
if (valid) return null;
|
|
253
|
+
return formatValidationErrors(validateFn.errors);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Validates parsed object against the schema for the given type.
|
|
258
|
+
* @param {Object} parsed - Parsed config object
|
|
259
|
+
* @param {string} schemaType - 'application' | 'external-system' | 'external-datasource'
|
|
260
|
+
* @param {string} filePath - File path (for error messages)
|
|
261
|
+
* @throws {Error} If validation fails
|
|
262
|
+
*/
|
|
263
|
+
function validateParsedForType(parsed, schemaType, filePath) {
|
|
264
|
+
let messages = [];
|
|
265
|
+
if (schemaType === 'application') {
|
|
266
|
+
const result = validateObjectAgainstApplicationSchema(parsed);
|
|
267
|
+
if (!result.valid) messages = result.errors;
|
|
268
|
+
} else if (schemaType === 'external-system') {
|
|
269
|
+
messages = getValidationErrors(loadExternalSystemSchema(), parsed) || [];
|
|
270
|
+
} else if (schemaType === 'external-datasource') {
|
|
271
|
+
messages = getValidationErrors(loadExternalDataSourceSchema(), parsed) || [];
|
|
272
|
+
} else {
|
|
273
|
+
throw new Error(`Unknown schema type: ${schemaType}`);
|
|
274
|
+
}
|
|
275
|
+
if (messages.length > 0) {
|
|
276
|
+
throw new Error(`Validation failed for ${filePath}: ${messages.join('; ')}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
224
280
|
/**
|
|
225
281
|
* Extracts version from parsed object
|
|
226
282
|
* @function extractVersion
|
|
@@ -267,12 +323,31 @@ function buildComparisonResult(comparison, parsed1, parsed2, file1, file2) {
|
|
|
267
323
|
};
|
|
268
324
|
}
|
|
269
325
|
|
|
270
|
-
async function compareFiles(file1, file2) {
|
|
326
|
+
async function compareFiles(file1, file2, options = {}) {
|
|
327
|
+
const shouldValidate = options.validate !== false;
|
|
328
|
+
|
|
271
329
|
validateFilePaths(file1, file2);
|
|
272
330
|
|
|
273
331
|
const parsed1 = readAndParseFile(file1);
|
|
274
332
|
const parsed2 = readAndParseFile(file2);
|
|
275
333
|
|
|
334
|
+
const type1 = detectSchemaTypeFromParsed(parsed1, file1);
|
|
335
|
+
const type2 = detectSchemaTypeFromParsed(parsed2, file2);
|
|
336
|
+
const userType1 = toUserFacingType(type1);
|
|
337
|
+
const userType2 = toUserFacingType(type2);
|
|
338
|
+
|
|
339
|
+
if (userType1 !== userType2) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Type mismatch: ${file1} is ${userType1} config and ${file2} is ${userType2} config. ` +
|
|
342
|
+
'Both files must be the same type (app, system, or datasource).'
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (shouldValidate) {
|
|
347
|
+
validateParsedForType(parsed1, type1, file1);
|
|
348
|
+
validateParsedForType(parsed2, type2, file2);
|
|
349
|
+
}
|
|
350
|
+
|
|
276
351
|
const comparison = compareObjects(parsed1, parsed2);
|
|
277
352
|
return buildComparisonResult(comparison, parsed1, parsed2, file1, file2);
|
|
278
353
|
}
|