@aifabrix/builder 2.46.0 → 2.47.1
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/.npmrc.token +1 -0
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
- package/.temp/validation/summary.txt +5 -0
- package/jest.isolated-projects.js +18 -2
- package/jest.projects.js +24 -0
- package/lib/api/dataplane-health.api.js +19 -7
- package/lib/api/lifecycle.api.js +22 -1
- package/lib/api/resource-types.api.js +107 -0
- package/lib/api/runtime-configuration.api.js +72 -0
- package/lib/api/types/resource-types.types.js +45 -0
- package/lib/api/user-groups.api.js +9 -9
- package/lib/api/users.api.js +8 -8
- package/lib/api/wizard.api.js +10 -2
- package/lib/app/down.js +58 -26
- package/lib/app/index.js +2 -2
- package/lib/app/run-resolve-image.js +2 -1
- package/lib/app/run-stop-containers.js +113 -0
- package/lib/app/run.js +1 -31
- package/lib/app/show-display.js +35 -0
- package/lib/app/show.js +32 -1
- package/lib/build/index.js +17 -5
- package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
- package/lib/channels/add-channel.js +348 -0
- package/lib/channels/channel-artifact-generator.js +141 -0
- package/lib/channels/channel-audit.js +101 -0
- package/lib/channels/channel-auth-context.js +37 -0
- package/lib/channels/channel-credential.js +200 -0
- package/lib/channels/channel-display-name.js +64 -0
- package/lib/channels/channel-external-system.js +106 -0
- package/lib/channels/channel-integration-generator.js +274 -0
- package/lib/channels/channel-probe.js +89 -0
- package/lib/channels/channel-runtime-resolver.js +152 -0
- package/lib/channels/channel-status-display.js +141 -0
- package/lib/channels/channel-targets.js +115 -0
- package/lib/cli/index.js +18 -4
- package/lib/cli/infra-guided-auth.js +36 -12
- package/lib/cli/setup-app.js +54 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
- package/lib/cli/setup-infra-up-platform-action.js +2 -1
- package/lib/cli/setup-npm.js +59 -0
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-secrets.js +1 -1
- package/lib/cli/setup-utility-help-after.js +4 -1
- package/lib/cli/setup-utility-resolve.js +8 -5
- package/lib/commands/app-logs.js +54 -26
- package/lib/commands/app.js +41 -0
- package/lib/commands/auth-status-dataplane-version.js +97 -61
- package/lib/commands/auth-status.js +2 -7
- package/lib/commands/channel.js +58 -0
- package/lib/commands/datasource.js +1 -1
- package/lib/commands/identity-apply-sync.js +49 -8
- package/lib/commands/identity-group-cli.js +66 -26
- package/lib/commands/identity-membership-role-cache.js +58 -37
- package/lib/commands/identity-shared.js +22 -1
- package/lib/commands/identity-user-cli.js +80 -34
- package/lib/commands/login-device.js +11 -3
- package/lib/commands/login.js +8 -3
- package/lib/commands/npm-config.js +103 -0
- package/lib/commands/repair-auth-apply.js +30 -4
- package/lib/commands/repair-auth-config.js +8 -13
- package/lib/commands/repair-datasource-com-v2.js +15 -0
- package/lib/commands/repair-datasource-service-contract.js +206 -0
- package/lib/commands/repair-datasource.js +53 -18
- package/lib/commands/repair-env-template.js +12 -12
- package/lib/commands/repair-openapi-sync.js +26 -4
- package/lib/commands/repair-steps.js +28 -13
- package/lib/commands/repair-system-alignment.js +7 -4
- package/lib/commands/resource-type.js +371 -0
- package/lib/commands/setup-modes.js +1 -1
- package/lib/commands/setup-prompts-platform-mode.js +24 -21
- package/lib/commands/test-trust-external.js +39 -90
- package/lib/commands/up-common.js +33 -1
- package/lib/commands/up-dataplane.js +8 -4
- package/lib/commands/upload.js +24 -3
- package/lib/commands/verify-trust-command-action.js +4 -1
- package/lib/commands/verify-trust-external.js +12 -20
- package/lib/core/config-setup-platform-mode.js +3 -3
- package/lib/core/setup-platform-mode.js +41 -6
- package/lib/datasource/capability/capability-diff-slice.js +2 -2
- package/lib/datasource/capability/capability-manifest.js +254 -0
- package/lib/datasource/capability/capability-resolve.js +3 -4
- package/lib/datasource/capability/copy-operations.js +5 -3
- package/lib/datasource/capability/remove-operations.js +2 -1
- package/lib/datasource/capability/run-capability-copy.js +32 -4
- package/lib/datasource/capability/run-capability-diff.js +3 -3
- package/lib/datasource/capability/run-capability-dimension.js +2 -4
- package/lib/datasource/capability/run-capability-edit.js +4 -7
- package/lib/datasource/capability/run-capability-relate.js +2 -4
- package/lib/datasource/capability/run-capability-remove.js +2 -4
- package/lib/datasource/capability/validate-capability-slice.js +2 -1
- package/lib/datasource/datasource-identifier.js +2 -1
- package/lib/datasource/datasource-validate-display.js +1 -1
- package/lib/datasource/field-reference-validator.js +24 -3
- package/lib/datasource/orphan-profiles-validator.js +98 -0
- package/lib/datasource/validate.js +41 -23
- package/lib/external-system/download-helpers.js +87 -0
- package/lib/external-system/download.js +61 -122
- package/lib/external-system/generator.js +1 -59
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +11 -1
- package/lib/generator/external-controller-manifest.js +5 -2
- package/lib/generator/split.js +3 -1
- package/lib/generator/wizard.js +1 -28
- package/lib/identity/fk-reference.js +65 -0
- package/lib/identity/identity-apply-core.js +13 -58
- package/lib/identity/identity-apply-phases.js +5 -1
- package/lib/identity/identity-display.js +243 -0
- package/lib/identity/identity-response-helpers.js +52 -0
- package/lib/identity/identity-sync-errors.js +62 -0
- package/lib/identity/resolve-user-id.js +129 -0
- package/lib/identity/username-default.js +31 -0
- package/lib/lifecycle/certification-progress.js +2 -2
- package/lib/lifecycle/report-display-trust.js +70 -0
- package/lib/lifecycle/report-display.js +14 -13
- package/lib/resolvers/datasource-resolver.js +2 -3
- package/lib/resolvers/resource-type-file.js +159 -0
- package/lib/schema/application-schema.json +16 -0
- package/lib/schema/deployment-rules.yaml +2 -1
- package/lib/schema/external-datasource.schema.json +372 -58
- package/lib/schema/external-system.schema.json +331 -2
- package/lib/schema/type/document-storage.json +9 -5
- package/lib/utils/api.js +2 -1
- package/lib/utils/app-run-containers.js +25 -2
- package/lib/utils/authentication-shape.js +224 -0
- package/lib/utils/build-copy.js +36 -5
- package/lib/utils/build-resolve-image.js +66 -12
- package/lib/utils/celery-companion-apps.js +53 -0
- package/lib/utils/cli-utils.js +4 -0
- package/lib/utils/compose-celery-worker.js +23 -0
- package/lib/utils/compose-generate-docker-compose.js +9 -1
- package/lib/utils/configuration-env-resolver.js +36 -0
- package/lib/utils/controller-url.js +4 -1
- package/lib/utils/credential-secrets-env-kv.js +216 -0
- package/lib/utils/credential-secrets-env.js +52 -262
- package/lib/utils/dataplane-resolver.js +4 -115
- package/lib/utils/declarative-url-ports.js +24 -1
- package/lib/utils/dev-platform-tls.js +148 -0
- package/lib/utils/docker-build.js +1 -4
- package/lib/utils/external-env-template.js +10 -3
- package/lib/utils/external-system-validators.js +8 -2
- package/lib/utils/health-check-request.js +114 -0
- package/lib/utils/health-check-url.js +3 -1
- package/lib/utils/health-check.js +1 -113
- package/lib/utils/help-builder.js +3 -0
- package/lib/utils/keycloak-realm-ready.js +5 -1
- package/lib/utils/localhost-public-host.js +31 -0
- package/lib/utils/npm-user-config.js +145 -0
- package/lib/utils/paths.js +25 -0
- package/lib/utils/platform-controller-url.js +58 -4
- package/lib/utils/platform-kv-ref.js +57 -0
- package/lib/utils/runtime-configuration-download.js +189 -0
- package/lib/utils/runtime-configuration-values.js +119 -0
- package/lib/utils/schema-loader.js +19 -4
- package/lib/utils/url-declarative-public-base.js +33 -25
- package/lib/utils/worker-sidecars.js +142 -0
- package/lib/validation/external-manifest-validator.js +2 -0
- package/lib/validation/runtime-configuration-rules.js +98 -0
- package/lib/validation/validate-display-dimensions.js +154 -0
- package/lib/validation/validate-display-sections.js +276 -0
- package/lib/validation/validate-display-steps.js +67 -0
- package/lib/validation/validate-display.js +24 -402
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +5 -0
- package/templates/applications/dataplane/env.template +26 -13
- package/templates/applications/dataplane/rbac.yaml +108 -1
- package/templates/applications/miso-controller/application.yaml +2 -2
- package/templates/applications/miso-controller/env.template +37 -122
- package/templates/applications/miso-controller/rbac.yaml +28 -13
- package/templates/channels/chatgpt/install.template.md.hbs +49 -0
- package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
- package/templates/channels/slack/install.template.md.hbs +49 -0
- package/templates/channels/slack/manifest.template.json.hbs +66 -0
- package/templates/channels/teams-bot/install.template.md.hbs +49 -0
- package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
- package/templates/channels/web-embed/install.template.md.hbs +49 -0
- package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
- package/templates/python/docker-compose.hbs +56 -0
- package/templates/typescript/docker-compose.hbs +52 -0
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/* eslint-disable max-lines,max-lines-per-function,max-statements,complexity,max-depth -- Integration test flow with system-level and per-datasource paths */
|
|
9
9
|
|
|
10
|
-
const fs = require('fs').promises;
|
|
11
10
|
const fsSync = require('fs');
|
|
12
11
|
const path = require('path');
|
|
13
12
|
const chalk = require('chalk');
|
|
@@ -69,15 +68,15 @@ async function loadVariablesYamlFile(variablesPath) {
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
72
|
-
* Loads a single JSON
|
|
71
|
+
* Loads a single integration config file (JSON, YAML, or YML).
|
|
73
72
|
* @async
|
|
74
|
-
* @function
|
|
75
|
-
* @param {string} filePath - Path to
|
|
73
|
+
* @function loadIntegrationConfigFile
|
|
74
|
+
* @param {string} filePath - Path to config file
|
|
76
75
|
* @param {string} fileName - File name for error messages
|
|
77
|
-
* @returns {Promise<Object>} Parsed
|
|
78
|
-
* @throws {Error} If file not found or invalid
|
|
76
|
+
* @returns {Promise<Object>} Parsed config object
|
|
77
|
+
* @throws {Error} If file not found or invalid format
|
|
79
78
|
*/
|
|
80
|
-
async function
|
|
79
|
+
async function loadIntegrationConfigFile(filePath, fileName) {
|
|
81
80
|
if (!fsSync.existsSync(filePath)) {
|
|
82
81
|
// Use "System file not found" for system files to match test expectations
|
|
83
82
|
if (fileName.includes('system') || fileName.includes('deploy')) {
|
|
@@ -86,12 +85,8 @@ async function loadJsonFile(filePath, fileName) {
|
|
|
86
85
|
throw new Error(`${fileName} not found: ${filePath}`);
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
return JSON.parse(content);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
throw new Error(`Invalid JSON syntax in ${fileName}: ${error.message}`);
|
|
94
|
-
}
|
|
88
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
89
|
+
return loadConfigFile(filePath);
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
/**
|
|
@@ -121,7 +116,7 @@ async function loadSystemFiles(systemFiles, schemaBasePath, appPath) {
|
|
|
121
116
|
const systemJsonFiles = [];
|
|
122
117
|
for (const systemFile of systemFiles) {
|
|
123
118
|
const systemPath = resolveFilePath(schemaBasePath, appPath, systemFile);
|
|
124
|
-
const systemJson = await
|
|
119
|
+
const systemJson = await loadIntegrationConfigFile(systemPath, systemFile);
|
|
125
120
|
systemJsonFiles.push({ path: systemPath, data: systemJson });
|
|
126
121
|
}
|
|
127
122
|
return systemJsonFiles;
|
|
@@ -140,7 +135,7 @@ async function loadDatasourceFiles(datasourceFiles, schemaBasePath, appPath) {
|
|
|
140
135
|
const datasourceJsonFiles = [];
|
|
141
136
|
for (const datasourceFile of datasourceFiles) {
|
|
142
137
|
const datasourcePath = resolveFilePath(schemaBasePath, appPath, datasourceFile);
|
|
143
|
-
const datasourceJson = await
|
|
138
|
+
const datasourceJson = await loadIntegrationConfigFile(datasourcePath, datasourceFile);
|
|
144
139
|
datasourceJsonFiles.push({ path: datasourcePath, data: datasourceJson });
|
|
145
140
|
}
|
|
146
141
|
return datasourceJsonFiles;
|
|
@@ -267,6 +267,15 @@ function validateBuildFields(build) {
|
|
|
267
267
|
if (build.dockerfile && build.dockerfile.trim()) {
|
|
268
268
|
buildConfig.dockerfile = build.dockerfile;
|
|
269
269
|
}
|
|
270
|
+
if (build.worker === true) {
|
|
271
|
+
buildConfig.worker = true;
|
|
272
|
+
}
|
|
273
|
+
if (typeof build.imageRunWorker === 'string' && build.imageRunWorker.trim()) {
|
|
274
|
+
buildConfig.imageRunWorker = build.imageRunWorker.trim();
|
|
275
|
+
}
|
|
276
|
+
if (typeof build.imageRunBeat === 'string' && build.imageRunBeat.trim()) {
|
|
277
|
+
buildConfig.imageRunBeat = build.imageRunBeat.trim();
|
|
278
|
+
}
|
|
270
279
|
|
|
271
280
|
return Object.keys(buildConfig).length > 0 ? buildConfig : null;
|
|
272
281
|
}
|
|
@@ -390,6 +399,7 @@ module.exports = {
|
|
|
390
399
|
buildOptionalFields,
|
|
391
400
|
buildManifestStructure,
|
|
392
401
|
filterConfigurationByRegistryMode,
|
|
393
|
-
sanitizeAuthType
|
|
402
|
+
sanitizeAuthType,
|
|
403
|
+
validateBuildFields
|
|
394
404
|
};
|
|
395
405
|
|
|
@@ -27,11 +27,14 @@ const { loadVariables, loadRbac } = require('./helpers');
|
|
|
27
27
|
* @param {Object} systemJson - System JSON object (mutated in place)
|
|
28
28
|
*/
|
|
29
29
|
function normalizeLegacyAuthMethod(systemJson) {
|
|
30
|
+
const { getAuthCredentialTemplate } = require('../utils/authentication-shape');
|
|
30
31
|
const auth = systemJson && systemJson.authentication;
|
|
31
32
|
if (!auth || typeof auth !== 'object') return;
|
|
32
|
-
const
|
|
33
|
+
const credential = getAuthCredentialTemplate(auth);
|
|
34
|
+
if (!credential) return;
|
|
35
|
+
const method = String(credential.method || '').trim().toLowerCase();
|
|
33
36
|
if (method === 'bearerkey') {
|
|
34
|
-
|
|
37
|
+
credential.method = 'bearerToken';
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
package/lib/generator/split.js
CHANGED
package/lib/generator/wizard.js
CHANGED
|
@@ -32,34 +32,7 @@ function toKeySegment(str) {
|
|
|
32
32
|
return sanitized || 'default';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
* Align authentication.security kv:// namespaces and credentialKey with the final system key.
|
|
37
|
-
* Dataplane normalizes this before respond; when appName overrides a spec-derived key (e.g.
|
|
38
|
-
* OpenAPI title "Companies"), the builder still must rewrite nested auth so it matches env.template.
|
|
39
|
-
*
|
|
40
|
-
* @param {Object|null|undefined} authentication - authentication block from dataplane
|
|
41
|
-
* @param {string} systemKey - Final external system key (integration app name)
|
|
42
|
-
* @param {string} [authDisplayName] - Credential display name (typically title-cased app name)
|
|
43
|
-
*/
|
|
44
|
-
function normalizeAuthenticationToSystemKey(authentication, systemKey, authDisplayName) {
|
|
45
|
-
if (!authentication || typeof authentication !== 'object') return;
|
|
46
|
-
authentication.credentialKey = `${systemKey}-cred`;
|
|
47
|
-
const security = authentication.security;
|
|
48
|
-
if (security && typeof security === 'object') {
|
|
49
|
-
for (const k of Object.keys(security)) {
|
|
50
|
-
const v = security[k];
|
|
51
|
-
if (typeof v === 'string' && v.startsWith('kv://')) {
|
|
52
|
-
const rest = v.slice(5);
|
|
53
|
-
const idx = rest.indexOf('/');
|
|
54
|
-
const suffix = idx >= 0 ? rest.slice(idx + 1) : '';
|
|
55
|
-
security[k] = suffix ? `kv://${systemKey}/${suffix}` : `kv://${systemKey}`;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (authDisplayName) {
|
|
60
|
-
authentication.displayName = authDisplayName;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
35
|
+
const { normalizeAuthenticationToSystemKey } = require('../utils/authentication-shape');
|
|
63
36
|
|
|
64
37
|
/**
|
|
65
38
|
* Generate files from dataplane-generated wizard configurations
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Parse ForeignKeyReference objects from controller identity APIs (plan 194)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string|Object|null|undefined} ref
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
function readFkId(ref) {
|
|
14
|
+
if (typeof ref === 'string') {
|
|
15
|
+
return ref;
|
|
16
|
+
}
|
|
17
|
+
return String(ref?.id || '').trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string|Object|null|undefined} ref
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function readFkKey(ref) {
|
|
25
|
+
if (typeof ref === 'string') {
|
|
26
|
+
return ref;
|
|
27
|
+
}
|
|
28
|
+
return String(ref?.key || '').trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string|Object|null|undefined} ref
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
function readFkName(ref) {
|
|
36
|
+
if (typeof ref === 'string') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return String(ref?.name || '').trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string|Object|null|undefined} ref
|
|
44
|
+
* @param {string} [fallback]
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function formatFkLabel(ref, fallback = '—') {
|
|
48
|
+
const name = readFkName(ref);
|
|
49
|
+
if (name) {
|
|
50
|
+
return name;
|
|
51
|
+
}
|
|
52
|
+
const key = readFkKey(ref);
|
|
53
|
+
if (key) {
|
|
54
|
+
return key;
|
|
55
|
+
}
|
|
56
|
+
const id = readFkId(ref);
|
|
57
|
+
return id || fallback;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
readFkId,
|
|
62
|
+
readFkKey,
|
|
63
|
+
readFkName,
|
|
64
|
+
formatFkLabel
|
|
65
|
+
};
|
|
@@ -6,46 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!response || response.success === false) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
return response?.data?.data ?? response?.data ?? response;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {Object} response
|
|
24
|
-
* @returns {string}
|
|
25
|
-
*/
|
|
26
|
-
function apiErrorMessage(response) {
|
|
27
|
-
if (!response) {
|
|
28
|
-
return 'Request failed';
|
|
29
|
-
}
|
|
30
|
-
const err = response.error;
|
|
31
|
-
if (typeof err === 'string') {
|
|
32
|
-
return err;
|
|
33
|
-
}
|
|
34
|
-
if (err && typeof err === 'object') {
|
|
35
|
-
return err.formattedError || err.message || err.error || 'Request failed';
|
|
36
|
-
}
|
|
37
|
-
return response.formattedError || response.message || 'Request failed';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param {Object} response
|
|
42
|
-
* @throws {Error}
|
|
43
|
-
*/
|
|
44
|
-
function throwIfApiFailed(response) {
|
|
45
|
-
if (!response || response.success === false) {
|
|
46
|
-
throw new Error(apiErrorMessage(response));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
9
|
+
const { lookupUserByIdOrEmail } = require('./resolve-user-id');
|
|
10
|
+
const { buildSyncStatsFailureMessage } = require('./identity-sync-errors');
|
|
11
|
+
const {
|
|
12
|
+
unwrapControllerData,
|
|
13
|
+
apiErrorMessage,
|
|
14
|
+
throwIfApiFailed
|
|
15
|
+
} = require('./identity-response-helpers');
|
|
49
16
|
|
|
50
17
|
/**
|
|
51
18
|
* @param {Object|null|undefined} stats
|
|
@@ -55,11 +22,7 @@ function throwIfApiFailed(response) {
|
|
|
55
22
|
function assertSyncStatsOk(stats, opts = {}) {
|
|
56
23
|
const syncErrors = Number(stats?.errors ?? 0);
|
|
57
24
|
if (syncErrors > 0) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
`Dataplane full sync reported ${syncErrors} error(s). ` +
|
|
60
|
-
`usersProcessed=${stats?.usersProcessed ?? 0} groupsProcessed=${stats?.groupsProcessed ?? 0}. ` +
|
|
61
|
-
'Check miso-controller logs for the sync request.'
|
|
62
|
-
);
|
|
25
|
+
throw new Error(buildSyncStatsFailureMessage(stats));
|
|
63
26
|
}
|
|
64
27
|
const processed = Number(stats?.usersProcessed ?? 0);
|
|
65
28
|
if (processed === 0 && !opts.allowEmptySync) {
|
|
@@ -79,24 +42,16 @@ function assertSyncStatsOk(stats, opts = {}) {
|
|
|
79
42
|
async function findExistingUser(controllerUrl, authConfig, userSpec) {
|
|
80
43
|
const csvId = String(userSpec.csvId || '').trim();
|
|
81
44
|
if (csvId) {
|
|
82
|
-
const byId = await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return row;
|
|
45
|
+
const byId = await lookupUserByIdOrEmail(controllerUrl, authConfig, csvId);
|
|
46
|
+
if (byId?.id) {
|
|
47
|
+
return byId;
|
|
86
48
|
}
|
|
87
49
|
}
|
|
88
50
|
const email = String(userSpec.email || '').trim();
|
|
89
|
-
|
|
90
|
-
search: email,
|
|
91
|
-
pageSize: 10,
|
|
92
|
-
page: 1
|
|
93
|
-
});
|
|
94
|
-
const items = unwrapControllerData(listed);
|
|
95
|
-
const list = Array.isArray(items) ? items : items?.data;
|
|
96
|
-
if (!Array.isArray(list)) {
|
|
51
|
+
if (!email) {
|
|
97
52
|
return null;
|
|
98
53
|
}
|
|
99
|
-
return
|
|
54
|
+
return lookupUserByIdOrEmail(controllerUrl, authConfig, email);
|
|
100
55
|
}
|
|
101
56
|
|
|
102
57
|
module.exports = {
|
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
apiErrorMessage,
|
|
19
19
|
findExistingUser
|
|
20
20
|
} = require('./identity-apply-core');
|
|
21
|
+
const { resolveIdentityUsername } = require('./username-default');
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* @param {string} msg
|
|
@@ -75,7 +76,10 @@ async function ensureUser(controllerUrl, authConfig, userSpec, dryRun) {
|
|
|
75
76
|
firstName: userSpec.firstName,
|
|
76
77
|
lastName: userSpec.lastName,
|
|
77
78
|
displayName: userSpec.displayName,
|
|
78
|
-
username:
|
|
79
|
+
username: resolveIdentityUsername({
|
|
80
|
+
username: userSpec.username,
|
|
81
|
+
email: userSpec.email
|
|
82
|
+
}),
|
|
79
83
|
status: 'active'
|
|
80
84
|
};
|
|
81
85
|
Object.keys(body).forEach((k) => {
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TTY layout helpers for aifabrix identity commands (plan 151)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const {
|
|
12
|
+
formatBlockingError,
|
|
13
|
+
formatNextActions,
|
|
14
|
+
headerKeyValue,
|
|
15
|
+
sectionTitle
|
|
16
|
+
} = require('../utils/cli-layout-chalk');
|
|
17
|
+
const { readFkId, readFkKey, readFkName, formatFkLabel } = require('./fk-reference');
|
|
18
|
+
const { unwrapControllerData } = require('./identity-response-helpers');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {Object|null|undefined} response
|
|
22
|
+
* @returns {{ items: Object[], meta: Object|null }}
|
|
23
|
+
*/
|
|
24
|
+
function extractPaginatedList(response) {
|
|
25
|
+
if (!response || response.success === false) {
|
|
26
|
+
return { items: [], meta: null };
|
|
27
|
+
}
|
|
28
|
+
const body = response.data;
|
|
29
|
+
if (body && Array.isArray(body.data)) {
|
|
30
|
+
return { items: body.data, meta: body.meta ?? null };
|
|
31
|
+
}
|
|
32
|
+
const unwrapped = unwrapControllerData(response);
|
|
33
|
+
if (Array.isArray(unwrapped)) {
|
|
34
|
+
return { items: unwrapped, meta: null };
|
|
35
|
+
}
|
|
36
|
+
if (unwrapped && Array.isArray(unwrapped.data)) {
|
|
37
|
+
return { items: unwrapped.data, meta: unwrapped.meta ?? null };
|
|
38
|
+
}
|
|
39
|
+
return { items: [], meta: null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {Object} user
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function formatUserDisplayName(user) {
|
|
47
|
+
const display = String(user?.displayName || '').trim();
|
|
48
|
+
if (display) {
|
|
49
|
+
return display;
|
|
50
|
+
}
|
|
51
|
+
const parts = [user?.firstName, user?.lastName].filter(Boolean).map(String);
|
|
52
|
+
const joined = parts.join(' ').trim();
|
|
53
|
+
return joined || '—';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} value
|
|
58
|
+
* @param {number} width
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function padCell(value, width) {
|
|
62
|
+
const text = String(value ?? '—');
|
|
63
|
+
if (text.length <= width) {
|
|
64
|
+
return text.padEnd(width);
|
|
65
|
+
}
|
|
66
|
+
if (width <= 1) {
|
|
67
|
+
return text.slice(0, width);
|
|
68
|
+
}
|
|
69
|
+
return `${text.slice(0, width - 1)}…`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {{ emoji?: string, title: string, context?: string, shown?: number, total?: number|null }} opts
|
|
74
|
+
*/
|
|
75
|
+
function printIdentityListHeader(opts) {
|
|
76
|
+
const emoji = opts.emoji ? `${opts.emoji} ` : '';
|
|
77
|
+
const context = opts.context ? ` (${opts.context})` : '';
|
|
78
|
+
const shown = Number.isFinite(opts.shown) ? opts.shown : 0;
|
|
79
|
+
const total = Number.isFinite(opts.total) ? opts.total : null;
|
|
80
|
+
const count =
|
|
81
|
+
total !== null && total >= shown ? `${shown} shown of ${total}` : `${shown} shown`;
|
|
82
|
+
logger.log(chalk.bold(`\n${emoji}${opts.title}${context} — ${count}:\n`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {Array<{ label: string, width: number, value: (row: Object) => string }>} columns
|
|
87
|
+
* @param {Object[]} rows
|
|
88
|
+
*/
|
|
89
|
+
function printIdentityTable(columns, rows) {
|
|
90
|
+
const header = columns.map((col) => padCell(col.label, col.width)).join('');
|
|
91
|
+
logger.log(chalk.gray(header));
|
|
92
|
+
logger.log(chalk.gray('-'.repeat(columns.reduce((sum, col) => sum + col.width, 0))));
|
|
93
|
+
rows.forEach((row) => {
|
|
94
|
+
const line = columns.map((col) => padCell(col.value(row), col.width)).join('');
|
|
95
|
+
logger.log(line);
|
|
96
|
+
});
|
|
97
|
+
logger.log('');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {Object|null|undefined} meta
|
|
102
|
+
*/
|
|
103
|
+
function printListPaginationFooter(meta) {
|
|
104
|
+
if (!meta || !Number.isFinite(meta.totalItems)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const page = meta.currentPage ?? meta.page ?? 1;
|
|
108
|
+
const pageSize = meta.pageSize ?? meta.page_size;
|
|
109
|
+
const totalPages = meta.totalPages ?? meta.total_pages;
|
|
110
|
+
const parts = [`total ${meta.totalItems}`];
|
|
111
|
+
if (pageSize) {
|
|
112
|
+
parts.unshift(`page ${page}`);
|
|
113
|
+
}
|
|
114
|
+
if (totalPages) {
|
|
115
|
+
parts.push(`${totalPages} pages`);
|
|
116
|
+
}
|
|
117
|
+
logger.log(chalk.gray(` ${parts.join(' · ')}\n`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {string} title
|
|
122
|
+
* @param {Array<{ label: string, value: string }>} fields
|
|
123
|
+
*/
|
|
124
|
+
function printIdentityRecord(title, fields) {
|
|
125
|
+
logger.log(sectionTitle(title));
|
|
126
|
+
fields.forEach(({ label, value }) => {
|
|
127
|
+
logger.log(` ${headerKeyValue(`${label}:`, value || '—')}`);
|
|
128
|
+
});
|
|
129
|
+
logger.log('');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {string} message
|
|
134
|
+
* @param {string[]} [hints]
|
|
135
|
+
*/
|
|
136
|
+
function printIdentityEmpty(message, hints = []) {
|
|
137
|
+
logger.log(chalk.gray(` ${message}\n`));
|
|
138
|
+
if (hints.length > 0) {
|
|
139
|
+
logger.log(formatNextActions(hints));
|
|
140
|
+
logger.log('');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} entity
|
|
146
|
+
* @param {string} query
|
|
147
|
+
* @param {string[]} [hints]
|
|
148
|
+
*/
|
|
149
|
+
function printIdentityNotFound(entity, query, hints = []) {
|
|
150
|
+
logger.error(formatBlockingError(`${entity} not found: ${query}`));
|
|
151
|
+
if (hints.length > 0) {
|
|
152
|
+
logger.log(formatNextActions(hints));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const USER_LIST_COLUMNS = [
|
|
157
|
+
{ label: 'Id', width: 38, value: (u) => u.id || '—' },
|
|
158
|
+
{ label: 'Username', width: 16, value: (u) => u.username || '—' },
|
|
159
|
+
{ label: 'Name', width: 22, value: (u) => formatUserDisplayName(u) },
|
|
160
|
+
{ label: 'Email', width: 34, value: (u) => u.email || '—' },
|
|
161
|
+
{ label: 'Status', width: 10, value: (u) => u.status || '—' }
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const GROUP_LIST_COLUMNS = [
|
|
165
|
+
{ label: 'Id', width: 38, value: (g) => g.id || '—' },
|
|
166
|
+
{ label: 'Name', width: 28, value: (g) => g.name || '—' },
|
|
167
|
+
{ label: 'Display name', width: 28, value: (g) => g.displayName || '—' }
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {Object[]} memberships
|
|
172
|
+
*/
|
|
173
|
+
function printUserGroupsTable(memberships) {
|
|
174
|
+
printIdentityTable(
|
|
175
|
+
[
|
|
176
|
+
{ label: 'Group key', width: 28, value: (m) => readFkKey(m.groupId) || '—' },
|
|
177
|
+
{ label: 'Group name', width: 28, value: (m) => formatFkLabel(m.groupId) },
|
|
178
|
+
{ label: 'Group id', width: 38, value: (m) => readFkId(m.groupId) || '—' },
|
|
179
|
+
{ label: 'Role hint', width: 12, value: (m) => m.roleHint || '—' }
|
|
180
|
+
],
|
|
181
|
+
memberships
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {Object[]} memberships
|
|
187
|
+
*/
|
|
188
|
+
function printGroupMembersTable(memberships) {
|
|
189
|
+
printIdentityTable(
|
|
190
|
+
[
|
|
191
|
+
{ label: 'User key', width: 22, value: (m) => readFkKey(m.userId) || '—' },
|
|
192
|
+
{ label: 'User name', width: 24, value: (m) => formatFkLabel(m.userId) },
|
|
193
|
+
{ label: 'User id', width: 38, value: (m) => readFkId(m.userId) || '—' },
|
|
194
|
+
{ label: 'Role hint', width: 12, value: (m) => m.roleHint || '—' }
|
|
195
|
+
],
|
|
196
|
+
memberships
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {Object} user
|
|
202
|
+
*/
|
|
203
|
+
function printUserRecord(user) {
|
|
204
|
+
printIdentityRecord('User', [
|
|
205
|
+
{ label: 'Id', value: user.id },
|
|
206
|
+
{ label: 'Email', value: user.email },
|
|
207
|
+
{ label: 'Username', value: user.username },
|
|
208
|
+
{ label: 'Name', value: formatUserDisplayName(user) },
|
|
209
|
+
{ label: 'Display name', value: user.displayName },
|
|
210
|
+
{ label: 'Status', value: user.status },
|
|
211
|
+
{ label: 'Verified', value: user.emailVerified ? 'yes' : 'no' }
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {Object} group
|
|
217
|
+
*/
|
|
218
|
+
function printGroupRecord(group) {
|
|
219
|
+
printIdentityRecord('Group', [
|
|
220
|
+
{ label: 'Id', value: group.id },
|
|
221
|
+
{ label: 'Name', value: group.name },
|
|
222
|
+
{ label: 'Display name', value: group.displayName },
|
|
223
|
+
{ label: 'Description', value: group.description }
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
extractPaginatedList,
|
|
229
|
+
formatUserDisplayName,
|
|
230
|
+
printIdentityListHeader,
|
|
231
|
+
printIdentityTable,
|
|
232
|
+
printListPaginationFooter,
|
|
233
|
+
printIdentityRecord,
|
|
234
|
+
printIdentityEmpty,
|
|
235
|
+
printIdentityNotFound,
|
|
236
|
+
USER_LIST_COLUMNS,
|
|
237
|
+
GROUP_LIST_COLUMNS,
|
|
238
|
+
printUserGroupsTable,
|
|
239
|
+
printGroupMembersTable,
|
|
240
|
+
printUserRecord,
|
|
241
|
+
printGroupRecord,
|
|
242
|
+
readFkName
|
|
243
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared controller response helpers for identity modules
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Object} response
|
|
11
|
+
* @returns {any}
|
|
12
|
+
*/
|
|
13
|
+
function unwrapControllerData(response) {
|
|
14
|
+
if (!response || response.success === false) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return response?.data?.data ?? response?.data ?? response;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {Object} response
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function apiErrorMessage(response) {
|
|
25
|
+
if (!response) {
|
|
26
|
+
return 'Request failed';
|
|
27
|
+
}
|
|
28
|
+
const err = response.error;
|
|
29
|
+
if (typeof err === 'string') {
|
|
30
|
+
return err;
|
|
31
|
+
}
|
|
32
|
+
if (err && typeof err === 'object') {
|
|
33
|
+
return err.formattedError || err.message || err.error || 'Request failed';
|
|
34
|
+
}
|
|
35
|
+
return response.formattedError || response.message || 'Request failed';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {Object} response
|
|
40
|
+
* @throws {Error}
|
|
41
|
+
*/
|
|
42
|
+
function throwIfApiFailed(response) {
|
|
43
|
+
if (!response || response.success === false) {
|
|
44
|
+
throw new Error(apiErrorMessage(response));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
unwrapControllerData,
|
|
50
|
+
apiErrorMessage,
|
|
51
|
+
throwIfApiFailed
|
|
52
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview User-facing identity sync failure messages
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Object|null|undefined} stats
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
function isZeroProgressSyncFailure(stats) {
|
|
14
|
+
const usersProcessed = Number(stats?.usersProcessed ?? 0);
|
|
15
|
+
const groupsProcessed = Number(stats?.groupsProcessed ?? 0);
|
|
16
|
+
return usersProcessed === 0 && groupsProcessed === 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build a multi-line CLI message when controller sync stats report errors.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object|null|undefined} stats
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
function buildSyncStatsFailureMessage(stats) {
|
|
26
|
+
const syncErrors = Number(stats?.errors ?? 0);
|
|
27
|
+
const usersProcessed = Number(stats?.usersProcessed ?? 0);
|
|
28
|
+
const groupsProcessed = Number(stats?.groupsProcessed ?? 0);
|
|
29
|
+
|
|
30
|
+
const lines = [
|
|
31
|
+
`Dataplane full sync reported ${syncErrors} error(s).`,
|
|
32
|
+
`usersProcessed=${usersProcessed} groupsProcessed=${groupsProcessed}.`
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
if (isZeroProgressSyncFailure(stats)) {
|
|
36
|
+
lines.push(
|
|
37
|
+
'',
|
|
38
|
+
'The controller accepted the sync request, but no identity data reached the dataplane.',
|
|
39
|
+
'Common causes:',
|
|
40
|
+
' • Expired or invalid dataplane client token (controller → dataplane auth)',
|
|
41
|
+
' • Dataplane unreachable or unhealthy',
|
|
42
|
+
' • Dataplane MISO_CLIENTID / MISO_CLIENTSECRET out of sync with the controller',
|
|
43
|
+
'',
|
|
44
|
+
'Try:',
|
|
45
|
+
' aifabrix auth status',
|
|
46
|
+
' aifabrix run dataplane --reload',
|
|
47
|
+
'Then retry:',
|
|
48
|
+
' aifabrix identity sync -e dev --purge-cache',
|
|
49
|
+
'',
|
|
50
|
+
'If it still fails, check miso-controller logs for the sync request.'
|
|
51
|
+
);
|
|
52
|
+
} else {
|
|
53
|
+
lines.push('Check miso-controller logs for the sync request.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
isZeroProgressSyncFailure,
|
|
61
|
+
buildSyncStatsFailureMessage
|
|
62
|
+
};
|