@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.
Files changed (207) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +48 -2
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/validation-runner.js +46 -25
  17. package/lib/app/deploy-config.js +11 -1
  18. package/lib/app/deploy-status-display.js +3 -3
  19. package/lib/app/deploy.js +36 -14
  20. package/lib/app/display.js +15 -11
  21. package/lib/app/push.js +46 -23
  22. package/lib/app/register.js +1 -1
  23. package/lib/app/restart-display.js +95 -0
  24. package/lib/app/rotate-secret.js +1 -1
  25. package/lib/app/run-container-start.js +12 -6
  26. package/lib/app/run-env-compose.js +30 -1
  27. package/lib/app/run-helpers.js +44 -12
  28. package/lib/app/run-reload-sync.js +148 -0
  29. package/lib/app/run-resolve-image.js +51 -1
  30. package/lib/app/run.js +99 -73
  31. package/lib/build/index.js +75 -45
  32. package/lib/cli/doctor-check.js +117 -0
  33. package/lib/cli/index.js +8 -2
  34. package/lib/cli/infra-guided.js +445 -0
  35. package/lib/cli/setup-app.js +20 -2
  36. package/lib/cli/setup-auth.js +26 -0
  37. package/lib/cli/setup-dev-path-commands.js +50 -3
  38. package/lib/cli/setup-infra.js +134 -61
  39. package/lib/cli/setup-integration-client.js +182 -0
  40. package/lib/cli/setup-parameters.js +21 -2
  41. package/lib/cli/setup-platform.js +102 -0
  42. package/lib/cli/setup-secrets.js +18 -6
  43. package/lib/cli/setup-utility.js +78 -33
  44. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  45. package/lib/commands/datasource-capability-output.js +29 -0
  46. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  47. package/lib/commands/datasource-capability.js +411 -0
  48. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  49. package/lib/commands/datasource.js +53 -13
  50. package/lib/commands/dev-down.js +3 -3
  51. package/lib/commands/dev-infra-gate.js +32 -0
  52. package/lib/commands/dev-init.js +13 -7
  53. package/lib/commands/dimension-value.js +179 -0
  54. package/lib/commands/dimension.js +330 -0
  55. package/lib/commands/integration-client.js +430 -0
  56. package/lib/commands/login-device.js +65 -30
  57. package/lib/commands/login.js +21 -10
  58. package/lib/commands/parameters-validate.js +78 -13
  59. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  60. package/lib/commands/repair-datasource-keys.js +10 -5
  61. package/lib/commands/repair-datasource.js +19 -7
  62. package/lib/commands/repair-env-template.js +4 -1
  63. package/lib/commands/repair-openapi-sync.js +172 -0
  64. package/lib/commands/repair-persist.js +102 -0
  65. package/lib/commands/repair-rbac-extract.js +27 -0
  66. package/lib/commands/repair-rbac-migrate.js +186 -0
  67. package/lib/commands/repair-rbac.js +214 -31
  68. package/lib/commands/repair-system-alignment.js +246 -0
  69. package/lib/commands/repair-system-permissions.js +168 -0
  70. package/lib/commands/repair.js +120 -338
  71. package/lib/commands/secure.js +1 -1
  72. package/lib/commands/setup-modes.js +455 -0
  73. package/lib/commands/setup-prompts.js +388 -0
  74. package/lib/commands/setup.js +149 -0
  75. package/lib/commands/teardown.js +228 -0
  76. package/lib/commands/up-common.js +79 -19
  77. package/lib/commands/up-dataplane.js +33 -11
  78. package/lib/commands/up-miso.js +7 -11
  79. package/lib/commands/upload.js +109 -23
  80. package/lib/commands/wizard-core-helpers.js +14 -11
  81. package/lib/commands/wizard-core.js +6 -5
  82. package/lib/commands/wizard-dataplane.js +2 -2
  83. package/lib/commands/wizard-entity-selection.js +4 -3
  84. package/lib/commands/wizard-headless.js +2 -1
  85. package/lib/commands/wizard.js +2 -1
  86. package/lib/constants/infra-compose-service-names.js +40 -0
  87. package/lib/core/env-reader.js +16 -3
  88. package/lib/core/secrets-admin-env.js +101 -0
  89. package/lib/core/secrets-ensure-infra.js +34 -1
  90. package/lib/core/secrets-ensure.js +88 -66
  91. package/lib/core/secrets-env-content.js +432 -0
  92. package/lib/core/secrets-env-write.js +27 -1
  93. package/lib/core/secrets-load.js +248 -0
  94. package/lib/core/secrets-names.js +32 -0
  95. package/lib/core/secrets.js +17 -757
  96. package/lib/datasource/capability/basic-exposure.js +76 -0
  97. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  98. package/lib/datasource/capability/capability-key.js +34 -0
  99. package/lib/datasource/capability/capability-resolve.js +172 -0
  100. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  101. package/lib/datasource/capability/copy-operations.js +348 -0
  102. package/lib/datasource/capability/copy-test-payload.js +139 -0
  103. package/lib/datasource/capability/create-operations.js +235 -0
  104. package/lib/datasource/capability/dimension-operations.js +151 -0
  105. package/lib/datasource/capability/dimension-validate.js +219 -0
  106. package/lib/datasource/capability/json-pointer.js +31 -0
  107. package/lib/datasource/capability/reference-rewrite.js +51 -0
  108. package/lib/datasource/capability/relate-operations.js +325 -0
  109. package/lib/datasource/capability/relate-validate.js +219 -0
  110. package/lib/datasource/capability/remove-operations.js +275 -0
  111. package/lib/datasource/capability/run-capability-copy.js +152 -0
  112. package/lib/datasource/capability/run-capability-diff.js +135 -0
  113. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  114. package/lib/datasource/capability/run-capability-edit.js +377 -0
  115. package/lib/datasource/capability/run-capability-relate.js +193 -0
  116. package/lib/datasource/capability/run-capability-remove.js +105 -0
  117. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  118. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  119. package/lib/datasource/list.js +136 -23
  120. package/lib/datasource/log-viewer.js +2 -4
  121. package/lib/datasource/unified-validation-run.js +51 -16
  122. package/lib/datasource/validate.js +53 -1
  123. package/lib/deployment/deploy-poll-ui.js +60 -0
  124. package/lib/deployment/deployer-status.js +29 -3
  125. package/lib/deployment/deployer.js +48 -30
  126. package/lib/deployment/environment.js +7 -2
  127. package/lib/deployment/poll-interval.js +72 -0
  128. package/lib/deployment/push.js +11 -9
  129. package/lib/external-system/deploy.js +4 -1
  130. package/lib/external-system/download.js +61 -32
  131. package/lib/external-system/sync-deploy-manifest.js +33 -0
  132. package/lib/infrastructure/index.js +49 -19
  133. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  134. package/lib/parameters/infra-kv-discovery.js +29 -4
  135. package/lib/parameters/infra-parameter-catalog.js +6 -3
  136. package/lib/parameters/infra-parameter-validate.js +67 -19
  137. package/lib/resolvers/datasource-resolver.js +53 -0
  138. package/lib/resolvers/dimension-file.js +52 -0
  139. package/lib/resolvers/manifest-resolver.js +133 -0
  140. package/lib/schema/external-datasource.schema.json +183 -53
  141. package/lib/schema/external-system.schema.json +23 -10
  142. package/lib/schema/infra.parameter.yaml +26 -11
  143. package/lib/schema/wizard-config.schema.json +1 -1
  144. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  145. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  146. package/lib/utils/app-run-containers.js +2 -2
  147. package/lib/utils/bash-secret-env.js +59 -0
  148. package/lib/utils/cli-secrets-error-format.js +78 -0
  149. package/lib/utils/cli-test-layout-chalk.js +31 -9
  150. package/lib/utils/cli-utils.js +4 -36
  151. package/lib/utils/datasource-test-run-display.js +8 -0
  152. package/lib/utils/dev-hosts-helper.js +3 -2
  153. package/lib/utils/dev-init-ssh-merge.js +2 -1
  154. package/lib/utils/docker-build.js +17 -9
  155. package/lib/utils/docker-reload-mount.js +127 -0
  156. package/lib/utils/external-readme.js +71 -2
  157. package/lib/utils/external-system-local-test-tty.js +3 -2
  158. package/lib/utils/external-system-readiness-core.js +45 -12
  159. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  160. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  161. package/lib/utils/external-system-readiness-display.js +10 -1
  162. package/lib/utils/file-upload.js +40 -3
  163. package/lib/utils/health-check-db-init.js +107 -0
  164. package/lib/utils/health-check-public-warn.js +69 -0
  165. package/lib/utils/health-check-url.js +19 -4
  166. package/lib/utils/health-check.js +135 -105
  167. package/lib/utils/help-builder.js +5 -1
  168. package/lib/utils/image-name.js +34 -7
  169. package/lib/utils/integration-file-backup.js +74 -0
  170. package/lib/utils/mutagen-install.js +30 -3
  171. package/lib/utils/paths.js +108 -25
  172. package/lib/utils/postgres-wipe.js +212 -0
  173. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  174. package/lib/utils/remote-dev-auth.js +21 -5
  175. package/lib/utils/remote-docker-env.js +9 -1
  176. package/lib/utils/remote-secrets-loader.js +42 -3
  177. package/lib/utils/resolve-docker-image-ref.js +9 -3
  178. package/lib/utils/secrets-ancestor-paths.js +47 -0
  179. package/lib/utils/secrets-helpers.js +17 -10
  180. package/lib/utils/secrets-kv-refs.js +42 -0
  181. package/lib/utils/secrets-kv-scope.js +19 -2
  182. package/lib/utils/secrets-materialize-local.js +134 -0
  183. package/lib/utils/secrets-path.js +24 -10
  184. package/lib/utils/secrets-utils.js +2 -2
  185. package/lib/utils/system-builder-root.js +34 -0
  186. package/lib/utils/url-declarative-resolve-build.js +6 -1
  187. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  188. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  189. package/lib/utils/urls-local-registry.js +23 -12
  190. package/lib/utils/validation-poll-ui.js +81 -0
  191. package/lib/utils/validation-run-poll.js +29 -5
  192. package/lib/utils/with-muted-logger.js +53 -0
  193. package/package.json +1 -1
  194. package/templates/applications/dataplane/application.yaml +1 -1
  195. package/templates/applications/dataplane/rbac.yaml +10 -10
  196. package/templates/applications/keycloak/env.template +8 -6
  197. package/templates/applications/miso-controller/application.yaml +7 -0
  198. package/templates/applications/miso-controller/env.template +1 -1
  199. package/templates/applications/miso-controller/rbac.yaml +9 -9
  200. package/templates/external-system/README.md.hbs +83 -123
  201. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  202. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  203. package/.nyc_output/processinfo/index.json +0 -1
  204. package/lib/api/service-users.api.js +0 -150
  205. package/lib/api/types/service-users.types.js +0 -65
  206. package/lib/cli/setup-service-user.js +0 -187
  207. package/lib/commands/service-user.js +0 -429
