@aifabrix/builder 2.50.0 → 2.51.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/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +374 -0
- package/.cursor/rules/assistant-driven-integration-build.mdc +40 -0
- package/integration/hubspot-test/create-hubspot.js +1 -16
- package/jest.isolated-projects.js +77 -1
- package/jest.projects.js +5 -426
- package/lib/api/assistants.api.js +67 -0
- package/lib/api/datasources-extended.api.js +6 -6
- package/lib/api/evidence-authoring-helpers.js +267 -0
- package/lib/api/evidence-authoring.api.js +337 -0
- package/lib/api/evidence-kinds.api.js +128 -0
- package/lib/api/evidence.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +47 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/resource-types.api.js +4 -4
- package/lib/api/roles.api.js +121 -0
- package/lib/api/types/evidence-kinds.types.js +42 -0
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/types/roles.types.js +36 -0
- package/lib/api/work.api.js +283 -0
- package/lib/app/certification-verify-rows.js +1 -1
- package/lib/app/run-deploy-job.js +108 -0
- package/lib/app/run-helpers.js +10 -3
- package/lib/app/run.js +8 -0
- package/lib/app/show.js +2 -2
- package/lib/certification/merge-certification-from-artifact.js +1 -1
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +8 -7
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +16 -3
- package/lib/channels/channel-credential.js +3 -1
- package/lib/channels/channel-probe-evidence.js +79 -0
- package/lib/channels/channel-probe.js +265 -13
- package/lib/channels/channel-runtime-resolver.js +120 -29
- package/lib/channels/channel-status-display.js +44 -0
- package/lib/channels/channel-targets.js +7 -0
- package/lib/channels/chatgpt-runtime-contract.js +29 -0
- package/lib/channels/microsoft-copilot-package.js +280 -0
- package/lib/channels/runtime-tool-catalog.js +165 -0
- package/lib/cli/index.js +6 -0
- package/lib/cli/setup-app.js +26 -5
- package/lib/cli/setup-dev.js +23 -7
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +12 -0
- package/lib/cli/setup-utility-help-after.js +12 -0
- package/lib/cli/setup-utility-validate.js +41 -2
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +23 -0
- package/lib/commands/deploy-gate-orchestrator.js +64 -18
- package/lib/commands/dev-cli-handlers.js +6 -3
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/dev-init.js +7 -109
- package/lib/commands/dev-refresh.js +197 -0
- package/lib/commands/evidence-kind.js +478 -0
- package/lib/commands/governance-command-action.js +4 -2
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/lifecycle-external.js +10 -4
- package/lib/commands/repair-datasource-apply.js +10 -1
- package/lib/commands/repair-datasource-run.js +4 -2
- package/lib/commands/repair-datasource-test-payload.js +80 -19
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
- package/lib/commands/repair-internal.js +9 -1
- package/lib/commands/repair-openapi-export.js +1 -1
- package/lib/commands/repair-openapi-sync.js +43 -18
- package/lib/commands/resource-type.js +101 -18
- package/lib/commands/role-assistant.js +313 -0
- package/lib/commands/role.js +452 -0
- package/lib/commands/setup.js +1 -1
- package/lib/commands/upload-data-sync-finalize.js +5 -4
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- package/lib/commands/validate-json.js +230 -0
- package/lib/commands/verify-governance-combined-external.js +73 -9
- package/lib/commands/verify-governance-db.js +80 -25
- package/lib/commands/verify-governance-external.js +29 -5
- package/lib/commands/verify-operations-step-plan.js +78 -40
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/core/audit-logger.js +1 -1
- package/lib/core/secrets-load.js +1 -3
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/capability/relate-operations.js +1 -1
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +35 -1
- package/lib/external-system/deploy.js +23 -10
- package/lib/external-system/download-helpers.js +111 -2
- package/lib/external-system/download-role-assistant.js +55 -0
- package/lib/external-system/download.js +45 -26
- package/lib/external-system/test-helpers.js +1 -1
- package/lib/external-system/test.js +39 -3
- package/lib/generator/builders.js +3 -0
- package/lib/generator/helpers.js +13 -5
- package/lib/generator/split-variables.js +9 -3
- package/lib/generator/wizard.js +51 -4
- package/lib/infrastructure/helpers-docker-check.js +6 -0
- package/lib/infrastructure/helpers.js +1 -1
- package/lib/lifecycle/access-control-abac.js +121 -0
- package/lib/lifecycle/lifecycle-governance-options.js +4 -1
- package/lib/lifecycle/product-model.js +3 -2
- package/lib/lifecycle/report-display.js +14 -2
- package/lib/programmatic/auth-config.js +3 -2
- package/lib/programmatic/bearer-auth.js +1 -1
- package/lib/programmatic/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- package/lib/programmatic/enterprise-mcp-client.js +18 -2
- package/lib/programmatic/index.js +4 -0
- package/lib/programmatic/manifest-patch-section.js +48 -9
- package/lib/programmatic/manifest-section-registry-data.js +3 -2
- package/lib/programmatic/manifest-section-registry.js +18 -1
- package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
- package/lib/programmatic/openapi-component-schemas.js +16 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +55 -5
- package/lib/programmatic/openapi-evidence-schemas.js +161 -0
- package/lib/programmatic/openapi-manifest-schemas.js +6 -2
- package/lib/programmatic/openapi-operation-bindings.js +99 -0
- package/lib/programmatic/openapi-request-schemas.js +36 -1
- package/lib/programmatic/openapi-result-schemas.js +29 -12
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +6 -3
- package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
- package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
- package/lib/programmatic/openapi-schema-helpers.js +16 -1
- package/lib/programmatic/openapi-spec.js +48 -29
- package/lib/programmatic/orchestration-openapi-meta.js +5 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +89 -2
- package/lib/programmatic/run-certify.js +15 -2
- package/lib/programmatic/run-deploy.js +74 -0
- package/lib/programmatic/run-evidence-authoring.js +80 -0
- package/lib/programmatic/run-protection.js +124 -0
- package/lib/programmatic/run-repository-emcp.js +11 -3
- package/lib/programmatic/run-template-workspace.js +42 -2
- package/lib/programmatic/workspace-context.js +6 -0
- package/lib/protection/auth-context.js +48 -6
- package/lib/protection/paths.js +22 -0
- package/lib/protection/run-protection-create-helpers.js +22 -1
- package/lib/protection/run-protection-create.js +1 -1
- package/lib/protection/sync-after-upload.js +1 -1
- package/lib/resolvers/evidence-kind-file.js +230 -0
- package/lib/resolvers/resource-type-file.js +36 -1
- package/lib/resolvers/role-file.js +227 -0
- package/lib/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-sync.js +465 -0
- package/lib/role-assistant/settings.js +126 -0
- package/lib/role-assistant/test-cases-evaluate.js +170 -0
- package/lib/role-assistant/test-cases-honesty.js +184 -0
- package/lib/role-assistant/test-cases.js +364 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
- package/lib/role-assistant/test-runner-workhub.js +385 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +36 -2
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +121 -4
- package/lib/schema/external-system.schema.json +20 -6
- package/lib/schema/infra.parameter.yaml +45 -1
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/schema/type/credential-field-registry.json +19 -3
- package/lib/utils/api.js +52 -21
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/app-register-config.js +4 -3
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/cli-sync-options.js +59 -3
- package/lib/utils/cli-utils.js +11 -3
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/dev-ca-install.js +5 -6
- package/lib/utils/dev-config.js +2 -2
- package/lib/utils/dev-list-sort.js +145 -0
- package/lib/utils/dev-platform-tls.js +0 -63
- package/lib/utils/docker-build.js +46 -27
- package/lib/utils/docker-daemon-tls-ca.js +1 -1
- package/lib/utils/docker-exec.js +91 -6
- package/lib/utils/docker-run-ephemeral.js +55 -0
- package/lib/utils/enterprise-knowledge-paths.js +29 -12
- package/lib/utils/env-copy-keycloak.js +196 -0
- package/lib/utils/env-copy.js +35 -4
- package/lib/utils/error-formatters/error-parser.js +0 -3
- package/lib/utils/external-env-template.js +1 -1
- package/lib/utils/external-system-readiness-compact-blocks.js +28 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/health-check-request.js +2 -3
- package/lib/utils/help-builder.js +4 -0
- package/lib/utils/image-name.js +22 -4
- package/lib/utils/logger.js +37 -4
- package/lib/utils/manifest-source-emit.js +1 -1
- package/lib/utils/paths.js +98 -26
- package/lib/utils/pipeline-upload-auth-retry.js +19 -3
- package/lib/utils/postgres-platform-bootstrap.js +24 -17
- package/lib/utils/postgres-wipe.js +5 -2
- package/lib/utils/remote-dev-auth.js +39 -4
- package/lib/utils/resolve-docker-image-ref.js +36 -6
- package/lib/utils/role-assistant-paths.js +98 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +47 -22
- package/lib/utils/upload-sync-options.js +17 -3
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/openapi-contract-surface-validator.js +4 -0
- package/lib/validation/validate-external-file.js +8 -1
- package/lib/validation/validator.js +6 -5
- package/package.json +35 -12
- package/scripts/export-builder-api-evidence.js +3 -3
- package/scripts/lib/plan-actor.js +2 -2
- package/scripts/lib/plan-meta.js +1 -1
- package/scripts/lib/plan-parse.js +1 -1
- package/scripts/lib/plan-prevalidate-gates.js +12 -1
- package/scripts/lib/plan-run-report.js +13 -2
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +10 -10
- package/templates/applications/dataplane/application.yaml +47 -17
- package/templates/applications/dataplane/env.template +28 -9
- package/templates/applications/dataplane/rbac.yaml +78 -2
- package/templates/applications/miso-controller/application.yaml +10 -1
- package/templates/applications/miso-controller/env.template +13 -8
- package/templates/applications/miso-controller/rbac.yaml +72 -0
- package/templates/channels/chatgpt/install.template.md.hbs +42 -24
- package/templates/channels/chatgpt/integration/README.md +10 -7
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
- package/templates/channels/chatgpt/integration/env.template +1 -1
- package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
- package/templates/channels/microsoft-copilot/integration/README.md +9 -7
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +1 -1
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
- package/templates/channels/slack/integration/slack-system.json +60 -2
- package/templates/python/requirements.txt +1 -1
|
@@ -50,7 +50,7 @@ function handleHealthResponse(res, data, debug, resolve) {
|
|
|
50
50
|
resolve(isHealthy);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject,
|
|
53
|
+
function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject, _allowInsecureTls = false) {
|
|
54
54
|
try {
|
|
55
55
|
const urlObj = new URL(healthCheckUrl);
|
|
56
56
|
const transport = urlObj.protocol === 'https:' ? https : http;
|
|
@@ -58,8 +58,7 @@ function doHealthCheckRequest(healthCheckUrl, debug, resolve, reject, allowInsec
|
|
|
58
58
|
hostname: urlObj.hostname,
|
|
59
59
|
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
60
60
|
path: urlObj.pathname + urlObj.search,
|
|
61
|
-
method: 'GET'
|
|
62
|
-
...(allowInsecureTls ? { rejectUnauthorized: false } : {})
|
|
61
|
+
method: 'GET'
|
|
63
62
|
};
|
|
64
63
|
if (debug) {
|
|
65
64
|
logger.log(chalk.gray(`[DEBUG] Health check request: ${healthCheckUrl}`));
|
|
@@ -78,6 +78,9 @@ const CATEGORIES = [
|
|
|
78
78
|
{ name: 'dimension' },
|
|
79
79
|
{ name: 'dimension value', term: 'dimension value <command>' },
|
|
80
80
|
{ name: 'resource-type' },
|
|
81
|
+
{ name: 'role' },
|
|
82
|
+
{ name: 'role-assistant' },
|
|
83
|
+
{ name: 'evidence-kind' },
|
|
81
84
|
{ name: 'enterprise-knowledge' },
|
|
82
85
|
{ name: 'identity' },
|
|
83
86
|
{ name: 'protection' },
|
|
@@ -93,6 +96,7 @@ const CATEGORIES = [
|
|
|
93
96
|
{ name: 'convert', term: 'convert <appKey|systemKey>' },
|
|
94
97
|
{ name: 'show', term: 'show <appKey|systemKey>' },
|
|
95
98
|
{ name: 'validate', term: 'validate [appKey|systemKey|file]' },
|
|
99
|
+
{ name: 'validate-json', term: 'validate-json [appKey|systemKey]' },
|
|
96
100
|
{ name: 'parameters', term: 'parameters validate' },
|
|
97
101
|
{ name: 'diff', term: 'diff <file1> <file2>' }
|
|
98
102
|
]
|
package/lib/utils/image-name.js
CHANGED
|
@@ -9,6 +9,23 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
const DOCKER_REPOSITORY_PATH_RE = /^[a-z0-9][a-z0-9._/-]*$/i;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} repositoryPath
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function assertSafeRepositoryPath(repositoryPath) {
|
|
19
|
+
const normalized = String(repositoryPath || '').trim();
|
|
20
|
+
if (!normalized) {
|
|
21
|
+
throw new Error('Repository path is required');
|
|
22
|
+
}
|
|
23
|
+
if (!DOCKER_REPOSITORY_PATH_RE.test(normalized)) {
|
|
24
|
+
throw new Error(`Repository path contains unsafe characters: ${repositoryPath}`);
|
|
25
|
+
}
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
/**
|
|
13
30
|
* Builds a developer-scoped image name for local Docker builds.
|
|
14
31
|
* Format: "<base>-dev<developerId>" when developerId is a positive integer.
|
|
@@ -55,18 +72,19 @@ function buildDevImageRepositoryPath(repositoryPath, developerId) {
|
|
|
55
72
|
if (!repositoryPath || typeof repositoryPath !== 'string') {
|
|
56
73
|
throw new Error('Repository path is required and must be a string');
|
|
57
74
|
}
|
|
75
|
+
const safeRepositoryPath = assertSafeRepositoryPath(repositoryPath);
|
|
58
76
|
const idNum =
|
|
59
77
|
typeof developerId === 'number' ? developerId : parseInt(String(developerId), 10);
|
|
60
78
|
if (!Number.isFinite(idNum) || idNum === 0) {
|
|
61
|
-
return
|
|
79
|
+
return safeRepositoryPath;
|
|
62
80
|
}
|
|
63
|
-
const idx =
|
|
64
|
-
const tail = idx === -1 ?
|
|
81
|
+
const idx = safeRepositoryPath.lastIndexOf('/');
|
|
82
|
+
const tail = idx === -1 ? safeRepositoryPath : safeRepositoryPath.slice(idx + 1);
|
|
65
83
|
const scopedTail = buildDevImageName(tail, developerId);
|
|
66
84
|
if (idx === -1) {
|
|
67
85
|
return scopedTail;
|
|
68
86
|
}
|
|
69
|
-
return `${
|
|
87
|
+
return `${safeRepositoryPath.slice(0, idx)}/${scopedTail}`;
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
module.exports = {
|
package/lib/utils/logger.js
CHANGED
|
@@ -11,6 +11,39 @@
|
|
|
11
11
|
|
|
12
12
|
/* eslint-disable no-console */
|
|
13
13
|
|
|
14
|
+
const SENSITIVE_KEY_RE = /(password|secret|token|apikey|apiKey|authorization|clientSecret|x-client-token)/i;
|
|
15
|
+
const BEARER_TOKEN_RE = /(Bearer\s+)[A-Za-z0-9._~-]+/gi;
|
|
16
|
+
|
|
17
|
+
function maskString(value) {
|
|
18
|
+
const asString = String(value);
|
|
19
|
+
return asString.replace(BEARER_TOKEN_RE, '$1[REDACTED]');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sanitizeValue(value) {
|
|
23
|
+
if (value instanceof Error) {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'string') {
|
|
27
|
+
return maskString(value);
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return value.map((item) => sanitizeValue(item));
|
|
31
|
+
}
|
|
32
|
+
if (value && typeof value === 'object') {
|
|
33
|
+
/** @type {Record<string, any>} */
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
36
|
+
if (SENSITIVE_KEY_RE.test(key)) {
|
|
37
|
+
out[key] = '[REDACTED]';
|
|
38
|
+
} else {
|
|
39
|
+
out[key] = sanitizeValue(entry);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
/**
|
|
15
48
|
* Logger utility that wraps console methods
|
|
16
49
|
* All console statements should use this logger to avoid eslint warnings
|
|
@@ -21,7 +54,7 @@ const logger = {
|
|
|
21
54
|
* @param {...any} args - Arguments to log
|
|
22
55
|
*/
|
|
23
56
|
log: (...args) => {
|
|
24
|
-
console.log(...args);
|
|
57
|
+
console.log(...args.map((arg) => sanitizeValue(arg)));
|
|
25
58
|
},
|
|
26
59
|
|
|
27
60
|
/**
|
|
@@ -29,7 +62,7 @@ const logger = {
|
|
|
29
62
|
* @param {...any} args - Arguments to log
|
|
30
63
|
*/
|
|
31
64
|
error: (...args) => {
|
|
32
|
-
console.error(...args);
|
|
65
|
+
console.error(...args.map((arg) => sanitizeValue(arg)));
|
|
33
66
|
},
|
|
34
67
|
|
|
35
68
|
/**
|
|
@@ -37,7 +70,7 @@ const logger = {
|
|
|
37
70
|
* @param {...any} args - Arguments to log
|
|
38
71
|
*/
|
|
39
72
|
warn: (...args) => {
|
|
40
|
-
console.warn(...args);
|
|
73
|
+
console.warn(...args.map((arg) => sanitizeValue(arg)));
|
|
41
74
|
},
|
|
42
75
|
|
|
43
76
|
/**
|
|
@@ -45,7 +78,7 @@ const logger = {
|
|
|
45
78
|
* @param {...any} args - Arguments to log
|
|
46
79
|
*/
|
|
47
80
|
info: (...args) => {
|
|
48
|
-
console.log(...args);
|
|
81
|
+
console.log(...args.map((arg) => sanitizeValue(arg)));
|
|
49
82
|
}
|
|
50
83
|
};
|
|
51
84
|
|
package/lib/utils/paths.js
CHANGED
|
@@ -382,15 +382,41 @@ function getIntegrationBuilderBaseDir() {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
/**
|
|
385
|
-
* Returns the default integration root
|
|
386
|
-
*
|
|
385
|
+
* Returns the default integration root: {@link getCwdIntegrationRoot} when present, else
|
|
386
|
+
* under {@link getAppsMaterializationParent}. Listing may still scan both trees
|
|
387
|
+
* (see {@link listIntegrationAppNames}).
|
|
387
388
|
*
|
|
388
389
|
* @returns {string} Absolute path to integration/ directory
|
|
389
390
|
*/
|
|
390
391
|
function getIntegrationRoot() {
|
|
392
|
+
const cwdRoot = getCwdIntegrationRoot();
|
|
393
|
+
if (cwdRoot) {
|
|
394
|
+
return cwdRoot;
|
|
395
|
+
}
|
|
391
396
|
return path.join(getAppsMaterializationParent(), 'integration');
|
|
392
397
|
}
|
|
393
398
|
|
|
399
|
+
/**
|
|
400
|
+
* True when path exists and is a directory (best-effort; false on error).
|
|
401
|
+
* @param {string} dirPath
|
|
402
|
+
* @returns {boolean}
|
|
403
|
+
*/
|
|
404
|
+
function isExistingDirectoryPath(dirPath) {
|
|
405
|
+
if (!dirPath || typeof dirPath !== 'string') {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
const disk = nodeFs();
|
|
410
|
+
if (!disk.existsSync(dirPath)) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
const st = disk.statSync(dirPath);
|
|
414
|
+
return Boolean(st && typeof st.isDirectory === 'function' && st.isDirectory());
|
|
415
|
+
} catch {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
394
420
|
/**
|
|
395
421
|
* Absolute `integration/` next to {@link process.cwd} when that directory exists.
|
|
396
422
|
*
|
|
@@ -537,19 +563,47 @@ function isAppSubdirSync(root, name) {
|
|
|
537
563
|
}
|
|
538
564
|
|
|
539
565
|
/**
|
|
540
|
-
*
|
|
566
|
+
* True when an integration/builder app directory has application config (yaml/json or legacy variables.yaml).
|
|
567
|
+
* Skips helper folders such as fixtures/ and wizard-configs/.
|
|
568
|
+
* @param {string} appDir - Absolute path to candidate app directory
|
|
569
|
+
* @returns {boolean}
|
|
570
|
+
*/
|
|
571
|
+
function hasApplicationConfigSync(appDir) {
|
|
572
|
+
if (!appDir || typeof appDir !== 'string') return false;
|
|
573
|
+
const disk = nodeFs();
|
|
574
|
+
const names = [
|
|
575
|
+
'application.yaml',
|
|
576
|
+
'application.yml',
|
|
577
|
+
'application.json',
|
|
578
|
+
'variables.yaml'
|
|
579
|
+
];
|
|
580
|
+
return names.some((name) => {
|
|
581
|
+
try {
|
|
582
|
+
return disk.existsSync(path.join(appDir, name));
|
|
583
|
+
} catch {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Lists app names (directories) under integration root. Excludes dot-prefixed entries
|
|
591
|
+
* and directories without application config (e.g. fixtures/, wizard-configs/).
|
|
541
592
|
* Returns [] if root does not exist.
|
|
542
593
|
* @returns {string[]} Sorted list of app directory names
|
|
543
594
|
*/
|
|
544
595
|
function listIntegrationAppNames() {
|
|
545
596
|
const disk = nodeFs();
|
|
546
597
|
const names = new Set();
|
|
598
|
+
const withAppConfig = (root) => (name) => hasApplicationConfigSync(path.join(root, name));
|
|
547
599
|
const cwdRoot = getCwdIntegrationRoot();
|
|
548
600
|
if (cwdRoot) {
|
|
549
|
-
addBuilderSubdirNamesToSet(disk, cwdRoot, names,
|
|
601
|
+
addBuilderSubdirNamesToSet(disk, cwdRoot, names, withAppConfig(cwdRoot));
|
|
602
|
+
}
|
|
603
|
+
const matInt = path.join(getAppsMaterializationParent(), 'integration');
|
|
604
|
+
if (!cwdRoot || path.resolve(matInt) !== path.resolve(cwdRoot)) {
|
|
605
|
+
addBuilderSubdirNamesToSet(disk, matInt, names, withAppConfig(matInt));
|
|
550
606
|
}
|
|
551
|
-
const matInt = getIntegrationRoot();
|
|
552
|
-
addBuilderSubdirNamesToSet(disk, matInt, names, null);
|
|
553
607
|
return [...names].sort();
|
|
554
608
|
}
|
|
555
609
|
|
|
@@ -595,14 +649,19 @@ function listBuilderAppNames() {
|
|
|
595
649
|
}
|
|
596
650
|
roots.add(path.resolve(getSystemBuilderRoot()));
|
|
597
651
|
for (const r of roots) {
|
|
598
|
-
addBuilderSubdirNamesToSet(disk, r, names,
|
|
652
|
+
addBuilderSubdirNamesToSet(disk, r, names, (name) =>
|
|
653
|
+
hasApplicationConfigSync(path.join(r, name))
|
|
654
|
+
);
|
|
599
655
|
}
|
|
600
656
|
return [...names].sort();
|
|
601
657
|
}
|
|
602
658
|
|
|
603
659
|
/**
|
|
604
|
-
* Gets the integration folder path
|
|
605
|
-
*
|
|
660
|
+
* Gets the integration folder path.
|
|
661
|
+
* Resolution: existing `cwd/integration/<app>` → existing materialization copy →
|
|
662
|
+
* `cwd/integration/<app>` when `cwd/integration/` exists (new download/create) →
|
|
663
|
+
* materialization parent. Download should use {@link getAppCreatePath} when it must
|
|
664
|
+
* always land under cwd even if a materialization orphan exists.
|
|
606
665
|
*
|
|
607
666
|
* @param {string} appName - Application name
|
|
608
667
|
* @returns {string} Absolute path to integration directory
|
|
@@ -620,18 +679,21 @@ function getIntegrationPath(appName) {
|
|
|
620
679
|
} catch {
|
|
621
680
|
// programmatic module optional for CLI-only usage
|
|
622
681
|
}
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
682
|
+
const cwdRoot = getCwdIntegrationRoot();
|
|
683
|
+
const matApp = path.join(getAppsMaterializationParent(), 'integration', appName);
|
|
684
|
+
if (cwdRoot) {
|
|
685
|
+
const cwdApp = path.join(cwdRoot, appName);
|
|
686
|
+
if (isExistingDirectoryPath(cwdApp)) {
|
|
687
|
+
return cwdApp;
|
|
630
688
|
}
|
|
631
|
-
} catch {
|
|
632
|
-
// ignore
|
|
633
689
|
}
|
|
634
|
-
|
|
690
|
+
if (isExistingDirectoryPath(matApp)) {
|
|
691
|
+
return matApp;
|
|
692
|
+
}
|
|
693
|
+
if (cwdRoot) {
|
|
694
|
+
return path.join(cwdRoot, appName);
|
|
695
|
+
}
|
|
696
|
+
return matApp;
|
|
635
697
|
}
|
|
636
698
|
|
|
637
699
|
/**
|
|
@@ -769,13 +831,15 @@ function resolveExistingWorkspacePath(appName, options) {
|
|
|
769
831
|
if (isExistingAppDirectory(appPath)) {
|
|
770
832
|
return { appPath, baseDir };
|
|
771
833
|
}
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
834
|
+
const alternates = ['integration', 'enterprise-knowledge', 'role-assistant']
|
|
835
|
+
.filter((workspaceName) => workspaceName !== baseDir);
|
|
836
|
+
for (const workspaceName of alternates) {
|
|
837
|
+
const alt = resolveAppWorkspace(appName, { ...options, workspace: workspaceName });
|
|
838
|
+
if (isExistingAppDirectory(alt.appPath)) {
|
|
839
|
+
return { appPath: alt.appPath, baseDir: alt.baseDir };
|
|
840
|
+
}
|
|
777
841
|
}
|
|
778
|
-
return
|
|
842
|
+
return null;
|
|
779
843
|
}
|
|
780
844
|
|
|
781
845
|
function externalWorkspaceFolderFromConfig(appPath, baseDir, scopedWorkspace) {
|
|
@@ -898,7 +962,7 @@ async function detectAppType(appName, options = {}) {
|
|
|
898
962
|
const builderAfterTemplate = checkBuilderFolder(appName);
|
|
899
963
|
if (builderAfterTemplate) return builderAfterTemplate;
|
|
900
964
|
throw new Error(
|
|
901
|
-
`App '${appName}' not found in integration/${appName}, enterprise-knowledge/${appName}, or builder/${appName}`
|
|
965
|
+
`App '${appName}' not found in integration/${appName}, enterprise-knowledge/${appName}, role-assistant/${appName}, or builder/${appName}`
|
|
902
966
|
);
|
|
903
967
|
}
|
|
904
968
|
|
|
@@ -936,6 +1000,11 @@ async function getResolveAppPath(appName, options = {}) {
|
|
|
936
1000
|
if (workspaceHit) {
|
|
937
1001
|
return workspaceHit;
|
|
938
1002
|
}
|
|
1003
|
+
const { getRoleAssistantPath } = require('./role-assistant-paths');
|
|
1004
|
+
const roleAssistantHit = tryResolveEnvOnlyPath(getRoleAssistantPath(appName));
|
|
1005
|
+
if (roleAssistantHit) {
|
|
1006
|
+
return roleAssistantHit;
|
|
1007
|
+
}
|
|
939
1008
|
const integrationHit = tryResolveEnvOnlyPath(getIntegrationPath(appName));
|
|
940
1009
|
if (integrationHit) {
|
|
941
1010
|
return integrationHit;
|
|
@@ -1045,6 +1114,9 @@ module.exports = {
|
|
|
1045
1114
|
getEnterpriseKnowledgePath: require('./enterprise-knowledge-paths').getEnterpriseKnowledgePath,
|
|
1046
1115
|
getEnterpriseKnowledgeRoot: require('./enterprise-knowledge-paths').getEnterpriseKnowledgeRoot,
|
|
1047
1116
|
listEnterpriseKnowledgeAnchors: require('./enterprise-knowledge-paths').listEnterpriseKnowledgeAnchors,
|
|
1117
|
+
getRoleAssistantPath: require('./role-assistant-paths').getRoleAssistantPath,
|
|
1118
|
+
getRoleAssistantRoot: require('./role-assistant-paths').getRoleAssistantRoot,
|
|
1119
|
+
getCwdRoleAssistantRoot: require('./role-assistant-paths').getCwdRoleAssistantRoot,
|
|
1048
1120
|
resolveAppWorkspace: require('./app-path-resolve').resolveAppWorkspace,
|
|
1049
1121
|
resolveExternalSystemPath: require('./app-path-resolve').resolveExternalSystemPath
|
|
1050
1122
|
};
|
|
@@ -57,13 +57,29 @@ async function resolveClientTokenAuthForPipelineRetry(systemKey) {
|
|
|
57
57
|
* @param {Object} authConfig
|
|
58
58
|
* @param {Object} payload
|
|
59
59
|
* @param {string} systemKey - External system key for credential lookup on retry
|
|
60
|
+
* @param {Object} [options] - Forwarded to uploadApplicationViaPipeline (e.g. timeoutMs)
|
|
60
61
|
* @returns {Promise<Object>}
|
|
61
62
|
*/
|
|
62
|
-
async function uploadApplicationViaPipelineWithAuthRetry(
|
|
63
|
-
|
|
63
|
+
async function uploadApplicationViaPipelineWithAuthRetry(
|
|
64
|
+
dataplaneUrl,
|
|
65
|
+
authConfig,
|
|
66
|
+
payload,
|
|
67
|
+
systemKey,
|
|
68
|
+
options = {}
|
|
69
|
+
) {
|
|
70
|
+
const first = await uploadApplicationViaPipeline(
|
|
71
|
+
dataplaneUrl,
|
|
72
|
+
authConfig,
|
|
73
|
+
payload,
|
|
74
|
+
options
|
|
75
|
+
);
|
|
64
76
|
if (!isPipelineAuthFailure(first) || authConfig.type !== 'bearer') {
|
|
65
77
|
return first;
|
|
66
78
|
}
|
|
79
|
+
// Token-forward: do not swap the caller JWT for application client-token.
|
|
80
|
+
if (authConfig.preserveBearer) {
|
|
81
|
+
return first;
|
|
82
|
+
}
|
|
67
83
|
const clientAuth = await resolveClientTokenAuthForPipelineRetry(systemKey);
|
|
68
84
|
if (!clientAuth || clientAuth.type === 'bearer') {
|
|
69
85
|
return first;
|
|
@@ -73,7 +89,7 @@ async function uploadApplicationViaPipelineWithAuthRetry(dataplaneUrl, authConfi
|
|
|
73
89
|
'Device login token was rejected for pipeline publish; retrying with application client token…'
|
|
74
90
|
)
|
|
75
91
|
);
|
|
76
|
-
return uploadApplicationViaPipeline(dataplaneUrl, clientAuth, payload);
|
|
92
|
+
return uploadApplicationViaPipeline(dataplaneUrl, clientAuth, payload, options);
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
module.exports = {
|
|
@@ -68,29 +68,37 @@ function extractPasswordFromUrlOrValue(urlOrPassword) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* Run psql -c inside the Postgres container without a shell.
|
|
72
|
+
* Passes SQL as an argv element so dollar-quoting (DO $$ … $$) is not mangled.
|
|
73
73
|
*
|
|
74
|
-
* @param {string} s
|
|
75
|
-
* @returns {string}
|
|
76
|
-
*/
|
|
77
|
-
function escapeShellDoubleQuoted(s) {
|
|
78
|
-
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
74
|
* @async
|
|
83
75
|
* @param {string} container
|
|
84
76
|
* @param {string} sql
|
|
85
77
|
* @param {string} adminPassword
|
|
78
|
+
* @param {string} [database='postgres']
|
|
86
79
|
* @returns {Promise<void>}
|
|
87
80
|
*/
|
|
88
81
|
async function runPsqlCommand(container, sql, adminPassword, database = 'postgres') {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
await dockerExec.execFileWithDockerEnv(
|
|
83
|
+
'docker',
|
|
84
|
+
[
|
|
85
|
+
'exec',
|
|
86
|
+
'-e',
|
|
87
|
+
'PGPASSWORD',
|
|
88
|
+
'-i',
|
|
89
|
+
container,
|
|
90
|
+
'psql',
|
|
91
|
+
'-U',
|
|
92
|
+
SUPERUSER_ROLE,
|
|
93
|
+
'-d',
|
|
94
|
+
database,
|
|
95
|
+
'-v',
|
|
96
|
+
'ON_ERROR_STOP=1',
|
|
97
|
+
'-c',
|
|
98
|
+
sql
|
|
99
|
+
],
|
|
100
|
+
{ env: { PGPASSWORD: adminPassword } }
|
|
101
|
+
);
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
/**
|
|
@@ -194,6 +202,5 @@ module.exports = {
|
|
|
194
202
|
PLATFORM_DATABASE_SLOTS,
|
|
195
203
|
bootstrapPlatformPostgresDatabases,
|
|
196
204
|
pgRoleName,
|
|
197
|
-
extractPasswordFromUrlOrValue
|
|
198
|
-
escapeShellDoubleQuoted
|
|
205
|
+
extractPasswordFromUrlOrValue
|
|
199
206
|
};
|
|
@@ -86,9 +86,12 @@ async function runPsql(container, sql, adminPassword) {
|
|
|
86
86
|
if (!adminPassword || typeof adminPassword !== 'string') {
|
|
87
87
|
throw new Error('Admin password is required');
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(container)) {
|
|
90
|
+
throw new Error(`Unsafe Postgres container name: ${container}`);
|
|
91
|
+
}
|
|
91
92
|
const env = { PGPASSWORD: adminPassword };
|
|
93
|
+
const safeSqlToken = JSON.stringify(String(sql));
|
|
94
|
+
const cmd = `docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d postgres -tAc ${safeSqlToken}`;
|
|
92
95
|
const result = (await dockerExec.execWithDockerEnv(cmd, { env })) || {};
|
|
93
96
|
return String(result.stdout || '').trim();
|
|
94
97
|
}
|
|
@@ -24,6 +24,17 @@ function expandConfiguredSecretsTilde(filePath) {
|
|
|
24
24
|
return filePath;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** One auto-renew attempt per CLI process (shared by concurrent getRemoteDevAuth callers). */
|
|
28
|
+
let autoRenewPromise = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Reset process-level auto-renew gate (tests only).
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
34
|
+
function resetDevCertAutoRenewGateForTests() {
|
|
35
|
+
autoRenewPromise = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* Single API object so resolveSharedSecretsEndpoint and callers share one getRemoteDevAuth
|
|
29
40
|
* (Jest spies and partial mocks work reliably).
|
|
@@ -39,12 +50,11 @@ const remoteDevAuth = {
|
|
|
39
50
|
},
|
|
40
51
|
|
|
41
52
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* Use for cert-authenticated dev API calls (settings, users, ssh-keys, secrets).
|
|
53
|
+
* Read remote server URL + client cert from disk without auto-renew.
|
|
54
|
+
* Use when renewing (avoids recursion) or when only a local presence check is needed.
|
|
45
55
|
* @returns {Promise<{ serverUrl: string, clientCertPem: string, serverCaPem: string|null }|null>}
|
|
46
56
|
*/
|
|
47
|
-
async
|
|
57
|
+
async readRemoteDevAuth() {
|
|
48
58
|
const serverUrl = await config.getRemoteServer();
|
|
49
59
|
if (!serverUrl) return null;
|
|
50
60
|
const devId = await config.getDeveloperId();
|
|
@@ -55,6 +65,30 @@ const remoteDevAuth = {
|
|
|
55
65
|
return { serverUrl, clientCertPem, serverCaPem };
|
|
56
66
|
},
|
|
57
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Get Builder Server URL, client cert PEM, and optional dev root CA when remote is configured; otherwise null.
|
|
70
|
+
* When the client cert is still valid but expires within 14 days, renews it once per process before returning.
|
|
71
|
+
* serverCaPem comes from ca.pem (saved at dev init); required for Node TLS to private-CA servers.
|
|
72
|
+
* Use for cert-authenticated dev API calls (settings, users, ssh-keys, secrets).
|
|
73
|
+
* @returns {Promise<{ serverUrl: string, clientCertPem: string, serverCaPem: string|null }|null>}
|
|
74
|
+
*/
|
|
75
|
+
async getRemoteDevAuth() {
|
|
76
|
+
const existing = await remoteDevAuth.readRemoteDevAuth();
|
|
77
|
+
if (!existing) return null;
|
|
78
|
+
if (!autoRenewPromise) {
|
|
79
|
+
autoRenewPromise = (async() => {
|
|
80
|
+
const { ensureDevCertFreshIfDue } = require('../commands/dev-refresh');
|
|
81
|
+
return ensureDevCertFreshIfDue({ quiet: false });
|
|
82
|
+
})();
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await autoRenewPromise;
|
|
86
|
+
} catch {
|
|
87
|
+
// ensureDevCertFreshIfDue already handles failures; keep using current cert
|
|
88
|
+
}
|
|
89
|
+
return remoteDevAuth.readRemoteDevAuth();
|
|
90
|
+
},
|
|
91
|
+
|
|
58
92
|
/**
|
|
59
93
|
* Resolve where shared secrets read/write should go. After dev init, config may still hold a
|
|
60
94
|
* server-side filesystem path from GET /api/dev/settings (not an http URL) if merge did not rewrite it.
|
|
@@ -148,3 +182,4 @@ function normalizeSharedSecretsFilePath(configured) {
|
|
|
148
182
|
}
|
|
149
183
|
|
|
150
184
|
module.exports = remoteDevAuth;
|
|
185
|
+
module.exports.resetDevCertAutoRenewGateForTests = resetDevCertAutoRenewGateForTests;
|
|
@@ -12,6 +12,32 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
const { parseImageOverride } = require('./parse-image-ref');
|
|
15
|
+
const DOCKER_REPOSITORY_RE = /^[a-z0-9][a-z0-9._/-]*$/i;
|
|
16
|
+
const DOCKER_TAG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} repository
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function assertSafeRepository(repository) {
|
|
23
|
+
const trimmed = String(repository || '').trim();
|
|
24
|
+
if (!trimmed || !DOCKER_REPOSITORY_RE.test(trimmed)) {
|
|
25
|
+
throw new Error(`Invalid Docker repository path: ${repository}`);
|
|
26
|
+
}
|
|
27
|
+
return trimmed;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} tag
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function assertSafeTag(tag) {
|
|
35
|
+
const trimmed = String(tag || '').trim();
|
|
36
|
+
if (!trimmed || !DOCKER_TAG_RE.test(trimmed)) {
|
|
37
|
+
throw new Error(`Invalid Docker image tag: ${tag}`);
|
|
38
|
+
}
|
|
39
|
+
return trimmed;
|
|
40
|
+
}
|
|
15
41
|
|
|
16
42
|
/**
|
|
17
43
|
* Repository path without host (same rules as compose-generator getImageName).
|
|
@@ -59,7 +85,11 @@ function normalizeDockerRegistryPrefix(registry) {
|
|
|
59
85
|
if (!t) {
|
|
60
86
|
return '';
|
|
61
87
|
}
|
|
62
|
-
|
|
88
|
+
let end = t.length;
|
|
89
|
+
while (end > 0 && t[end - 1] === '/') {
|
|
90
|
+
end -= 1;
|
|
91
|
+
}
|
|
92
|
+
return t.slice(0, end);
|
|
63
93
|
}
|
|
64
94
|
|
|
65
95
|
/**
|
|
@@ -81,18 +111,18 @@ function resolveDockerImageRef(appName, appConfig, runOptions = {}) {
|
|
|
81
111
|
if (opts.image) {
|
|
82
112
|
const parsed = parseImageOverride(opts.image);
|
|
83
113
|
return {
|
|
84
|
-
imageName: parsed ? parsed.name : getRepositoryPathFromConfig(appConfig, appName),
|
|
85
|
-
imageTag: parsed ? parsed.tag : tagFromOpts || imageTagFromConfig(appConfig)
|
|
114
|
+
imageName: assertSafeRepository(parsed ? parsed.name : getRepositoryPathFromConfig(appConfig, appName)),
|
|
115
|
+
imageTag: assertSafeTag(parsed ? parsed.tag : tagFromOpts || imageTagFromConfig(appConfig))
|
|
86
116
|
};
|
|
87
117
|
}
|
|
88
118
|
|
|
89
|
-
const baseRepo = getRepositoryPathFromConfig(appConfig, appName);
|
|
90
|
-
const imageTag = tagFromOpts || imageTagFromConfig(appConfig);
|
|
119
|
+
const baseRepo = assertSafeRepository(getRepositoryPathFromConfig(appConfig, appName));
|
|
120
|
+
const imageTag = assertSafeTag(tagFromOpts || imageTagFromConfig(appConfig));
|
|
91
121
|
const prefix =
|
|
92
122
|
normalizeDockerRegistryPrefix(opts.registry) ||
|
|
93
123
|
normalizeDockerRegistryPrefix(appConfig?.image?.registry ?? '');
|
|
94
124
|
if (prefix) {
|
|
95
|
-
return { imageName: `${prefix}/${baseRepo}
|
|
125
|
+
return { imageName: assertSafeRepository(`${prefix}/${baseRepo}`), imageTag };
|
|
96
126
|
}
|
|
97
127
|
return { imageName: baseRepo, imageTag };
|
|
98
128
|
}
|