@aifabrix/builder 2.44.4 → 2.44.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) 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 +68 -17
  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/types/wizard.types.js +2 -1
  17. package/lib/api/validation-runner.js +46 -25
  18. package/lib/app/deploy-config.js +11 -1
  19. package/lib/app/deploy-status-display.js +3 -3
  20. package/lib/app/deploy.js +36 -14
  21. package/lib/app/display.js +15 -11
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/restart-display.js +95 -0
  25. package/lib/app/rotate-secret.js +1 -1
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +44 -12
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +99 -73
  32. package/lib/build/index.js +75 -45
  33. package/lib/cli/doctor-check.js +117 -0
  34. package/lib/cli/index.js +8 -2
  35. package/lib/cli/infra-guided.js +445 -0
  36. package/lib/cli/setup-app.help.js +1 -1
  37. package/lib/cli/setup-app.js +20 -2
  38. package/lib/cli/setup-app.test-commands.js +9 -5
  39. package/lib/cli/setup-auth.js +26 -0
  40. package/lib/cli/setup-dev-path-commands.js +50 -3
  41. package/lib/cli/setup-infra.js +138 -61
  42. package/lib/cli/setup-integration-client.js +182 -0
  43. package/lib/cli/setup-parameters.js +21 -2
  44. package/lib/cli/setup-platform.js +102 -0
  45. package/lib/cli/setup-secrets.js +18 -6
  46. package/lib/cli/setup-utility.js +97 -33
  47. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  48. package/lib/commands/datasource-capability-output.js +29 -0
  49. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  50. package/lib/commands/datasource-capability.js +411 -0
  51. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  52. package/lib/commands/datasource.js +53 -13
  53. package/lib/commands/dev-down.js +3 -3
  54. package/lib/commands/dev-infra-gate.js +32 -0
  55. package/lib/commands/dev-init.js +13 -7
  56. package/lib/commands/dimension-value.js +179 -0
  57. package/lib/commands/dimension.js +330 -0
  58. package/lib/commands/integration-client.js +430 -0
  59. package/lib/commands/login-device.js +65 -30
  60. package/lib/commands/login.js +21 -10
  61. package/lib/commands/parameters-validate.js +78 -13
  62. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  63. package/lib/commands/repair-datasource-keys.js +10 -5
  64. package/lib/commands/repair-datasource.js +19 -7
  65. package/lib/commands/repair-env-template.js +4 -1
  66. package/lib/commands/repair-openapi-sync.js +172 -0
  67. package/lib/commands/repair-persist.js +102 -0
  68. package/lib/commands/repair-rbac-extract.js +27 -0
  69. package/lib/commands/repair-rbac-migrate.js +186 -0
  70. package/lib/commands/repair-rbac.js +225 -19
  71. package/lib/commands/repair-system-alignment.js +246 -0
  72. package/lib/commands/repair-system-permissions.js +168 -0
  73. package/lib/commands/repair.js +120 -354
  74. package/lib/commands/secure.js +1 -1
  75. package/lib/commands/setup-modes.js +455 -0
  76. package/lib/commands/setup-prompts.js +388 -0
  77. package/lib/commands/setup.js +149 -0
  78. package/lib/commands/teardown.js +228 -0
  79. package/lib/commands/test-e2e-external.js +4 -3
  80. package/lib/commands/up-common.js +97 -12
  81. package/lib/commands/up-dataplane.js +33 -11
  82. package/lib/commands/up-miso.js +7 -11
  83. package/lib/commands/upload.js +109 -23
  84. package/lib/commands/wizard-core-helpers.js +14 -11
  85. package/lib/commands/wizard-core.js +58 -15
  86. package/lib/commands/wizard-dataplane.js +2 -2
  87. package/lib/commands/wizard-entity-selection.js +72 -14
  88. package/lib/commands/wizard-headless.js +7 -3
  89. package/lib/commands/wizard-helpers.js +13 -1
  90. package/lib/commands/wizard.js +210 -61
  91. package/lib/constants/infra-compose-service-names.js +40 -0
  92. package/lib/core/env-reader.js +16 -3
  93. package/lib/core/secrets-admin-env.js +101 -0
  94. package/lib/core/secrets-ensure-infra.js +34 -1
  95. package/lib/core/secrets-ensure.js +88 -66
  96. package/lib/core/secrets-env-content.js +432 -0
  97. package/lib/core/secrets-env-write.js +27 -1
  98. package/lib/core/secrets-load.js +248 -0
  99. package/lib/core/secrets-names.js +32 -0
  100. package/lib/core/secrets.js +17 -757
  101. package/lib/datasource/capability/basic-exposure.js +76 -0
  102. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  103. package/lib/datasource/capability/capability-key.js +34 -0
  104. package/lib/datasource/capability/capability-resolve.js +172 -0
  105. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  106. package/lib/datasource/capability/copy-operations.js +348 -0
  107. package/lib/datasource/capability/copy-test-payload.js +139 -0
  108. package/lib/datasource/capability/create-operations.js +235 -0
  109. package/lib/datasource/capability/dimension-operations.js +151 -0
  110. package/lib/datasource/capability/dimension-validate.js +219 -0
  111. package/lib/datasource/capability/json-pointer.js +31 -0
  112. package/lib/datasource/capability/reference-rewrite.js +51 -0
  113. package/lib/datasource/capability/relate-operations.js +325 -0
  114. package/lib/datasource/capability/relate-validate.js +219 -0
  115. package/lib/datasource/capability/remove-operations.js +275 -0
  116. package/lib/datasource/capability/run-capability-copy.js +152 -0
  117. package/lib/datasource/capability/run-capability-diff.js +135 -0
  118. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  119. package/lib/datasource/capability/run-capability-edit.js +377 -0
  120. package/lib/datasource/capability/run-capability-relate.js +193 -0
  121. package/lib/datasource/capability/run-capability-remove.js +105 -0
  122. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  123. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  124. package/lib/datasource/list.js +136 -23
  125. package/lib/datasource/log-viewer.js +2 -4
  126. package/lib/datasource/unified-validation-run.js +51 -16
  127. package/lib/datasource/validate.js +53 -1
  128. package/lib/deployment/deploy-poll-ui.js +60 -0
  129. package/lib/deployment/deployer-status.js +29 -3
  130. package/lib/deployment/deployer.js +48 -30
  131. package/lib/deployment/environment.js +7 -2
  132. package/lib/deployment/poll-interval.js +72 -0
  133. package/lib/deployment/push.js +11 -9
  134. package/lib/external-system/deploy.js +4 -1
  135. package/lib/external-system/download.js +61 -32
  136. package/lib/external-system/sync-deploy-manifest.js +33 -0
  137. package/lib/generator/wizard-prompts.js +7 -1
  138. package/lib/generator/wizard.js +34 -0
  139. package/lib/infrastructure/index.js +49 -19
  140. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  141. package/lib/parameters/infra-kv-discovery.js +29 -4
  142. package/lib/parameters/infra-parameter-catalog.js +6 -3
  143. package/lib/parameters/infra-parameter-validate.js +67 -19
  144. package/lib/resolvers/datasource-resolver.js +53 -0
  145. package/lib/resolvers/dimension-file.js +52 -0
  146. package/lib/resolvers/manifest-resolver.js +133 -0
  147. package/lib/schema/external-datasource.schema.json +183 -53
  148. package/lib/schema/external-system.schema.json +23 -10
  149. package/lib/schema/infra.parameter.yaml +26 -11
  150. package/lib/schema/wizard-config.schema.json +2 -2
  151. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  152. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  153. package/lib/utils/app-run-containers.js +2 -2
  154. package/lib/utils/bash-secret-env.js +59 -0
  155. package/lib/utils/cli-secrets-error-format.js +78 -0
  156. package/lib/utils/cli-test-layout-chalk.js +31 -9
  157. package/lib/utils/cli-utils.js +4 -36
  158. package/lib/utils/datasource-test-run-display.js +8 -0
  159. package/lib/utils/dev-hosts-helper.js +3 -2
  160. package/lib/utils/dev-init-ssh-merge.js +2 -1
  161. package/lib/utils/docker-build.js +17 -9
  162. package/lib/utils/docker-reload-mount.js +127 -0
  163. package/lib/utils/external-readme.js +117 -4
  164. package/lib/utils/external-system-local-test-tty.js +3 -2
  165. package/lib/utils/external-system-readiness-core.js +45 -12
  166. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  167. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  168. package/lib/utils/external-system-readiness-display.js +10 -1
  169. package/lib/utils/file-upload.js +40 -3
  170. package/lib/utils/health-check-db-init.js +107 -0
  171. package/lib/utils/health-check-public-warn.js +69 -0
  172. package/lib/utils/health-check-url.js +19 -4
  173. package/lib/utils/health-check.js +135 -105
  174. package/lib/utils/help-builder.js +5 -1
  175. package/lib/utils/image-name.js +34 -7
  176. package/lib/utils/integration-file-backup.js +74 -0
  177. package/lib/utils/mutagen-install.js +30 -3
  178. package/lib/utils/paths.js +108 -25
  179. package/lib/utils/postgres-wipe.js +212 -0
  180. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  181. package/lib/utils/remote-dev-auth.js +21 -5
  182. package/lib/utils/remote-docker-env.js +9 -1
  183. package/lib/utils/remote-secrets-loader.js +42 -3
  184. package/lib/utils/resolve-docker-image-ref.js +9 -3
  185. package/lib/utils/secrets-ancestor-paths.js +47 -0
  186. package/lib/utils/secrets-helpers.js +17 -10
  187. package/lib/utils/secrets-kv-refs.js +42 -0
  188. package/lib/utils/secrets-kv-scope.js +19 -2
  189. package/lib/utils/secrets-materialize-local.js +134 -0
  190. package/lib/utils/secrets-path.js +24 -10
  191. package/lib/utils/secrets-utils.js +2 -2
  192. package/lib/utils/system-builder-root.js +34 -0
  193. package/lib/utils/url-declarative-resolve-build.js +6 -1
  194. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  195. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  196. package/lib/utils/urls-local-registry.js +73 -20
  197. package/lib/utils/validation-poll-ui.js +81 -0
  198. package/lib/utils/validation-run-poll.js +29 -5
  199. package/lib/utils/with-muted-logger.js +53 -0
  200. package/package.json +1 -1
  201. package/templates/applications/dataplane/application.yaml +1 -1
  202. package/templates/applications/dataplane/rbac.yaml +10 -10
  203. package/templates/applications/keycloak/env.template +8 -6
  204. package/templates/applications/miso-controller/application.yaml +7 -0
  205. package/templates/applications/miso-controller/env.template +7 -7
  206. package/templates/applications/miso-controller/rbac.yaml +9 -9
  207. package/templates/external-system/README.md.hbs +89 -102
  208. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  209. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  210. package/.nyc_output/processinfo/index.json +0 -1
  211. package/lib/api/service-users.api.js +0 -150
  212. package/lib/api/types/service-users.types.js +0 -65
  213. package/lib/cli/setup-service-user.js +0 -187
  214. 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