@@ -0,0 +1,228 @@
1
+ /**
2
+ * `aifabrix teardown` — full teardown of local infra and CLI state.
3
+ *
4
+ * Performs:
5
+ * 1. `down-infra -v` (stop infra + apps, remove all Docker volumes).
6
+ * 2. Remove every entry inside `~/.aifabrix/` (or the equivalent
7
+ * `getAifabrixSystemDir()`-resolved directory) except `config.yaml`.
8
+ * This includes `secrets.local.yaml`, `admin-secrets.env`,
9
+ * auth/token files, and any `infra-dev*` directories.
10
+ *
11
+ * Confirmation is required by default; pass `--yes` / `-y` to skip.
12
+ *
13
+ * @fileoverview aifabrix teardown handler
14
+ * @author AI Fabrix Team
15
+ * @version 2.0.0
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const inquirer = require('inquirer');
23
+ const chalk = require('chalk');
24
+ const ora = require('ora');
25
+
26
+ const config = require('../core/config');
27
+ const infra = require('../infrastructure');
28
+ const pathsUtil = require('../utils/paths');
29
+ const logger = require('../utils/logger');
30
+ const { withMutedLogger } = require('../utils/with-muted-logger');
31
+ const {
32
+ formatSuccessLine,
33
+ formatSuccessParagraph,
34
+ formatProgress,
35
+ successGlyph
36
+ } = require('../utils/cli-test-layout-chalk');
37
+
38
+ /** File name kept by teardown (lowercase exact match). */
39
+ const PRESERVE_FILE = 'config.yaml';
40
+ /** Directory preserved by teardown (holds developer TLS client certs). */
41
+ const PRESERVE_CERTS_DIR = 'certs';
42
+
43
+ const PRESERVE_ENTRY_NAMES = new Set([PRESERVE_FILE, PRESERVE_CERTS_DIR]);
44
+
45
+ const SEPARATOR = '────────────────────────────────────────';
46
+
47
+ function title(text) {
48
+ return chalk.bold(text);
49
+ }
50
+
51
+ function shouldUseSpinner() {
52
+ return Boolean(process && process.stdout && process.stdout.isTTY);
53
+ }
54
+
55
+ function startSpinner(text) {
56
+ if (!shouldUseSpinner()) {
57
+ logger.log(formatProgress(text));
58
+ return null;
59
+ }
60
+ return ora({ text, spinner: 'dots' }).start();
61
+ }
62
+
63
+ function stopSpinnerSuccess(spinner, text) {
64
+ if (!spinner) {
65
+ logger.log(formatSuccessLine(text));
66
+ return;
67
+ }
68
+ spinner.succeed(text);
69
+ }
70
+
71
+ /**
72
+ * Ask the user to confirm teardown unless `assumeYes` is true.
73
+ * @async
74
+ * @param {boolean} assumeYes
75
+ * @returns {Promise<boolean>}
76
+ */
77
+ async function confirmTeardown(assumeYes) {
78
+ if (assumeYes) return true;
79
+ const { ok } = await inquirer.prompt([
80
+ {
81
+ type: 'confirm',
82
+ name: 'ok',
83
+ message:
84
+ 'This will stop all infra + apps, DELETE every Docker volume, and remove every file in ~/.aifabrix/ except config.yaml and certs/. Continue?',
85
+ default: false
86
+ }
87
+ ]);
88
+ return ok === true;
89
+ }
90
+
91
+ /**
92
+ * Remove every entry in the AI Fabrix system directory except `config.yaml`.
93
+ * Each removal is logged. Errors per entry are caught and logged so the
94
+ * teardown surfaces partial-success information instead of bailing on the
95
+ * first ENOENT/EBUSY.
96
+ *
97
+ * @returns {{ removed: string[], failed: string[] }}
98
+ */
99
+ function cleanAifabrixSystemDir() {
100
+ const dir = pathsUtil.getAifabrixSystemDir();
101
+ const removed = [];
102
+ const failed = [];
103
+ if (!fs.existsSync(dir)) {
104
+ return { removed, failed };
105
+ }
106
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
107
+ for (const entry of entries) {
108
+ if (PRESERVE_ENTRY_NAMES.has(entry.name)) continue;
109
+ const target = path.join(dir, entry.name);
110
+ try {
111
+ fs.rmSync(target, { recursive: true, force: true });
112
+ removed.push(target);
113
+ } catch (err) {
114
+ failed.push(target);
115
+ }
116
+ }
117
+ return { removed, failed };
118
+ }
119
+
120
+ /**
121
+ * Stop infra with `down-infra -v` semantics. Tolerates already-down state.
122
+ * @async
123
+ * @returns {Promise<void>}
124
+ */
125
+ async function stopInfraQuietly() {
126
+ try {
127
+ const spin = startSpinner('Stopping infrastructure (down-infra -v)...');
128
+ await withMutedLogger(async() => {
129
+ await infra.stopInfraWithVolumes();
130
+ });
131
+ stopSpinnerSuccess(spin, 'Infrastructure stopped and volumes removed');
132
+ } catch (err) {
133
+ logger.log(
134
+ chalk.yellow(
135
+ `Infrastructure already down or could not be stopped cleanly: ${err.message}`
136
+ )
137
+ );
138
+ }
139
+ }
140
+
141
+ function logFooterStart(label) {
142
+ logger.log('');
143
+ logger.log(SEPARATOR);
144
+ logger.log(title(label));
145
+ logger.log(SEPARATOR);
146
+ logger.log('');
147
+ }
148
+
149
+ function logFooterEnd() {
150
+ logger.log(SEPARATOR);
151
+ }
152
+
153
+ function logSection(label, value) {
154
+ logger.log(title(label));
155
+ logger.log(` ${value}`);
156
+ logger.log('');
157
+ }
158
+
159
+ async function buildDeveloperLabel() {
160
+ const developerId = await config.getDeveloperId();
161
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
162
+ if (!Number.isFinite(idNum)) return 'unknown';
163
+ return `dev${String(idNum).padStart(2, '0')} (id: ${idNum})`;
164
+ }
165
+
166
+ function logTeardownHeader() {
167
+ logger.log('');
168
+ logger.log(title('AI Fabrix Shutdown'));
169
+ logger.log(SEPARATOR);
170
+ logger.log('');
171
+ }
172
+
173
+ function logTeardownFooter({ devStr, removedCount, failedCount }) {
174
+ logFooterStart('Stopped');
175
+ logSection('Developer', devStr);
176
+ logSection(
177
+ 'Cleaned',
178
+ `${successGlyph()} Removed ${removedCount} item(s) (${PRESERVE_FILE} and ${PRESERVE_CERTS_DIR}/ preserved)`
179
+ );
180
+ logger.log(chalk.yellow('⚠ Volumes removed: all local data deleted'));
181
+ logger.log('');
182
+ if (failedCount > 0) {
183
+ logger.log(chalk.yellow(`⚠ Could not remove ${failedCount} item(s)`));
184
+ logger.log('');
185
+ }
186
+ logFooterEnd();
187
+ }
188
+
189
+ function cleanFilesWithSpinner() {
190
+ const cleanSpin = startSpinner('Removing installation files...');
191
+ const result = cleanAifabrixSystemDir();
192
+ stopSpinnerSuccess(cleanSpin, `Removed ${result.removed.length} installation item(s)`);
193
+ return result;
194
+ }
195
+
196
+ /**
197
+ * Run the teardown.
198
+ *
199
+ * @async
200
+ * @function handleTeardown
201
+ * @param {Object} [options] - Commander options
202
+ * @param {boolean} [options.yes] - Skip the confirmation prompt
203
+ * @returns {Promise<void>}
204
+ */
205
+ async function handleTeardown(options = {}) {
206
+ const assumeYes = options.yes === true || options.assumeYes === true;
207
+ logTeardownHeader();
208
+
209
+ const ok = await confirmTeardown(assumeYes);
210
+ if (!ok) {
211
+ logger.log(chalk.yellow('Aborted by user.'));
212
+ return;
213
+ }
214
+ await stopInfraQuietly();
215
+ const { removed, failed } = cleanFilesWithSpinner();
216
+ const devStr = await buildDeveloperLabel();
217
+ logTeardownFooter({ devStr, removedCount: removed.length, failedCount: failed.length });
218
+
219
+ logger.log(formatSuccessParagraph('aifabrix teardown complete.'));
220
+ }
221
+
222
+ module.exports = {
223
+ handleTeardown,
224
+ cleanAifabrixSystemDir,
225
+ stopInfraQuietly,
226
+ PRESERVE_FILE,
227
+ PRESERVE_CERTS_DIR
228
+ };
@@ -20,6 +20,7 @@ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
20
20
  const { isYamlPath } = require('../utils/config-format');
