@aifabrix/builder 2.44.4 → 2.44.6
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 +1 -1
- package/.cursor/rules/project-rules.mdc +1 -1
- package/.npmrc.token +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 +68 -17
- 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/types/wizard.types.js +2 -1
- 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/push.js +46 -23
- package/lib/app/register.js +1 -1
- package/lib/app/restart-display.js +95 -0
- package/lib/app/rotate-secret.js +1 -1
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +44 -12
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +99 -73
- package/lib/build/index.js +75 -45
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +445 -0
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.js +20 -2
- package/lib/cli/setup-app.test-commands.js +9 -5
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +138 -61
- 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.js +97 -33
- 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 +225 -19
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -354
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +455 -0
- package/lib/commands/setup-prompts.js +388 -0
- package/lib/commands/setup.js +149 -0
- package/lib/commands/teardown.js +228 -0
- package/lib/commands/test-e2e-external.js +4 -3
- package/lib/commands/up-common.js +97 -12
- package/lib/commands/up-dataplane.js +33 -11
- package/lib/commands/up-miso.js +7 -11
- package/lib/commands/upload.js +109 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +58 -15
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +72 -14
- package/lib/commands/wizard-headless.js +7 -3
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +210 -61
- package/lib/constants/infra-compose-service-names.js +40 -0
- 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 +432 -0
- package/lib/core/secrets-env-write.js +27 -1
- package/lib/core/secrets-load.js +248 -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 +4 -1
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -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/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -11
- package/lib/schema/wizard-config.schema.json +2 -2
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-run-containers.js +2 -2
- 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/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/external-readme.js +117 -4
- 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 +19 -4
- package/lib/utils/health-check.js +135 -105
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +108 -25
- 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 +42 -3
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- 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 +24 -10
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/system-builder-root.js +34 -0
- package/lib/utils/url-declarative-resolve-build.js +6 -1
- package/lib/utils/url-declarative-runtime-base-path.js +32 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry.js +73 -20
- 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 +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +7 -0
- package/templates/applications/miso-controller/env.template +7 -7
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +89 -102
- 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
package/lib/app/push.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
formatSuccessLine,
|
|
3
|
+
formatSuccessParagraph,
|
|
4
|
+
sectionTitle,
|
|
5
|
+
headerKeyValue,
|
|
6
|
+
metadata,
|
|
7
|
+
formatWarningLine,
|
|
8
|
+
formatNextActions
|
|
9
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
2
10
|
/**
|
|
3
11
|
* Application Push Utilities
|
|
4
12
|
*
|
|
@@ -9,9 +17,9 @@ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test
|
|
|
9
17
|
* @version 2.0.0
|
|
10
18
|
*/
|
|
11
19
|
|
|
12
|
-
const chalk = require('chalk');
|
|
13
20
|
const pushUtils = require('../deployment/push');
|
|
14
21
|
const logger = require('../utils/logger');
|
|
22
|
+
const { detectAppType } = require('../utils/paths');
|
|
15
23
|
|
|
16
24
|
/**
|
|
17
25
|
* Validate application name format
|
|
@@ -150,6 +158,33 @@ async function authenticateWithRegistry(registry) {
|
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Layout-aligned notice when push is skipped for external integrations.
|
|
163
|
+
* @param {string} appName
|
|
164
|
+
*/
|
|
165
|
+
function logExternalIntegrationPushNotice(appName) {
|
|
166
|
+
logger.log('');
|
|
167
|
+
logger.log(sectionTitle('Push'));
|
|
168
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
169
|
+
logger.log(formatWarningLine('External integrations have no application image to push.'));
|
|
170
|
+
logger.log(metadata('Use upload or deploy for integration artifacts.'));
|
|
171
|
+
logger.log(formatNextActions([`aifabrix upload ${appName}`, `aifabrix deploy ${appName}`]));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Header after validation passes for a regular (non-external) push.
|
|
176
|
+
* @param {string} appName
|
|
177
|
+
* @param {string} registry
|
|
178
|
+
* @param {string} imageName
|
|
179
|
+
*/
|
|
180
|
+
function logPushCommandHeader(appName, registry, imageName) {
|
|
181
|
+
logger.log('');
|
|
182
|
+
logger.log(sectionTitle('Push'));
|
|
183
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
184
|
+
logger.log(metadata(`Registry: ${registry}`));
|
|
185
|
+
logger.log(metadata(`Repository: ${imageName}`));
|
|
186
|
+
}
|
|
187
|
+
|
|
153
188
|
/**
|
|
154
189
|
* Pushes image tags to registry
|
|
155
190
|
* @async
|
|
@@ -171,7 +206,9 @@ async function pushImageTags(imageName, registry, tags) {
|
|
|
171
206
|
(errorMessage.includes('authentication') && errorMessage.includes('401'));
|
|
172
207
|
|
|
173
208
|
if (isAuthError) {
|
|
174
|
-
logger.log(
|
|
209
|
+
logger.log(
|
|
210
|
+
formatWarningLine('Registry authentication expired; re-authenticating, then retrying push.')
|
|
211
|
+
);
|
|
175
212
|
await authenticateWithRegistry(registry);
|
|
176
213
|
// Retry push after re-authentication
|
|
177
214
|
await Promise.all(tags.map(async(tag) => {
|
|
@@ -190,9 +227,9 @@ async function pushImageTags(imageName, registry, tags) {
|
|
|
190
227
|
* @param {Array<string>} tags - Image tags
|
|
191
228
|
*/
|
|
192
229
|
function displayPushResults(registry, imageName, tags) {
|
|
193
|
-
logger.log(formatSuccessParagraph(`
|
|
194
|
-
logger.log(
|
|
195
|
-
logger.log(
|
|
230
|
+
logger.log(formatSuccessParagraph(`Pushed ${tags.length} tag(s) to ${registry}`));
|
|
231
|
+
logger.log(headerKeyValue('Image:', `${registry}/${imageName}`));
|
|
232
|
+
logger.log(headerKeyValue('Tags:', tags.join(', ')));
|
|
196
233
|
}
|
|
197
234
|
|
|
198
235
|
/**
|
|
@@ -204,38 +241,25 @@ function displayPushResults(registry, imageName, tags) {
|
|
|
204
241
|
* @returns {Promise<void>} Resolves when push is complete
|
|
205
242
|
*/
|
|
206
243
|
async function pushApp(appName, options = {}) {
|
|
207
|
-
// Check if app type is external - skip push
|
|
208
|
-
const { detectAppType } = require('../utils/paths');
|
|
209
244
|
try {
|
|
210
245
|
const { isExternal } = await detectAppType(appName);
|
|
211
246
|
if (isExternal) {
|
|
212
|
-
|
|
247
|
+
logExternalIntegrationPushNotice(appName);
|
|
213
248
|
return;
|
|
214
249
|
}
|
|
215
|
-
} catch (
|
|
250
|
+
} catch (_error) {
|
|
216
251
|
// If detection fails, continue with normal push
|
|
217
252
|
// (detectAppType throws if app doesn't exist, which is fine for push command)
|
|
218
253
|
}
|
|
219
254
|
try {
|
|
220
|
-
// Validate app name
|
|
221
255
|
validateAppName(appName);
|
|
222
|
-
|
|
223
|
-
// Load configuration
|
|
224
256
|
const { registry, imageName } = await loadPushConfig(appName, options);
|
|
225
|
-
|
|
226
|
-
// Validate push configuration
|
|
227
257
|
await validatePushConfig(registry, imageName, appName);
|
|
228
|
-
|
|
229
|
-
// Authenticate with registry
|
|
258
|
+
logPushCommandHeader(appName, registry, imageName);
|
|
230
259
|
await authenticateWithRegistry(registry);
|
|
231
|
-
|
|
232
|
-
// Push image tags
|
|
233
260
|
const tags = options.tag ? options.tag.split(',').map(t => t.trim()) : ['latest'];
|
|
234
261
|
await pushImageTags(imageName, registry, tags);
|
|
235
|
-
|
|
236
|
-
// Display results
|
|
237
262
|
displayPushResults(registry, imageName, tags);
|
|
238
|
-
|
|
239
263
|
} catch (error) {
|
|
240
264
|
throw new Error(`Failed to push application: ${error.message}`);
|
|
241
265
|
}
|
|
@@ -245,4 +269,3 @@ module.exports = {
|
|
|
245
269
|
pushApp,
|
|
246
270
|
validateAppName
|
|
247
271
|
};
|
|
248
|
-
|
package/lib/app/register.js
CHANGED
|
@@ -99,7 +99,7 @@ async function saveLocalCredentials(responseData, apiUrl) {
|
|
|
99
99
|
|
|
100
100
|
// Regenerate .env file with updated credentials
|
|
101
101
|
try {
|
|
102
|
-
await generateEnvFile(registeredAppKey, null, 'local');
|
|
102
|
+
await generateEnvFile(registeredAppKey, null, 'local', true);
|
|
103
103
|
logger.log(formatSuccessLine('.env file updated with new credentials'));
|
|
104
104
|
} catch (error) {
|
|
105
105
|
logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* After `docker restart`, describe dev workspace mounts for developer clarity.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Bind mount vs remote-engine hints (aligns with run --reload messaging)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const config = require('../core/config');
|
|
13
|
+
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
14
|
+
const { sectionTitle, headerKeyValue, metadata } = require('../utils/cli-test-layout-chalk');
|
|
15
|
+
const { isReloadBindMountOnEngineHost } = require('../utils/docker-reload-mount');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {unknown} mounts - docker inspect .Mounts
|
|
19
|
+
* @returns {{ Type: string, Source: string, Destination: string }|null}
|
|
20
|
+
*/
|
|
21
|
+
function findAppBindMount(mounts) {
|
|
22
|
+
if (!Array.isArray(mounts)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const hit = mounts.find((m) => m && m.Type === 'bind' && m.Destination === '/app');
|
|
26
|
+
return hit && typeof hit.Source === 'string' ? hit : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} stdout
|
|
31
|
+
* @returns {unknown|null}
|
|
32
|
+
*/
|
|
33
|
+
function parseInspectMountsStdout(stdout) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(String(stdout || '').trim());
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} containerName
|
|
43
|
+
* @returns {Promise<unknown|null>}
|
|
44
|
+
*/
|
|
45
|
+
async function fetchContainerMountsJson(containerName) {
|
|
46
|
+
try {
|
|
47
|
+
const { stdout } = await execWithDockerEnv(
|
|
48
|
+
`docker inspect --format '{{json .Mounts}}' ${containerName}`,
|
|
49
|
+
{ maxBuffer: 2 * 1024 * 1024 }
|
|
50
|
+
);
|
|
51
|
+
return parseInspectMountsStdout(stdout);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Log workspace transport after a successful container restart (mounts unchanged).
|
|
59
|
+
* @param {string} containerName - Docker container name
|
|
60
|
+
* @returns {Promise<void>}
|
|
61
|
+
*/
|
|
62
|
+
async function logRestartDevMountSummary(containerName) {
|
|
63
|
+
if (!containerName || typeof containerName !== 'string') {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const mounts = await fetchContainerMountsJson(containerName);
|
|
67
|
+
const appBind = findAppBindMount(mounts);
|
|
68
|
+
if (!appBind) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const endpoint = await config.getDockerEndpoint();
|
|
72
|
+
const localEngine = isReloadBindMountOnEngineHost(endpoint);
|
|
73
|
+
|
|
74
|
+
logger.log('');
|
|
75
|
+
logger.log(sectionTitle('Dev workspace (unchanged by restart)'));
|
|
76
|
+
if (localEngine) {
|
|
77
|
+
logger.log(headerKeyValue('Transport:', 'Direct bind mount on the Docker host (no Mutagen).'));
|
|
78
|
+
logger.log(headerKeyValue('Host path → container:', `${appBind.Source} → /app`));
|
|
79
|
+
logger.log(metadata('Edits under the host path are visible inside the container immediately.'));
|
|
80
|
+
} else {
|
|
81
|
+
logger.log(
|
|
82
|
+
headerKeyValue(
|
|
83
|
+
'Transport:',
|
|
84
|
+
'Bind mount on the Docker engine (see path below; Mutagen may sync to this path when using --reload).'
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
logger.log(headerKeyValue('Engine path → container:', `${appBind.Source} → /app`));
|
|
88
|
+
}
|
|
89
|
+
logger.log('');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
findAppBindMount,
|
|
94
|
+
logRestartDevMountSummary
|
|
95
|
+
};
|
package/lib/app/rotate-secret.js
CHANGED
|
@@ -287,7 +287,7 @@ async function saveCredentialsLocally(appKey, credentials, actualControllerUrl)
|
|
|
287
287
|
|
|
288
288
|
// Regenerate .env file with updated credentials
|
|
289
289
|
try {
|
|
290
|
-
await generateEnvFile(appKey, null, 'local');
|
|
290
|
+
await generateEnvFile(appKey, null, 'local', true);
|
|
291
291
|
logger.log(formatSuccessLine('.env file updated with new credentials'));
|
|
292
292
|
} catch (error) {
|
|
293
293
|
logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
formatSuccessLine,
|
|
10
|
+
formatProgress,
|
|
11
|
+
formatWarningLine
|
|
12
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
9
13
|
|
|
10
14
|
const fs = require('fs').promises;
|
|
11
15
|
const chalk = require('chalk');
|
|
@@ -31,8 +35,8 @@ const execAsync = promisify(exec);
|
|
|
31
35
|
async function emitAndRunDockerFallback(appName, appConfig, port, opts) {
|
|
32
36
|
const { debug, runEnvPath, runOptions, misoEnvironment } = opts;
|
|
33
37
|
logger.log(
|
|
34
|
-
|
|
35
|
-
'Docker Compose not found; using docker run (apps without
|
|
38
|
+
formatWarningLine(
|
|
39
|
+
'Docker Compose not found; using docker run (apps without Postgres/Redis only). ' +
|
|
36
40
|
'Install docker-compose-plugin for full compose support.'
|
|
37
41
|
)
|
|
38
42
|
);
|
|
@@ -116,7 +120,7 @@ async function waitForHealthyAndCleanupEnvFiles(appName, port, appConfig, o) {
|
|
|
116
120
|
const displayUrl = (healthUrl && typeof healthUrl === 'string')
|
|
117
121
|
? healthUrl
|
|
118
122
|
: `http://localhost:${port}${healthCheckPath}`;
|
|
119
|
-
logger.log(
|
|
123
|
+
logger.log(formatProgress(`Waiting for healthy: ${displayUrl}…`));
|
|
120
124
|
await healthCheck.waitForHealthCheck(appName, 90, port, appConfig, debug, ro);
|
|
121
125
|
|
|
122
126
|
for (const p of [runEnvPath, runEnvAdminPath]) {
|
|
@@ -124,7 +128,9 @@ async function waitForHealthyAndCleanupEnvFiles(appName, port, appConfig, o) {
|
|
|
124
128
|
try {
|
|
125
129
|
await fs.unlink(p);
|
|
126
130
|
} catch (err) {
|
|
127
|
-
if (err.code !== 'ENOENT')
|
|
131
|
+
if (err.code !== 'ENOENT') {
|
|
132
|
+
logger.log(formatWarningLine(`Could not remove run .env: ${err.message}`));
|
|
133
|
+
}
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
}
|
|
@@ -148,7 +154,7 @@ async function startContainer(appName, composePath, port, appConfig = null, opts
|
|
|
148
154
|
devMountPath = null,
|
|
149
155
|
misoEnvironment = 'dev'
|
|
150
156
|
} = opts;
|
|
151
|
-
logger.log(
|
|
157
|
+
logger.log(formatProgress(`Starting ${appName}…`));
|
|
152
158
|
|
|
153
159
|
let composeCmdBase;
|
|
154
160
|
let usedDockerRunFallback = false;
|
|
@@ -14,9 +14,36 @@ const fs = require('fs').promises;
|
|
|
14
14
|
const fsSync = require('fs');
|
|
15
15
|
const pathsUtil = require('../utils/paths');
|
|
16
16
|
const adminSecrets = require('../core/admin-secrets');
|
|
17
|
+
const secretsEnsure = require('../core/secrets-ensure');
|
|
17
18
|
const secretsEnvWrite = require('../core/secrets-env-write');
|
|
18
19
|
const { getContainerPort } = require('../utils/port-resolver');
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Backfill only secrets needed for `aifabrix run <app>`: kv:// keys from that app's env.template
|
|
23
|
+
* (catalog defaults via placeholderContext). Does not run full `ensureInfraSecrets`.
|
|
24
|
+
*
|
|
25
|
+
* Docker Postgres admin password for compose/db-init comes from **admin-secrets.env** (see
|
|
26
|
+
* `ensureAdminSecrets` / `readAndDecryptAdminSecrets`), not from `postgres-passwordKeyVault` on every run.
|
|
27
|
+
* The kv key is used when **generating** admin-secrets (e.g. first `up-infra`) via `generateAdminSecretsEnv`;
|
|
28
|
+
* we do not duplicate it here when an admin file already exists.
|
|
29
|
+
*
|
|
30
|
+
* @async
|
|
31
|
+
* @param {string} appName - Builder application key
|
|
32
|
+
* @returns {Promise<void>}
|
|
33
|
+
*/
|
|
34
|
+
async function ensureRunSecretsForApp(appName) {
|
|
35
|
+
const placeholderContext = secretsEnsure.buildInfraPlaceholderContext({});
|
|
36
|
+
const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
37
|
+
const templatePath = path.join(pathsUtil.getBuilderPath(appName), 'env.template');
|
|
38
|
+
if (fsSync.existsSync(templatePath)) {
|
|
39
|
+
await secretsEnsure.ensureSecretsFromEnvTemplate(templatePath, {
|
|
40
|
+
placeholderContext,
|
|
41
|
+
preferredFilePath: userSecretsPath,
|
|
42
|
+
useMergedSecretsForMissingKeys: true
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
20
47
|
/**
|
|
21
48
|
* Clean applications directory: remove generated docker-compose.yaml and .env.* files.
|
|
22
49
|
* @param {string|number} developerId - Developer ID
|
|
@@ -204,6 +231,7 @@ async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId
|
|
|
204
231
|
const ensureAdminSecretsFn = typeof infra.ensureAdminSecrets === 'function'
|
|
205
232
|
? infra.ensureAdminSecrets
|
|
206
233
|
: require('../infrastructure/helpers').ensureAdminSecrets;
|
|
234
|
+
await ensureRunSecretsForApp(appName);
|
|
207
235
|
await ensureAdminSecretsFn();
|
|
208
236
|
const adminObj = await adminSecrets.readAndDecryptAdminSecrets();
|
|
209
237
|
const runEnvKey = scopeOpts && scopeOpts.runEnvKey ? String(scopeOpts.runEnvKey).toLowerCase() : 'dev';
|
|
@@ -248,5 +276,6 @@ function assertNoPasswordLiteralsInCompose(composeContent) {
|
|
|
248
276
|
module.exports = {
|
|
249
277
|
cleanApplicationsDir,
|
|
250
278
|
buildMergedRunEnvAndWrite,
|
|
251
|
-
assertNoPasswordLiteralsInCompose
|
|
279
|
+
assertNoPasswordLiteralsInCompose,
|
|
280
|
+
ensureRunSecretsForApp
|
|
252
281
|
};
|
package/lib/app/run-helpers.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
formatSuccessLine,
|
|
3
|
+
formatSuccessParagraph,
|
|
4
|
+
formatProgress,
|
|
5
|
+
formatNextActions,
|
|
6
|
+
headerKeyValue,
|
|
7
|
+
metadata,
|
|
8
|
+
infoLine
|
|
9
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
2
10
|
/**
|
|
3
11
|
* AI Fabrix Builder - App Run Helpers
|
|
4
12
|
*
|
|
@@ -28,6 +36,8 @@ const { resolveRunImage } = require('./run-resolve-image');
|
|
|
28
36
|
const { startContainer } = require('./run-container-start');
|
|
29
37
|
const { resolveEnvOutputPath, writeEnvOutputForReload, writeEnvOutputForLocal } = require('../utils/env-copy');
|
|
30
38
|
const { resolveVersionForApp } = require('../utils/image-version');
|
|
39
|
+
const healthCheckUtil = require('../utils/health-check');
|
|
40
|
+
const { computeTraefikPublicAppUrl } = require('../utils/health-check-url');
|
|
31
41
|
|
|
32
42
|
/** Template apps (keycloak, miso-controller, dataplane) - never update application config when running */
|
|
33
43
|
const TEMPLATE_APP_KEYS = ['keycloak', 'miso-controller', 'dataplane'];
|
|
@@ -179,7 +189,7 @@ async function checkPrerequisites(appName, appConfig, debug = false, skipInfraCh
|
|
|
179
189
|
logger.log(chalk.gray(`[DEBUG] Image name: ${imageName}, tag: ${imageTag}`));
|
|
180
190
|
}
|
|
181
191
|
|
|
182
|
-
logger.log(
|
|
192
|
+
logger.log(formatProgress(`Checking image ${fullImageName}…`));
|
|
183
193
|
const imageExists = await checkImageExists(imageName, imageTag, debug);
|
|
184
194
|
if (!imageExists) {
|
|
185
195
|
const isTemplateApp = TEMPLATE_APP_KEYS.includes(appName);
|
|
@@ -208,12 +218,12 @@ async function checkInfraHealthOrThrow(debug, appConfig) {
|
|
|
208
218
|
const requirements = getAppInfraRequirements(appConfig);
|
|
209
219
|
if (requirements && !requirements.needsPostgres && !requirements.needsRedis) {
|
|
210
220
|
logger.log(
|
|
211
|
-
|
|
221
|
+
infoLine('Skipping Postgres/Redis health check (not required by this application).')
|
|
212
222
|
);
|
|
213
223
|
return;
|
|
214
224
|
}
|
|
215
225
|
|
|
216
|
-
logger.log(
|
|
226
|
+
logger.log(formatProgress('Checking infrastructure health…'));
|
|
217
227
|
const infra = require('../infrastructure');
|
|
218
228
|
const healthOptions =
|
|
219
229
|
requirements === null
|
|
@@ -280,7 +290,7 @@ function calculateComposePort(options, appConfig, developerId) {
|
|
|
280
290
|
* @returns {Promise<string>} Path to compose file
|
|
281
291
|
*/
|
|
282
292
|
async function generateComposeFile(appName, appConfig, composeOptions, devDir) {
|
|
283
|
-
logger.log(
|
|
293
|
+
logger.log(formatProgress('Generating Docker Compose configuration…'));
|
|
284
294
|
const composeContent = await composeGenerator.generateDockerCompose(appName, appConfig, composeOptions);
|
|
285
295
|
runEnvCompose.assertNoPasswordLiteralsInCompose(composeContent);
|
|
286
296
|
const tempComposePath = path.join(devDir, 'docker-compose.yaml');
|
|
@@ -338,7 +348,7 @@ async function prepareEnvironment(appName, appConfig, options) {
|
|
|
338
348
|
);
|
|
339
349
|
|
|
340
350
|
runEnvCompose.cleanApplicationsDir(developerId);
|
|
341
|
-
logger.log(
|
|
351
|
+
logger.log(formatProgress('Building merged .env (admin + app secrets)…'));
|
|
342
352
|
const { runEnvPath, runEnvAdminPath } = await runEnvCompose.buildMergedRunEnvAndWrite(
|
|
343
353
|
appName,
|
|
344
354
|
appConfig,
|
|
@@ -367,15 +377,37 @@ async function prepareEnvironment(appName, appConfig, options) {
|
|
|
367
377
|
* @param {string} appName - Application name
|
|
368
378
|
* @param {number} port - Application port
|
|
369
379
|
* @param {Object} appConfig - Application configuration (with developerId property)
|
|
380
|
+
* @param {Object|null} [runScopeOpts] - Optional env-scoped container naming
|
|
381
|
+
* @param {Object} [runOptions] - Run options (traefikEnabled / probeViaTraefik for public URL)
|
|
370
382
|
*/
|
|
371
|
-
async function displayRunStatus(appName, port, appConfig, runScopeOpts = null) {
|
|
383
|
+
async function displayRunStatus(appName, port, appConfig, runScopeOpts = null, runOptions = {}) {
|
|
372
384
|
const containerName = containerHelpers.getContainerName(appName, appConfig.developerId, runScopeOpts);
|
|
373
|
-
const
|
|
374
|
-
const
|
|
385
|
+
const ro = runOptions && typeof runOptions === 'object' ? runOptions : {};
|
|
386
|
+
const wantsPublic = Boolean(ro.probeViaTraefik === true || ro.traefikEnabled === true);
|
|
375
387
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
388
|
+
let primaryAppUrl = `http://localhost:${port}`;
|
|
389
|
+
if (wantsPublic) {
|
|
390
|
+
const pub = await computeTraefikPublicAppUrl(appName, port, appConfig);
|
|
391
|
+
if (pub) primaryAppUrl = pub;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const healthTipUrl = await healthCheckUtil.computeHealthCheckUrl(appName, port, appConfig, {
|
|
395
|
+
runOptions: ro
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
logger.log(formatSuccessParagraph(`App running at ${primaryAppUrl}`));
|
|
399
|
+
if (wantsPublic && primaryAppUrl.indexOf('localhost') === -1) {
|
|
400
|
+
logger.log(metadata(`Local direct: http://localhost:${port}`));
|
|
401
|
+
}
|
|
402
|
+
logger.log(headerKeyValue('Health check:', healthTipUrl));
|
|
403
|
+
logger.log(headerKeyValue('Container:', containerName));
|
|
404
|
+
logger.log('');
|
|
405
|
+
logger.log(
|
|
406
|
+
formatNextActions([
|
|
407
|
+
`Validate errors: aifabrix logs ${appName} -l error`,
|
|
408
|
+
`Follow output: aifabrix logs ${appName} -f -t 100`
|
|
409
|
+
])
|
|
410
|
+
);
|
|
379
411
|
}
|
|
380
412
|
|
|
381
413
|
module.exports = {
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutagen sync for `aifabrix run --reload` when Docker runs on another host.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview ensureReloadSync extracted from run.js (size limits)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const { formatProgress } = require('../utils/cli-test-layout-chalk');
|
|
13
|
+
const config = require('../core/config');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const mutagen = require('../utils/mutagen');
|
|
16
|
+
const pathsUtil = require('../utils/paths');
|
|
17
|
+
const {
|
|
18
|
+
isReloadBindMountOnEngineHost,
|
|
19
|
+
isLocalhostSyncSshHost
|
|
20
|
+
} = require('../utils/docker-reload-mount');
|
|
21
|
+
const { sectionTitle, headerKeyValue, metadata } = require('../utils/cli-test-layout-chalk');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} ReloadSyncSummaryBind
|
|
25
|
+
* @property {'bind-mount'} transport
|
|
26
|
+
* @property {string} hostPath - Host directory bind-mounted to /app in the container
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} ReloadSyncSummaryMutagen
|
|
31
|
+
* @property {'mutagen'} transport
|
|
32
|
+
* @property {string} remotePath - Path on sync host used for Docker -v
|
|
33
|
+
* @property {string} sessionName - Mutagen session name
|
|
34
|
+
* @property {string} localPath - Local folder synced (same as build context)
|
|
35
|
+
* @property {string} syncSshHost - sync-ssh-host from config
|
|
36
|
+
* @property {string} sshUrl - Mutagen endpoint (user@host:path)
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {ReloadSyncSummaryBind|ReloadSyncSummaryMutagen} ReloadSyncSummary
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Install Mutagen if needed, create sync session, return summary for compose and UX.
|
|
45
|
+
* @param {string} appName
|
|
46
|
+
* @param {string} developerId
|
|
47
|
+
* @param {boolean} debug
|
|
48
|
+
* @param {string} codePath
|
|
49
|
+
* @param {string} [remoteSyncPath]
|
|
50
|
+
* @param {string} syncSshHost
|
|
51
|
+
* @returns {Promise<ReloadSyncSummaryMutagen>}
|
|
52
|
+
*/
|
|
53
|
+
async function startMutagenReloadSync(
|
|
54
|
+
appName,
|
|
55
|
+
developerId,
|
|
56
|
+
debug,
|
|
57
|
+
codePath,
|
|
58
|
+
remoteSyncPath,
|
|
59
|
+
syncSshHost
|
|
60
|
+
) {
|
|
61
|
+
const [userMutagenFolder, syncSshUser] = await Promise.all([
|
|
62
|
+
config.getUserMutagenFolder(),
|
|
63
|
+
config.getSyncSshUser()
|
|
64
|
+
]);
|
|
65
|
+
if (!userMutagenFolder || !syncSshUser || !syncSshHost) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
'run --reload requires remote server sync settings. Run "aifabrix dev init" or set user-mutagen-folder, sync-ssh-user, sync-ssh-host in config.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const mutagenPath = await mutagen.ensureMutagenPath(logger.log);
|
|
71
|
+
const remotePath = mutagen.getRemotePath(userMutagenFolder, appName, remoteSyncPath);
|
|
72
|
+
const sshUrl = mutagen.getSyncSshUrl(syncSshUser, syncSshHost, remotePath);
|
|
73
|
+
const sessionName = mutagen.getSessionName(developerId, appName);
|
|
74
|
+
const localPath = (codePath && typeof codePath === 'string') ? codePath : pathsUtil.getBuilderPath(appName);
|
|
75
|
+
if (debug) logger.log(chalk.gray(`[DEBUG] Mutagen sync: ${sessionName} ${localPath} <-> ${sshUrl}`));
|
|
76
|
+
logger.log(formatProgress('Reload: ensuring Mutagen sync (remote Docker engine)…'));
|
|
77
|
+
await mutagen.ensureSyncSession(mutagenPath, sessionName, localPath, sshUrl);
|
|
78
|
+
return {
|
|
79
|
+
transport: 'mutagen',
|
|
80
|
+
remotePath,
|
|
81
|
+
sessionName,
|
|
82
|
+
localPath,
|
|
83
|
+
syncSshHost,
|
|
84
|
+
sshUrl
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* When run --reload in dev with remote: ensure Mutagen sync session; return summary for mounts and CLI copy.
|
|
90
|
+
* Uses codePath (resolved build.context) as Mutagen local path so one config field drives both local and remote.
|
|
91
|
+
* Skips Mutagen when docker-endpoint targets this host (bind mount) or sync-ssh-host is localhost.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} appName - Application name
|
|
94
|
+
* @param {string} developerId - Developer ID
|
|
95
|
+
* @param {boolean} debug - Debug flag
|
|
96
|
+
* @param {string} codePath - Resolved build.context (absolute path to app code)
|
|
97
|
+
* @param {string} [remoteSyncPath] - Optional relative path under user-mutagen-folder (from build.remoteSyncPath); when unset, defaults to dev/<appKey>
|
|
98
|
+
* @returns {Promise<ReloadSyncSummary>}
|
|
99
|
+
* @throws {Error} If --reload but remote not configured, or Mutagen install fails
|
|
100
|
+
*/
|
|
101
|
+
async function ensureReloadSync(appName, developerId, debug, codePath, remoteSyncPath) {
|
|
102
|
+
const endpoint = await config.getDockerEndpoint();
|
|
103
|
+
if (isReloadBindMountOnEngineHost(endpoint)) {
|
|
104
|
+
if (debug) {
|
|
105
|
+
logger.log(
|
|
106
|
+
chalk.gray('[DEBUG] Docker engine shares this host filesystem; skipping Mutagen for --reload')
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return { transport: 'bind-mount', hostPath: codePath };
|
|
110
|
+
}
|
|
111
|
+
const syncSshHost = await config.getSyncSshHost();
|
|
112
|
+
if (isLocalhostSyncSshHost(syncSshHost || '')) {
|
|
113
|
+
if (debug) {
|
|
114
|
+
logger.log(chalk.gray('[DEBUG] sync-ssh-host is localhost; skipping Mutagen, using local path'));
|
|
115
|
+
}
|
|
116
|
+
return { transport: 'bind-mount', hostPath: codePath };
|
|
117
|
+
}
|
|
118
|
+
return startMutagenReloadSync(appName, developerId, debug, codePath, remoteSyncPath, syncSshHost);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Log how --reload is wired (bind vs Mutagen) after compose/env prep, before container start.
|
|
123
|
+
* @param {boolean} reload
|
|
124
|
+
* @param {ReloadSyncSummary|undefined} summary
|
|
125
|
+
*/
|
|
126
|
+
function logReloadDevSummary(reload, summary) {
|
|
127
|
+
if (!reload || !summary) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
logger.log('');
|
|
131
|
+
logger.log(sectionTitle('Reload (dev)'));
|
|
132
|
+
if (summary.transport === 'bind-mount') {
|
|
133
|
+
logger.log(headerKeyValue('Transport:', 'Direct bind mount on the Docker host (no Mutagen).'));
|
|
134
|
+
logger.log(headerKeyValue('Host path → container:', `${summary.hostPath} → /app`));
|
|
135
|
+
logger.log(metadata('Edits under the host path are visible inside the container immediately.'));
|
|
136
|
+
logger.log('');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
logger.log(headerKeyValue('Transport:', 'Mutagen two-way sync (Docker engine on another host).'));
|
|
140
|
+
logger.log(headerKeyValue('Session:', summary.sessionName));
|
|
141
|
+
logger.log(headerKeyValue('Sync host:', summary.syncSshHost));
|
|
142
|
+
logger.log(headerKeyValue('Local folder:', summary.localPath));
|
|
143
|
+
logger.log(headerKeyValue('Remote mount (-v):', summary.remotePath));
|
|
144
|
+
logger.log(metadata(`Mutagen endpoint: ${summary.sshUrl}`));
|
|
145
|
+
logger.log('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { ensureReloadSync, logReloadDevSummary };
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
+
const config = require('../core/config');
|
|
9
10
|
const { resolveDockerImageRef } = require('../utils/resolve-docker-image-ref');
|
|
11
|
+
const { checkImageExists } = require('../utils/app-run-containers');
|
|
12
|
+
const { buildDevImageRepositoryPath } = require('../utils/image-name');
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* @param {string} appName
|
|
@@ -18,4 +21,51 @@ function resolveRunImage(appName, appConfig, runOptions) {
|
|
|
18
21
|
return resolveDockerImageRef(appName, appConfig, runOptions);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Resolves which local image to run: developer-scoped ref first when present, else manifest base.
|
|
26
|
+
* With {@link runOptions.base} true, uses manifest base only (no dev-first probe).
|
|
27
|
+
*
|
|
28
|
+
* @async
|
|
29
|
+
* @param {string} appName
|
|
30
|
+
* @param {Object} appConfig
|
|
31
|
+
* @param {Object} [runOptions]
|
|
32
|
+
* @param {string} [runOptions.image] - Full override (skips fallback logic)
|
|
33
|
+
* @param {boolean} [runOptions.base] - When true, manifest base ref only
|
|
34
|
+
* @returns {Promise<{ imageName: string, imageTag: string }>}
|
|
35
|
+
*/
|
|
36
|
+
async function resolveRunImageWithLocalFallback(appName, appConfig, runOptions = {}) {
|
|
37
|
+
const opts = runOptions || {};
|
|
38
|
+
if (opts.image) {
|
|
39
|
+
return resolveDockerImageRef(appName, appConfig, opts);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const baseRef = resolveDockerImageRef(appName, appConfig, opts);
|
|
43
|
+
|
|
44
|
+
if (opts.base === true) {
|
|
45
|
+
return baseRef;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const developerId = await config.getDeveloperId();
|
|
49
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
50
|
+
if (!Number.isFinite(idNum) || idNum === 0) {
|
|
51
|
+
return baseRef;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const tag = baseRef.imageTag;
|
|
55
|
+
const devRepo = buildDevImageRepositoryPath(baseRef.imageName, developerId);
|
|
56
|
+
const devExists = await checkImageExists(devRepo, tag, false);
|
|
57
|
+
if (devExists) {
|
|
58
|
+
return { imageName: devRepo, imageTag: tag };
|
|
59
|
+
}
|
|
60
|
+
const baseExists = await checkImageExists(baseRef.imageName, tag, false);
|
|
61
|
+
if (baseExists) {
|
|
62
|
+
return baseRef;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Docker image not found (tried ${devRepo}:${tag} then ${baseRef.imageName}:${tag})\n` +
|
|
67
|
+
`Run 'aifabrix build ${appName}' or pull the manifest image, or use --base after pulling the base image.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { resolveRunImage, resolveRunImageWithLocalFallback };
|