+ };
@@ -85,13 +85,14 @@ function getDatasourceKeys(appPath, configPath, variables, systemKey, systemPars
85
85
  }
86
86
 
87
87
  /**
88
- * Full upload to dataplane when --sync (same path as `aifabrix upload <systemKey>`).
88
+ * Publish local integration files to the dataplane before E2E (same path as `aifabrix upload <systemKey>`),
89
+ * unless ``options.noSync`` is true.
89
90
  * @param {string} systemKey
90
91
  * @param {Object} options
91
92
  * @returns {Promise<void>}
92
93
  */
93
94
  async function syncLocalIfRequested(systemKey, options) {
94
- if (options.sync !== true) return;
95
+ if (options.noSync === true) return;
95
96
  logger.log(chalk.cyan('Syncing local config to dataplane…'));
96
97
  const { uploadExternalSystem } = require('./upload');
97
98
  await uploadExternalSystem(systemKey, {
@@ -112,7 +113,7 @@ async function syncLocalIfRequested(systemKey, options) {
112
113
  * @param {boolean} [options.debug] - Include debug, write log
113
114
  * @param {boolean} [options.verbose] - Verbose output
114
115
  * @param {boolean} [options.async] - If false, sync mode (default true)
115
- * @param {boolean} [options.sync] - When true, run full upload (`uploadExternalSystem`) before per-datasource E2E
116
+ * @param {boolean} [options.noSync] - When true, skip upload (E2E uses dataplane config already deployed)
116
117
  * @returns {Promise<{ success: boolean, results: Array<{ key: string, success: boolean, error?: string }> }>}
117
118
  */
118
119
  async function runTestE2EForExternalSystem(externalSystem, options = {}) {
@@ -13,11 +13,14 @@ const path = require('path');
13
13
  const fs = require('fs');
14
14
  const chalk = require('chalk');
15
15
  const logger = require('../utils/logger');
16
+ const config = require('../core/config');
17
+ const { getDefaultControllerUrl } = require('../utils/controller-url');
16
18
  const pathsUtil = require('../utils/paths');
17
19
  const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
18
20
  const { isYamlPath } = require('../utils/config-format');
19
21
  const { copyTemplateFiles } = require('../validation/template');
20
22
  const { ensureReadmeForAppPath, ensureReadmeForApp } = require('../app/readme');
23
+ const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-registry');
21
24
 
22
25
  /**
23
26
  * Copy template to a target path if application config is missing there.
@@ -96,8 +99,9 @@ function patchEnvOutputPathInFile(configPath) {
96
99
  function validateEnvOutputPathFolderOrNull(appName) {
97
100
  if (!appName || typeof appName !== 'string') return;
98
101
  const pathsToPatch = [pathsUtil.getBuilderPath(appName)];
102
+ const envBuilderRoot = process.env.AIFABRIX_BUILDER_DIR && String(process.env.AIFABRIX_BUILDER_DIR).trim();
99
103
  const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
100
- if (path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
104
+ if (envBuilderRoot && path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
101
105
  pathsToPatch.push(cwdBuilderPath);
102
106
  }
103
107
  for (const appPath of pathsToPatch) {
@@ -167,34 +171,90 @@ function patchEnvOutputPathForDeployOnly(appName) {
167
171
  }
168
172
 
169
173
  /**
170
- * Removes builder app directories for the given app names. Only removes paths under the builder root
171
- * to prevent path traversal. Uses getBuilderPath for each app and validates before removal.
174
+ * For ``up-platform --force`` only: clear all stored auth tokens, set ``environment`` to ``dev``,
175
+ * set ``controller`` to the default URL for the current ``developer-id`` (``http://localhost`` +
176
+ * app port = 3000 + developerId × 100), and persist config. Does not change ``developer-id``.
177
+ * Builder dirs (keycloak, miso-controller, dataplane) are cleaned separately.
178
+ *
179
+ * @returns {Promise<void>}
180
+ */
181
+ async function applyUpPlatformForceConfig(opts = {}) {
182
+ const silent = Boolean(opts.silent);
183
+ const deviceCleared = await config.clearAllDeviceTokens();
184
+ const clientCleared = await config.clearAllClientTokens();
185
+ await config.setCurrentEnvironment('dev');
186
+ const defaultControllerUrl = await getDefaultControllerUrl();
187
+ await config.setControllerUrl(defaultControllerUrl);
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;
215
+ }
216
+
217
+ /**
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).
172
221
  *
173
222
  * @param {string[]} appNames - Application names (e.g. ['keycloak', 'miso-controller', 'dataplane'])
174
223
  * @returns {Promise<void>}
175
- * @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)
176
225
  */
177
- async function cleanBuilderAppDirs(appNames) {
226
+ async function cleanBuilderAppDirs(appNames, opts = {}) {
227
+ const silent = Boolean(opts.silent);
178
228
  if (!Array.isArray(appNames) || appNames.length === 0) return;
179
- const builderRoot = path.resolve(pathsUtil.getBuilderRoot());
229
+ const allowedRoots = [path.resolve(pathsUtil.getBuilderRoot()), path.resolve(pathsUtil.getSystemBuilderRoot())];
230
+ const cleaned = [];
180
231
  for (const appName of appNames) {
181
232
  if (!appName || typeof appName !== 'string') continue;
182
233
  const appPath = path.resolve(pathsUtil.getBuilderPath(appName));
183
- if (!appPath.startsWith(builderRoot + path.sep) && appPath !== builderRoot) {
184
- 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
+ );
185
238
  }
186
239
  if (fs.existsSync(appPath)) {
187
240
  fs.rmSync(appPath, { recursive: true });
188
- logger.log(chalk.blue(`Cleaned builder/${appName}`));
241
+ cleaned.push(appName);
242
+ if (!silent) {
243
+ logger.log(chalk.blue(`Cleaned builder/${appName}`));
244
+ }
189
245
  }
190
246
  }
247
+ return cleaned;
191
248
  }
192
249
 
193
250
  /**
194
251
  * Ensures builder app directory exists from template if application config is missing.
195
252
  * If builder/<appName>/application config does not exist, copies from templates/applications/<appName>.
196
253
  * Uses AIFABRIX_BUILDER_DIR when set (e.g. by up-miso/up-dataplane from config aifabrix-env-config).
197
- * 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).
198
258
  *
199
259
  * @async
200
260
  * @function ensureAppFromTemplate
@@ -217,8 +277,12 @@ async function ensureAppFromTemplate(appName) {
217
277
  logger.log(formatSuccessLine(`Copied template for ${appName}`));
218
278
  }
219
279
 
280
+ const envBuilderRoot = process.env.AIFABRIX_BUILDER_DIR && String(process.env.AIFABRIX_BUILDER_DIR).trim();
220
281
  const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
221
- if (path.resolve(cwdBuilderPath) !== path.resolve(appPath)) {
282
+ if (
283
+ envBuilderRoot &&
284
+ path.resolve(cwdBuilderPath) !== path.resolve(appPath)
285
+ ) {
222
286
  const cwdCopied = await ensureTemplateAtPath(appName, cwdBuilderPath);
223
287
  if (cwdCopied) {
224
288
  logger.log(chalk.blue(`Creating builder/${appName} in project (from template)...`));
@@ -230,10 +294,31 @@ async function ensureAppFromTemplate(appName) {
230
294
  return primaryCopied;
231
295
  }
232
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
+
233
316
  module.exports = {
317
+ applyUpPlatformForceConfig,
234
318
  cleanBuilderAppDirs,
235
319
  ensureAppFromTemplate,
236
320
  patchEnvOutputPathForDeployOnly,
237
321
  validateEnvOutputPathFolderOrNull,
238
- getEnvOutputPathFolder
322
+ getEnvOutputPathFolder,
323
+ prepareUrlsLocalRegistryForUpPlatform
239
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