@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
@@ -26,6 +26,7 @@ const {
26
26
  validateAndCheckAppDirectory,
27
27
  formatDataplaneRejectedTokenMessage,
28
28
  extractSessionId,
29
+ handleSourceSelection,
29
30
  handleOpenApiParsing,
30
31
  handleCredentialSelection,
31
32
  handleTypeDetection,
@@ -49,6 +50,150 @@ const {
49
50
  ensureIntegrationDir
50
51
  } = require('./wizard-helpers');
51
52
  const { humanizeAppKey } = require('../generator/wizard-prompts-secondary');
53
+ const { formatSuccessLine } = require('../utils/cli-layout-chalk');
54
+
55
+ /**
56
+ * Map resolved source type/data onto wizard state.source.
57
+ * @param {Object} state - Mutable wizard state
58
+ * @param {string} sourceType - Source type key
59
+ * @param {unknown} sourceData - Raw source payload from prompts
60
+ */
61
+ function applySourceSelectionToState(state, sourceType, sourceData) {
62
+ state.source = { type: sourceType };
63
+ if (sourceType === 'openapi-file') state.source.filePath = sourceData;
64
+ else if (sourceType === 'openapi-url') state.source.url = sourceData;
65
+ else if (sourceType === 'mcp-server') state.source.serverUrl = JSON.parse(sourceData).serverUrl;
66
+ else if (sourceType === 'known-platform') state.source.platform = sourceData;
67
+ }
68
+
69
+ /**
70
+ * Interactive or prefill source selection (Step 2).
71
+ * @returns {Promise<{ sourceType: string, sourceData: unknown }>}
72
+ */
73
+ async function resolveWizardSourcePhase(dataplaneUrl, sessionId, authConfig, platforms, prefill) {
74
+ if (prefill?.source?.type) {
75
+ logger.log(chalk.gray(
76
+ `Using source from wizard.yaml (${prefill.source.type}). Skipping source prompts.`
77
+ ));
78
+ return handleSourceSelection(dataplaneUrl, sessionId, authConfig, prefill.source);
79
+ }
80
+ return handleInteractiveSourceSelection(dataplaneUrl, sessionId, authConfig, platforms);
81
+ }
82
+
83
+ /**
84
+ * Credential selection with optional prefill (Step 3).
85
+ * @returns {Promise<string>} credentialIdOrKey for generation step
86
+ */
87
+ async function resolveWizardCredentialPhase(dataplaneUrl, authConfig, prefill, state) {
88
+ if (prefill?.credential) {
89
+ state.credential = prefill.credential;
90
+ return handleCredentialSelection(dataplaneUrl, authConfig, prefill.credential);
91
+ }
92
+ const credentialAction = await promptForCredentialAction();
93
+ const configCredential = await resolveCredentialConfig(dataplaneUrl, authConfig, credentialAction);
94
+ state.credential = configCredential;
95
+ return handleCredentialSelection(dataplaneUrl, authConfig, configCredential);
96
+ }
97
+
98
+ /**
99
+ * Collect user intent and UI preferences (Step 5), with optional wizard.yaml prefill.
100
+ * @returns {Promise<{ userIntent: string, preferences: Object, hasPrefillIntent: boolean }>}
101
+ */
102
+ async function collectIntentAndPreferences(preferencesPrefill) {
103
+ const prefillPrefs = preferencesPrefill;
104
+ const hasPrefillIntent =
105
+ prefillPrefs &&
106
+ typeof prefillPrefs.intent === 'string' &&
107
+ prefillPrefs.intent.trim().length > 0;
108
+
109
+ if (hasPrefillIntent) {
110
+ logger.log(chalk.gray(
111
+ 'Using preferences from wizard.yaml (intent and toggles). Skipping preference prompts.'
112
+ ));
113
+ const level = prefillPrefs.fieldOnboardingLevel;
114
+ const validLevel = level === 'standard' || level === 'minimal' ? level : 'full';
115
+ return {
116
+ userIntent: prefillPrefs.intent.trim(),
117
+ preferences: {
118
+ fieldOnboardingLevel: validLevel,
119
+ mcp: Boolean(prefillPrefs.enableMCP),
120
+ abac: Boolean(prefillPrefs.enableABAC),
121
+ rbac: Boolean(prefillPrefs.enableRBAC)
122
+ },
123
+ hasPrefillIntent: true
124
+ };
125
+ }
126
+
127
+ const userIntent = await promptForUserIntent();
128
+ const preferences = await promptForUserPreferences();
129
+ return { userIntent, preferences, hasPrefillIntent: false };
130
+ }
131
+
132
+ /**
133
+ * Run Step 5 (generate), Step 6–7 (review), and save files; updates state.preferences.
134
+ * @param {Object} payload
135
+ * @param {string} payload.appKey - Application key
136
+ * @param {string} payload.dataplaneUrl - Dataplane URL
137
+ * @param {Object} payload.authConfig - Auth configuration
138
+ * @param {string} payload.sessionId - Wizard session ID
139
+ * @param {Object} payload.state - Mutable wizard state
140
+ * @param {Object} payload.flowOpts - Flow options (mode, systemIdOrKey, debug, prefill, etc.)
141
+ * @param {Object} payload.genInput - Generation inputs (openapiSpec, detectedType, credential…)
142
+ * @returns {Promise<Object|null>} Updated state or null if review cancelled
143
+ */
144
+ async function completeWizardGenerateReviewSave(payload) {
145
+ const {
146
+ appKey,
147
+ dataplaneUrl,
148
+ authConfig,
149
+ sessionId,
150
+ state,
151
+ flowOpts,
152
+ genInput
153
+ } = payload;
154
+ const { mode, systemIdOrKey, debug, prefill } = flowOpts;
155
+ const genResult = await handleInteractiveConfigGeneration({
156
+ dataplaneUrl,
157
+ authConfig,
158
+ mode,
159
+ openapiSpec: genInput.openapiSpec,
160
+ detectedType: genInput.detectedType,
161
+ credentialIdOrKey: genInput.credentialIdOrKey,
162
+ systemIdOrKey,
163
+ sourceType: genInput.sourceType,
164
+ sourceData: genInput.sourceData,
165
+ entityName: genInput.entityName,
166
+ appName: appKey,
167
+ debug,
168
+ systemDisplayName: flowOpts.systemDisplayName,
169
+ preferencesPrefill: prefill?.preferences
170
+ });
171
+ const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
172
+ state.preferences = savedPrefs || {};
173
+
174
+ const finalConfigs = await handleConfigurationReview(
175
+ dataplaneUrl,
176
+ authConfig,
177
+ sessionId,
178
+ systemConfig,
179
+ datasourceConfigs,
180
+ { appKey, debug }
181
+ );
182
+ if (!finalConfigs) return null;
183
+
184
+ await handleFileSaving(
185
+ appKey,
186
+ finalConfigs.systemConfig,
187
+ finalConfigs.datasourceConfigs,
188
+ systemKey || appKey,
189
+ {
190
+ dataplaneUrl,
191
+ authConfig,
192
+ enableRBAC: Boolean(savedPrefs?.enableRBAC)
193
+ }
194
+ );
195
+ return state;
196
+ }
52
197
 
53
198
  /**
54
199
  * Create wizard session with given mode and optional systemIdOrKey (no prompts)
@@ -125,12 +270,15 @@ async function handleInteractiveSourceSelection(dataplaneUrl, sessionId, authCon
125
270
  * @param {Object} options.detectedType - Detected type info
126
271
  * @param {string} [options.credentialIdOrKey] - Credential ID or key (optional)
127
272
  * @param {string} [options.systemIdOrKey] - System ID or key (optional)
273
+ * @param {Object} [options.preferencesPrefill] - From wizard.yaml `preferences` (skip Step 5 prompts when intent is set)
128
274
  * @returns {Promise<Object>} Generated configuration and preferences { systemConfig, datasourceConfigs, systemKey, preferences }
129
275
  */
130
276
  async function handleInteractiveConfigGeneration(options) {
131
277
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 5: User Preferences'));
132
- const userIntent = await promptForUserIntent();
133
- const preferences = await promptForUserPreferences();
278
+
279
+ const { userIntent, preferences, hasPrefillIntent } = await collectIntentAndPreferences(
280
+ options.preferencesPrefill
281
+ );
134
282
 
135
283
  const configPrefs = {
136
284
  intent: userIntent,
@@ -138,6 +286,9 @@ async function handleInteractiveConfigGeneration(options) {
138
286
  enableMCP: preferences.mcp,
139
287
  enableABAC: preferences.abac,
140
288
  enableRBAC: preferences.rbac,
289
+ enableOpenAPIGeneration: hasPrefillIntent
290
+ ? options.preferencesPrefill?.enableOpenAPIGeneration !== false
291
+ : true,
141
292
  debug: options.debug === true
142
293
  };
143
294
 
@@ -222,62 +373,43 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, sessionId, sy
222
373
  * @returns {Promise<Object>} Collected state (source, credential, preferences) for wizard.yaml save
223
374
  */
224
375
  async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOpts, state) {
225
- const { mode, systemIdOrKey, platforms, debug } = flowOpts;
226
- const { sourceType, sourceData } = await handleInteractiveSourceSelection(
227
- dataplaneUrl, sessionId, authConfig, platforms
376
+ const { platforms, prefill } = flowOpts;
377
+
378
+ const { sourceType, sourceData } = await resolveWizardSourcePhase(
379
+ dataplaneUrl,
380
+ sessionId,
381
+ authConfig,
382
+ platforms,
383
+ prefill
228
384
  );
229
- state.source = { type: sourceType };
230
- if (sourceType === 'openapi-file') state.source.filePath = sourceData;
231
- else if (sourceType === 'openapi-url') state.source.url = sourceData;
232
- else if (sourceType === 'mcp-server') state.source.serverUrl = JSON.parse(sourceData).serverUrl;
233
- else if (sourceType === 'known-platform') state.source.platform = sourceData;
385
+ applySourceSelectionToState(state, sourceType, sourceData);
234
386
 
235
387
  const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
236
- const credentialAction = await promptForCredentialAction();
237
- const configCredential = await resolveCredentialConfig(dataplaneUrl, authConfig, credentialAction);
238
- state.credential = configCredential;
239
- const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig, configCredential);
388
+ const credentialIdOrKey = await resolveWizardCredentialPhase(dataplaneUrl, authConfig, prefill, state);
240
389
 
241
390
  const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
391
+ const prefillEntityName = prefill?.source?.entityName;
242
392
  const entityName = openapiSpec && sourceType !== 'known-platform'
243
- ? await handleEntitySelection(dataplaneUrl, authConfig, openapiSpec) : null;
244
- const genResult = await handleInteractiveConfigGeneration({
245
- dataplaneUrl,
246
- authConfig,
247
- mode,
248
- openapiSpec,
249
- detectedType,
250
- credentialIdOrKey,
251
- systemIdOrKey,
252
- sourceType,
253
- sourceData,
254
- entityName: entityName || undefined,
255
- appName: appKey,
256
- debug,
257
- systemDisplayName: flowOpts.systemDisplayName
258
- });
259
- const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
260
- state.preferences = savedPrefs || {};
261
-
262
- const finalConfigs = await handleConfigurationReview(
393
+ ? await handleEntitySelection(dataplaneUrl, authConfig, openapiSpec, prefillEntityName) : null;
394
+ if (entityName && state.source) {
395
+ state.source.entityName = entityName;
396
+ }
397
+ return completeWizardGenerateReviewSave({
398
+ appKey,
263
399
  dataplaneUrl,
264
400
  authConfig,
265
401
  sessionId,
266
- systemConfig,
267
- datasourceConfigs,
268
- { appKey, debug }
269
- );
270
- if (!finalConfigs) return null;
271
-
272
- await handleFileSaving(
273
- appKey,
274
- finalConfigs.systemConfig,
275
- finalConfigs.datasourceConfigs,
276
- systemKey || appKey,
277
- dataplaneUrl,
278
- authConfig
279
- );
280
- return state;
402
+ state,
403
+ flowOpts,
404
+ genInput: {
405
+ openapiSpec,
406
+ detectedType,
407
+ credentialIdOrKey,
408
+ sourceType,
409
+ sourceData,
410
+ entityName: entityName || undefined
411
+ }
412
+ });
281
413
  }
282
414
 
283
415
  async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sessionId, flowOpts) {
@@ -317,14 +449,14 @@ async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sess
317
449
  * @returns {Promise<void>} Resolves when wizard flow completes
318
450
  */
319
451
  async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}) {
320
- const { mode, systemIdOrKey, configPath, debug, systemDisplayName } = flowOpts;
452
+ const { mode, systemIdOrKey, configPath, debug, systemDisplayName, prefill } = flowOpts;
321
453
 
322
454
  if (debug) {
323
455
  logger.log(chalk.gray(`[DEBUG] Wizard debug mode enabled for app: ${appKey}`));
324
456
  }
325
457
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 1: Create Session'));
326
458
  const sessionId = await createSessionFromParams(dataplaneUrl, authConfig, mode, systemIdOrKey, appKey);
327
- logger.log(chalk.green('\u2713 Session created'));
459
+ logger.log(formatSuccessLine('Session created'));
328
460
 
329
461
  const platforms = mode === 'add-datasource' ? [] : await getWizardPlatforms(dataplaneUrl, authConfig);
330
462
  const state = await runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sessionId, {
@@ -333,7 +465,8 @@ async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}
333
465
  platforms,
334
466
  configPath,
335
467
  debug: flowOpts.debug,
336
- systemDisplayName
468
+ systemDisplayName,
469
+ prefill
337
470
  });
