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