21
21
  const { copyTemplateFiles } = require('../validation/template');
22
22
  const { ensureReadmeForAppPath, ensureReadmeForApp } = require('../app/readme');
23
+ const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-registry');
23
24
 
24
25
  /**
25
26
  * Copy template to a target path if application config is missing there.
@@ -98,8 +99,9 @@ function patchEnvOutputPathInFile(configPath) {
98
99
  function validateEnvOutputPathFolderOrNull(appName) {
99
100
  if (!appName || typeof appName !== 'string') return;
100
101
  const pathsToPatch = [pathsUtil.getBuilderPath(appName)];
102
+ const envBuilderRoot = process.env.AIFABRIX_BUILDER_DIR && String(process.env.AIFABRIX_BUILDER_DIR).trim();
101
103
  const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
102
- if (path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
104
+ if (envBuilderRoot && path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
103
105
  pathsToPatch.push(cwdBuilderPath);
104
106
  }
105
107
  for (const appPath of pathsToPatch) {
@@ -176,49 +178,83 @@ function patchEnvOutputPathForDeployOnly(appName) {
176
178
  *
177
179
  * @returns {Promise<void>}
178
180
  */
179
- async function applyUpPlatformForceConfig() {
181
+ async function applyUpPlatformForceConfig(opts = {}) {
182
+ const silent = Boolean(opts.silent);
180
183
  const deviceCleared = await config.clearAllDeviceTokens();
181
184
  const clientCleared = await config.clearAllClientTokens();
182
185
  await config.setCurrentEnvironment('dev');
183
186
  const defaultControllerUrl = await getDefaultControllerUrl();
184
187
  await config.setControllerUrl(defaultControllerUrl);
185
- logger.log(
186
- chalk.blue(
187
- `--force: cleared ${deviceCleared} device token(s) and ${clientCleared} client token(s); ` +
188
- `environment set to dev; default controller set to ${defaultControllerUrl} (run aifabrix login after platform is up)`
189
- )
190
- );
188
+
189
+ const summary = { deviceCleared, clientCleared, defaultControllerUrl, environment: 'dev' };
190
+ if (!silent) {
191
+ logger.log(
192
+ chalk.blue(
193
+ `--force: cleared ${deviceCleared} device token(s) and ${clientCleared} client token(s); ` +
194
+ `environment set to dev; default controller set to ${defaultControllerUrl} (run aifabrix login after platform is up)`
195
+ )
196
+ );
197
+ }
198
+ return summary;
199
+ }
200
+
201
+ /**
202
+ * True when resolved path is the root itself or a subdirectory of an allowed builder root.
203
+ * @param {string} resolvedAppPath
204
+ * @param {string[]} allowedRoots - Absolute resolved directory roots
205
+ * @returns {boolean}
206
+ */
207
+ function isUnderAllowedBuilderRoot(resolvedAppPath, allowedRoots) {
208
+ for (const root of allowedRoots) {
209
+ const r = path.resolve(root);
210
+ if (resolvedAppPath === r || resolvedAppPath.startsWith(r + path.sep)) {
211
+ return true;
212
+ }
213
+ }
214
+ return false;
191
215
  }
192
216
 
193
217
  /**
194
- * Removes builder app directories for the given app names. Only removes paths under the builder root
195
- * to prevent path traversal. Uses getBuilderPath for each app and validates before removal.
218
+ * Removes builder app directories for the given app names. Only removes paths under an allowed
219
+ * builder root (project `builder/` or `~/.aifabrix/builder/`) to prevent path traversal.
220
+ * Uses {@link pathsUtil.getBuilderPath} for each app (system apps may resolve under the config dir).
196
221
  *
197
222
  * @param {string[]} appNames - Application names (e.g. ['keycloak', 'miso-controller', 'dataplane'])
198
223
  * @returns {Promise<void>}
199
- * @throws {Error} If any path is outside builder root (path traversal attempt)
224
+ * @throws {Error} If any path is outside allowed builder roots (path traversal attempt)
200
225
  */
201
- async function cleanBuilderAppDirs(appNames) {
226
+ async function cleanBuilderAppDirs(appNames, opts = {}) {
227
+ const silent = Boolean(opts.silent);
202
228
  if (!Array.isArray(appNames) || appNames.length === 0) return;
203
- const builderRoot = path.resolve(pathsUtil.getBuilderRoot());
229
+ const allowedRoots = [path.resolve(pathsUtil.getBuilderRoot()), path.resolve(pathsUtil.getSystemBuilderRoot())];
230
+ const cleaned = [];
204
231
  for (const appName of appNames) {
205
232
  if (!appName || typeof appName !== 'string') continue;
206
233
  const appPath = path.resolve(pathsUtil.getBuilderPath(appName));
207
- if (!appPath.startsWith(builderRoot + path.sep) && appPath !== builderRoot) {
208
- throw new Error(`Path ${appPath} is outside builder root ${builderRoot}; refusing to clean`);
234
+ if (!isUnderAllowedBuilderRoot(appPath, allowedRoots)) {
235
+ throw new Error(
236
+ `Path ${appPath} is outside allowed builder roots (${allowedRoots.join(', ')}); refusing to clean`
237
+ );
209
238
  }
210
239
  if (fs.existsSync(appPath)) {
211
240
  fs.rmSync(appPath, { recursive: true });
212
- logger.log(chalk.blue(`Cleaned builder/${appName}`));
241
+ cleaned.push(appName);
242
+ if (!silent) {
243
+ logger.log(chalk.blue(`Cleaned builder/${appName}`));
244
+ }
213
245
  }
214
246
  }
247
+ return cleaned;
215
248
  }
216
249
 
217
250
  /**
218
251
  * Ensures builder app directory exists from template if application config is missing.
219
252
  * If builder/<appName>/application config does not exist, copies from templates/applications/<appName>.
220
253
  * Uses AIFABRIX_BUILDER_DIR when set (e.g. by up-miso/up-dataplane from config aifabrix-env-config).
221
- * When using a custom builder dir, also populates cwd/builder/<appName> so the repo's builder/ is not empty.
254
+ * When `process.env.AIFABRIX_BUILDER_DIR` is set and the primary app path differs from
255
+ * `cwd/builder/<appName>`, also copies into `cwd/builder/<appName>` so the repo tree is not empty
256
+ * while using a custom builder root. Skips that extra copy when no custom dir is in use (e.g.
257
+ * platform apps materialized only under the system builder root).
222
258
  *
223
259
  * @async
224
260
  * @function ensureAppFromTemplate
@@ -241,8 +277,12 @@ async function ensureAppFromTemplate(appName) {
241
277
  logger.log(formatSuccessLine(`Copied template for ${appName}`));
242
278
  }
243
279
 
280
+ const envBuilderRoot = process.env.AIFABRIX_BUILDER_DIR && String(process.env.AIFABRIX_BUILDER_DIR).trim();
244
281
  const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
245
- if (path.resolve(cwdBuilderPath) !== path.resolve(appPath)) {
282
+ if (
283
+ envBuilderRoot &&
284
+ path.resolve(cwdBuilderPath) !== path.resolve(appPath)
285
+ ) {
246
286
  const cwdCopied = await ensureTemplateAtPath(appName, cwdBuilderPath);
247
287
  if (cwdCopied) {
248
288
  logger.log(chalk.blue(`Creating builder/${appName} in project (from template)...`));
@@ -254,11 +294,31 @@ async function ensureAppFromTemplate(appName) {
254
294
  return primaryCopied;
255
295
  }
256
296
 
297
+ /**
298
+ * For `aifabrix up-platform` only: align builder dir env with up-miso/up-dataplane, materialize all three
299
+ * platform apps from templates if missing, then refresh `~/.aifabrix/urls.local.yaml` so declarative
300
+ * `url://` expansion (e.g. cross-references between miso-controller, dataplane, keycloak) sees every
301
+ * app's port and pattern before Keycloak or Miso resolve their `.env` files.
302
+ *
303
+ * @returns {Promise<void>}
304
+ */
305
+ async function prepareUrlsLocalRegistryForUpPlatform() {
306
+ const builderDir = await config.getAifabrixBuilderDir();
307
+ if (builderDir) {
308
+ process.env.AIFABRIX_BUILDER_DIR = path.resolve(builderDir);
309
+ }
310
+ await ensureAppFromTemplate('keycloak');
311
+ await ensureAppFromTemplate('miso-controller');
312
+ await ensureAppFromTemplate('dataplane');
313
+ refreshUrlsLocalRegistryFromBuilder(pathsUtil.getProjectRoot());
314
+ }
315
+
257
316
  module.exports = {
258
317
  applyUpPlatformForceConfig,
259
318
  cleanBuilderAppDirs,
260
319
  ensureAppFromTemplate,
261
320
  patchEnvOutputPathForDeployOnly,
262
321
  validateEnvOutputPathFolderOrNull,
263
- getEnvOutputPathFolder
322
+ getEnvOutputPathFolder,
323
+ prepareUrlsLocalRegistryForUpPlatform
264
324
  };
@@ -2,10 +2,10 @@ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test
2
2
  /**
3
3
  * AI Fabrix Builder - Up Dataplane Command
4
4
  *
5
- * Always local deployment: registers or rotates dataplane app in dev, sends
6
- * deployment manifest to Miso Controller, then runs the dataplane app locally
7
- * (same as aifabrix deploy dataplane --local). If app is already
8
- * registered, uses rotate-secret; otherwise registers.
5
+ * Always local deployment: requires dev infra (same gate as up-miso), then registers or
6
+ * rotates dataplane in dev, sends deployment manifest to Miso Controller, then runs dataplane
7
+ * locally (same as aifabrix deploy dataplane --local). If app is already registered, uses
8
+ * rotate-secret; otherwise registers.
9
9
  *
10
10
  * @fileoverview up-dataplane command implementation
11
11
  * @author AI Fabrix Team
@@ -29,6 +29,7 @@ const { checkApplicationExists } = require('../utils/app-existence');
29
29
  const { checkHealthEndpoint } = require('../utils/health-check');
30
30
  const { validateControllerUrl } = require('../utils/auth-config-validator');
31
31
  const app = require('../app');
32
+ const { assertDevInfraUp } = require('./dev-infra-gate');
32
33
  const { ensureAppFromTemplate, validateEnvOutputPathFolderOrNull } = require('./up-common');
33
34
 
34
35
  const CONTROLLER_HEALTH_PATH = '/health';
@@ -130,7 +131,14 @@ async function registerOrRotateDataplane(options, controllerUrl, environmentKey,
130
131
  */
131
132
  async function deployDataplaneToController(options) {
132
133
  const imageOverride = options.image || buildDataplaneImageRef(options);
133
- const deployOpts = { imageOverride, image: imageOverride, registryMode: options.registryMode };
134
+ const deployOpts = {
135
+ imageOverride,
136
+ image: imageOverride,
137
+ registryMode: options.registryMode,
138
+ // Guided up-platform already shows a top-level spinner for the dataplane step.
139
+ // Avoid nested deploy polling spinners/logs.
140
+ silentPoll: options.platformInstall === true
141
+ };
134
142
  await app.deployApp('dataplane', deployOpts);
135
143
  }
136
144
 
@@ -160,6 +168,17 @@ function buildDataplaneImageRef(options = {}) {
160
168
  }
161
169
  }
162
170
 
171
+ /**
172
+ * Sets `AIFABRIX_BUILDER_DIR` when configured (shared by deploy/run paths).
173
+ * @returns {Promise<void>}
174
+ */
175
+ async function applyAifabrixBuilderDirEnv() {
176
+ const builderDir = await config.getAifabrixBuilderDir();
177
+ if (builderDir) {
178
+ process.env.AIFABRIX_BUILDER_DIR = path.resolve(builderDir);
179
+ }
180
+ }
181
+
163
182
  /**
164
183
  * Handle up-dataplane command: ensure logged in, environment dev, ensure dataplane,
165
184
  * register or rotate (if already registered), deploy (send manifest to controller),
@@ -171,16 +190,18 @@ function buildDataplaneImageRef(options = {}) {
171
190
  * @param {string} [options.registry] - Override registry for dataplane
172
191
  * @param {string} [options.registryMode] - Override registry mode (acr|external)
173
192
  * @param {string} [options.image] - Override image reference for dataplane
193
+ * @param {boolean} [options.skipInfraCheck] - When true, skip Postgres/Redis gate (caller already verified, e.g. guided up-dataplane).
174
194
  * @returns {Promise<void>}
175
- * @throws {Error} If not logged in, environment not dev, or any step fails
195
+ * @throws {Error} If infra not up, controller unavailable, not logged in, environment not dev, or any step fails
176
196
  */
177
197
  async function handleUpDataplane(options = {}) {
178
- const builderDir = await config.getAifabrixBuilderDir();
179
- if (builderDir) {
180
- process.env.AIFABRIX_BUILDER_DIR = path.resolve(builderDir);
181
- }
198
+ await applyAifabrixBuilderDirEnv();
182
199
  logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
183
200
 
201
+ if (options.skipInfraCheck !== true) {
202
+ await assertDevInfraUp();
203
+ }
204
+
184
205
  const controllerUrl = await resolveControllerUrlWithHealthCheck();
185
206
  const environmentKey = await resolveEnvironment();
186
207
  const authConfig = await checkAuthentication(controllerUrl, environmentKey, { throwOnFailure: true });
@@ -204,7 +225,8 @@ async function handleUpDataplane(options = {}) {
204
225
  logger.log('');
205
226
  await app.runApp('dataplane', {
206
227
  skipEnvOutputPath: true,
207
- registry: options.registry || undefined
228
+ registry: options.registry || undefined,
229
+ base: options.base !== false
208
230
  });
209
231
 
210
232
  logger.log(formatSuccessParagraph('up-dataplane complete. Dataplane is registered, deployed in dev, and running locally.'));
@@ -1,9 +1,9 @@
1
- const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
1
+ const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
2
2
  /**
3
3
  * AI Fabrix Builder - Up Miso Command
4
4
  *
5
5
  * Installs keycloak and miso-controller from images (no build). For dataplane, use up-dataplane.
6
- * Assumes infra is up; sets dev secrets and resolves (no force; existing .env values preserved).
6
+ * Ensures dev Postgres/Redis are up, then sets dev secrets and resolves (no force; existing .env values preserved).
7
7
  *
8
8
  * @fileoverview up-miso command implementation
9
9
  * @author AI Fabrix Team
@@ -14,8 +14,8 @@ const path = require('path');
14
14
  const chalk = require('chalk');
15
15
  const logger = require('../utils/logger');
16
16
  const config = require('../core/config');
17
- const infra = require('../infrastructure');
18
17
  const app = require('../app');
18
+ const { assertDevInfraUp } = require('./dev-infra-gate');
19
19
  const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly, validateEnvOutputPathFolderOrNull } = require('./up-common');
20
20
 
21
21
  /**
@@ -45,11 +45,13 @@ function parseImageOptions(imageOpts) {
45
45
  */
46
46
  async function runMisoApps(options) {
47
47
  const imageMap = parseImageOptions(options.image);
48
+ const useBaseImage = options.base !== false;
48
49
  const common = {
49
50
  registry: options.registry,
50
51
  registryMode: options.registryMode,
51
52
  skipEnvOutputPath: true,
52
- skipInfraCheck: true
53
+ skipInfraCheck: true,
54
+ base: useBaseImage
53
55
  };
54
56
  const keycloakRunOpts = { ...common };
55
57
  if (imageMap.keycloak) {
@@ -83,13 +85,7 @@ async function handleUpMiso(options = {}) {
83
85
  process.env.AIFABRIX_BUILDER_DIR = path.resolve(builderDir);
84
86
  }
85
87
  logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller from images)...\n'));
86
- // Strict: only this developer's infra (same as status), so up-miso and status agree
87
- const health = await infra.checkInfraHealth(undefined, { strict: true });
88
- const allHealthy = Object.values(health).every(status => status === 'healthy');
89
- if (!allHealthy) {
90
- throw new Error('Infrastructure is not up. Run \'aifabrix up-infra\' first.');
91
- }
92
- logger.log(formatSuccessLine('Infrastructure is up'));
88
+ await assertDevInfraUp();
93
89
  await ensureAppFromTemplate('keycloak');
94
90
  await ensureAppFromTemplate('miso-controller');
95
91
  // If envOutputPath target folder does not exist, set envOutputPath to null