@aifabrix/builder 2.44.6 → 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.
Files changed (86) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/jest.projects.js +56 -0
  3. package/lib/app/helpers.js +3 -3
  4. package/lib/app/index.js +3 -3
  5. package/lib/app/register.js +7 -6
  6. package/lib/app/restart-display.js +52 -21
  7. package/lib/app/rotate-secret.js +7 -6
  8. package/lib/app/run-helpers.js +15 -8
  9. package/lib/app/run.js +57 -9
  10. package/lib/app/show-display.js +7 -0
  11. package/lib/app/show.js +87 -5
  12. package/lib/build/index.js +9 -5
  13. package/lib/cli/infra-guided.js +42 -27
  14. package/lib/cli/installation-log-command.js +73 -0
  15. package/lib/cli/setup-app.js +11 -1
  16. package/lib/cli/setup-auth.js +94 -49
  17. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  18. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  19. package/lib/cli/setup-infra.js +60 -119
  20. package/lib/cli/setup-platform.js +1 -1
  21. package/lib/cli/setup-utility-resolve.js +132 -0
  22. package/lib/cli/setup-utility.js +65 -51
  23. package/lib/commands/app-logs.js +81 -33
  24. package/lib/commands/auth-config.js +116 -18
  25. package/lib/commands/setup-modes.js +19 -6
  26. package/lib/commands/setup-prompts.js +41 -8
  27. package/lib/commands/setup.js +114 -9
  28. package/lib/commands/teardown.js +54 -5
  29. package/lib/commands/up-common.js +48 -14
  30. package/lib/commands/up-dataplane.js +21 -18
  31. package/lib/commands/up-miso.js +12 -8
  32. package/lib/commands/upload.js +5 -3
  33. package/lib/core/audit-logger.js +1 -34
  34. package/lib/core/config-admin-email.js +56 -0
  35. package/lib/core/config-normalize.js +60 -0
  36. package/lib/core/config-registered-controller-urls.js +54 -0
  37. package/lib/core/config.js +33 -50
  38. package/lib/core/secrets-ensure-infra.js +1 -1
  39. package/lib/core/secrets-env-content.js +86 -90
  40. package/lib/core/secrets-env-declarative-expand.js +170 -0
  41. package/lib/core/secrets-env-write.js +2 -0
  42. package/lib/core/secrets-load.js +106 -102
  43. package/lib/external-system/deploy.js +5 -1
  44. package/lib/internal/node-fs.js +2 -0
  45. package/lib/schema/application-schema.json +4 -0
  46. package/lib/schema/infra.parameter.yaml +10 -0
  47. package/lib/utils/app-config-resolver.js +24 -1
  48. package/lib/utils/applications-config-defaults.js +206 -0
  49. package/lib/utils/auth-config-validator.js +2 -12
  50. package/lib/utils/bash-secret-env.js +1 -1
  51. package/lib/utils/compose-generate-docker-compose.js +111 -6
  52. package/lib/utils/compose-generator.js +17 -8
  53. package/lib/utils/controller-url.js +50 -7
  54. package/lib/utils/env-copy.js +99 -14
  55. package/lib/utils/env-template.js +5 -1
  56. package/lib/utils/health-check-url.js +18 -15
  57. package/lib/utils/health-check.js +7 -5
  58. package/lib/utils/infra-optional-service-flags.js +69 -0
  59. package/lib/utils/installation-log-core.js +282 -0
  60. package/lib/utils/installation-log-record.js +237 -0
  61. package/lib/utils/installation-log.js +123 -0
  62. package/lib/utils/log-redaction.js +105 -0
  63. package/lib/utils/manifest-location.js +164 -0
  64. package/lib/utils/manifest-source-emit.js +162 -0
  65. package/lib/utils/paths.js +238 -89
  66. package/lib/utils/remote-secrets-loader.js +7 -1
  67. package/lib/utils/run-cli-flags.js +29 -0
  68. package/lib/utils/secrets-canonical.js +10 -3
  69. package/lib/utils/secrets-path.js +3 -4
  70. package/lib/utils/secrets-utils.js +20 -10
  71. package/lib/utils/system-builder-root.js +10 -2
  72. package/lib/utils/url-declarative-public-base.js +80 -12
  73. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  74. package/lib/utils/url-declarative-resolve-build.js +24 -393
  75. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  76. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  77. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  78. package/lib/utils/url-declarative-resolve.js +47 -7
  79. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  80. package/lib/utils/urls-local-registry-scan.js +103 -0
  81. package/lib/utils/urls-local-registry.js +161 -90
  82. package/package.json +3 -1
  83. package/templates/applications/dataplane/application.yaml +4 -0
  84. package/templates/applications/miso-controller/application.yaml +2 -0
  85. package/templates/applications/miso-controller/env.template +27 -29
  86. package/.npmrc.token +0 -1
