@aifabrix/builder 2.44.5 → 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 +48 -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/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.js +20 -2
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +134 -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 +78 -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 +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 +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/up-common.js +79 -19
- 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 +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/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/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 +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-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 +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 +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 +23 -12
- 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 +1 -1
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- 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
|
@@ -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 };
|
package/lib/app/run.js
CHANGED
|
@@ -10,15 +10,23 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
|
+
const {
|
|
14
|
+
formatWarningLine,
|
|
15
|
+
sectionTitle,
|
|
16
|
+
headerKeyValue,
|
|
17
|
+
metadata,
|
|
18
|
+
formatNextActions
|
|
19
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
13
20
|
const config = require('../core/config');
|
|
14
21
|
const logger = require('../utils/logger');
|
|
15
22
|
const pathsUtil = require('../utils/paths');
|
|
16
23
|
const { checkPortAvailable, waitForHealthCheck } = require('../utils/health-check');
|
|
17
24
|
const composeGenerator = require('../utils/compose-generator');
|
|
18
25
|
const containerHelpers = require('../utils/app-run-containers');
|
|
19
|
-
const mutagen = require('../utils/mutagen');
|
|
20
26
|
// Helper functions extracted to reduce file size and complexity
|
|
21
27
|
const helpers = require('./run-helpers');
|
|
28
|
+
const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
|
|
29
|
+
const { logRestartDevMountSummary } = require('./restart-display');
|
|
22
30
|
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
23
31
|
|
|
24
32
|
/**
|
|
@@ -73,8 +81,16 @@ async function validateAppForRun(appName, _debug) {
|
|
|
73
81
|
try {
|
|
74
82
|
const { isExternal, baseDir } = await detectAppType(appName);
|
|
75
83
|
if (baseDir !== 'builder' || isExternal) {
|
|
76
|
-
logger.log(
|
|
77
|
-
logger.log(
|
|
84
|
+
logger.log('');
|
|
85
|
+
logger.log(sectionTitle('Run'));
|
|
86
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
87
|
+
logger.log(
|
|
88
|
+
formatWarningLine('External integrations are not started as local Docker containers.')
|
|
89
|
+
);
|
|
90
|
+
logger.log(
|
|
91
|
+
metadata('Build and deploy the integration, then use its APIs from your environment.')
|
|
92
|
+
);
|
|
93
|
+
logger.log(formatNextActions([`aifabrix build ${appName}`]));
|
|
78
94
|
return false;
|
|
79
95
|
}
|
|
80
96
|
} catch (error) {
|
|
@@ -114,7 +130,7 @@ async function checkAndStopContainer(appName, appConfig, options, debug) {
|
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
|
|
117
|
-
logger.log(
|
|
133
|
+
logger.log(formatWarningLine(`Container ${containerName} is already running`));
|
|
118
134
|
await helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts);
|
|
119
135
|
}
|
|
120
136
|
|
|
@@ -149,48 +165,6 @@ async function calculateHostPort(appConfig, options, debug) {
|
|
|
149
165
|
return hostPort;
|
|
150
166
|
}
|
|
151
167
|
|
|
152
|
-
/**
|
|
153
|
-
* When run --reload in dev with remote: ensure Mutagen sync session; return remote path for compose mount.
|
|
154
|
-
* Uses codePath (resolved build.context) as Mutagen local path so one config field drives both local and remote.
|
|
155
|
-
* When Docker endpoint, remote-server, or sync-ssh-host is localhost/127.0.0.1, returns null (no sync; use local path).
|
|
156
|
-
*
|
|
157
|
-
* @param {string} appName - Application name
|
|
158
|
-
* @param {string} developerId - Developer ID
|
|
159
|
-
* @param {boolean} debug - Debug flag
|
|
160
|
-
* @param {string} codePath - Resolved build.context (absolute path to app code)
|
|
161
|
-
* @param {string} [remoteSyncPath] - Optional relative path under user-mutagen-folder (from build.remoteSyncPath); when unset, defaults to dev/<appKey>
|
|
162
|
-
* @returns {Promise<string|null>} Remote path for -v mount or null if not remote/reload or already on server (localhost)
|
|
163
|
-
* @throws {Error} If --reload but remote not configured, or Mutagen install fails
|
|
164
|
-
*/
|
|
165
|
-
async function ensureReloadSync(appName, developerId, debug, codePath, remoteSyncPath) {
|
|
166
|
-
const endpoint = await config.getDockerEndpoint();
|
|
167
|
-
const serverUrl = await config.getRemoteServer();
|
|
168
|
-
if (!endpoint && !serverUrl) return null;
|
|
169
|
-
const syncSshHost = await config.getSyncSshHost();
|
|
170
|
-
if (isLocalhostEndpoint(endpoint) || isLocalhostEndpoint(serverUrl) || isLocalhostHost(syncSshHost || '')) {
|
|
171
|
-
if (debug) logger.log(chalk.gray('[DEBUG] Docker/remote/sync host is localhost; skipping Mutagen, using local path'));
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
const [userMutagenFolder, syncSshUser] = await Promise.all([
|
|
175
|
-
config.getUserMutagenFolder(),
|
|
176
|
-
config.getSyncSshUser()
|
|
177
|
-
]);
|
|
178
|
-
if (!userMutagenFolder || !syncSshUser || !syncSshHost) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
'run --reload requires remote server sync settings. Run "aifabrix dev init" or set user-mutagen-folder, sync-ssh-user, sync-ssh-host in config.'
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
const mutagenPath = await mutagen.ensureMutagenPath(logger.log);
|
|
184
|
-
const remotePath = mutagen.getRemotePath(userMutagenFolder, appName, remoteSyncPath);
|
|
185
|
-
const sshUrl = mutagen.getSyncSshUrl(syncSshUser, syncSshHost, remotePath);
|
|
186
|
-
const sessionName = mutagen.getSessionName(developerId, appName);
|
|
187
|
-
const localPath = (codePath && typeof codePath === 'string') ? codePath : pathsUtil.getBuilderPath(appName);
|
|
188
|
-
if (debug) logger.log(chalk.gray(`[DEBUG] Mutagen sync: ${sessionName} ${localPath} <-> ${sshUrl}`));
|
|
189
|
-
logger.log(chalk.blue('Ensuring Mutagen sync session...'));
|
|
190
|
-
await mutagen.ensureSyncSession(mutagenPath, sessionName, localPath, sshUrl);
|
|
191
|
-
return remotePath;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
168
|
/**
|
|
195
169
|
* Load and configure application
|
|
196
170
|
* @async
|
|
@@ -238,12 +212,21 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
|
|
|
238
212
|
devMountPath,
|
|
239
213
|
misoEnvironment
|
|
240
214
|
});
|
|
241
|
-
await helpers.displayRunStatus(
|
|
215
|
+
await helpers.displayRunStatus(
|
|
216
|
+
appName,
|
|
217
|
+
hostPort,
|
|
218
|
+
appConfig,
|
|
219
|
+
opts.runScopeOpts || null,
|
|
220
|
+
runOptions || {}
|
|
221
|
+
);
|
|
242
222
|
} catch (error) {
|
|
243
|
-
logger.log(
|
|
244
|
-
logger.log(
|
|
223
|
+
logger.log('');
|
|
224
|
+
logger.log(formatWarningLine(`Compose file preserved at ${tempComposePath}`));
|
|
225
|
+
logger.log(metadata(' Review the compose file, fix the issue, then run again.'));
|
|
245
226
|
if (runEnvPath || runEnvAdminPath) {
|
|
246
|
-
logger.log(
|
|
227
|
+
logger.log(
|
|
228
|
+
metadata(' Run .env file(s) contain secrets and were not deleted; remove manually if needed.')
|
|
229
|
+
);
|
|
247
230
|
}
|
|
248
231
|
if (debug) {
|
|
249
232
|
logger.log(chalk.gray(`[DEBUG] Error during container start: ${error.message}`));
|
|
@@ -271,30 +254,46 @@ async function resolveRunOptions(appName, appConfig, options, envKey, debug, eff
|
|
|
271
254
|
const codePath = pathsUtil.resolveBuildContext(builderPath, appConfig.build?.context || '.');
|
|
272
255
|
if (options.reload && envKey === 'dev') {
|
|
273
256
|
const remoteSyncPath = appConfig.build?.remoteSyncPath;
|
|
274
|
-
const
|
|
275
|
-
runOptions.
|
|
257
|
+
const reloadSummary = await ensureReloadSync(appName, appConfig.developerId, debug, codePath, remoteSyncPath);
|
|
258
|
+
runOptions.reloadSyncSummary = reloadSummary;
|
|
259
|
+
runOptions.devMountPath =
|
|
260
|
+
reloadSummary.transport === 'mutagen' ? reloadSummary.remotePath : codePath;
|
|
276
261
|
}
|
|
277
262
|
return runOptions;
|
|
278
263
|
}
|
|
279
264
|
|
|
280
265
|
/**
|
|
281
|
-
*
|
|
282
|
-
* @param {
|
|
283
|
-
* @param {Object}
|
|
284
|
-
* @param {boolean} debug - Debug flag
|
|
285
|
-
* @returns {Promise<{ appConfig: Object, tempComposePath: string, hostPort: number }|null>} Prepared run context or null if should not continue
|
|
266
|
+
* When Traefik is enabled in user config, pass through for health checks (DNS + localhost order).
|
|
267
|
+
* @param {Object} runOptions
|
|
268
|
+
* @param {Object} userCfg
|
|
286
269
|
*/
|
|
287
|
-
|
|
288
|
-
|
|
270
|
+
function applyTraefikFlagToRunOptions(runOptions, userCfg) {
|
|
271
|
+
if (userCfg && userCfg.traefik === true) {
|
|
272
|
+
runOptions.traefikEnabled = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {string} envKey
|
|
278
|
+
* @throws {Error} If env is not dev, tst, or pro
|
|
279
|
+
*/
|
|
280
|
+
function assertValidRunEnvKey(envKey) {
|
|
289
281
|
if (envKey !== 'dev' && envKey !== 'tst' && envKey !== 'pro') {
|
|
290
282
|
throw new Error('--env must be dev, tst, or pro');
|
|
291
283
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Image resolution, prereqs, port, compose env — after app config and user config are loaded.
|
|
288
|
+
* @param {string} appName
|
|
289
|
+
* @param {Object} appConfig
|
|
290
|
+
* @param {Object} options - Original run options
|
|
291
|
+
* @param {string} envKey
|
|
292
|
+
* @param {Object} userCfg - getConfig() result
|
|
293
|
+
* @param {boolean} debug
|
|
294
|
+
* @returns {Promise<Object>} Prepared run context (same shape as prepareAppRun return)
|
|
295
|
+
*/
|
|
296
|
+
async function computeRunPreparationCore(appName, appConfig, options, envKey, userCfg, debug) {
|
|
298
297
|
const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
|
|
299
298
|
const effectiveEnvironmentScopedResources = computeEffectiveEnvironmentScopedResources(
|
|
300
299
|
Boolean(userCfg.useEnvironmentScopedResources),
|
|
@@ -304,17 +303,24 @@ async function prepareAppRun(appName, options, debug) {
|
|
|
304
303
|
const runScopeOpts = effectiveEnvironmentScopedResources
|
|
305
304
|
? { effectiveEnvironmentScopedResources: true, env: envKey }
|
|
306
305
|
: null;
|
|
307
|
-
|
|
308
|
-
await
|
|
309
|
-
const
|
|
306
|
+
const { resolveRunImageWithLocalFallback } = require('./run-resolve-image');
|
|
307
|
+
const resolvedRef = await resolveRunImageWithLocalFallback(appName, appConfig, options);
|
|
308
|
+
const effectiveRunOptions = {
|
|
309
|
+
...options,
|
|
310
|
+
image: `${resolvedRef.imageName}:${resolvedRef.imageTag}`
|
|
311
|
+
};
|
|
312
|
+
await helpers.checkPrerequisites(appName, appConfig, debug, effectiveRunOptions.skipInfraCheck === true, effectiveRunOptions);
|
|
313
|
+
await checkAndStopContainer(appName, appConfig, effectiveRunOptions, debug);
|
|
314
|
+
const hostPort = await calculateHostPort(appConfig, effectiveRunOptions, debug);
|
|
310
315
|
const runOptions = await resolveRunOptions(
|
|
311
316
|
appName,
|
|
312
317
|
appConfig,
|
|
313
|
-
|
|
318
|
+
effectiveRunOptions,
|
|
314
319
|
envKey,
|
|
315
320
|
debug,
|
|
316
321
|
effectiveEnvironmentScopedResources
|
|
317
322
|
);
|
|
323
|
+
applyTraefikFlagToRunOptions(runOptions, userCfg);
|
|
318
324
|
const { composePath: tempComposePath, runEnvPath, runEnvAdminPath } = await helpers.prepareEnvironment(appName, appConfig, runOptions);
|
|
319
325
|
const result = {
|
|
320
326
|
appConfig,
|
|
@@ -329,6 +335,28 @@ async function prepareAppRun(appName, options, debug) {
|
|
|
329
335
|
return result;
|
|
330
336
|
}
|
|
331
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Prepare run: validate app, load config, check prereqs, stop existing container, resolve port and reload, prepare env.
|
|
340
|
+
* @param {string} appName - Application name
|
|
341
|
+
* @param {Object} options - Run options
|
|
342
|
+
* @param {boolean} debug - Debug flag
|
|
343
|
+
* @returns {Promise<{ appConfig: Object, tempComposePath: string, hostPort: number }|null>} Prepared run context or null if should not continue
|
|
344
|
+
*/
|
|
345
|
+
async function prepareAppRun(appName, options, debug) {
|
|
346
|
+
const envKey = (options.env || 'dev').toLowerCase();
|
|
347
|
+
assertValidRunEnvKey(envKey);
|
|
348
|
+
const shouldContinue = await validateAppForRun(appName, debug);
|
|
349
|
+
if (!shouldContinue) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
logger.log('');
|
|
353
|
+
logger.log(sectionTitle('Run'));
|
|
354
|
+
logger.log(headerKeyValue('Application:', appName));
|
|
355
|
+
const appConfig = await loadAndConfigureApp(appName, debug);
|
|
356
|
+
const userCfg = await config.getConfig();
|
|
357
|
+
return computeRunPreparationCore(appName, appConfig, options, envKey, userCfg, debug);
|
|
358
|
+
}
|
|
359
|
+
|
|
332
360
|
/**
|
|
333
361
|
* Runs the application locally using Docker
|
|
334
362
|
* Starts container with proper port mapping and environment
|
|
@@ -360,10 +388,7 @@ async function runApp(appName, options = {}) {
|
|
|
360
388
|
if (debug) {
|
|
361
389
|
logger.log(chalk.gray(`[DEBUG] Compose file generated: ${prepared.tempComposePath}`));
|
|
362
390
|
}
|
|
363
|
-
|
|
364
|
-
logger.log(chalk.gray('With --reload: workspace mounted from host at /app (container runs as your user for write access).'));
|
|
365
|
-
logger.log(chalk.gray(` Host path: ${prepared.devMountPath}`));
|
|
366
|
-
}
|
|
391
|
+
logReloadDevSummary(Boolean(options.reload), prepared.mergedRunOptions.reloadSyncSummary);
|
|
367
392
|
await startAppContainer(appName, prepared.tempComposePath, prepared.hostPort, prepared.appConfig, {
|
|
368
393
|
debug,
|
|
369
394
|
runEnvPath: prepared.runEnvPath,
|
|
@@ -401,6 +426,7 @@ async function restartApp(appName) {
|
|
|
401
426
|
const containerName = containerHelpers.getContainerName(appName, developerId);
|
|
402
427
|
try {
|
|
403
428
|
await execWithDockerEnv(`docker restart ${containerName}`);
|
|
429
|
+
await logRestartDevMountSummary(containerName);
|
|
404
430
|
} catch (error) {
|
|
405
431
|
const msg = (error.stderr || error.stdout || error.message || '').toLowerCase();
|
|
406
432
|
if (msg.includes('no such container') || msg.includes('is not running')) {
|