@aifabrix/builder 2.45.5 → 2.46.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/.cursor/rules/cli-layout.mdc +2 -0
- package/.cursor/rules/cli-product-neutral.mdc +43 -0
- package/.cursor/rules/project-rules.mdc +1 -0
- package/README.md +43 -17
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/jest.config.default.js +2 -1
- package/jest.config.isolated.js +2 -1
- package/jest.global-live-fabrix-hooks.js +36 -0
- package/jest.isolated-projects.js +130 -0
- package/jest.projects.js +41 -136
- package/lib/api/api-response-errors.js +124 -0
- package/lib/api/governance-scenario-pack.api.js +159 -0
- package/lib/api/index.js +43 -3
- package/lib/api/lifecycle.api.js +77 -0
- package/lib/api/pipeline.api.js +3 -1
- package/lib/api/records-bulk.api.js +14 -1
- package/lib/api/validation-runner.js +3 -1
- package/lib/app/deploy-config.js +4 -1
- package/lib/app/down.js +20 -1
- package/lib/app/push.js +4 -5
- package/lib/app/register.js +7 -4
- package/lib/app/run-env-compose.js +84 -30
- package/lib/build/index.js +5 -0
- package/lib/certification/sync-system-certification.js +13 -6
- package/lib/cli/index.js +4 -0
- package/lib/cli/infra-guided-auth.js +109 -0
- package/lib/cli/infra-guided.js +84 -81
- package/lib/cli/setup-app.help.js +0 -31
- package/lib/cli/setup-app.js +28 -2
- package/lib/cli/setup-app.test-commands.js +5 -57
- package/lib/cli/setup-auth.js +25 -0
- package/lib/cli/setup-certification-commands.js +147 -0
- package/lib/cli/setup-certification.help.js +141 -0
- package/lib/cli/setup-environment.js +19 -2
- package/lib/cli/setup-external-system-help.js +62 -0
- package/lib/cli/setup-external-system.js +25 -3
- package/lib/cli/setup-infra.js +9 -1
- package/lib/cli/setup-platform.js +6 -1
- package/lib/cli/setup-secrets.js +33 -1
- package/lib/cli/setup-utility-help-after.js +22 -2
- package/lib/cli/setup-utility-repair.js +198 -0
- package/lib/cli/setup-utility-resolve.js +40 -4
- package/lib/cli/setup-utility-validate.js +197 -0
- package/lib/cli/setup-utility.js +4 -239
- package/lib/commands/auth-status-client-tokens.js +214 -0
- package/lib/commands/auth-status-dataplane-version.js +58 -20
- package/lib/commands/auth-status.js +41 -47
- package/lib/commands/datasource-test-trust-cli.js +27 -23
- package/lib/commands/datasource-unified-test-cli.js +6 -3
- package/lib/commands/datasource-unified-test-cli.options.js +5 -1
- package/lib/commands/datasource.js +7 -7
- package/lib/commands/dev-show-display.js +8 -1
- package/lib/commands/governance-cli-shared.js +94 -0
- package/lib/commands/governance-command-action.js +132 -0
- package/lib/commands/governance-download-external.js +34 -0
- package/lib/commands/governance-generate-external.js +71 -0
- package/lib/commands/governance-upload-external.js +36 -0
- package/lib/commands/governance-verify-external.js +64 -0
- package/lib/commands/governance.js +144 -0
- package/lib/commands/lifecycle-command-action.js +123 -0
- package/lib/commands/lifecycle-external.js +268 -0
- package/lib/commands/login.js +11 -2
- package/lib/commands/repair-auth-apply.js +127 -0
- package/lib/commands/repair-changed-files.js +41 -0
- package/lib/commands/repair-datasource-keys.js +4 -1
- package/lib/commands/repair-datasource-run.js +59 -0
- package/lib/commands/repair-datasource-test-payload.js +259 -0
- package/lib/commands/repair-datasource.js +1 -51
- package/lib/commands/repair-env-template.js +64 -13
- package/lib/commands/repair-external-run.js +216 -0
- package/lib/commands/repair-openapi-sync.js +23 -0
- package/lib/commands/repair-persist.js +4 -0
- package/lib/commands/repair-rbac.js +52 -1
- package/lib/commands/repair-steps.js +145 -0
- package/lib/commands/repair.js +29 -296
- package/lib/commands/secrets-get.js +184 -0
- package/lib/commands/secrets-remove-all.js +3 -1
- package/lib/commands/setup-image-refresh.js +146 -0
- package/lib/commands/setup-modes.js +158 -162
- package/lib/commands/setup-platform-auth.js +76 -0
- package/lib/commands/setup-prompts-admin.js +244 -0
- package/lib/commands/setup-prompts-platform-mode.js +67 -0
- package/lib/commands/setup-prompts-validators.js +36 -0
- package/lib/commands/setup-prompts.js +24 -76
- package/lib/commands/setup-run-context.js +45 -0
- package/lib/commands/setup-spinners.js +39 -0
- package/lib/commands/setup.js +27 -5
- package/lib/commands/teardown.js +39 -0
- package/lib/commands/test-e2e-external.js +24 -8
- package/lib/commands/test-governance-external.js +3 -24
- package/lib/commands/test-trust-external.js +20 -21
- package/lib/commands/up-common.js +25 -13
- package/lib/commands/up-dataplane.js +50 -13
- package/lib/commands/upload-publish-context.js +75 -0
- package/lib/commands/upload-sidecar-followups.js +89 -0
- package/lib/commands/upload.js +90 -108
- package/lib/commands/verify-governance-combined-external.js +61 -0
- package/lib/commands/verify-governance-command-action.js +64 -0
- package/lib/commands/verify-governance-db.js +102 -0
- package/lib/commands/verify-governance-external.js +77 -0
- package/lib/commands/verify-governance-result-builders.js +71 -0
- package/lib/commands/verify-operations-command-action.js +68 -0
- package/lib/commands/verify-operations-external.js +268 -0
- package/lib/commands/verify-operations-persist.js +75 -0
- package/lib/commands/verify-operations-step-plan.js +68 -0
- package/lib/commands/verify-operations-steps.js +111 -0
- package/lib/commands/verify-trust-command-action.js +70 -0
- package/lib/commands/verify-trust-external.js +67 -0
- package/lib/commands/wizard-config-normalizer.js +43 -23
- package/lib/commands/wizard-core.js +2 -0
- package/lib/core/admin-password-generator.js +38 -0
- package/lib/core/admin-secrets-bundle.js +111 -0
- package/lib/core/admin-secrets-env-overlay.js +62 -0
- package/lib/core/admin-secrets.js +32 -4
- package/lib/core/config-attach-extensions.js +3 -0
- package/lib/core/config-platform-controller.js +44 -0
- package/lib/core/config-setup-installation-profile.js +42 -0
- package/lib/core/config-setup-platform-mode.js +46 -0
- package/lib/core/config.js +46 -36
- package/lib/core/secrets-admin-env.js +6 -2
- package/lib/core/secrets-ensure.js +4 -0
- package/lib/core/secrets-env-content.js +54 -48
- package/lib/core/secrets-env-write.js +10 -1
- package/lib/core/secrets-infra-placeholder-sync.js +2 -0
- package/lib/core/setup-installation-profile.js +43 -0
- package/lib/core/setup-platform-mode.js +55 -0
- package/lib/datasource/agent-trust-debug-log.js +1 -1
- package/lib/datasource/agent-trust-run.js +5 -3
- package/lib/datasource/audit-evidence-extract-debug.js +30 -18
- package/lib/datasource/audit-evidence-extract.js +7 -4
- package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
- package/lib/datasource/bulk-loader-run-batches.js +15 -3
- package/lib/datasource/datasource-load-export-context.js +3 -0
- package/lib/datasource/log-cleaner.js +2 -2
- package/lib/datasource/log-viewer.js +8 -8
- package/lib/datasource/test-e2e.js +4 -1
- package/lib/datasource/unified-validation-run-body.js +3 -1
- package/lib/datasource/unified-validation-run.js +5 -2
- package/lib/deployment/environment-poll-status.js +138 -0
- package/lib/deployment/environment.js +42 -34
- package/lib/external-system/download.js +21 -20
- package/lib/external-system/generator.js +5 -1
- package/lib/external-system/integration-auth-context.js +45 -0
- package/lib/external-system/test-auth.js +12 -5
- package/lib/external-system/test.js +6 -6
- package/lib/generator/external-controller-manifest.js +15 -0
- package/lib/governance/governance-cli-errors.js +96 -0
- package/lib/governance/governance-download-display.js +38 -0
- package/lib/governance/governance-run-display.js +215 -25
- package/lib/infrastructure/admin-secrets-apply.js +84 -0
- package/lib/infrastructure/admin-secrets-ensure.js +130 -0
- package/lib/infrastructure/admin-secrets-resolve.js +95 -0
- package/lib/infrastructure/admin-secrets-sync.js +91 -0
- package/lib/infrastructure/helpers.js +23 -129
- package/lib/infrastructure/index.js +5 -6
- package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
- package/lib/lifecycle/certification-progress.js +231 -0
- package/lib/lifecycle/dataplane-ui-url.js +145 -0
- package/lib/lifecycle/exit-codes.js +24 -0
- package/lib/lifecycle/json-envelope.js +25 -0
- package/lib/lifecycle/lifecycle-governance-options.js +51 -0
- package/lib/lifecycle/logger-quiet.js +29 -0
- package/lib/lifecycle/open-api-published.js +59 -0
- package/lib/lifecycle/operations-readiness-metrics.js +101 -0
- package/lib/lifecycle/product-model.js +196 -0
- package/lib/lifecycle/report-display.js +471 -0
- package/lib/lifecycle/scenario-upload.js +191 -0
- package/lib/lifecycle/verify-step-progress.js +87 -0
- package/lib/parameters/infra-kv-discovery.js +27 -2
- package/lib/protection/auth-context.js +10 -4
- package/lib/schema/application-schema.json +1 -1
- package/lib/schema/datasource-test-run.schema.json +89 -6
- package/lib/schema/external-datasource.schema.json +2636 -2622
- package/lib/schema/external-system.schema.json +1089 -761
- package/lib/schema/infra.parameter.yaml +45 -0
- package/lib/schema/type/credential-field-registry.json +103 -97
- package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
- package/lib/utils/api.js +55 -31
- package/lib/utils/app-relative-path-env.js +112 -0
- package/lib/utils/cli-secrets-error-format.js +83 -21
- package/lib/utils/cli-utils.js +16 -4
- package/lib/utils/compose-generate-docker-compose.js +4 -0
- package/lib/utils/compose-traefik-ingress-base.js +16 -1
- package/lib/utils/config-secrets-path.js +46 -0
- package/lib/utils/controller-url.js +88 -1
- package/lib/utils/credential-secrets-env.js +137 -9
- package/lib/utils/dataplane-resolver.js +129 -2
- package/lib/utils/declarative-url-ports.js +3 -3
- package/lib/utils/deployment-auth-mode.js +41 -0
- package/lib/utils/developer-volume-id.js +33 -0
- package/lib/utils/env-copy.js +14 -9
- package/lib/utils/env-kv-orphan-lines.js +43 -0
- package/lib/utils/error-formatters/network-errors.js +13 -4
- package/lib/utils/external-env-template.js +1 -0
- package/lib/utils/external-system-readiness-core.js +10 -0
- package/lib/utils/external-system-readiness-display.js +80 -23
- package/lib/utils/health-check.js +4 -1
- package/lib/utils/help-builder.js +11 -8
- package/lib/utils/keycloak-realm-ready.js +101 -0
- package/lib/utils/load-cip-capacity-display-config.js +61 -15
- package/lib/utils/manifest-source-emit.js +2 -0
- package/lib/utils/pipeline-upload-auth-retry.js +82 -0
- package/lib/utils/platform-controller-url.js +156 -0
- package/lib/utils/postgres-platform-bootstrap.js +199 -0
- package/lib/utils/postgres-wipe.js +9 -2
- package/lib/utils/resolve-env-mode.js +47 -0
- package/lib/utils/secrets-generator.js +31 -25
- package/lib/utils/secrets-missing-error.js +164 -0
- package/lib/utils/service-url-label.js +81 -0
- package/lib/utils/setup-quiet-output.js +25 -0
- package/lib/utils/template-helpers.js +15 -0
- package/lib/utils/token-manager-client-credentials.js +201 -0
- package/lib/utils/token-manager-messages.js +8 -0
- package/lib/utils/token-manager-refresh.js +12 -2
- package/lib/utils/token-manager.js +86 -105
- package/lib/utils/upload-pipeline-ui.js +12 -5
- package/lib/utils/upload-sync-options.js +42 -0
- package/lib/utils/url-declarative-public-base.js +21 -3
- package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
- package/lib/utils/validation-poll-ui.js +8 -0
- package/lib/utils/validation-run-stage.js +45 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +2 -1
- package/templates/applications/dataplane/env.template +24 -1
- package/templates/applications/miso-controller/env.template +16 -0
- package/templates/external-system/README.md.hbs +74 -17
- package/templates/python/docker-compose.hbs +19 -18
- package/templates/typescript/docker-compose.hbs +18 -17
- package/lib/commands/test-governance-command-action.js +0 -55
- package/lib/commands/test-trust-command-action.js +0 -118
|
@@ -280,21 +280,146 @@ function storedCountFromResponse(res, fallback) {
|
|
|
280
280
|
* @param {Array<{ key: string, value: string }>} items - Items to send
|
|
281
281
|
* @returns {Promise<{ pushed: number, warning?: string }>}
|
|
282
282
|
*/
|
|
283
|
+
/**
|
|
284
|
+
* For bearerToken auth, credential template resolves security.token only.
|
|
285
|
+
* Legacy .env keys KV_*_APIKEY with a plain token wrongly map to kv://.../apiKey.
|
|
286
|
+
* Prefer token path; drop duplicate apiKey when both would be pushed.
|
|
287
|
+
*
|
|
288
|
+
* @param {Map<string, string>} itemsByKey
|
|
289
|
+
* @param {string} [systemKey]
|
|
290
|
+
* @param {Object} [authConfig]
|
|
291
|
+
*/
|
|
292
|
+
function reconcileBearerTokenSecretPaths(itemsByKey, systemKey, authConfig) {
|
|
293
|
+
const method = (
|
|
294
|
+
authConfig?.method ||
|
|
295
|
+
authConfig?.type ||
|
|
296
|
+
''
|
|
297
|
+
)
|
|
298
|
+
.toString()
|
|
299
|
+
.toLowerCase()
|
|
300
|
+
.replace(/_/g, '');
|
|
301
|
+
if (method !== 'bearertoken' || !systemKey) return;
|
|
302
|
+
const tokenPath = `kv://${systemKey}/token`;
|
|
303
|
+
const apiKeyPath = `kv://${systemKey}/apiKey`;
|
|
304
|
+
if (!itemsByKey.has(apiKeyPath)) return;
|
|
305
|
+
if (!itemsByKey.has(tokenPath)) {
|
|
306
|
+
itemsByKey.set(tokenPath, itemsByKey.get(apiKeyPath));
|
|
307
|
+
}
|
|
308
|
+
itemsByKey.delete(apiKeyPath);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Optional dev/lab dataplane API key (Bearer bypass) for credential secret push only.
|
|
313
|
+
* Does not alter pipeline publish auth when AIFABRIX_DEPLOYMENT_AUTH=client-credentials.
|
|
314
|
+
* @returns {string|null}
|
|
315
|
+
*/
|
|
316
|
+
function resolveLabDataplaneApiKey() {
|
|
317
|
+
const candidates = [
|
|
318
|
+
process.env.AIFABRIX_DATAPLANE_API_KEY,
|
|
319
|
+
process.env.DATAPLANE_API_KEY,
|
|
320
|
+
process.env.API_KEY
|
|
321
|
+
];
|
|
322
|
+
for (const raw of candidates) {
|
|
323
|
+
const trimmed = typeof raw === 'string' ? raw.trim() : '';
|
|
324
|
+
if (trimmed) return trimmed;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @param {string} token
|
|
331
|
+
* @returns {{ type: string, token: string }}
|
|
332
|
+
*/
|
|
333
|
+
function apiKeyAuthConfig(token) {
|
|
334
|
+
return { type: 'bearer', token };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const PERMISSION_DENIED_WARNING =
|
|
338
|
+
'Could not push credential secrets (permission denied or unauthenticated). Ensure dataplane role has credential:create if you use KV_* in .env, or set AIFABRIX_DATAPLANE_API_KEY for local lab bypass.';
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @param {Object} res
|
|
342
|
+
* @returns {boolean}
|
|
343
|
+
*/
|
|
344
|
+
function isStoreResponseFailed(res) {
|
|
345
|
+
return Boolean(res && (res.success === false || res.data?.success === false));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @param {Object} res
|
|
350
|
+
* @returns {number|undefined}
|
|
351
|
+
*/
|
|
352
|
+
function storeResponseStatus(res) {
|
|
353
|
+
return res?.status ?? res?.statusCode;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @param {Object} res
|
|
358
|
+
* @returns {{ pushed: number, warning: string }}
|
|
359
|
+
*/
|
|
360
|
+
function storeErrorWarning(res) {
|
|
361
|
+
const errMsg =
|
|
362
|
+
res.formattedError ||
|
|
363
|
+
res.data?.formattedError ||
|
|
364
|
+
res.error ||
|
|
365
|
+
res.data?.error ||
|
|
366
|
+
'Failed to push credential secrets to dataplane.';
|
|
367
|
+
return { pushed: 0, warning: errMsg };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* @param {string} dataplaneUrl
|
|
372
|
+
* @param {Object} authConfig
|
|
373
|
+
* @param {Array<{ key: string, value: string }>} items
|
|
374
|
+
* @returns {Promise<{ pushed: number }|null>}
|
|
375
|
+
*/
|
|
376
|
+
async function tryLabApiKeyRetry(dataplaneUrl, authConfig, items) {
|
|
377
|
+
const labKey = resolveLabDataplaneApiKey();
|
|
378
|
+
if (!labKey || authConfig?.token === labKey) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const retry = await storeCredentialSecrets(
|
|
382
|
+
dataplaneUrl,
|
|
383
|
+
apiKeyAuthConfig(labKey),
|
|
384
|
+
items
|
|
385
|
+
);
|
|
386
|
+
if (isStoreResponseFailed(retry)) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
return { pushed: storedCountFromResponse(retry, items.length) };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @param {string} dataplaneUrl
|
|
394
|
+
* @param {Object} authConfig
|
|
395
|
+
* @param {Array<{ key: string, value: string }>} items
|
|
396
|
+
* @param {Object} res
|
|
397
|
+
* @returns {Promise<{ pushed: number, warning?: string }>}
|
|
398
|
+
*/
|
|
399
|
+
async function handleFailedStoreResponse(dataplaneUrl, authConfig, items, res) {
|
|
400
|
+
const status = storeResponseStatus(res);
|
|
401
|
+
if (status === 403 || status === 401) {
|
|
402
|
+
const retryResult = await tryLabApiKeyRetry(dataplaneUrl, authConfig, items);
|
|
403
|
+
if (retryResult) {
|
|
404
|
+
return retryResult;
|
|
405
|
+
}
|
|
406
|
+
return { pushed: 0, warning: PERMISSION_DENIED_WARNING };
|
|
407
|
+
}
|
|
408
|
+
return storeErrorWarning(res);
|
|
409
|
+
}
|
|
410
|
+
|
|
283
411
|
async function sendCredentialSecrets(dataplaneUrl, authConfig, items) {
|
|
284
412
|
try {
|
|
285
413
|
const res = await storeCredentialSecrets(dataplaneUrl, authConfig, items);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const status = res.status ?? res.statusCode;
|
|
289
|
-
if (status === 403 || status === 401) {
|
|
290
|
-
return { pushed: 0, warning: 'Could not push credential secrets (permission denied or unauthenticated). Ensure dataplane role has credential:create if you use KV_* in .env.' };
|
|
291
|
-
}
|
|
292
|
-
const errMsg = res.formattedError || res.data?.formattedError || res.error || res.data?.error || 'Failed to push credential secrets to dataplane.';
|
|
293
|
-
return { pushed: 0, warning: errMsg };
|
|
414
|
+
if (isStoreResponseFailed(res)) {
|
|
415
|
+
return handleFailedStoreResponse(dataplaneUrl, authConfig, items, res);
|
|
294
416
|
}
|
|
295
417
|
return { pushed: storedCountFromResponse(res, items.length) };
|
|
296
418
|
} catch (err) {
|
|
297
|
-
return {
|
|
419
|
+
return {
|
|
420
|
+
pushed: 0,
|
|
421
|
+
warning: err.message || 'Failed to push credential secrets to dataplane.'
|
|
422
|
+
};
|
|
298
423
|
}
|
|
299
424
|
}
|
|
300
425
|
|
|
@@ -322,6 +447,7 @@ async function pushCredentialSecrets(dataplaneUrl, authConfig, options = {}) {
|
|
|
322
447
|
const itemsByKey = new Map();
|
|
323
448
|
buildItemsFromEnv(envFilePath, secrets, itemsByKey, appName);
|
|
324
449
|
buildItemsFromPayload(payload, secrets, itemsByKey);
|
|
450
|
+
reconcileBearerTokenSecretPaths(itemsByKey, appName, authConfig);
|
|
325
451
|
|
|
326
452
|
const items = Array.from(itemsByKey.entries())
|
|
327
453
|
.filter(([k]) => isValidKvPath(k))
|
|
@@ -362,6 +488,8 @@ module.exports = {
|
|
|
362
488
|
collectKvEnvVarsAsSecretItems,
|
|
363
489
|
collectKvRefsFromPayload,
|
|
364
490
|
pushCredentialSecrets,
|
|
491
|
+
reconcileBearerTokenSecretPaths,
|
|
492
|
+
resolveLabDataplaneApiKey,
|
|
365
493
|
kvEnvKeyToPath,
|
|
366
494
|
getKvPathSegmentForSecurityKey,
|
|
367
495
|
systemKeyToKvPrefix,
|
|
@@ -8,7 +8,127 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const config = require('../core/config');
|
|
11
15
|
const { discoverDataplaneUrl } = require('../commands/wizard-dataplane');
|
|
16
|
+
const pathsUtil = require('./paths');
|
|
17
|
+
const { loadConfigFile } = require('./config-format');
|
|
18
|
+
|
|
19
|
+
/** Matches `templates/applications/dataplane/application.yaml` and `calculateComposePort`. */
|
|
20
|
+
const DEFAULT_DATAPLANE_MANIFEST_PORT = 3001;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} url
|
|
24
|
+
* @returns {number|null}
|
|
25
|
+
*/
|
|
26
|
+
function localhostPortFromUrl(url) {
|
|
27
|
+
if (!url || typeof url !== 'string') return null;
|
|
28
|
+
const normalized = url.replace(/127\.0\.0\.1/g, 'localhost');
|
|
29
|
+
const match = normalized.match(/localhost:(\d+)/);
|
|
30
|
+
if (!match) return null;
|
|
31
|
+
const port = parseInt(match[1], 10);
|
|
32
|
+
return Number.isFinite(port) ? port : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read dataplane `port` from materialized builder config or bundled template.
|
|
37
|
+
*
|
|
38
|
+
* @returns {number}
|
|
39
|
+
*/
|
|
40
|
+
function loadDataplaneManifestListenPort() {
|
|
41
|
+
const tryLoad = (configPath) => {
|
|
42
|
+
if (!fs.existsSync(configPath)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const variables = loadConfigFile(configPath);
|
|
46
|
+
const port = variables && variables.port !== undefined ? Number(variables.port) : NaN;
|
|
47
|
+
return Number.isFinite(port) && port > 0 ? port : null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const builderPath = pathsUtil.getBuilderPath('dataplane');
|
|
52
|
+
if (fs.existsSync(builderPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const fromBuilder = tryLoad(pathsUtil.resolveApplicationConfigPath(builderPath));
|
|
55
|
+
if (fromBuilder) {
|
|
56
|
+
return fromBuilder;
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Fall through to bundled template when builder dir exists but config is missing.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// ignore
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const templatePath = path.join(
|
|
67
|
+
pathsUtil.getProjectRoot(),
|
|
68
|
+
'templates',
|
|
69
|
+
'applications',
|
|
70
|
+
'dataplane',
|
|
71
|
+
'application.yaml'
|
|
72
|
+
);
|
|
73
|
+
const fromTemplate = tryLoad(templatePath);
|
|
74
|
+
return fromTemplate || DEFAULT_DATAPLANE_MANIFEST_PORT;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Local Docker host port: `application.yaml` `port` (default 3001) + developerId×100.
|
|
79
|
+
* Same rule as {@link calculateComposePort} / `aifabrix up-platform` — not
|
|
80
|
+
* `APP_SERVICE_ENV_DEFAULTS_LOCAL.DATAPLANE_PUBLIC_PORT` (3011), which is for other local profiles.
|
|
81
|
+
*
|
|
82
|
+
* @returns {Promise<string>}
|
|
83
|
+
*/
|
|
84
|
+
async function expectedLocalDataplaneBaseUrl() {
|
|
85
|
+
const developerIdRaw = await config.getDeveloperId();
|
|
86
|
+
const idNum =
|
|
87
|
+
typeof developerIdRaw === 'string'
|
|
88
|
+
? parseInt(developerIdRaw, 10)
|
|
89
|
+
: Number(developerIdRaw);
|
|
90
|
+
const devId = Number.isFinite(idNum) && idNum >= 0 ? idNum : 0;
|
|
91
|
+
const basePort = loadDataplaneManifestListenPort();
|
|
92
|
+
const hostPort = devId === 0 ? basePort : basePort + devId * 100;
|
|
93
|
+
return `http://localhost:${hostPort}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Controller application.url can lag after dev-id changes; prefer builder host port formula for localhost.
|
|
98
|
+
* @param {string} discoveredUrl
|
|
99
|
+
* @returns {Promise<string>}
|
|
100
|
+
*/
|
|
101
|
+
async function alignDiscoveredDataplaneUrl(discoveredUrl) {
|
|
102
|
+
if (!discoveredUrl || !/localhost|127\.0\.0\.1/i.test(discoveredUrl)) {
|
|
103
|
+
return discoveredUrl;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const expectedUrl = await expectedLocalDataplaneBaseUrl();
|
|
107
|
+
const discoveredPort = localhostPortFromUrl(discoveredUrl);
|
|
108
|
+
const expectedPort = localhostPortFromUrl(expectedUrl);
|
|
109
|
+
if (
|
|
110
|
+
discoveredPort !== null &&
|
|
111
|
+
expectedPort !== null &&
|
|
112
|
+
discoveredPort !== expectedPort
|
|
113
|
+
) {
|
|
114
|
+
return expectedUrl;
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// Keep controller-discovered URL when developer config is unavailable.
|
|
118
|
+
}
|
|
119
|
+
return discoveredUrl;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @returns {string|null}
|
|
124
|
+
*/
|
|
125
|
+
function dataplaneUrlFromProcessEnv() {
|
|
126
|
+
const raw = process.env.DP || process.env.DATAPLANE_URL;
|
|
127
|
+
if (!raw || typeof raw !== 'string') {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return raw.trim().replace(/\/$/, '');
|
|
131
|
+
}
|
|
12
132
|
|
|
13
133
|
/**
|
|
14
134
|
* Resolve dataplane URL by discovering from controller
|
|
@@ -22,9 +142,16 @@ const { discoverDataplaneUrl } = require('../commands/wizard-dataplane');
|
|
|
22
142
|
* @throws {Error} If dataplane URL cannot be resolved
|
|
23
143
|
*/
|
|
24
144
|
async function resolveDataplaneUrl(controllerUrl, environment, authConfig, opts = {}) {
|
|
25
|
-
|
|
145
|
+
const envUrl = dataplaneUrlFromProcessEnv();
|
|
146
|
+
if (envUrl) {
|
|
147
|
+
return envUrl;
|
|
148
|
+
}
|
|
149
|
+
const discovered = await discoverDataplaneUrl(controllerUrl, environment, authConfig, opts);
|
|
150
|
+
return alignDiscoveredDataplaneUrl(discovered);
|
|
26
151
|
}
|
|
27
152
|
|
|
28
153
|
module.exports = {
|
|
29
|
-
resolveDataplaneUrl
|
|
154
|
+
resolveDataplaneUrl,
|
|
155
|
+
expectedLocalDataplaneBaseUrl,
|
|
156
|
+
alignDiscoveredDataplaneUrl
|
|
30
157
|
};
|
|
@@ -21,7 +21,7 @@ function parseDeveloperIdNum(raw) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Host port for compose publish, localhost URLs, and health checks: port + devId×100 (dev 0 → base port).
|
|
25
25
|
* @param {number} appPort - Manifest listen port
|
|
26
26
|
* @param {number} developerIdNum
|
|
27
27
|
* @returns {number}
|
|
@@ -31,13 +31,13 @@ function publishedHostPort(appPort, developerIdNum) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Same as {@link publishedHostPort} (legacy name kept for call sites).
|
|
35
35
|
* @param {number} appPort
|
|
36
36
|
* @param {number} developerIdNum
|
|
37
37
|
* @returns {number}
|
|
38
38
|
*/
|
|
39
39
|
function localHostPort(appPort, developerIdNum) {
|
|
40
|
-
return appPort
|
|
40
|
+
return publishedHostPort(appPort, developerIdNum);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
module.exports = {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment auth mode for pipeline publish / external integration CLI.
|
|
3
|
+
* @fileoverview AIFABRIX_DEPLOYMENT_AUTH and options.deploymentAuth parsing
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {'auto'|'client-credentials'|'device'} DeploymentAuthMode
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} [options] - CLI or caller options
|
|
16
|
+
* @returns {DeploymentAuthMode}
|
|
17
|
+
*/
|
|
18
|
+
function getDeploymentAuthMode(options = {}) {
|
|
19
|
+
const raw = options.deploymentAuth || process.env.AIFABRIX_DEPLOYMENT_AUTH || 'auto';
|
|
20
|
+
const mode = String(raw).trim().toLowerCase().replace(/_/g, '-');
|
|
21
|
+
if (mode === 'client-credentials' || mode === 'client') {
|
|
22
|
+
return 'client-credentials';
|
|
23
|
+
}
|
|
24
|
+
if (mode === 'device' || mode === 'device-token' || mode === 'bearer') {
|
|
25
|
+
return 'device';
|
|
26
|
+
}
|
|
27
|
+
return 'auto';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {DeploymentAuthMode} mode
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
function shouldUseDeviceTokenForDeployment(mode) {
|
|
35
|
+
return mode !== 'client-credentials';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
getDeploymentAuthMode,
|
|
40
|
+
shouldUseDeviceTokenForDeployment
|
|
41
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Developer ID suffix variants for Docker volume names (`dev6` vs `dev06`).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Volume name suffix normalization for multi-dev Docker
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string|number} developerId
|
|
13
|
+
* @returns {string[]}
|
|
14
|
+
*/
|
|
15
|
+
function collectDeveloperVolumeIdSuffixes(developerId) {
|
|
16
|
+
const raw = String(developerId).trim();
|
|
17
|
+
const idNum = parseInt(raw, 10);
|
|
18
|
+
if (Number.isNaN(idNum) || idNum === 0) {
|
|
19
|
+
return ['0'];
|
|
20
|
+
}
|
|
21
|
+
const suffixes = new Set([String(idNum), raw]);
|
|
22
|
+
if (raw.length === 1) {
|
|
23
|
+
suffixes.add(raw.padStart(2, '0'));
|
|
24
|
+
}
|
|
25
|
+
if (String(idNum).length === 1) {
|
|
26
|
+
suffixes.add(String(idNum).padStart(2, '0'));
|
|
27
|
+
}
|
|
28
|
+
return [...suffixes];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
collectDeveloperVolumeIdSuffixes
|
|
33
|
+
};
|
package/lib/utils/env-copy.js
CHANGED
|
@@ -272,14 +272,16 @@ async function patchEnvContentForLocal(envContent, variables) {
|
|
|
272
272
|
* @param {string} outputPath - Resolved `build.envOutputPath`
|
|
273
273
|
* @param {string} envOutputPathLabel - Label for log (e.g. raw `build.envOutputPath` value)
|
|
274
274
|
*/
|
|
275
|
-
async function syncWrittenBuilderEnvToOutputPath(envPath, outputPath, envOutputPathLabel) {
|
|
275
|
+
async function syncWrittenBuilderEnvToOutputPath(envPath, outputPath, envOutputPathLabel, copyOptions = {}) {
|
|
276
276
|
const { parseEnvContentToMap, mergeEnvMapIntoContent } = require('../core/secrets');
|
|
277
277
|
const raw = fs.readFileSync(envPath, 'utf8');
|
|
278
278
|
const synced = substituteMntDataForLocal(raw, outputPath);
|
|
279
279
|
let toWrite = synced;
|
|
280
|
-
if (fs.existsSync(outputPath)) {
|
|
280
|
+
if (!copyOptions.freshEnv && fs.existsSync(outputPath)) {
|
|
281
281
|
const existingContent = fs.readFileSync(outputPath, 'utf8');
|
|
282
|
-
toWrite = mergeEnvMapIntoContent(existingContent, parseEnvContentToMap(synced)
|
|
282
|
+
toWrite = mergeEnvMapIntoContent(existingContent, parseEnvContentToMap(synced), {
|
|
283
|
+
appendMissingFromNewMap: true
|
|
284
|
+
});
|
|
283
285
|
}
|
|
284
286
|
fs.writeFileSync(outputPath, toWrite, { mode: 0o600 });
|
|
285
287
|
logger.log(formatSuccessLine(`Copied resolved .env to envOutputPath (${envOutputPathLabel})`));
|
|
@@ -300,10 +302,10 @@ async function writeLocalEnvToOutputPath(outputPath, appName, secretsPath, envOu
|
|
|
300
302
|
let localEnvContent = await generateEnvContent(appName, secretsPath, 'local', false, genOpts);
|
|
301
303
|
localEnvContent = substituteMntDataForLocal(localEnvContent, outputPath);
|
|
302
304
|
let toWrite = localEnvContent;
|
|
303
|
-
if (fs.existsSync(outputPath)) {
|
|
305
|
+
if (!extraOpts.freshEnv && fs.existsSync(outputPath)) {
|
|
304
306
|
const existingContent = fs.readFileSync(outputPath, 'utf8');
|
|
305
307
|
const localMap = parseEnvContentToMap(localEnvContent);
|
|
306
|
-
toWrite = mergeEnvMapIntoContent(existingContent, localMap);
|
|
308
|
+
toWrite = mergeEnvMapIntoContent(existingContent, localMap, { appendMissingFromNewMap: true });
|
|
307
309
|
}
|
|
308
310
|
fs.writeFileSync(outputPath, toWrite, { mode: 0o600 });
|
|
309
311
|
logger.log(formatSuccessLine(`Generated local .env at: ${envOutputPathLabel}`));
|
|
@@ -350,15 +352,16 @@ async function copyOrRegenerateEnvForNamedApp(opts) {
|
|
|
350
352
|
secretsPath,
|
|
351
353
|
label,
|
|
352
354
|
preferLocal,
|
|
353
|
-
appPathForLocal
|
|
355
|
+
appPathForLocal,
|
|
356
|
+
freshEnv
|
|
354
357
|
} = opts;
|
|
355
|
-
const localOpts = { appPath: appPathForLocal || undefined };
|
|
358
|
+
const localOpts = { appPath: appPathForLocal || undefined, freshEnv: freshEnv === true };
|
|
356
359
|
if (preferLocal) {
|
|
357
360
|
await writeLocalEnvToOutputPath(outputPath, appName, secretsPath, label, localOpts);
|
|
358
361
|
return;
|
|
359
362
|
}
|
|
360
363
|
if (fs.existsSync(envPath)) {
|
|
361
|
-
await syncWrittenBuilderEnvToOutputPath(envPath, outputPath, label);
|
|
364
|
+
await syncWrittenBuilderEnvToOutputPath(envPath, outputPath, label, { freshEnv: freshEnv === true });
|
|
362
365
|
return;
|
|
363
366
|
}
|
|
364
367
|
await writeLocalEnvToOutputPath(outputPath, appName, secretsPath, label, localOpts);
|
|
@@ -386,6 +389,7 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath,
|
|
|
386
389
|
(variablesPath ? path.dirname(variablesPath) : null);
|
|
387
390
|
const preferLocal =
|
|
388
391
|
copyOptions && typeof copyOptions === 'object' && copyOptions.preferLocalEnvOutputPath === true;
|
|
392
|
+
const freshEnv = copyOptions && typeof copyOptions === 'object' && copyOptions.freshEnv === true;
|
|
389
393
|
if (appName) {
|
|
390
394
|
await copyOrRegenerateEnvForNamedApp({
|
|
391
395
|
envPath,
|
|
@@ -394,7 +398,8 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath,
|
|
|
394
398
|
secretsPath,
|
|
395
399
|
label,
|
|
396
400
|
preferLocal,
|
|
397
|
-
appPathForLocal
|
|
401
|
+
appPathForLocal,
|
|
402
|
+
freshEnv
|
|
398
403
|
});
|
|
399
404
|
} else {
|
|
400
405
|
await writePatchedEnvToOutputPath(envPath, outputPath, variables, label);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment active KV_* env lines that are not in the expected key set (stale auth vars).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Shared orphan KV line handling for env.template repair and resolve merge
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Comments active KV_* lines whose env key is not in the expected set.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} content - .env or env.template content
|
|
15
|
+
* @param {Set<string>|Map<string, unknown>} expectedKeys - Keys that should stay active
|
|
16
|
+
* @returns {{ content: string, changed: boolean }}
|
|
17
|
+
*/
|
|
18
|
+
function commentOrphanKvEnvLines(content, expectedKeys) {
|
|
19
|
+
const expected =
|
|
20
|
+
expectedKeys instanceof Set ? expectedKeys : new Set(expectedKeys.keys());
|
|
21
|
+
const changedRef = { value: false };
|
|
22
|
+
const out = (content || '').split(/\r?\n/).map((line) => {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
25
|
+
return line;
|
|
26
|
+
}
|
|
27
|
+
const eq = line.indexOf('=');
|
|
28
|
+
if (eq <= 0 || !/^KV_/i.test(trimmed)) {
|
|
29
|
+
return line;
|
|
30
|
+
}
|
|
31
|
+
const key = line.substring(0, eq).trim();
|
|
32
|
+
if (expected.has(key)) {
|
|
33
|
+
return line;
|
|
34
|
+
}
|
|
35
|
+
changedRef.value = true;
|
|
36
|
+
return `${line.match(/^\s*/)[0]}# ${trimmed}`;
|
|
37
|
+
});
|
|
38
|
+
const joined = out.join('\n');
|
|
39
|
+
const normalized = joined.length > 0 && !joined.endsWith('\n') ? `${joined}\n` : joined;
|
|
40
|
+
return { content: normalized, changed: changedRef.value };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { commentOrphanKvEnvLines };
|
|
@@ -10,6 +10,7 @@ const { formatBlockingError } = require('../cli-test-layout-chalk');
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
|
+
const { serviceLabelDisplayName } = require('../service-url-label');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Normalizes error message to string
|
|
@@ -56,9 +57,16 @@ function addControllerUrlToMessage(lines, errorData) {
|
|
|
56
57
|
* @param {Object} errorData - Error response data
|
|
57
58
|
*/
|
|
58
59
|
function formatConnectionRefusedError(lines, errorData) {
|
|
59
|
-
|
|
60
|
+
const label = serviceLabelDisplayName(errorData?.serviceLabel || 'service');
|
|
61
|
+
lines.push(chalk.yellow(`Cannot connect to ${label}.`));
|
|
60
62
|
addControllerUrlToMessage(lines, errorData);
|
|
61
|
-
|
|
63
|
+
if (label === 'dataplane') {
|
|
64
|
+
lines.push(chalk.gray('Check if the dataplane is running (e.g. aifabrix run dataplane --reload).'));
|
|
65
|
+
} else if (label === 'controller') {
|
|
66
|
+
lines.push(chalk.gray('Check if the controller is running.'));
|
|
67
|
+
} else {
|
|
68
|
+
lines.push(chalk.gray('Check the service URL and network.'));
|
|
69
|
+
}
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
/**
|
|
@@ -68,9 +76,10 @@ function formatConnectionRefusedError(lines, errorData) {
|
|
|
68
76
|
* @param {Object} errorData - Error response data
|
|
69
77
|
*/
|
|
70
78
|
function formatHostnameNotFoundError(lines, errorData) {
|
|
71
|
-
|
|
79
|
+
const label = serviceLabelDisplayName(errorData?.serviceLabel || 'service');
|
|
80
|
+
lines.push(chalk.yellow(`${label.charAt(0).toUpperCase()}${label.slice(1)} hostname not found.`));
|
|
72
81
|
addControllerUrlToMessage(lines, errorData);
|
|
73
|
-
lines.push(chalk.gray(
|
|
82
|
+
lines.push(chalk.gray(`Check your ${label} URL (see aifabrix auth status).`));
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
/**
|
|
@@ -28,6 +28,15 @@ function isPublicationResultShape(obj) {
|
|
|
28
28
|
return !!(obj && typeof obj === 'object' && (obj.uploadId || obj.system) && Array.isArray(obj.datasources));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* True when dataplane skipped publish because manifest matches running config.
|
|
33
|
+
* @param {*} publication - Parsed PublicationResult
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function isPublicationUnchanged(publication) {
|
|
37
|
+
return !!(publication && publication.uploadStatus === 'unchanged');
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
/**
|
|
32
41
|
* Returns publication payload from upload API response.
|
|
33
42
|
* @param {*} res - Response from uploadApplicationViaPipeline
|
|
@@ -432,6 +441,7 @@ module.exports = {
|
|
|
432
441
|
unwrapApiData,
|
|
433
442
|
unwrapPublicationResult,
|
|
434
443
|
isPublicationResultShape,
|
|
444
|
+
isPublicationUnchanged,
|
|
435
445
|
classifyDatasourceTierADetail,
|
|
436
446
|
classifyDatasourceTierA,
|
|
437
447
|
formatTierAPartialHint,
|