@@ -12,7 +12,7 @@ When you touch **CLI surface** (`bin/aifabrix.js`, `lib/cli.js`, `lib/cli/**`, `
12
12
  | Document | Role |
13
13
  | -------- | ---- |
14
14
  | [layout.md](./layout.md) | Visual and semantic spec: colors, sections, glyphs, blocking vs warning, non-TTY/CI, helper map to `cli-test-layout-chalk.js`. |
15
- | [cli-output-command-matrix.md](./cli-output-command-matrix.md) | **One row per leaf command**: expected **output profile** (layout-blocks, tty-summary, stream-logs, json-opt, stdout-only, delegate). |
15
+ | [cli-output-command-matrix.md](./cli-output-command-matrix.md) | **One row per leaf command**: expected **output profile** (layout-blocks, tty-summary, stream-logs, json-opt, stdout-only, delegate) and **Manifest roots (141)** (when to emit gray manifest path line — see plan 141). |
16
16
 
17
17
  If implementation and `layout.md` disagree, **fix implementation** or **update docs** in the same change so they stay one story.
18
18
 
@@ -27,9 +27,13 @@ Use the matrix row for your command to decide UX:
27
27
  - **stdout-only** — Stable, script-friendly stdout; minimal chalk; often used for diffs or piping.
28
28
  - **delegate** — Output comes from delegated library (wizard, etc.); still avoid forbidden glyphs and spurious decoration in anything you add.
29
29
 
30
- **New leaf command:** add a row to [cli-output-command-matrix.md](./cli-output-command-matrix.md) with the correct profile combination.
30
+ **New leaf command:** add a row to [cli-output-command-matrix.md](./cli-output-command-matrix.md) with the correct profile combination and **Manifest roots (141)** code (`141`, `141+`, `int`, `cfg`, `—`).
31
31
 
32
- ## Glyphs and semantics (terminal only)
32
+ ## Manifest path visibility (plan 141)
33
+
34
+ When a command’s matrix cell is **141**, **141+**, or **int** and the implementation reads an `application.yaml` (or integration equivalent), print **one** gray metadata line for humans (e.g. `Manifest: cwd/builder — /abs/path/application.yaml`) using `metadata()` or a small helper from `cli-test-layout-chalk.js`. Do not spam per-substep. Follow [.cursor/plans/141-manifest-location.plan.md](../plans/141-manifest-location.plan.md).
35
+
36
+ **`aifabrix setup`:** destructive / conflict prompts that list folders to be replaced must use **resolved absolute paths** for every directory (plan § Guided setup — not only `builder/<app>/` under an otherwise absolute builder root).
33
37
 
34
38
  From [layout.md](./layout.md) contributor appendix:
35
39
 
package/jest.projects.js CHANGED
@@ -141,6 +141,34 @@ const defaultProject = {
141
141
  '\\\\tests\\\\lib\\\\utils\\\\url-declarative-vdir-inactive-env.test.js',
142
142
  'lib/utils/url-declarative-vdir-inactive-env.test.js',
143
143
  'url-declarative-vdir-inactive-env\\.test\\.js',
144
+ '/tests/lib/utils/url-declarative-user-cfg-per-app-proxy.test.js',
145
+ '\\\\tests\\\\lib\\\\utils\\\\url-declarative-user-cfg-per-app-proxy.test.js',
146
+ 'lib/utils/url-declarative-user-cfg-per-app-proxy.test.js',
147
+ 'url-declarative-user-cfg-per-app-proxy\\.test\\.js',
148
+ '/tests/lib/utils/url-declarative-expand-traefik-off-no-usercfg.test.js',
149
+ '\\\\tests\\\\lib\\\\utils\\\\url-declarative-expand-traefik-off-no-usercfg.test.js',
150
+ 'lib/utils/url-declarative-expand-traefik-off-no-usercfg.test.js',
151
+ 'url-declarative-expand-traefik-off-no-usercfg\\.test\\.js',
152
+ '/tests/lib/core/secrets-env-declarative-show-urls.test.js',
153
+ '\\\\tests\\\\lib\\\\core\\\\secrets-env-declarative-show-urls.test.js',
154
+ 'lib/core/secrets-env-declarative-show-urls.test.js',
155
+ 'secrets-env-declarative-show-urls\\.test\\.js',
156
+ '/tests/lib/utils/url-declarative-registry-internal-docker-origin.test.js',
157
+ '\\\\tests\\\\lib\\\\utils\\\\url-declarative-registry-internal-docker-origin.test.js',
158
+ 'lib/utils/url-declarative-registry-internal-docker-origin.test.js',
159
+ 'url-declarative-registry-internal-docker-origin\\.test\\.js',
160
+ '/tests/lib/commands/platform-urls-registry.validation.test.js',
161
+ '\\\\tests\\\\lib\\\\commands\\\\platform-urls-registry.validation.test.js',
162
+ 'lib/commands/platform-urls-registry.validation.test.js',
163
+ 'platform-urls-registry\\.validation\\.test\\.js',
164
+ '/tests/lib/utils/env-copy-resolve-output.test.js',
165
+ '\\\\tests\\\\lib\\\\utils\\\\env-copy-resolve-output.test.js',
166
+ 'lib/utils/env-copy-resolve-output.test.js',
167
+ 'env-copy-resolve-output\\.test\\.js',
168
+ '/tests/lib/utils/write-env-output-reload.test.js',
169
+ '\\\\tests\\\\lib\\\\utils\\\\write-env-output-reload.test.js',
170
+ 'lib/utils/write-env-output-reload.test.js',
171
+ 'write-env-output-reload\\.test\\.js',
144
172
  '/tests/lib/utils/app-service-env-from-builder.test.js',
145
173
  '\\\\tests\\\\lib\\\\utils\\\\app-service-env-from-builder.test.js',
146
174
  'lib/utils/app-service-env-from-builder.test.js',
@@ -165,6 +193,18 @@ const defaultProject = {
165
193
  '\\\\tests\\\\lib\\\\utils\\\\paths-system-builder-resolution.test.js',
166
194
  'lib/utils/paths-system-builder-resolution.test.js',
167
195
  'paths-system-builder-resolution\\.test\\.js',
196
+ '/tests/lib/utils/manifest-location.test.js',
197
+ '\\\\tests\\\\lib\\\\utils\\\\manifest-location.test.js',
198
+ 'lib/utils/manifest-location.test.js',
199
+ 'manifest-location\\.test\\.js',
200
+ '/tests/lib/utils/installation-log.test.js',
201
+ '\\\\tests\\\\lib\\\\utils\\\\installation-log.test.js',
202
+ 'lib/utils/installation-log.test.js',
203
+ 'installation-log\\.test\\.js',
204
+ '/tests/lib/utils/manifest-source-emit.test.js',
205
+ '\\\\tests\\\\lib\\\\utils\\\\manifest-source-emit.test.js',
206
+ 'lib/utils/manifest-source-emit.test.js',
207
+ 'manifest-source-emit\\.test\\.js',
168
208
  '/tests/lib/utils/secrets-ancestor-paths.test.js',
169
209
  '\\\\tests\\\\lib\\\\utils\\\\secrets-ancestor-paths.test.js',
170
210
  'lib/utils/secrets-ancestor-paths.test.js',
@@ -263,6 +303,8 @@ const isolatedProjects = [
263
303
  makeIsolatedProject('paths-system-builder-resolution', [
264
304
  '**/tests/lib/utils/paths-system-builder-resolution.test.js'
265
305
  ]),
306
+ makeIsolatedProject('manifest-location', ['**/tests/lib/utils/manifest-location.test.js']),
307
+ makeIsolatedProject('installation-log', ['**/tests/lib/utils/installation-log.test.js']),
266
308
  makeIsolatedProject('secrets-ancestor-paths', ['**/tests/lib/utils/secrets-ancestor-paths.test.js']),
267
309
  makeIsolatedProject('datasource-validation-watch', [
268
310
  '**/tests/lib/utils/datasource-validation-watch.test.js'
@@ -272,6 +314,7 @@ const isolatedProjects = [
272
314
  '**/tests/lib/datasource/log-viewer-structural.test.js',
273
315
  '**/tests/lib/datasource/log-viewer-run.test.js'
274
316
  ]),
317
+ makeIsolatedProject('manifest-source-emit', ['**/tests/lib/utils/manifest-source-emit.test.js']),
275
318
  makeIsolatedProject('register-aifabrix-shell-env', [
276
319
  '**/tests/lib/utils/register-aifabrix-shell-env.test.js'
277
320
  ]),
@@ -314,6 +357,19 @@ const isolatedProjects = [
314
357
  makeIsolatedProject('url-declarative-vdir-inactive-env', [
315
358
  '**/tests/lib/utils/url-declarative-vdir-inactive-env.test.js'
316
359
  ]),
360
+ makeIsolatedProject('url-declarative-user-cfg-per-app-proxy', [
361
+ '**/tests/lib/utils/url-declarative-user-cfg-per-app-proxy.test.js'
362
+ ]),
363
+ makeIsolatedProject('declarative-url-paths-spy-suites', [
364
+ '**/tests/lib/utils/url-declarative-expand-traefik-off-no-usercfg.test.js',
365
+ '**/tests/lib/core/secrets-env-declarative-show-urls.test.js',
366
+ '**/tests/lib/utils/url-declarative-registry-internal-docker-origin.test.js'
367
+ ]),
368
+ makeIsolatedProject('platform-urls-registry-validation', [
369
+ '**/tests/lib/commands/platform-urls-registry.validation.test.js'
370
+ ]),
371
+ makeIsolatedProject('env-copy-resolve-output', ['**/tests/lib/utils/env-copy-resolve-output.test.js']),
372
+ makeIsolatedProject('write-env-output-reload', ['**/tests/lib/utils/write-env-output-reload.test.js']),
317
373
  makeIsolatedProject('app-service-env-from-builder', [
318
374
  '**/tests/lib/utils/app-service-env-from-builder.test.js'
319
375
  ]),
@@ -14,7 +14,7 @@ const path = require('path');
14
14
  const chalk = require('chalk');
15
15
  const { validateTemplate, copyTemplateFiles, copyAppFiles } = require('../validation/template');
16
16
  const logger = require('../utils/logger');
17
- const { getIntegrationPath, getBuilderPath } = require('../utils/paths');
17
+ const paths = require('../utils/paths');
18
18
 
19
19
  /**
20
20
  * Validates that no app or external system with this name exists in integration/ or builder/.
@@ -25,8 +25,8 @@ const { getIntegrationPath, getBuilderPath } = require('../utils/paths');
25
25
  * @throws {Error} If integration/<systemKey> or builder/<appKey> already exists
26
26
  */
27
27
  async function validateAppOrExternalNameNotExists(appName) {
28
- const integrationPath = getIntegrationPath(appName);
29
- const builderPath = getBuilderPath(appName);
28
+ const integrationPath = paths.getIntegrationPath(appName);
29
+ const builderPath = paths.getBuilderPath(appName);
30
30
  try {
31
31
  await fs.access(integrationPath);
32
32
  throw new Error(
package/lib/app/index.js CHANGED
@@ -21,7 +21,7 @@ const { loadTemplateVariables, updateTemplateVariables, mergeTemplateVariables }
21
21
  const { validateTemplate } = require('../validation/template');
22
22
  const auditLogger = require('../core/audit-logger');
23
23
  const { downApp } = require('./down');
24
- const { getAppPath } = require('../utils/paths');
24
+ const paths = require('../utils/paths');
25
25
  const { displaySuccessMessage } = require('./display');
26
26
  const {
27
27
  validateAppDirectoryNotExists,
@@ -93,7 +93,7 @@ function validateAppNameAndSetup(appName, options) {
93
93
 
94
94
  const initialType = options.type || 'external';
95
95
  const baseDir = getBaseDirForAppType(initialType);
96
- const appPath = getAppPath(appName, initialType);
96
+ const appPath = paths.getAppPath(appName, initialType);
97
97
 
98
98
  return { initialType, baseDir, appPath };
99
99
  }
@@ -124,7 +124,7 @@ async function handleTemplateSetup(options) {
124
124
  */
125
125
  async function prepareFinalAppPath(appName, config, initialAppPath) {
126
126
  const finalBaseDir = getBaseDirForAppType(config.type);
127
- const finalAppPath = getAppPath(appName, config.type);
127
+ const finalAppPath = paths.getAppPath(appName, config.type);
128
128
 
129
129
  // If path changed, validate the new path
130
130
  if (finalAppPath !== initialAppPath) {
@@ -94,19 +94,20 @@ async function saveLocalCredentials(responseData, apiUrl) {
94
94
  await saveLocalSecret(clientIdKey, responseData.credentials.clientId);
95
95
  await saveLocalSecret(clientSecretKey, responseData.credentials.clientSecret);
96
96
 
97
- // Update env.template
97
+ // Update env.template (kv:// refs only; no resolved values touch disk)
98
98
  await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, apiUrl);
99
99
 
100
- // Regenerate .env file with updated credentials
100
+ // Resolve in-memory so any missing-secret / kv:// error surfaces here, but never
101
+ // materialize <appPath>/.env or envOutputPath — that is only done by `aifabrix resolve`.
101
102
  try {
102
- await generateEnvFile(registeredAppKey, null, 'local', true);
103
- logger.log(formatSuccessLine('.env file updated with new credentials'));
103
+ await generateEnvFile(registeredAppKey, null, 'local', true, { noWrite: true });
104
104
  } catch (error) {
105
- logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
105
+ logger.warn(chalk.yellow(`⚠ Could not validate env resolution: ${error.message}`));
106
106
  }
107
107
 
108
108
  logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
109
- logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
109
+ logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL'));
110
+ logger.log(formatSuccessLine('Run "aifabrix resolve ' + registeredAppKey + '" to materialize an on-disk .env\n'));
110
111
  } catch (error) {
111
112
  logger.warn(chalk.yellow(`⚠ Could not save credentials locally: ${error.message}`));
112
113
  }
@@ -13,6 +13,7 @@ const config = require('../core/config');
13
13
  const { execWithDockerEnv } = require('../utils/docker-exec');
14
14
  const { sectionTitle, headerKeyValue, metadata } = require('../utils/cli-test-layout-chalk');
15
15
  const { isReloadBindMountOnEngineHost } = require('../utils/docker-reload-mount');
16
+ const { isApplicationsReloadDefaultOn } = require('../utils/applications-config-defaults');
16
17
 
17
18
  /**
18
19
  * @param {unknown} mounts - docker inspect .Mounts
@@ -54,42 +55,72 @@ async function fetchContainerMountsJson(containerName) {
54
55
  }
55
56
  }
56
57
 
58
+ /**
59
+ * Log saved reload default for the app (same source as `aifabrix run` dev persist / `aifabrix show`).
60
+ * @param {string|null|undefined} appName - Application key (e.g. miso-controller)
61
+ * @returns {Promise<void>}
62
+ */
63
+ async function logReloadConfigSummaryForRestart(appName) {
64
+ if (!appName || typeof appName !== 'string') {
65
+ return;
66
+ }
67
+ const userCfg = await config.getConfig();
68
+ const reloadOn = isApplicationsReloadDefaultOn(userCfg, appName);
69
+ logger.log('');
70
+ logger.log(sectionTitle('Reload (config)'));
71
+ logger.log(
72
+ headerKeyValue(
73
+ 'Next dev run:',
74
+ reloadOn ? 'reload on (applications.<app>.reload in config)' : 'reload off'
75
+ )
76
+ );
77
+ logger.log(
78
+ metadata(
79
+ 'Persisted when you last ran this app in dev with or without --reload. Use aifabrix show <app> to inspect.'
80
+ )
81
+ );
82
+ logger.log('');
83
+ }
84
+
57
85
  /**
58
86
  * Log workspace transport after a successful container restart (mounts unchanged).
59
87
  * @param {string} containerName - Docker container name
88
+ * @param {string|null} [appName] - Application key; when set, logs reload default from config after mount info
60
89
  * @returns {Promise<void>}
61
90
  */
62
- async function logRestartDevMountSummary(containerName) {
91
+ async function logRestartDevMountSummary(containerName, appName = null) {
63
92
  if (!containerName || typeof containerName !== 'string') {
93
+ await logReloadConfigSummaryForRestart(appName);
64
94
  return;
65
95
  }
66
96
  const mounts = await fetchContainerMountsJson(containerName);
67
97
  const appBind = findAppBindMount(mounts);
68
- if (!appBind) {
69
- return;
70
- }
71
- const endpoint = await config.getDockerEndpoint();
72
- const localEngine = isReloadBindMountOnEngineHost(endpoint);
98
+ if (appBind) {
99
+ const endpoint = await config.getDockerEndpoint();
100
+ const localEngine = isReloadBindMountOnEngineHost(endpoint);
73
101
 
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`));
102
+ logger.log('');
103
+ logger.log(sectionTitle('Dev workspace (unchanged by restart)'));
104
+ if (localEngine) {
105
+ logger.log(headerKeyValue('Transport:', 'Direct bind mount on the Docker host (no Mutagen).'));
106
+ logger.log(headerKeyValue('Host path → container:', `${appBind.Source} → /app`));
107
+ logger.log(metadata('Edits under the host path are visible inside the container immediately.'));
108
+ } else {
109
+ logger.log(
110
+ headerKeyValue(
111
+ 'Transport:',
112
+ 'Bind mount on the Docker engine (see path below; Mutagen may sync to this path when using --reload).'
113
+ )
114
+ );
115
+ logger.log(headerKeyValue('Engine path → container:', `${appBind.Source} → /app`));
116
+ }
117
+ logger.log('');
88
118
  }