338
471
  if (!state) return;
339
472
 
@@ -348,6 +481,11 @@ async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}
348
481
  * @param {string} [appName] - App name (for log message)
349
482
  * @returns {Promise<Object|null>} Loaded config or null
350
483
  */
484
+ /**
485
+ * Load wizard.yaml when present. Returns full valid config, or invalid-but-parsed config for interactive prefill.
486
+ *
487
+ * @returns {Promise<{ valid: true, config: Object } | { valid: false, config: Object, errors: string[] } | null>}
488
+ */
351
489
  async function loadWizardConfigIfExists(configPath, appName) {
352
490
  if (!configPath) return null;
353
491
  const displayPath = appName ? `integration/${appName}/wizard.yaml` : configPath;
@@ -360,10 +498,16 @@ async function loadWizardConfigIfExists(configPath, appName) {
360
498
  const result = await validateWizardConfig(configPath, { validateFilePaths: false });
361
499
  if (result.valid && result.config) {
362
500
  logger.log(chalk.green(`Loaded saved state from ${displayPath}. Resuming with saved choices.`));
363
- return result.config;
501
+ return { valid: true, config: result.config };
364
502
  }
365
503
  if (result.errors?.length) {
366
- logger.log(chalk.yellow(`Loaded ${displayPath} but it has errors; prompting for missing fields.`));
504
+ logger.log(chalk.yellow(
505
+ `Loaded ${displayPath} but it does not fully validate; prefilling from file where possible.`
506
+ ));
507
+ result.errors.forEach(err => logger.log(chalk.gray(` • ${err}`)));
508
+ }
509
+ if (result.config) {
510
+ return { valid: false, config: result.config, errors: result.errors || [] };
367
511
  }
368
512
  } catch (e) {
369
513
  logger.log(chalk.gray(`Could not load wizard config from ${displayPath}: ${e.message}`));
@@ -470,15 +614,16 @@ async function handleWizardWithSavedConfig(options, loadedConfig, displayPath) {
470
614
  }
471
615
 
472
616
  async function handleWizardInteractive(options) {
617
+ const prefill = options.wizardPrefill;
473
618
  const allowAddDatasource = !options.app;
474
619
  const mode = allowAddDatasource ? await promptForMode(undefined, true) : 'create-system';
475
620
  const resolved = mode === 'create-system'
476
- ? await resolveCreateNewPath(options, null)
477
- : await resolveAddDatasourcePath(options, null);
621
+ ? await resolveCreateNewPath(options, prefill || null)
622
+ : await resolveAddDatasourcePath(options, prefill || null);
478
623
  if (!resolved) return;
479
624
  const { appKey, configPath, dataplaneUrl, authConfig } = resolved;
480
625
  const systemIdOrKey = mode === 'add-datasource' ? resolved.systemIdOrKey : undefined;
481
- const systemDisplayName = options.systemDisplayName || options.displayName ||
626
+ const systemDisplayName = options.systemDisplayName || options.displayName || prefill?.systemDisplayName ||
482
627
  (mode === 'create-system' ? humanizeAppKey(appKey) : undefined);
483
628
  try {
484
629
  await executeWizardFlow(appKey, dataplaneUrl, authConfig, {
@@ -486,7 +631,8 @@ async function handleWizardInteractive(options) {
486
631
  systemIdOrKey,
487
632
  configPath,
488
633
  debug: options.debug,
489
- systemDisplayName
634
+ systemDisplayName,
635
+ prefill: prefill || undefined
490
636
  });
491
637
  logger.log(chalk.gray(`To change settings, edit integration/${appKey}/wizard.yaml and run: aifabrix wizard ${appKey}`));
492
638
  } catch (error) {
@@ -504,9 +650,12 @@ async function handleWizard(options = {}) {
504
650
  return await handleWizardSilent(options);
505
651
  }
506
652
  logger.log(chalk.blue('\n\uD83E\uDDD9 AI Fabrix External System Wizard\n'));
507
- const loadedConfig = await loadWizardConfigIfExists(options.configPath, options.app);
508
- if (loadedConfig) {
509
- return await handleWizardWithSavedConfig(options, loadedConfig, displayPath);
653
+ const loadResult = await loadWizardConfigIfExists(options.configPath, options.app);
654
+ if (loadResult?.valid && loadResult.config) {
655
+ return await handleWizardWithSavedConfig(options, loadResult.config, displayPath);
656
+ }
657
+ if (loadResult?.config && loadResult.valid === false) {
658
+ return await handleWizardInteractive({ ...options, wizardPrefill: loadResult.config });
510
659
  }
511
660
  return await handleWizardInteractive(options);
512
661
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Infra compose service names accepted by `aifabrix restart` and {@link restartService}.
3
+ *
4
+ * @fileoverview Single source of truth for CLI help + validation
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /** @type {ReadonlyArray<{ name: string, description: string }>} */
12
+ const RESTARTABLE_INFRA_SERVICES = Object.freeze([
13
+ { name: 'postgres', description: 'PostgreSQL database' },
14
+ { name: 'redis', description: 'Redis' },
15
+ { name: 'pgadmin', description: 'pgAdmin 4 web UI (only if enabled when you ran up-infra)' },
16
+ { name: 'redis-commander', description: 'Redis Commander web UI (only if enabled when you ran up-infra)' },
17
+ { name: 'traefik', description: 'Traefik reverse proxy (only if enabled when you ran up-infra)' }
18
+ ]);
19
+
20
+ /**
21
+ * @returns {string[]} service names in compose order
22
+ */
23
+ function getRestartableInfraServiceNames() {
24
+ return RESTARTABLE_INFRA_SERVICES.map((s) => s.name);
25
+ }
26
+
27
+ /**
28
+ * Aligned lines for Commander `addHelpText('after', …)`.
29
+ * @returns {string}
30
+ */
31
+ function buildRestartInfraHelpLines() {
32
+ const col = 22;
33
+ return RESTARTABLE_INFRA_SERVICES.map((s) => ` ${s.name.padEnd(col)}${s.description}`).join('\n');
34
+ }
35
+
36
+ module.exports = {
37
+ RESTARTABLE_INFRA_SERVICES,
38
+ getRestartableInfraServiceNames,
39
+ buildRestartInfraHelpLines
40
+ };
@@ -115,6 +115,19 @@ function detectSensitiveValue(key, value) {
115
115
  return false;
116
116
  }
117
117
 
118
+ /**
119
+ * Path segment after `kv://` for sensitive env vars; must match infra.parameter.yaml keys.
120
+ * API_KEY uses the shared miso-controller/dataplane catalog entry (not a flat `api-key` slug).
121
+ * @param {string} key - Environment variable name
122
+ * @returns {string}
123
+ */
124
+ function sensitiveKvPathSegmentFromEnvKey(key) {
125
+ if (key === 'API_KEY') {
126
+ return 'miso-controller-secrets-apiKeyVault';
127
+ }
128
+ return key.toLowerCase().replace(/[^a-z0-9]/g, '-');
129
+ }
130
+
118
131
  /**
119
132
  * Convert existing .env variables to env.template format
120
133
  * @param {Object} existingEnv - Existing environment variables
@@ -128,7 +141,7 @@ function convertToEnvTemplate(existingEnv, requiredVars) {
128
141
  Object.entries(existingEnv).forEach(([key, value]) => {
129
142
  if (detectSensitiveValue(key, value)) {
130
143
  // Convert sensitive values to kv:// references
131
- convertedEnv[key] = `kv://${key.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
144
+ convertedEnv[key] = `kv://${sensitiveKvPathSegmentFromEnvKey(key)}`;
132
145
  } else {
133
146
  // Keep non-sensitive values as-is
134
147
  convertedEnv[key] = value;
@@ -148,8 +161,8 @@ function generateSecretsFromEnv(envVars) {
148
161
 
149
162
  Object.entries(envVars).forEach(([key, value]) => {
150
163
  if (detectSensitiveValue(key, value)) {
151
- // Use centralized resolver for canonical secret names
152
- const secretName = getCanonicalSecretName(key);
164
+ const secretName =
165
+ key === 'API_KEY' ? sensitiveKvPathSegmentFromEnvKey(key) : getCanonicalSecretName(key);
153
166
  secrets[secretName] = value;
154
167
  }
155
168
  });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Infrastructure admin-secrets.env generation (PG/Redis Commander defaults).
3
+ *
4
+ * @fileoverview Split from secrets.js for module size limits
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const logger = require('../utils/logger');
14
+ const config = require('./config');
15
+ const {
16
+ mergeInfraParameterDefaultsForCli,
17
+ getInfraParameterCatalog,
18
+ readRelaxedCatalogDefaults
19
+ } = require('../parameters/infra-parameter-catalog');
20
+ const { createDefaultSecrets } = require('../utils/secrets-generator');
21
+ const pathsUtil = require('../utils/paths');
22
+ const { loadSecrets } = require('./secrets-load');
23
+
24
+ /**
25
+ * Writes admin env key-value pairs to content; encrypts values when encryption key is set.
26
+ * @async
27
+ * @param {Object.<string, string>} adminObj - Key-value object (e.g. POSTGRES_PASSWORD, ...)
28
+ * @returns {Promise<string>} .env-style content (plaintext or secure:// for secrets)
29
+ */
30
+ async function formatAdminSecretsContent(adminObj) {
31
+ const encryptionKey = await config.getSecretsEncryptionKey();
32
+ const { encryptSecret } = require('../utils/secrets-encryption');
33
+ const lines = ['# Infrastructure Admin Credentials'];
34
+ for (const [k, v] of Object.entries(adminObj)) {
35
+ const value = (v === null || v === undefined) ? '' : String(v).replace(/\n/g, ' ').trim();
36
+ const valueToWrite = encryptionKey ? encryptSecret(value, encryptionKey) : value;
37
+ lines.push(`${k}=${valueToWrite}`);
38
+ }
39
+ return lines.join('\n');
40
+ }
41
+
42
+ async function loadSecretsOrBootstrapForAdmin(secretsPath) {
43
+ try {
44
+ return await loadSecrets(secretsPath);
45
+ } catch (error) {
46
+ const defaultSecretsPath = secretsPath || path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
47
+ if (!fs.existsSync(defaultSecretsPath)) {
48
+ logger.log('Creating default secrets file...');
49
+ await createDefaultSecrets(defaultSecretsPath);
50
+ return await loadSecrets(secretsPath);
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+
56
+ function getInfraDefaultsMergedForAdmin() {
57
+ try {
58
+ return mergeInfraParameterDefaultsForCli(getInfraParameterCatalog().data, {});
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+
64
+ function buildLocalAdminSecretsObject(secrets, infraDefaults) {
65
+ const raw = secrets['postgres-passwordKeyVault'];
66
+ const relaxed = readRelaxedCatalogDefaults();
67
+ const postgresPassword =
68
+ (raw && String(raw).trim()) ||
69
+ infraDefaults.adminPassword ||
70
+ relaxed.adminPassword ||
71
+ '';
72
+ const pgAdminEmail = infraDefaults.adminEmail || relaxed.adminEmail || '';
73
+ return {
74
+ POSTGRES_PASSWORD: postgresPassword,
75
+ PGADMIN_DEFAULT_EMAIL: pgAdminEmail,
76
+ PGADMIN_DEFAULT_PASSWORD: postgresPassword,
77
+ REDIS_HOST: 'local:redis:6379:0:',
78
+ REDIS_COMMANDER_USER: 'admin',
79
+ REDIS_COMMANDER_PASSWORD: postgresPassword
80
+ };
81
+ }
82
+
83
+ /** Generates admin secrets for infrastructure (beside config.yaml, typically ~/.aifabrix/admin-secrets.env). Defaults from infra.parameter.yaml `defaults`. */
84
+ async function generateAdminSecretsEnv(secretsPath) {
85
+ const secrets = await loadSecretsOrBootstrapForAdmin(secretsPath);
86
+ const infraDefaults = getInfraDefaultsMergedForAdmin();
87
+ const adminObj = buildLocalAdminSecretsObject(secrets, infraDefaults);
88
+ const aifabrixDir = pathsUtil.getAifabrixSystemDir();
89
+ const adminEnvPath = path.join(aifabrixDir, 'admin-secrets.env');
90
+ if (!fs.existsSync(aifabrixDir)) {
91
+ fs.mkdirSync(aifabrixDir, { recursive: true, mode: 0o700 });
92
+ }
93
+ const adminSecrets = await formatAdminSecretsContent(adminObj);
94
+ fs.writeFileSync(adminEnvPath, adminSecrets, { mode: 0o600 });
95
+ return adminEnvPath;
96
+ }
97
+
98
+ module.exports = {
99
+ formatAdminSecretsContent,
100
+ generateAdminSecretsEnv
101
+ };
@@ -70,8 +70,41 @@ function getInfraSecretKeysForUpInfra() {
70
70
  }
71
71
  }
72
72
 
73
+ /**
74
+ * Same merge as runtime `loadSecrets()` (user local + project path + remote shared API + defaults).
75
+ * Ensures missing-key checks treat remote `--shared` keys as already satisfied.
76
+ *
77
+ * @returns {Promise<Object.<string, string>>}
78
+ */
79
+ async function loadMergedSecretsForEnsureMissingCheck() {
80
+ const { loadSecrets } = require('./secrets-load');
81
+ return loadSecrets(undefined);
82
+ }
83
+
84
+ /**
85
+ * Default on for writes to the primary user secrets file: consult full {@link loadSecrets} merge so remote
86
+ * `--shared` keys are not duplicated locally. Opt out with `useMergedSecretsForMissingKeys: false`.
87
+ *
88
+ * @param {{ useMergedSecretsForMissingKeys?: boolean }} options
89
+ * @param {{ type?: string, filePath?: string }} target
90
+ * @returns {boolean}
91
+ */
92
+ function shouldUseMergedSecretsForMissingKeys(options, target) {
93
+ if (options.useMergedSecretsForMissingKeys === false) return false;
94
+ if (options.useMergedSecretsForMissingKeys === true) return true;
95
+ if (!target || target.type !== 'file' || !target.filePath) return false;
96
+ try {
97
+ const primary = pathsUtil.getPrimaryUserSecretsLocalPath();
98
+ return path.resolve(target.filePath) === path.resolve(primary);
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
73
104
  module.exports = {
74
105
  buildInfraPlaceholderContext,
75
106
  isSecretKeyAllowedEmpty,
76
- getInfraSecretKeysForUpInfra
107
+ getInfraSecretKeysForUpInfra,
108
+ loadMergedSecretsForEnsureMissingCheck,
109
+ shouldUseMergedSecretsForMissingKeys
77
110
  };