@aifabrix/builder 2.44.5 → 2.45.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 +8 -4
- package/.cursor/rules/project-rules.mdc +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +104 -2
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +126 -0
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +58 -19
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +148 -74
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +83 -49
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +460 -0
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +31 -3
- package/lib/cli/setup-auth.js +98 -27
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +132 -118
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +143 -84
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +468 -0
- package/lib/commands/setup-prompts.js +421 -0
- package/lib/commands/setup.js +254 -0
- package/lib/commands/teardown.js +277 -0
- package/lib/commands/up-common.js +113 -19
- package/lib/commands/up-dataplane.js +44 -19
- package/lib/commands/up-miso.js +18 -18
- package/lib/commands/upload.js +111 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +428 -0
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +29 -1
- package/lib/core/secrets-load.js +252 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +9 -2
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/internal/node-fs.js +2 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/external-readme.js +71 -2
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +28 -10
- package/lib/utils/health-check.js +139 -107
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +308 -76
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +49 -4
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +26 -13
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +42 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -388
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +52 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +158 -76
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +5 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +9 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- package/.npmrc.token +0 -1
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
3
|
+
const logger = require('./logger');
|
|
4
|
+
const { execWithDockerEnv } = require('./docker-exec');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Checks if db-init container exists.
|
|
8
|
+
* @async
|
|
9
|
+
* @param {string} dbInitContainer
|
|
10
|
+
* @returns {Promise<boolean>}
|
|
11
|
+
*/
|
|
12
|
+
async function checkDbInitContainerExists(dbInitContainer) {
|
|
13
|
+
try {
|
|
14
|
+
const { stdout } = await execWithDockerEnv(
|
|
15
|
+
`docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`
|
|
16
|
+
);
|
|
17
|
+
return stdout.trim() === dbInitContainer;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets container exit code.
|
|
25
|
+
* @async
|
|
26
|
+
* @param {string} dbInitContainer
|
|
27
|
+
* @returns {Promise<string>}
|
|
28
|
+
*/
|
|
29
|
+
async function getContainerExitCode(dbInitContainer) {
|
|
30
|
+
const { stdout: exitCode } = await execWithDockerEnv(
|
|
31
|
+
`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`
|
|
32
|
+
);
|
|
33
|
+
return exitCode.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handles exited container status.
|
|
38
|
+
* @async
|
|
39
|
+
* @param {string} dbInitContainer
|
|
40
|
+
* @returns {Promise<boolean>}
|
|
41
|
+
*/
|
|
42
|
+
async function handleExitedContainer(dbInitContainer) {
|
|
43
|
+
const { stdout: status } = await execWithDockerEnv(
|
|
44
|
+
`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`
|
|
45
|
+
);
|
|
46
|
+
if (status.trim() === 'exited') {
|
|
47
|
+
const exitCode = await getContainerExitCode(dbInitContainer);
|
|
48
|
+
if (exitCode === '0') {
|
|
49
|
+
logger.log(formatSuccessLine('Database initialization already completed'));
|
|
50
|
+
} else {
|
|
51
|
+
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Waits for container to exit (best-effort).
|
|
60
|
+
* @async
|
|
61
|
+
* @param {string} dbInitContainer
|
|
62
|
+
* @param {number} maxAttempts
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
async function waitForContainerExit(dbInitContainer, maxAttempts) {
|
|
66
|
+
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
67
|
+
const { stdout: currentStatus } = await execWithDockerEnv(
|
|
68
|
+
`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`
|
|
69
|
+
);
|
|
70
|
+
if (currentStatus.trim() === 'exited') {
|
|
71
|
+
const exitCode = await getContainerExitCode(dbInitContainer);
|
|
72
|
+
if (exitCode === '0') {
|
|
73
|
+
logger.log(formatSuccessLine('Database initialization completed'));
|
|
74
|
+
} else {
|
|
75
|
+
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Checks if db-init container exists and waits for it to complete.
|
|
85
|
+
* @async
|
|
86
|
+
* @param {string} appName
|
|
87
|
+
* @returns {Promise<void>}
|
|
88
|
+
*/
|
|
89
|
+
async function waitForDbInit(appName) {
|
|
90
|
+
const dbInitContainer = `aifabrix-${appName}-db-init`;
|
|
91
|
+
try {
|
|
92
|
+
if (!(await checkDbInitContainerExists(dbInitContainer))) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (await handleExitedContainer(dbInitContainer)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logger.log(chalk.blue('Waiting for database initialization to complete...'));
|
|
101
|
+
await waitForContainerExit(dbInitContainer, 30);
|
|
102
|
+
} catch {
|
|
103
|
+
// db-init container might not exist, which is fine
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { waitForDbInit };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-success warnings when public/Traefik health URL was skipped or not verified.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Split from health-check.js for file size limits
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('./logger');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* When Traefik URL is set, drop it if the hostname does not resolve; retain full URL for UX.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} traefikUrl
|
|
16
|
+
* @param {boolean} debug
|
|
17
|
+
* @param {Function} isHostnameResolvableFn - (hostname, debug) => Promise<boolean>
|
|
18
|
+
* @returns {Promise<{ traefikUrl: string, skippedPublicHealthUrl: string }>}
|
|
19
|
+
*/
|
|
20
|
+
async function filterTraefikUrlByDns(traefikUrl, debug, isHostnameResolvableFn) {
|
|
21
|
+
if (!traefikUrl) {
|
|
22
|
+
return { traefikUrl: '', skippedPublicHealthUrl: '' };
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const hn = new URL(traefikUrl).hostname;
|
|
26
|
+
const ok = await isHostnameResolvableFn(hn, debug);
|
|
27
|
+
if (!ok) {
|
|
28
|
+
return { traefikUrl: '', skippedPublicHealthUrl: traefikUrl };
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
return { traefikUrl: '', skippedPublicHealthUrl: '' };
|
|
32
|
+
}
|
|
33
|
+
return { traefikUrl, skippedPublicHealthUrl: '' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {Object} p
|
|
38
|
+
* @param {string} [p.skippedPublicHealthUrl]
|
|
39
|
+
* @param {string[]} [p.urlsToTry]
|
|
40
|
+
* @param {number} p.resolvedIndex
|
|
41
|
+
*/
|
|
42
|
+
function logPublicHealthUrlWarningIfNeeded(p) {
|
|
43
|
+
const { skippedPublicHealthUrl, urlsToTry, resolvedIndex } = p;
|
|
44
|
+
if (typeof resolvedIndex !== 'number' || resolvedIndex < 0) return;
|
|
45
|
+
|
|
46
|
+
if (skippedPublicHealthUrl) {
|
|
47
|
+
logger.log(
|
|
48
|
+
chalk.yellow(
|
|
49
|
+
`⚠ Public URL was not verified (DNS): ${skippedPublicHealthUrl}. ` +
|
|
50
|
+
'The application reported healthy via localhost only. Validate DNS names and Traefik routing for this host.'
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (Array.isArray(urlsToTry) && urlsToTry.length > 1 && resolvedIndex > 0) {
|
|
56
|
+
const pub = urlsToTry[0];
|
|
57
|
+
logger.log(
|
|
58
|
+
chalk.yellow(
|
|
59
|
+
`⚠ Public URL was not verified: ${pub}. ` +
|
|
60
|
+
'Health checks succeeded via localhost only. Validate Traefik routing, TLS, and DNS.'
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
filterTraefikUrlByDns,
|
|
68
|
+
logPublicHealthUrlWarningIfNeeded
|
|
69
|
+
};
|
|
@@ -63,9 +63,8 @@ function frontDoorPattern(appConfig) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* Returns null when Traefik/frontDoorRouting isn't active or cannot be resolved.
|
|
66
|
+
* Public app URL (Traefik + frontDoor mount path), without the health path — for CLI summaries.
|
|
67
|
+
* Requires infra Traefik on, `frontDoorRouting.enabled`, host, and pattern.
|
|
69
68
|
*
|
|
70
69
|
* @async
|
|
71
70
|
* @param {string} appName
|
|
@@ -73,8 +72,8 @@ function frontDoorPattern(appConfig) {
|
|
|
73
72
|
* @param {Object|null} appConfig
|
|
74
73
|
* @returns {Promise<string|null>}
|
|
75
74
|
*/
|
|
76
|
-
async function
|
|
77
|
-
if (!frontDoorEnabled(appConfig)) return null;
|
|
75
|
+
async function computeTraefikPublicAppUrl(_appName, _healthCheckPort, appConfig) {
|
|
76
|
+
if (!appConfig || !frontDoorEnabled(appConfig)) return null;
|
|
78
77
|
const pattern = frontDoorPattern(appConfig);
|
|
79
78
|
if (!pattern) return null;
|
|
80
79
|
|
|
@@ -82,14 +81,20 @@ async function computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig)
|
|
|
82
81
|
const userCfg = await coreConfig.getConfig();
|
|
83
82
|
if (!(userCfg && userCfg.traefik)) return null;
|
|
84
83
|
|
|
84
|
+
const fd = appConfig.frontDoorRouting;
|
|
85
|
+
if (!fd || typeof fd !== 'object') return null;
|
|
86
|
+
const hostTemplate = typeof fd.host === 'string' ? fd.host.trim() : '';
|
|
87
|
+
if (!hostTemplate) return null;
|
|
88
|
+
|
|
89
|
+
const mountPath = normalizeFrontDoorPatternForHealth(pattern);
|
|
90
|
+
|
|
85
91
|
const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
|
|
86
92
|
const remoteServer = await coreConfig.getRemoteServer();
|
|
87
93
|
const developerIdRaw = await coreConfig.getDeveloperId();
|
|
88
94
|
const developerIdNum = parseDeveloperIdNum(developerIdRaw);
|
|
89
95
|
|
|
90
|
-
//
|
|
96
|
+
// URLs are resolved for the CLI on the host, not from inside a container.
|
|
91
97
|
const profile = 'local';
|
|
92
|
-
const fd = appConfig.frontDoorRouting;
|
|
93
98
|
const listenPort = Number(appConfig?.port || 3000);
|
|
94
99
|
|
|
95
100
|
const publicBase = computePublicUrlBaseString({
|
|
@@ -105,15 +110,28 @@ async function computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig)
|
|
|
105
110
|
infraTlsEnabled
|
|
106
111
|
});
|
|
107
112
|
|
|
113
|
+
return joinUrlPath(publicBase, mountPath);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Traefik public URL including health path.
|
|
118
|
+
*
|
|
119
|
+
* @async
|
|
120
|
+
* @param {string} appName
|
|
121
|
+
* @param {number} healthCheckPort
|
|
122
|
+
* @param {Object|null} appConfig
|
|
123
|
+
* @returns {Promise<string|null>}
|
|
124
|
+
*/
|
|
125
|
+
async function computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig) {
|
|
126
|
+
const baseWithFrontDoor = await computeTraefikPublicAppUrl(appName, healthCheckPort, appConfig);
|
|
127
|
+
if (!baseWithFrontDoor) return null;
|
|
108
128
|
const healthCheckPath = appConfig?.healthCheck?.path || '/health';
|
|
109
|
-
const mountPath = normalizeFrontDoorPatternForHealth(pattern);
|
|
110
|
-
const baseWithFrontDoor = joinUrlPath(publicBase, mountPath);
|
|
111
129
|
return joinUrlPath(baseWithFrontDoor, healthCheckPath);
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
module.exports = {
|
|
115
133
|
joinUrlPath,
|
|
116
134
|
normalizeFrontDoorPatternForHealth,
|
|
135
|
+
computeTraefikPublicAppUrl,
|
|
117
136
|
computeTraefikHealthCheckUrl
|
|
118
137
|
};
|
|
119
|
-
|
|
@@ -12,16 +12,25 @@ const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
|
12
12
|
const http = require('http');
|
|
13
13
|
const https = require('https');
|
|
14
14
|
const net = require('net');
|
|
15
|
+
const dns = require('dns');
|
|
15
16
|
const chalk = require('chalk');
|
|
16
17
|
const logger = require('./logger');
|
|
17
18
|
const { execWithDockerEnv } = require('./docker-exec');
|
|
18
|
-
|
|
19
|
+
/** Namespace require so tests can `jest.spyOn` on `./health-check-url` exports. */
|
|
20
|
+
const healthCheckUrl = require('./health-check-url');
|
|
21
|
+
const { computePathActive } = require('./url-declarative-url-flags');
|
|
22
|
+
const { isFrontDoorRoutingEnabledInDoc } = require('./url-declarative-vdir-inactive-env');
|
|
23
|
+
const { waitForDbInit } = require('./health-check-db-init');
|
|
24
|
+
const {
|
|
25
|
+
filterTraefikUrlByDns,
|
|
26
|
+
logPublicHealthUrlWarningIfNeeded
|
|
27
|
+
} = require('./health-check-public-warn');
|
|
19
28
|
|
|
20
29
|
/**
|
|
21
30
|
* Compute the health check URL for an app.
|
|
22
31
|
*
|
|
23
|
-
* - Default (
|
|
24
|
-
* -
|
|
32
|
+
* - Default (path inactive): http://localhost:<port><healthPath> (e.g. Keycloak with KC_HTTP_RELATIVE_PATH=/)
|
|
33
|
+
* - Path active (Traefik on ∧ frontDoorRouting.enabled): localhost probe uses same vdir as the container (e.g. /auth/health/ready)
|
|
25
34
|
*
|
|
26
35
|
* @async
|
|
27
36
|
* @param {string} appName
|
|
@@ -29,117 +38,68 @@ const { computeTraefikHealthCheckUrl } = require('./health-check-url');
|
|
|
29
38
|
* @param {Object|null} appConfig
|
|
30
39
|
* @param {Object} opts
|
|
31
40
|
* @param {Object} [opts.runOptions] - runApp options (may include env + effectiveEnvironmentScopedResources)
|
|
41
|
+
* @param {boolean} [opts.skipTraefikPublicUrl] - Omit Traefik URL (localhost leg in dual-probe flow only).
|
|
32
42
|
* @returns {Promise<string>}
|
|
33
43
|
*/
|
|
34
44
|
async function computeHealthCheckUrl(appName, healthCheckPort, appConfig, _opts = {}) {
|
|
35
|
-
const
|
|
45
|
+
const rawHealthPath = appConfig?.healthCheck?.path || '/health';
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* @function waitForDbInit
|
|
53
|
-
* @param {string} appName - Application name
|
|
54
|
-
* @throws {Error} If db-init fails
|
|
55
|
-
*/
|
|
56
|
-
/**
|
|
57
|
-
* Checks if db-init container exists
|
|
58
|
-
* @async
|
|
59
|
-
* @function checkDbInitContainerExists
|
|
60
|
-
* @param {string} dbInitContainer - Container name
|
|
61
|
-
* @returns {Promise<boolean>} True if container exists
|
|
62
|
-
*/
|
|
63
|
-
async function checkDbInitContainerExists(dbInitContainer) {
|
|
64
|
-
try {
|
|
65
|
-
const { stdout } = await execWithDockerEnv(`docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`);
|
|
66
|
-
return stdout.trim() === dbInitContainer;
|
|
67
|
-
} catch {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Gets container exit code
|
|
74
|
-
* @async
|
|
75
|
-
* @function getContainerExitCode
|
|
76
|
-
* @param {string} dbInitContainer - Container name
|
|
77
|
-
* @returns {Promise<string>} Exit code
|
|
78
|
-
*/
|
|
79
|
-
async function getContainerExitCode(dbInitContainer) {
|
|
80
|
-
const { stdout: exitCode } = await execWithDockerEnv(`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`);
|
|
81
|
-
return exitCode.trim();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Handles exited container status
|
|
86
|
-
* @async
|
|
87
|
-
* @function handleExitedContainer
|
|
88
|
-
* @param {string} dbInitContainer - Container name
|
|
89
|
-
* @returns {Promise<boolean>} True if handled (container already exited)
|
|
90
|
-
*/
|
|
91
|
-
async function handleExitedContainer(dbInitContainer) {
|
|
92
|
-
const { stdout: status } = await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
|
|
93
|
-
if (status.trim() === 'exited') {
|
|
94
|
-
const exitCode = await getContainerExitCode(dbInitContainer);
|
|
95
|
-
if (exitCode === '0') {
|
|
96
|
-
logger.log(formatSuccessLine('Database initialization already completed'));
|
|
97
|
-
} else {
|
|
98
|
-
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
|
|
47
|
+
function computeLocalhostHealthPath() {
|
|
48
|
+
// Plan 124 pathActive: prepend front-door pattern when Traefik on; keep bare /health for miso/dataplane style.
|
|
49
|
+
try {
|
|
50
|
+
const runOptions = _opts && typeof _opts === 'object' && _opts.runOptions ? _opts.runOptions : null;
|
|
51
|
+
const traefikOn = Boolean(runOptions && runOptions.traefikEnabled === true);
|
|
52
|
+
const fd = appConfig && appConfig.frontDoorRouting ? appConfig.frontDoorRouting : null;
|
|
53
|
+
const pattern = fd && typeof fd.pattern === 'string' ? fd.pattern : null;
|
|
54
|
+
const pathActive = computePathActive(traefikOn, isFrontDoorRoutingEnabledInDoc(appConfig || null));
|
|
55
|
+
const shouldMount = pathActive && Boolean(pattern) && rawHealthPath !== '/health';
|
|
56
|
+
if (!shouldMount) return rawHealthPath;
|
|
57
|
+
const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('./health-check-url');
|
|
58
|
+
const mountPath = normalizeFrontDoorPatternForHealth(pattern);
|
|
59
|
+
return joinUrlPath(mountPath, rawHealthPath);
|
|
60
|
+
} catch {
|
|
61
|
+
return rawHealthPath;
|
|
99
62
|
}
|
|
100
|
-
return true;
|
|
101
63
|
}
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
64
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
|
|
121
|
-
}
|
|
122
|
-
return;
|
|
65
|
+
const localhostHealthPath = computeLocalhostHealthPath();
|
|
66
|
+
|
|
67
|
+
// Local readiness probes localhost; optional Traefik URL is for display / dual-probe (see waitForHealthCheck).
|
|
68
|
+
async function maybeGetTraefikUrl() {
|
|
69
|
+
if (_opts && _opts.skipTraefikPublicUrl) return '';
|
|
70
|
+
const runOptions = (_opts && typeof _opts === 'object') ? _opts.runOptions : null;
|
|
71
|
+
const wantsTraefik =
|
|
72
|
+
Boolean(runOptions) &&
|
|
73
|
+
(runOptions.probeViaTraefik === true || runOptions.traefikEnabled === true) &&
|
|
74
|
+
isFrontDoorRoutingEnabledInDoc(appConfig);
|
|
75
|
+
if (!wantsTraefik) return '';
|
|
76
|
+
try {
|
|
77
|
+
return await healthCheckUrl.computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig);
|
|
78
|
+
} catch {
|
|
79
|
+
return '';
|
|
123
80
|
}
|
|
124
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
125
81
|
}
|
|
82
|
+
|
|
83
|
+
const traefikUrl = await maybeGetTraefikUrl();
|
|
84
|
+
if (traefikUrl) return traefikUrl;
|
|
85
|
+
|
|
86
|
+
return `http://localhost:${healthCheckPort}${localhostHealthPath}`;
|
|
126
87
|
}
|
|
127
88
|
|
|
128
|
-
async function
|
|
129
|
-
|
|
89
|
+
async function isHostnameResolvable(hostname, debug) {
|
|
90
|
+
if (!hostname) return false;
|
|
91
|
+
const hn = String(hostname).trim().toLowerCase();
|
|
92
|
+
if (!hn) return false;
|
|
93
|
+
if (hn === 'localhost' || hn === '127.0.0.1' || hn === '::1') return true;
|
|
130
94
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
95
|
+
await dns.promises.lookup(hn);
|
|
96
|
+
return true;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
// ENOTFOUND: caller may log a single post-success warning with the full public health URL.
|
|
99
|
+
if (debug && !(err && err.code === 'ENOTFOUND')) {
|
|
100
|
+
logger.log(chalk.gray(`[DEBUG] DNS lookup failed for ${hostname}: ${err.message}`));
|
|
137
101
|
}
|
|
138
|
-
|
|
139
|
-
logger.log(chalk.blue('Waiting for database initialization to complete...'));
|
|
140
|
-
await waitForContainerExit(dbInitContainer, 30);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
// db-init container might not exist, which is fine
|
|
102
|
+
return false;
|
|
143
103
|
}
|
|
144
104
|
}
|
|
145
105
|
|
|
@@ -411,24 +371,96 @@ async function performHealthCheckAttempt(healthCheckUrl, attempt, maxAttempts, d
|
|
|
411
371
|
return false;
|
|
412
372
|
}
|
|
413
373
|
|
|
374
|
+
async function computePreferredHealthCheckUrls(appName, healthCheckPort, config, runOptions, debug) {
|
|
375
|
+
const localhostUrl = await computeHealthCheckUrl(appName, healthCheckPort, config, {
|
|
376
|
+
runOptions: runOptions && typeof runOptions === 'object' ? runOptions : {},
|
|
377
|
+
skipTraefikPublicUrl: true
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
let traefikUrl = '';
|
|
381
|
+
/** Full Traefik/public health URL when DNS fails — used for one post-success warning. */
|
|
382
|
+
let skippedPublicHealthUrl = '';
|
|
383
|
+
const wantsTraefikFirst = Boolean(
|
|
384
|
+
runOptions && (runOptions.probeViaTraefik === true || runOptions.traefikEnabled === true)
|
|
385
|
+
) && isFrontDoorRoutingEnabledInDoc(config);
|
|
386
|
+
if (wantsTraefikFirst) {
|
|
387
|
+
try {
|
|
388
|
+
traefikUrl = await healthCheckUrl.computeTraefikHealthCheckUrl(appName, healthCheckPort, config);
|
|
389
|
+
} catch {
|
|
390
|
+
traefikUrl = '';
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const filtered = await filterTraefikUrlByDns(traefikUrl, debug, isHostnameResolvable);
|
|
395
|
+
traefikUrl = filtered.traefikUrl;
|
|
396
|
+
skippedPublicHealthUrl = filtered.skippedPublicHealthUrl;
|
|
397
|
+
|
|
398
|
+
const urlsToTry = traefikUrl ? [traefikUrl, localhostUrl] : [localhostUrl];
|
|
399
|
+
if (urlsToTry.length > 1) {
|
|
400
|
+
logger.log(
|
|
401
|
+
chalk.gray(
|
|
402
|
+
`ℹ Health check order: Traefik/DNS (${urlsToTry[0]}), then localhost (${urlsToTry[1]}).`
|
|
403
|
+
)
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (debug) {
|
|
407
|
+
logger.log(chalk.gray(`[DEBUG] Health check URLs: ${urlsToTry.join(' | ')}`));
|
|
408
|
+
}
|
|
409
|
+
return { urlsToTry, skippedPublicHealthUrl };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function performHealthCheckAttemptForUrls(urlsToTry, attempt, maxAttempts, debug) {
|
|
413
|
+
for (let i = 0; i < urlsToTry.length; i++) {
|
|
414
|
+
const url = urlsToTry[i];
|
|
415
|
+
const passed = await performHealthCheckAttempt(url, attempt, maxAttempts, debug);
|
|
416
|
+
if (passed) {
|
|
417
|
+
return { ok: true, resolvedIndex: i };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return { ok: false, resolvedIndex: -1 };
|
|
421
|
+
}
|
|
422
|
+
|
|
414
423
|
async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false, runOptions = {}) {
|
|
415
424
|
await waitForDbInit(appName);
|
|
416
425
|
|
|
417
426
|
const healthCheckPort = await determineHealthCheckPort(port, appName, debug);
|
|
418
427
|
const { maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
428
|
+
const { urlsToTry, skippedPublicHealthUrl } = await computePreferredHealthCheckUrls(
|
|
429
|
+
appName,
|
|
430
|
+
healthCheckPort,
|
|
431
|
+
config,
|
|
432
|
+
runOptions,
|
|
433
|
+
debug
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
if (skippedPublicHealthUrl && urlsToTry.length === 1) {
|
|
437
|
+
logger.log(
|
|
438
|
+
chalk.gray(
|
|
439
|
+
`ℹ Health check: public URL not used (DNS): ${skippedPublicHealthUrl}. ` +
|
|
440
|
+
`Probing ${urlsToTry[0]} only until the app responds.`
|
|
441
|
+
)
|
|
442
|
+
);
|
|
422
443
|
}
|
|
423
444
|
|
|
424
445
|
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
446
|
+
const attemptResult = await performHealthCheckAttemptForUrls(urlsToTry, attempts, maxAttempts, debug);
|
|
447
|
+
if (attemptResult.ok) {
|
|
448
|
+
logPublicHealthUrlWarningIfNeeded({
|
|
449
|
+
skippedPublicHealthUrl,
|
|
450
|
+
urlsToTry,
|
|
451
|
+
resolvedIndex: attemptResult.resolvedIndex
|
|
452
|
+
});
|
|
427
453
|
return;
|
|
428
454
|
}
|
|
429
455
|
|
|
430
456
|
if (attempts < maxAttempts - 1) {
|
|
431
|
-
|
|
457
|
+
const probeHint =
|
|
458
|
+
urlsToTry.length > 1
|
|
459
|
+
? `trying ${urlsToTry[0]}, then ${urlsToTry[1]}`
|
|
460
|
+
: (urlsToTry[0] || 'health URL');
|
|
461
|
+
logger.log(
|
|
462
|
+
chalk.yellow(`Waiting for health check… (${attempts + 1}/${maxAttempts}) (${probeHint})`)
|
|
463
|
+
);
|
|
432
464
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
433
465
|
}
|
|
434
466
|
}
|
|
@@ -20,6 +20,8 @@ const CATEGORIES = [
|
|
|
20
20
|
{
|
|
21
21
|
name: 'Infrastructure (Local Development)',
|
|
22
22
|
commands: [
|
|
23
|
+
{ name: 'setup' },
|
|
24
|
+
{ name: 'teardown' },
|
|
23
25
|
{ name: 'up-infra' },
|
|
24
26
|
{ name: 'up-platform' },
|
|
25
27
|
{ name: 'up-miso' },
|
|
@@ -72,7 +74,7 @@ const CATEGORIES = [
|
|
|
72
74
|
{ name: 'app' },
|
|
73
75
|
{ name: 'credential' },
|
|
74
76
|
{ name: 'deployment' },
|
|
75
|
-
{ name: '
|
|
77
|
+
{ name: 'integration-client' }
|
|
76
78
|
]
|
|
77
79
|
},
|
|
78
80
|
{
|
|
@@ -96,6 +98,8 @@ const CATEGORIES = [
|
|
|
96
98
|
{ name: 'delete', term: 'delete <systemKey>' },
|
|
97
99
|
{ name: 'repair', term: 'repair <systemKey>' },
|
|
98
100
|
{ name: 'datasource' },
|
|
101
|
+
{ name: 'dimension' },
|
|
102
|
+
{ name: 'dimension-value' },
|
|
99
103
|
{ name: 'test', term: 'test <app>' },
|
|
100
104
|
{ name: 'test-e2e', term: 'test-e2e <app>' },
|
|
101
105
|
{ name: 'test-integration', term: 'test-integration <app>' }
|
package/lib/utils/image-name.js
CHANGED
|
@@ -11,18 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Builds a developer-scoped image name for local Docker builds.
|
|
14
|
-
* Format: "<base>-dev<developerId>".
|
|
15
|
-
* If developerId is missing, non-numeric, or 0 →
|
|
14
|
+
* Format: "<base>-dev<developerId>" when developerId is a positive integer.
|
|
15
|
+
* If developerId is missing, non-numeric, or 0 → returns baseName (manifest image; no dev suffix).
|
|
16
16
|
*
|
|
17
17
|
* @function buildDevImageName
|
|
18
18
|
* @param {string} baseName - Base image name (no registry), e.g., "myapp"
|
|
19
19
|
* @param {(string|number|null|undefined)} developerId - Developer identifier
|
|
20
|
-
* @returns {string} Developer-scoped image name
|
|
20
|
+
* @returns {string} Developer-scoped image name or base name when id is 0 / absent
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
23
|
* buildDevImageName('myapp', 123) // "myapp-dev123"
|
|
24
|
-
* buildDevImageName('myapp', '0') // "myapp
|
|
25
|
-
* buildDevImageName('myapp') // "myapp
|
|
24
|
+
* buildDevImageName('myapp', '0') // "myapp"
|
|
25
|
+
* buildDevImageName('myapp') // "myapp"
|
|
26
26
|
*/
|
|
27
27
|
function buildDevImageName(baseName, developerId) {
|
|
28
28
|
const id =
|
|
@@ -37,13 +37,40 @@ function buildDevImageName(baseName, developerId) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (!Number.isFinite(id) || id === 0) {
|
|
40
|
-
return
|
|
40
|
+
return baseName;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
return `${baseName}-dev${id}`;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Developer-scoped repository path for run/build resolution (may include registry prefix).
|
|
48
|
+
* For qualified paths (slashes), only the last segment gets `-dev<id>`; id 0 returns path unchanged.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} repositoryPath - Full repository path (e.g. "reg/ns/app" or "app")
|
|
51
|
+
* @param {(string|number|null|undefined)} developerId - Developer id from config
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function buildDevImageRepositoryPath(repositoryPath, developerId) {
|
|
55
|
+
if (!repositoryPath || typeof repositoryPath !== 'string') {
|
|
56
|
+
throw new Error('Repository path is required and must be a string');
|
|
57
|
+
}
|
|
58
|
+
const idNum =
|
|
59
|
+
typeof developerId === 'number' ? developerId : parseInt(String(developerId), 10);
|
|
60
|
+
if (!Number.isFinite(idNum) || idNum === 0) {
|
|
61
|
+
return repositoryPath;
|
|
62
|
+
}
|
|
63
|
+
const idx = repositoryPath.lastIndexOf('/');
|
|
64
|
+
const tail = idx === -1 ? repositoryPath : repositoryPath.slice(idx + 1);
|
|
65
|
+
const scopedTail = buildDevImageName(tail, developerId);
|
|
66
|
+
if (idx === -1) {
|
|
67
|
+
return scopedTail;
|
|
68
|
+
}
|
|
69
|
+
return `${repositoryPath.slice(0, idx)}/${scopedTail}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
46
72
|
module.exports = {
|
|
47
|
-
buildDevImageName
|
|
73
|
+
buildDevImageName,
|
|
74
|
+
buildDevImageRepositoryPath
|
|
48
75
|
};
|
|
49
76
|
|