89
- logger.log('');
119
+ await logReloadConfigSummaryForRestart(appName);
90
120
  }
91
121
 
92
122
  module.exports = {
93
123
  findAppBindMount,
124
+ logReloadConfigSummaryForRestart,
94
125
  logRestartDevMountSummary
95
126
  };
@@ -281,20 +281,21 @@ async function saveCredentialsLocally(appKey, credentials, actualControllerUrl)
281
281
  await saveLocalSecret(clientIdKey, credentials.clientId);
282
282
  await saveLocalSecret(clientSecretKey, credentials.clientSecret);
283
283
 
284
- // Update env.template if localhost
284
+ // Update env.template if localhost (kv:// refs only; no resolved values touch disk)
285
285
  if (isLocalhost(actualControllerUrl)) {
286
286
  await updateEnvTemplate(appKey, clientIdKey, clientSecretKey, actualControllerUrl);
287
287
 
288
- // Regenerate .env file with updated credentials
288
+ // Resolve in-memory so any missing-secret / kv:// error surfaces here, but never
289
+ // materialize <appPath>/.env or envOutputPath — that is only done by `aifabrix resolve`.
289
290
  try {
290
- await generateEnvFile(appKey, null, 'local', true);
291
- logger.log(formatSuccessLine('.env file updated with new credentials'));
291
+ await generateEnvFile(appKey, null, 'local', true, { noWrite: true });
292
292
  } catch (error) {
293
- logger.warn(chalk.yellow(`⚠ Could not regenerate .env file: ${error.message}`));
293
+ logger.warn(chalk.yellow(`⚠ Could not validate env resolution: ${error.message}`));
294
294
  }
295
295
 
296
296
  logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
297
- logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
297
+ logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL'));
298
+ logger.log(formatSuccessLine('Run "aifabrix resolve ' + appKey + '" to materialize an on-disk .env\n'));
298
299
  } else {
299
300
  logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml\n'));
300
301
  }
@@ -38,6 +38,7 @@ const { resolveEnvOutputPath, writeEnvOutputForReload, writeEnvOutputForLocal }
38
38
  const { resolveVersionForApp } = require('../utils/image-version');
39
39
  const healthCheckUtil = require('../utils/health-check');
40
40
  const { computeTraefikPublicAppUrl } = require('../utils/health-check-url');
41
+ const { isFrontDoorRoutingEnabledInDoc } = require('../utils/url-declarative-vdir-inactive-env');
41
42
 
42
43
  /** Template apps (keycloak, miso-controller, dataplane) - never update application config when running */
43
44
  const TEMPLATE_APP_KEYS = ['keycloak', 'miso-controller', 'dataplane'];
@@ -72,7 +73,7 @@ function checkBuilderDirectory(appName) {
72
73
 
73
74
  /**
74
75
  * Load and validate config file exists
75
- * Uses paths.getBuilderPath so AIFABRIX_BUILDER_DIR (e.g. from up-miso) is respected.
76
+ * Uses paths.getBuilderPath (cwd / material `builder/` only; no `AIFABRIX_BUILDER_DIR` override).
76
77
  * @param {string} appName - Application name
77
78
  * @returns {Object} Application configuration
78
79
  * @throws {Error} If config file not found
@@ -299,12 +300,15 @@ async function generateComposeFile(appName, appConfig, composeOptions, devDir) {
299
300
  }
300
301
 
301
302
  /**
302
- * Writes .env to envOutputPath when application.yaml build.envOutputPath is set.
303
+ * Writes `build.envOutputPath` after each `aifabrix run`: **local**-flavored env for the host/IDE
304
+ * when not using `--reload`; with **`--reload`**, merges container `.env.run` into the existing file
305
+ * (preserves resolve comments) without appending run-only keys missing from the template.
306
+ *
303
307
  * @async
304
308
  * @param {string} appName - Application name
305
309
  * @param {Object} appConfig - Application configuration
306
- * @param {string} runEnvPath - Path to .env.run
307
- * @param {Object} options - Run options (reload flag)
310
+ * @param {string} runEnvPath - Path to `.env.run`
311
+ * @param {Object} options - Run options (`reload`, `skipEnvOutputPath`)
308
312
  */
309
313
  async function writeEnvOutputIfConfigured(appName, appConfig, runEnvPath, options) {
310
314
  if (options && options.skipEnvOutputPath === true) return;
@@ -318,8 +322,8 @@ async function writeEnvOutputIfConfigured(appName, appConfig, runEnvPath, option
318
322
  if (!fsSync.existsSync(outputDir)) {
319
323
  await fs.mkdir(outputDir, { recursive: true });
320
324
  }
321
- if (options.reload) {
322
- await writeEnvOutputForReload(outputPath, runEnvPath);
325
+ if (options && options.reload === true) {
326
+ await writeEnvOutputForReload(outputPath, runEnvPath, appName);
323
327
  } else {
324
328
  await writeEnvOutputForLocal(appName, outputPath);
325
329
  }
@@ -362,7 +366,8 @@ async function prepareEnvironment(appName, appConfig, options) {
362
366
  envFilePath: runEnvPath,
363
367
  dbInitEnvFilePath: runEnvAdminPath,
364
368
  effectiveEnvironmentScopedResources,
365
- env: runEnvKey
369
+ env: runEnvKey,
370
+ omitAppTraefikLabels: userCfg.traefik === false
366
371
  };
367
372
  composeOptions.port = calculateComposePort(composeOptions, appConfig, developerId);
368
373
  const composePath = await generateComposeFile(appName, appConfig, composeOptions, devDir);
@@ -383,7 +388,9 @@ async function prepareEnvironment(appName, appConfig, options) {
383
388
  async function displayRunStatus(appName, port, appConfig, runScopeOpts = null, runOptions = {}) {
384
389
  const containerName = containerHelpers.getContainerName(appName, appConfig.developerId, runScopeOpts);
385
390
  const ro = runOptions && typeof runOptions === 'object' ? runOptions : {};
386
- const wantsPublic = Boolean(ro.probeViaTraefik === true || ro.traefikEnabled === true);
391
+ const frontDoorOn = isFrontDoorRoutingEnabledInDoc(appConfig);
392
+ const wantsPublic =
393
+ frontDoorOn && Boolean(ro.probeViaTraefik === true || ro.traefikEnabled === true);
387
394
 
388
395
  let primaryAppUrl = `http://localhost:${port}`;
389
396
  if (wantsPublic) {
package/lib/app/run.js CHANGED
@@ -28,6 +28,7 @@ const helpers = require('./run-helpers');
28
28
  const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
29
29
  const { logRestartDevMountSummary } = require('./restart-display');
30
30
  const { execWithDockerEnv } = require('../utils/docker-exec');
31
+ const { isRunCliNoProxy } = require('../utils/run-cli-flags');
31
32
 
32
33
  /**
33
34
  * True if host is localhost or 127.0.0.1 (case-insensitive).
@@ -68,7 +69,7 @@ function isLocalhostEndpoint(urlOrEndpoint) {
68
69
  * @async
69
70
  * @param {string} appName - Application name
70
71
  * @param {boolean} _debug - Debug flag (unused)
71
- * @returns {Promise<boolean>} True if should continue, false if external system
72
+ * @returns {Promise<boolean>} True if run should continue, false if app is integration-scoped or external (not a runnable container app)
72
73
  * @throws {Error} If app name is invalid
73
74
  */
74
75
  async function validateAppForRun(appName, _debug) {
@@ -88,9 +89,16 @@ async function validateAppForRun(appName, _debug) {
88
89
  formatWarningLine('External integrations are not started as local Docker containers.')
89
90
  );
90
91
  logger.log(
91
- metadata('Build and deploy the integration, then use its APIs from your environment.')
92
+ metadata(
93
+ 'Publish config to the dataplane (upload) or through the controller (deploy), then use the integration APIs from your environment.'
94
+ )
95
+ );
96
+ logger.log(
97
+ formatNextActions([
98
+ `aifabrix upload ${appName}`,
99
+ `aifabrix deploy ${appName}`
100
+ ])
92
101
  );
93
- logger.log(formatNextActions([`aifabrix build ${appName}`]));
94
102
  return false;
95
103
  }
96
104
  } catch (error) {
@@ -263,12 +271,12 @@ async function resolveRunOptions(appName, appConfig, options, envKey, debug, eff
263
271
  }
264
272
 
265
273
  /**
266
- * When Traefik is enabled in user config, pass through for health checks (DNS + localhost order).
274
+ * When Traefik is enabled in user config and this run should use proxy hints (`runOptions.noProxy` is false), enable Traefik/DNS URL hints.
267
275
  * @param {Object} runOptions
268
276
  * @param {Object} userCfg
269
277
  */
270
278
  function applyTraefikFlagToRunOptions(runOptions, userCfg) {
271
- if (userCfg && userCfg.traefik === true) {
279
+ if (userCfg && userCfg.traefik === true && !runOptions.noProxy) {
272
280
  runOptions.traefikEnabled = true;
273
281
  }
274
282
  }
@@ -335,10 +343,28 @@ async function computeRunPreparationCore(appName, appConfig, options, envKey, us
335
343
  return result;
336
344
  }
337
345
 
346
+ /**
347
+ * Gray **Manifest:** line before container start (skipped when orchestration already emitted).
348
+ * @param {string} appName
349
+ * @param {Object} options
350
+ * @returns {void}
351
+ */
352
+ function emitRunManifestMetadataIfRequested(appName, options) {
353
+ if (options.skipManifestMetadataLine === true) return;
354
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
355
+ emitManifestMetadataLineIfTTY(logger, {
356
+ appKey: appName,
357
+ appPath: pathsUtil.getBuilderPath(appName),
358
+ envOnly: false,
359
+ json: false
360
+ });
361
+ }
362
+
338
363
  /**
339
364
  * Prepare run: validate app, load config, check prereqs, stop existing container, resolve port and reload, prepare env.
340
365
  * @param {string} appName - Application name
341
366
  * @param {Object} options - Run options
367
+ * @param {boolean} [options.skipManifestMetadataLine] - When true, skip gray **Manifest:** line (caller already emitted for platform orchestration)
342
368
  * @param {boolean} debug - Debug flag
343
369
  * @returns {Promise<{ appConfig: Object, tempComposePath: string, hostPort: number }|null>} Prepared run context or null if should not continue
344
370
  */
@@ -349,12 +375,25 @@ async function prepareAppRun(appName, options, debug) {
349
375
  if (!shouldContinue) {
350
376
  return null;
351
377
  }
378
+ let userCfg = await config.getConfig();
379
+ const appsDefaults = require('../utils/applications-config-defaults');
380
+ await appsDefaults.persistApplicationRunProxyFlag(appName, !isRunCliNoProxy(options));
381
+ if (envKey === 'dev') {
382
+ await appsDefaults.persistApplicationReloadFlag(appName, options.reload === true);
383
+ }
384
+ userCfg = await config.getConfig();
385
+ const savedProxyHint = appsDefaults.getApplicationsRunProxyHint(userCfg, appName);
386
+ const mergedOpts = {
387
+ ...options,
388
+ reload: options.reload === true,
389
+ noProxy: isRunCliNoProxy(options) || !savedProxyHint
390
+ };
352
391
  logger.log('');
353
392
  logger.log(sectionTitle('Run'));
354
393
  logger.log(headerKeyValue('Application:', appName));
355
394
  const appConfig = await loadAndConfigureApp(appName, debug);
356
- const userCfg = await config.getConfig();
357
- return computeRunPreparationCore(appName, appConfig, options, envKey, userCfg, debug);
395
+ emitRunManifestMetadataIfRequested(appName, options);
396
+ return computeRunPreparationCore(appName, appConfig, mergedOpts, envKey, userCfg, debug);
358
397
  }
359
398
 
360
399
  /**
@@ -367,6 +406,7 @@ async function prepareAppRun(appName, options, debug) {
367
406
  * @param {Object} options - Run options
368
407
  * @param {number} [options.port] - Override local port
369
408
  * @param {boolean} [options.debug] - Enable debug output
409
+ * @param {boolean} [options.skipManifestMetadataLine] - When true, skip gray **Manifest:** line in prepare (orchestration already emitted)
370
410
  * @returns {Promise<void>} Resolves when app is running
371
411
  * @throws {Error} If run fails or app is not built
372
412
  *
@@ -388,7 +428,7 @@ async function runApp(appName, options = {}) {
388
428
  if (debug) {
389
429
  logger.log(chalk.gray(`[DEBUG] Compose file generated: ${prepared.tempComposePath}`));
390
430
  }
391
- logReloadDevSummary(Boolean(options.reload), prepared.mergedRunOptions.reloadSyncSummary);
431
+ logReloadDevSummary(Boolean(prepared.mergedRunOptions.reload), prepared.mergedRunOptions.reloadSyncSummary);
392
432
  await startAppContainer(appName, prepared.tempComposePath, prepared.hostPort, prepared.appConfig, {
393
433
  debug,
394
434
  runEnvPath: prepared.runEnvPath,
@@ -422,11 +462,18 @@ async function restartApp(appName) {
422
462
  if (!appName || typeof appName !== 'string') {
423
463
  throw new Error('Application name is required and must be a string');
424
464
  }
465
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
466
+ emitManifestMetadataLineIfTTY(logger, {
467
+ appKey: appName,
468
+ appPath: pathsUtil.getBuilderPath(appName),
469
+ envOnly: false,
470
+ json: false
471
+ });
425
472
  const developerId = await config.getDeveloperId();
426
473
  const containerName = containerHelpers.getContainerName(appName, developerId);
427
474
  try {
428
475
  await execWithDockerEnv(`docker restart ${containerName}`);
429
- await logRestartDevMountSummary(containerName);
476
+ await logRestartDevMountSummary(containerName, appName);
430
477
  } catch (error) {
431
478
  const msg = (error.stderr || error.stdout || error.message || '').toLowerCase();
432
479
  if (msg.includes('no such container') || msg.includes('is not running')) {
@@ -439,6 +486,7 @@ async function restartApp(appName) {
439
486
  module.exports = {
440
487
  runApp,
441
488
  restartApp,
489
+ validateAppForRun,
442
490
  ensureReloadSync,
443
491
  isLocalhostHost,
444
492
  isLocalhostEndpoint,
@@ -96,6 +96,13 @@ function logApplicationSection(a, summary) {
96
96
  } else {
97
97
  logApplicationFields(a);
98
98
  }
99
+ if (summary.runReloadDefault && !summary.isExternal) {
100
+ logger.log(` ${'Run default:'.padEnd(16)} reload on (config)`);
101
+ }
102
+ if (!summary.isExternal) {
103
+ const proxyOn = summary.runProxyDefault === true;
104
+ logger.log(` ${'Run default:'.padEnd(16)} proxy ${proxyOn ? 'on' : 'off'} (config)`);
105
+ }
99
106
  /* External integration is logged after Dataplane block in display() when external */
100
107
  }
101
108