@aifabrix/builder 2.40.2 → 2.42.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 (198) hide show
  1. package/.cursor/rules/docs-rules.mdc +30 -0
  2. package/README.md +7 -5
  3. package/integration/hubspot/README.md +8 -4
  4. package/integration/hubspot/application.json +54 -0
  5. package/integration/hubspot/create-hubspot.js +9 -136
  6. package/integration/hubspot/env.template +3 -4
  7. package/integration/hubspot/hubspot-datasource-company.json +343 -5
  8. package/integration/hubspot/hubspot-datasource-contact.json +413 -5
  9. package/integration/hubspot/hubspot-datasource-deal.json +341 -4
  10. package/integration/hubspot/hubspot-datasource-users.json +116 -0
  11. package/integration/hubspot/hubspot-deploy.json +1250 -108
  12. package/integration/hubspot/hubspot-system.json +15 -32
  13. package/integration/hubspot/test-dataplane-down-tests.js +17 -16
  14. package/integration/hubspot/test-dataplane-down.js +2 -2
  15. package/integration/hubspot/test.js +1 -1
  16. package/jest.config.manual.js +2 -1
  17. package/lib/api/credential.api.js +40 -0
  18. package/lib/api/dev.api.js +423 -0
  19. package/lib/api/external-test.api.js +111 -0
  20. package/lib/api/index.js +42 -19
  21. package/lib/api/pipeline.api.js +66 -120
  22. package/lib/api/types/credential.types.js +23 -0
  23. package/lib/api/types/dev.types.js +140 -0
  24. package/lib/api/types/pipeline.types.js +37 -0
  25. package/lib/api/wizard-platform.api.js +61 -0
  26. package/lib/api/wizard.api.js +34 -1
  27. package/lib/app/config.js +44 -11
  28. package/lib/app/down.js +2 -1
  29. package/lib/app/index.js +12 -1
  30. package/lib/app/prompts.js +44 -29
  31. package/lib/app/push.js +36 -12
  32. package/lib/app/readme.js +9 -6
  33. package/lib/app/run-env-compose.js +264 -0
  34. package/lib/app/run-helpers.js +121 -118
  35. package/lib/app/run.js +148 -28
  36. package/lib/app/show-display.js +1 -1
  37. package/lib/app/show.js +5 -2
  38. package/lib/build/index.js +11 -3
  39. package/lib/cli/setup-app.js +172 -15
  40. package/lib/cli/setup-credential-deployment.js +31 -6
  41. package/lib/cli/setup-dev.js +206 -16
  42. package/lib/cli/setup-environment.js +16 -6
  43. package/lib/cli/setup-external-system.js +89 -24
  44. package/lib/cli/setup-infra.js +82 -15
  45. package/lib/cli/setup-secrets.js +52 -5
  46. package/lib/cli/setup-utility.js +129 -24
  47. package/lib/commands/app-install.js +172 -0
  48. package/lib/commands/app-shell.js +75 -0
  49. package/lib/commands/app-test.js +282 -0
  50. package/lib/commands/app.js +1 -1
  51. package/lib/commands/credential-env.js +162 -0
  52. package/lib/commands/credential-list.js +17 -22
  53. package/lib/commands/credential-push.js +96 -0
  54. package/lib/commands/datasource.js +77 -6
  55. package/lib/commands/dev-cli-handlers.js +141 -0
  56. package/lib/commands/dev-down.js +114 -0
  57. package/lib/commands/dev-init.js +347 -0
  58. package/lib/commands/repair-auth-config.js +99 -0
  59. package/lib/commands/repair-datasource-keys.js +208 -0
  60. package/lib/commands/repair-datasource.js +235 -0
  61. package/lib/commands/repair-env-template.js +348 -0
  62. package/lib/commands/repair-internal.js +85 -0
  63. package/lib/commands/repair-rbac.js +158 -0
  64. package/lib/commands/repair.js +507 -0
  65. package/lib/commands/secrets-list.js +118 -0
  66. package/lib/commands/secrets-remove.js +97 -0
  67. package/lib/commands/secrets-set.js +30 -17
  68. package/lib/commands/secrets-validate.js +50 -0
  69. package/lib/commands/test-e2e-external.js +165 -0
  70. package/lib/commands/up-dataplane.js +2 -2
  71. package/lib/commands/up-miso.js +0 -25
  72. package/lib/commands/upload.js +96 -40
  73. package/lib/commands/wizard-core-helpers.js +226 -4
  74. package/lib/commands/wizard-core.js +67 -29
  75. package/lib/commands/wizard-dataplane.js +1 -1
  76. package/lib/commands/wizard-entity-selection.js +43 -0
  77. package/lib/commands/wizard-headless.js +44 -5
  78. package/lib/commands/wizard-helpers.js +7 -3
  79. package/lib/commands/wizard.js +86 -64
  80. package/lib/core/admin-secrets.js +96 -0
  81. package/lib/core/config.js +7 -1
  82. package/lib/core/secrets-ensure.js +378 -0
  83. package/lib/core/secrets-env-write.js +157 -0
  84. package/lib/core/secrets.js +176 -89
  85. package/lib/datasource/deploy.js +12 -3
  86. package/lib/datasource/field-reference-validator.js +91 -0
  87. package/lib/datasource/test-e2e.js +219 -0
  88. package/lib/datasource/test-integration.js +154 -0
  89. package/lib/datasource/validate.js +21 -3
  90. package/lib/deployment/deployer.js +7 -5
  91. package/lib/deployment/environment-config.js +137 -0
  92. package/lib/deployment/environment.js +21 -98
  93. package/lib/deployment/push.js +32 -2
  94. package/lib/external-system/download.js +188 -203
  95. package/lib/external-system/generator.js +204 -56
  96. package/lib/external-system/test-auth.js +7 -3
  97. package/lib/external-system/test-execution.js +2 -1
  98. package/lib/external-system/test-system-level.js +73 -0
  99. package/lib/external-system/test.js +56 -19
  100. package/lib/generator/external-controller-manifest.js +29 -2
  101. package/lib/generator/external-schema-utils.js +1 -1
  102. package/lib/generator/external.js +10 -3
  103. package/lib/generator/index.js +177 -25
  104. package/lib/generator/split-readme.js +1 -0
  105. package/lib/generator/split-variables.js +7 -1
  106. package/lib/generator/split.js +194 -54
  107. package/lib/generator/wizard-prompts-secondary.js +294 -0
  108. package/lib/generator/wizard-prompts.js +105 -106
  109. package/lib/generator/wizard-readme.js +88 -0
  110. package/lib/generator/wizard.js +155 -158
  111. package/lib/infrastructure/compose.js +11 -1
  112. package/lib/infrastructure/helpers.js +103 -20
  113. package/lib/infrastructure/index.js +98 -12
  114. package/lib/infrastructure/services.js +88 -22
  115. package/lib/schema/application-schema.json +32 -8
  116. package/lib/schema/external-datasource.schema.json +49 -26
  117. package/lib/schema/external-system.schema.json +509 -411
  118. package/lib/schema/wizard-config.schema.json +16 -0
  119. package/lib/utils/api.js +41 -13
  120. package/lib/utils/app-register-auth.js +25 -3
  121. package/lib/utils/auth-headers.js +8 -7
  122. package/lib/utils/cli-utils.js +20 -0
  123. package/lib/utils/compose-generator.js +77 -76
  124. package/lib/utils/compose-handlebars-helpers.js +54 -0
  125. package/lib/utils/compose-vector-helper.js +18 -0
  126. package/lib/utils/config-format-preference.js +51 -0
  127. package/lib/utils/config-format.js +36 -0
  128. package/lib/utils/config-paths.js +127 -2
  129. package/lib/utils/configuration-env-resolver.js +179 -0
  130. package/lib/utils/credential-display.js +83 -0
  131. package/lib/utils/credential-secrets-env.js +357 -0
  132. package/lib/utils/dataplane-pipeline-warning.js +28 -0
  133. package/lib/utils/deployment-validation-helpers.js +4 -4
  134. package/lib/utils/dev-ca-install.js +139 -0
  135. package/lib/utils/dev-cert-helper.js +122 -0
  136. package/lib/utils/device-code-helpers.js +224 -0
  137. package/lib/utils/device-code.js +37 -336
  138. package/lib/utils/docker-build.js +40 -8
  139. package/lib/utils/env-copy.js +103 -13
  140. package/lib/utils/env-map.js +35 -5
  141. package/lib/utils/env-template.js +6 -5
  142. package/lib/utils/error-formatters/http-status-errors.js +20 -2
  143. package/lib/utils/error-formatters/permission-errors.js +0 -1
  144. package/lib/utils/error-formatters/validation-errors.js +0 -1
  145. package/lib/utils/external-readme.js +56 -29
  146. package/lib/utils/external-system-display.js +59 -1
  147. package/lib/utils/external-system-test-helpers.js +21 -8
  148. package/lib/utils/external-system-validators.js +3 -0
  149. package/lib/utils/file-upload.js +20 -50
  150. package/lib/utils/help-builder.js +16 -2
  151. package/lib/utils/infra-status.js +80 -45
  152. package/lib/utils/local-secrets.js +7 -52
  153. package/lib/utils/mutagen-install.js +195 -0
  154. package/lib/utils/mutagen.js +146 -0
  155. package/lib/utils/paths.js +128 -37
  156. package/lib/utils/port-resolver.js +28 -16
  157. package/lib/utils/remote-dev-auth.js +38 -0
  158. package/lib/utils/remote-docker-env.js +43 -0
  159. package/lib/utils/remote-secrets-loader.js +60 -0
  160. package/lib/utils/secrets-canonical.js +93 -0
  161. package/lib/utils/secrets-generator.js +114 -6
  162. package/lib/utils/secrets-helpers.js +108 -114
  163. package/lib/utils/secrets-path.js +2 -2
  164. package/lib/utils/secrets-utils.js +52 -1
  165. package/lib/utils/secrets-validation.js +84 -0
  166. package/lib/utils/ssh-key-helper.js +116 -0
  167. package/lib/utils/test-log-writer.js +56 -0
  168. package/lib/utils/token-manager-messages.js +90 -0
  169. package/lib/utils/token-manager.js +29 -36
  170. package/lib/utils/variable-transformer.js +3 -3
  171. package/lib/validation/env-template-auth.js +157 -0
  172. package/lib/validation/env-template-kv.js +41 -0
  173. package/lib/validation/external-manifest-validator.js +25 -0
  174. package/lib/validation/external-system-auth-rules.js +86 -0
  175. package/lib/validation/validate-batch.js +149 -0
  176. package/lib/validation/validate-datasource-keys-api.js +33 -0
  177. package/lib/validation/validate-display.js +94 -16
  178. package/lib/validation/validate.js +25 -12
  179. package/lib/validation/validator.js +72 -9
  180. package/lib/validation/wizard-datasource-validation.js +50 -0
  181. package/package.json +8 -3
  182. package/scripts/install-local.js +34 -15
  183. package/templates/README.md +0 -1
  184. package/templates/applications/README.md.hbs +4 -4
  185. package/templates/applications/dataplane/application.yaml +6 -5
  186. package/templates/applications/dataplane/env.template +15 -10
  187. package/templates/applications/dataplane/rbac.yaml +2 -2
  188. package/templates/applications/keycloak/env.template +2 -0
  189. package/templates/applications/miso-controller/application.yaml +1 -0
  190. package/templates/applications/miso-controller/env.template +12 -10
  191. package/templates/external-system/README.md.hbs +65 -25
  192. package/templates/external-system/deploy.js.hbs +4 -2
  193. package/templates/external-system/external-datasource.yaml.hbs +217 -0
  194. package/templates/external-system/external-system.json.hbs +1 -18
  195. package/templates/infra/compose.yaml.hbs +6 -0
  196. package/templates/python/docker-compose.hbs +49 -23
  197. package/templates/typescript/docker-compose.hbs +48 -22
  198. package/integration/hubspot/application.yaml +0 -37
@@ -15,7 +15,8 @@ const chalk = require('chalk');
15
15
  const logger = require('../utils/logger');
16
16
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
17
17
  const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
18
- const { generateExternalReadmeContent } = require('../utils/external-readme');
18
+ const { systemKeyToKvPrefix } = require('../utils/credential-secrets-env');
19
+ const { generateReadme } = require('./wizard-readme');
19
20
 
20
21
  /**
21
22
  * Converts a string to a schema-valid key segment (lowercase letters, numbers, hyphens only).
@@ -41,45 +42,59 @@ function toKeySegment(str) {
41
42
  * @returns {Promise<Object>} Object with generated file paths
42
43
  * @throws {Error} If file generation fails
43
44
  */
45
+ /** Extension for format */
46
+ const FORMAT_EXT = { yaml: '.yaml', json: '.json' };
47
+
44
48
  /**
45
- * Writes system JSON file
49
+ * Writes system config file
46
50
  * @async
47
51
  * @function writeSystemYamlFile
48
52
  * @param {string} appPath - Application path
49
53
  * @param {string} finalSystemKey - Final system key
50
54
  * @param {Object} systemConfig - System configuration
55
+ * @param {string} [format] - Output format: 'yaml' (default) or 'json'
51
56
  * @returns {Promise<string>} System file path
52
57
  */
53
- async function writeSystemYamlFile(appPath, finalSystemKey, systemConfig) {
54
- const systemFileName = `${finalSystemKey}-system.yaml`;
58
+ async function writeSystemYamlFile(appPath, finalSystemKey, systemConfig, format = 'yaml') {
59
+ const ext = FORMAT_EXT[format === 'json' ? 'json' : 'yaml'] || '.yaml';
60
+ const systemFileName = `${finalSystemKey}-system${ext}`;
55
61
  const systemFilePath = path.join(appPath, systemFileName);
56
- writeConfigFile(systemFilePath, systemConfig);
62
+ writeConfigFile(systemFilePath, systemConfig, format === 'json' ? 'json' : 'yaml');
57
63
  logger.log(chalk.green(`✓ Generated system file: ${systemFileName}`));
58
64
  return systemFilePath;
59
65
  }
60
66
 
61
67
  /**
62
- * Writes datasource YAML files
68
+ * Writes datasource config files
63
69
  * @async
64
70
  * @function writeDatasourceYamlFiles
65
71
  * @param {string} appPath - Application path
66
72
  * @param {string} finalSystemKey - Final system key
67
73
  * @param {Object[]} datasourceConfigs - Array of datasource configurations
74
+ * @param {string} [format] - Output format: 'yaml' (default) or 'json'
68
75
  * @returns {Promise<string[]>} Array of datasource file names
69
76
  */
70
- async function writeDatasourceYamlFiles(appPath, finalSystemKey, datasourceConfigs) {
77
+ async function writeDatasourceYamlFiles(appPath, finalSystemKey, datasourceConfigs, format = 'yaml') {
78
+ const ext = FORMAT_EXT[format === 'json' ? 'json' : 'yaml'] || '.yaml';
79
+ const fmt = format === 'json' ? 'json' : 'yaml';
71
80
  const datasourceFileNames = [];
81
+ const usedBaseNames = new Set();
72
82
  for (const datasourceConfig of datasourceConfigs) {
73
- const entityType = datasourceConfig.entityType || datasourceConfig.entityKey || datasourceConfig.key?.split('-').pop() || 'default';
74
- const keySegment = toKeySegment(entityType);
75
- const datasourceKey = datasourceConfig.key || `${finalSystemKey}-${keySegment}`;
76
- // Extract datasource key (remove system key prefix if present); use normalized segment for filename
83
+ const datasourceKey = datasourceConfig.key || '';
77
84
  const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${finalSystemKey}-`)
78
85
  ? datasourceKey.substring(finalSystemKey.length + 1)
79
- : keySegment;
80
- const datasourceFileName = `${finalSystemKey}-datasource-${datasourceKeyOnly}.yaml`;
86
+ : (datasourceConfig.entityType || datasourceConfig.entityKey || datasourceKey.split('-').pop() || 'default');
87
+ const keySegment = toKeySegment(datasourceKeyOnly);
88
+ let baseName = keySegment;
89
+ if (usedBaseNames.has(baseName)) {
90
+ let suffix = 1;
91
+ while (usedBaseNames.has(`${baseName}-${suffix}`)) suffix++;
92
+ baseName = `${baseName}-${suffix}`;
93
+ }
94
+ usedBaseNames.add(baseName);
95
+ const datasourceFileName = `${finalSystemKey}-datasource-${baseName}${ext}`;
81
96
  const datasourceFilePath = path.join(appPath, datasourceFileName);
82
- writeConfigFile(datasourceFilePath, datasourceConfig);
97
+ writeConfigFile(datasourceFilePath, datasourceConfig, fmt);
83
98
  datasourceFileNames.push(datasourceFileName);
84
99
  logger.log(chalk.green(`✓ Generated datasource file: ${datasourceFileName}`));
85
100
  }
@@ -102,32 +117,50 @@ async function writeDatasourceYamlFiles(appPath, finalSystemKey, datasourceConfi
102
117
  * @returns {Promise<Object>} Object with file paths
103
118
  */
104
119
  async function generateConfigFilesForWizard(params) {
105
- const { appPath, appName, finalSystemKey, systemFileName, datasourceFileNames, systemConfig, datasourceConfigs, aiGeneratedReadme } = params;
120
+ const { appPath, appName, finalSystemKey, systemFileName, datasourceFileNames, systemConfig, datasourceConfigs, aiGeneratedReadme, format } = params;
106
121
 
107
- // Generate or update application.yaml with externalIntegration block
122
+ // Generate or update application config with externalIntegration block
108
123
  const configPath = await generateOrUpdateVariablesYaml({
109
124
  appPath,
110
125
  appName,
111
126
  systemKey: finalSystemKey,
112
127
  systemFileName,
113
128
  datasourceFileNames,
114
- systemConfig
129
+ systemConfig,
130
+ format: format || 'yaml'
115
131
  });
116
132
 
117
- // Generate env.template with authentication variables
118
- await generateEnvTemplate(appPath, systemConfig);
133
+ // Generate env.template with KV_* authentication variables
134
+ await generateEnvTemplate(appPath, systemConfig, finalSystemKey);
135
+
136
+ const envTemplatePath = path.join(appPath, 'env.template');
137
+ try {
138
+ const secretsEnsure = require('../core/secrets-ensure');
139
+ await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, { emptyValuesForCredentials: true });
140
+ } catch (err) {
141
+ if (err.code !== 'ENOENT') logger.warn(`Could not ensure integration placeholder secrets: ${err.message}`);
142
+ }
119
143
 
120
144
  // Generate README.md (use AI-generated content if available)
121
- await generateReadme(appPath, appName, finalSystemKey, systemConfig, datasourceConfigs, aiGeneratedReadme);
145
+ await generateReadme({
146
+ appPath,
147
+ appName,
148
+ systemKey: finalSystemKey,
149
+ systemConfig,
150
+ datasourceConfigs,
151
+ aiGeneratedContent: aiGeneratedReadme,
152
+ format
153
+ });
122
154
 
123
155
  // Generate deployment scripts
124
156
  const deployScripts = await generateDeployScripts(appPath, finalSystemKey, systemFileName, datasourceFileNames);
125
157
 
126
158
  // Generate deployment manifest (<systemKey>-deploy.json) using controller format
127
- const { generateControllerManifest } = require('./external-controller-manifest');
159
+ const { generateControllerManifest, toDeployJsonShape } = require('./external-controller-manifest');
128
160
  const manifest = await generateControllerManifest(appName, { appPath });
161
+ const deployJson = toDeployJsonShape(manifest);
129
162
  const deployManifestPath = path.join(appPath, `${finalSystemKey}-deploy.json`);
130
- await fs.writeFile(deployManifestPath, JSON.stringify(manifest, null, 2), 'utf8');
163
+ await fs.writeFile(deployManifestPath, JSON.stringify(deployJson, null, 2), 'utf8');
131
164
  logger.log(chalk.green(`✓ Generated deployment manifest: ${finalSystemKey}-deploy.json`));
132
165
 
133
166
  return {
@@ -143,24 +176,40 @@ async function prepareWizardContext(appName, systemConfig, datasourceConfigs) {
143
176
  const appPath = path.join(process.cwd(), 'integration', appName);
144
177
  await fs.mkdir(appPath, { recursive: true });
145
178
  const finalSystemKey = appName;
179
+ const originalSystemKey = systemConfig.key || finalSystemKey;
146
180
  const appDisplayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
147
181
  const updatedSystemConfig = { ...systemConfig, key: finalSystemKey, displayName: appDisplayName };
182
+ const originalPrefix = `${originalSystemKey}-`;
148
183
  const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
149
- const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || 'default';
150
- const keySegment = toKeySegment(entityType);
151
- const entityDisplayName = entityType.charAt(0).toUpperCase() + entityType.slice(1).replace(/-/g, ' ');
152
- return { ...ds, key: `${finalSystemKey}-${keySegment}`, systemKey: finalSystemKey, displayName: `${appDisplayName} ${entityDisplayName}` };
184
+ let newKey;
185
+ const dsKey = ds.key || '';
186
+ if (dsKey && dsKey.startsWith(originalPrefix)) {
187
+ newKey = `${finalSystemKey}-${dsKey.substring(originalPrefix.length)}`;
188
+ } else {
189
+ const entityType = ds.entityType || ds.entityKey || dsKey.split('-').pop() || 'default';
190
+ const keySegment = toKeySegment(entityType);
191
+ newKey = `${finalSystemKey}-${keySegment}`;
192
+ }
193
+ const entityType = ds.entityType || ds.entityKey || newKey.split('-').pop() || 'default';
194
+ const entityDisplayName = String(entityType).charAt(0).toUpperCase() + String(entityType).slice(1).replace(/-/g, ' ');
195
+ return { ...ds, key: newKey, systemKey: finalSystemKey, displayName: ds.displayName || `${appDisplayName} ${entityDisplayName}` };
153
196
  });
154
197
  return { appPath, finalSystemKey, updatedSystemConfig, updatedDatasourceConfigs, appDisplayName };
155
198
  }
156
199
 
157
200
  async function generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, options = {}) {
158
201
  try {
159
- const { aiGeneratedReadme } = options || {};
202
+ const { aiGeneratedReadme, format } = options || {};
203
+ const fmt = format === 'json' ? 'json' : 'yaml';
204
+ const ext = FORMAT_EXT[fmt] || '.yaml';
160
205
  const { appPath, finalSystemKey, updatedSystemConfig, updatedDatasourceConfigs } = await prepareWizardContext(appName, systemConfig, datasourceConfigs);
161
- const systemFilePath = await writeSystemYamlFile(appPath, finalSystemKey, updatedSystemConfig);
162
- const datasourceFileNames = await writeDatasourceYamlFiles(appPath, finalSystemKey, updatedDatasourceConfigs);
163
- const systemFileName = `${finalSystemKey}-system.yaml`;
206
+ const systemConfigWithDataSourcesKeys = {
207
+ ...updatedSystemConfig,
208
+ dataSources: updatedDatasourceConfigs.map(ds => ds.key)
209
+ };
210
+ const systemFilePath = await writeSystemYamlFile(appPath, finalSystemKey, systemConfigWithDataSourcesKeys, fmt);
211
+ const datasourceFileNames = await writeDatasourceYamlFiles(appPath, finalSystemKey, updatedDatasourceConfigs, fmt);
212
+ const systemFileName = `${finalSystemKey}-system${ext}`;
164
213
  const configFiles = await generateConfigFilesForWizard({
165
214
  appPath,
166
215
  appName,
@@ -169,7 +218,8 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
169
218
  datasourceFileNames,
170
219
  systemConfig: updatedSystemConfig,
171
220
  datasourceConfigs: updatedDatasourceConfigs,
172
- aiGeneratedReadme
221
+ aiGeneratedReadme,
222
+ format: fmt
173
223
  });
174
224
  return {
175
225
  appPath,
@@ -182,22 +232,48 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
182
232
  }
183
233
  }
184
234
 
235
+ function mergeAppAndExternalIntegration(variables, appName, systemFileName, datasourceFileNames, systemConfig) {
236
+ if (!variables.app) {
237
+ variables.app = {
238
+ key: appName,
239
+ displayName: systemConfig.displayName || appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
240
+ description: systemConfig.description || `External system integration for ${appName}`,
241
+ type: 'external',
242
+ version: '1.0.0'
243
+ };
244
+ } else {
245
+ variables.app.version = variables.app.version || '1.0.0';
246
+ }
247
+ if (!variables.deployment) {
248
+ variables.deployment = { controllerUrl: '', environment: 'dev' };
249
+ }
250
+ variables.externalIntegration = {
251
+ schemaBasePath: './',
252
+ systems: [systemFileName],
253
+ dataSources: datasourceFileNames,
254
+ autopublish: true,
255
+ version: systemConfig.version || '1.0.0'
256
+ };
257
+ }
258
+
185
259
  /**
186
- * Generate or update application.yaml with externalIntegration block
260
+ * Generate or update application config with externalIntegration block
187
261
  * @async
188
262
  * @function generateOrUpdateVariablesYaml
189
263
  * @param {Object} params - Parameters object
190
264
  * @param {string} params.appPath - Application directory path
191
265
  * @param {string} params.appName - Application name
192
- * @param {string} params.systemKey - System key
193
266
  * @param {string} params.systemFileName - System file name
194
267
  * @param {string[]} params.datasourceFileNames - Array of datasource file names
195
268
  * @param {Object} params.systemConfig - System configuration
269
+ * @param {string} [params.format] - Output format: 'yaml' (default) or 'json'
196
270
  * @returns {Promise<string>} Path to application config file
197
271
  * @throws {Error} If generation fails
198
272
  */
199
273
  async function generateOrUpdateVariablesYaml(params) {
200
- const { appPath, appName, systemFileName, datasourceFileNames, systemConfig } = params;
274
+ const { appPath, appName, systemFileName, datasourceFileNames, systemConfig, format } = params;
275
+ const fmt = format === 'json' ? 'json' : 'yaml';
276
+ const ext = FORMAT_EXT[fmt] || '.yaml';
201
277
  let configPath;
202
278
  let variables = {};
203
279
  try {
@@ -205,115 +281,88 @@ async function generateOrUpdateVariablesYaml(params) {
205
281
  configPath = resolveApplicationConfigPath(appPath);
206
282
  variables = loadConfigFile(configPath) || {};
207
283
  } catch {
208
- configPath = path.join(appPath, 'application.yaml');
284
+ configPath = path.join(appPath, `application${ext}`);
209
285
  }
210
-
211
- // Set basic app info if not present
212
- if (!variables.app) {
213
- variables.app = {
214
- key: appName,
215
- displayName: systemConfig.displayName || appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
216
- description: systemConfig.description || `External system integration for ${appName}`,
217
- type: 'external',
218
- version: '1.0.0'
219
- };
220
- } else {
221
- variables.app.version = variables.app.version || '1.0.0';
222
- }
223
-
224
- // Set deployment config if not present
225
- if (!variables.deployment) {
226
- variables.deployment = {
227
- controllerUrl: '',
228
- environment: 'dev'
229
- };
286
+ mergeAppAndExternalIntegration(variables, appName, systemFileName, datasourceFileNames, systemConfig);
287
+ const targetPath = path.join(appPath, `application${ext}`);
288
+ writeConfigFile(targetPath, variables, fmt);
289
+ if (path.normalize(configPath) !== path.normalize(targetPath)) {
290
+ const fsSync = require('fs');
291
+ if (fsSync.existsSync(configPath)) fsSync.unlinkSync(configPath);
230
292
  }
231
-
232
- // Add or update externalIntegration block
233
- variables.externalIntegration = {
234
- schemaBasePath: './',
235
- systems: [systemFileName],
236
- dataSources: datasourceFileNames,
237
- autopublish: true,
238
- version: systemConfig.version || '1.0.0'
239
- };
240
-
241
- writeConfigFile(configPath, variables);
242
- logger.log(chalk.green('✓ Generated/updated application.yaml'));
243
- return configPath;
293
+ logger.log(chalk.green(`✓ Generated/updated application${ext}`));
294
+ return targetPath;
244
295
  } catch (error) {
245
296
  throw new Error(`Failed to generate application config: ${error.message}`);
246
297
  }
247
298
  }
248
299
 
249
300
  /**
250
- * Adds API key authentication lines to env template
251
- * @function addApiKeyAuthLines
301
+ * Adds API key authentication lines with KV_* convention
252
302
  * @param {Array<string>} lines - Lines array to append to
303
+ * @param {string} prefix - KV prefix (e.g. HUBSPOT)
253
304
  */
254
- function addApiKeyAuthLines(lines) {
305
+ function addApiKeyAuthLines(lines, prefix) {
255
306
  lines.push('# API Key Authentication');
256
- lines.push('API_KEY=kv://secrets/api-key');
307
+ lines.push(`KV_${prefix}_APIKEY=`);
257
308
  lines.push('');
258
309
  }
259
310
 
260
311
  /**
261
- * Adds OAuth2 authentication lines to env template
262
- * @function addOAuth2AuthLines
312
+ * Adds OAuth2 authentication lines with KV_* convention
263
313
  * @param {Array<string>} lines - Lines array to append to
264
314
  * @param {Object} auth - Authentication configuration
315
+ * @param {string} prefix - KV prefix
265
316
  */
266
- function addOAuth2AuthLines(lines, auth) {
317
+ function addOAuth2AuthLines(lines, auth, prefix) {
267
318
  lines.push('# OAuth2 Authentication');
268
- lines.push('CLIENT_ID=kv://secrets/client-id');
269
- lines.push('CLIENT_SECRET=kv://secrets/client-secret');
270
- lines.push('AUTH_URL=kv://secrets/auth-url');
271
- lines.push('TOKEN_URL=kv://secrets/token-url');
272
- if (auth.scope) {
273
- lines.push(`SCOPE=${auth.scope}`);
274
- }
319
+ lines.push(`KV_${prefix}_CLIENTID=`);
320
+ lines.push(`KV_${prefix}_CLIENTSECRET=`);
321
+ if (auth.scope) lines.push(`SCOPE=${auth.scope}`);
275
322
  lines.push('');
276
323
  }
277
324
 
278
325
  /**
279
- * Adds bearer token authentication lines to env template
280
- * @function addBearerTokenAuthLines
326
+ * Adds bearer token authentication lines with KV_* convention
281
327
  * @param {Array<string>} lines - Lines array to append to
328
+ * @param {string} prefix - KV prefix
282
329
  */
283
- function addBearerTokenAuthLines(lines) {
330
+ function addBearerTokenAuthLines(lines, prefix) {
284
331
  lines.push('# Bearer Token Authentication');
285
- lines.push('BEARER_TOKEN=kv://secrets/bearer-token');
332
+ lines.push(`KV_${prefix}_BEARERTOKEN=`);
286
333
  lines.push('');
287
334
  }
288
335
 
289
336
  /**
290
- * Adds basic authentication lines to env template
291
- * @function addBasicAuthLines
337
+ * Adds basic authentication lines with KV_* convention
292
338
  * @param {Array<string>} lines - Lines array to append to
339
+ * @param {string} prefix - KV prefix
293
340
  */
294
- function addBasicAuthLines(lines) {
341
+ function addBasicAuthLines(lines, prefix) {
295
342
  lines.push('# Basic Authentication');
296
- lines.push('USERNAME=kv://secrets/username');
297
- lines.push('PASSWORD=kv://secrets/password');
343
+ lines.push(`KV_${prefix}_USERNAME=`);
344
+ lines.push(`KV_${prefix}_PASSWORD=`);
298
345
  lines.push('');
299
346
  }
300
347
 
301
348
  /**
302
- * Adds authentication lines based on auth type
303
- * @function addAuthenticationLines
349
+ * Adds authentication lines based on auth type. Uses KV_<APPKEY>_<VAR> convention.
304
350
  * @param {Array<string>} lines - Lines array to append to
305
351
  * @param {Object} auth - Authentication configuration
306
352
  * @param {string} authType - Authentication type
353
+ * @param {string} systemKey - System key (e.g. hubspot) for KV_ prefix
307
354
  */
308
- function addAuthenticationLines(lines, auth, authType) {
355
+ function addAuthenticationLines(lines, auth, authType, systemKey) {
356
+ const prefix = systemKeyToKvPrefix(systemKey);
357
+ if (!prefix) return;
309
358
  if (authType === 'apikey' || authType === 'apiKey') {
310
- addApiKeyAuthLines(lines);
359
+ addApiKeyAuthLines(lines, prefix);
311
360
  } else if (authType === 'oauth2' || authType === 'oauth') {
312
- addOAuth2AuthLines(lines, auth);
361
+ addOAuth2AuthLines(lines, auth || {}, prefix);
313
362
  } else if (authType === 'bearer' || authType === 'token') {
314
- addBearerTokenAuthLines(lines);
363
+ addBearerTokenAuthLines(lines, prefix);
315
364
  } else if (authType === 'basic') {
316
- addBasicAuthLines(lines);
365
+ addBasicAuthLines(lines, prefix);
317
366
  }
318
367
  }
319
368
 
@@ -332,23 +381,24 @@ function addBaseUrlLines(lines, systemConfig) {
332
381
  }
333
382
 
334
383
  /**
335
- * Generate env.template with authentication variables
384
+ * Generate env.template with KV_* authentication variables
336
385
  * @async
337
386
  * @function generateEnvTemplate
338
387
  * @param {string} appPath - Application directory path
339
- * @param {Object} systemConfig - System configuration
388
+ * @param {Object} systemConfig - System configuration (must have key for systemKey)
389
+ * @param {string} [finalSystemKey] - Final system key for KV_ prefix (default: systemConfig.key)
340
390
  * @throws {Error} If generation fails
341
391
  */
342
- async function generateEnvTemplate(appPath, systemConfig) {
392
+ async function generateEnvTemplate(appPath, systemConfig, finalSystemKey) {
343
393
  try {
344
394
  const envTemplatePath = path.join(appPath, 'env.template');
345
- const lines = ['# Environment variables for external system integration', ''];
395
+ const systemKey = finalSystemKey || systemConfig?.key;
396
+ const lines = ['# Environment variables for external system integration', '# Use KV_* for credential push (aifabrix credential push)', ''];
346
397
 
347
- // Extract authentication variables from system config
348
- const auth = systemConfig.authentication || systemConfig.auth || {};
398
+ const auth = systemConfig?.authentication || systemConfig?.auth || {};
349
399
  const authType = auth.type || auth.authType || 'apikey';
350
400
 
351
- addAuthenticationLines(lines, auth, authType);
401
+ addAuthenticationLines(lines, auth, authType, systemKey);
352
402
  addBaseUrlLines(lines, systemConfig);
353
403
 
354
404
  await fs.writeFile(envTemplatePath, lines.join('\n'), 'utf8');
@@ -393,59 +443,6 @@ async function generateDeployScripts(appPath, systemKey, systemFileName, datasou
393
443
  }
394
444
  }
395
445
 
396
- /**
397
- * Generate README.md with basic documentation
398
- * @async
399
- * @function generateReadme
400
- * @param {string} appPath - Application directory path
401
- * @param {string} appName - Application name
402
- * @param {string} systemKey - System key
403
- * @param {Object} systemConfig - System configuration
404
- * @param {Object[]} datasourceConfigs - Array of datasource configurations
405
- * @param {string} [aiGeneratedContent] - Optional AI-generated README content from dataplane
406
- * @throws {Error} If generation fails
407
- */
408
- async function generateReadme(appPath, appName, systemKey, systemConfig, datasourceConfigs, aiGeneratedContent) {
409
- try {
410
- const readmePath = path.join(appPath, 'README.md');
411
-
412
- // Use AI-generated content if available, otherwise generate basic README
413
- if (aiGeneratedContent) {
414
- await fs.writeFile(readmePath, aiGeneratedContent, 'utf8');
415
- logger.log(chalk.green('✓ Generated README.md (AI-generated from dataplane)'));
416
- return;
417
- }
418
-
419
- const datasources = (Array.isArray(datasourceConfigs) ? datasourceConfigs : []).map((ds, index) => {
420
- const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || `datasource${index + 1}`;
421
- const keySegment = toKeySegment(entityType);
422
- const datasourceKey = ds.key || `${systemKey}-${keySegment}`;
423
- const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${systemKey}-`)
424
- ? datasourceKey.substring(systemKey.length + 1)
425
- : keySegment;
426
- return {
427
- entityType,
428
- displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
429
- fileName: `${systemKey}-datasource-${datasourceKeyOnly}.yaml`
430
- };
431
- });
432
-
433
- const readmeContent = generateExternalReadmeContent({
434
- appName,
435
- systemKey,
436
- systemType: systemConfig.type || systemConfig.systemType,
437
- displayName: systemConfig.displayName,
438
- description: systemConfig.description,
439
- datasources
440
- });
441
-
442
- await fs.writeFile(readmePath, readmeContent, 'utf8');
443
- logger.log(chalk.green('✓ Generated README.md (template)'));
444
- } catch (error) {
445
- throw new Error(`Failed to generate README.md: ${error.message}`);
446
- }
447
- }
448
-
449
446
  module.exports = {
450
447
  generateWizardFiles,
451
448
  generateDeployScripts
@@ -63,6 +63,8 @@ function validateTraefikConfig(traefikConfig) {
63
63
  * @param {string} infraDir - Infrastructure directory
64
64
  * @param {Object} [options] - Additional options
65
65
  * @param {Object|boolean} [options.traefik] - Traefik configuration
66
+ * @param {Object} [options.pgadmin] - pgAdmin config { enabled: boolean }
67
+ * @param {Object} [options.redisCommander] - Redis Commander config { enabled: boolean }
66
68
  * @returns {string} Path to generated compose file
67
69
  */
68
70
  function generateComposeFile(templatePath, devId, idNum, ports, infraDir, options = {}) {
@@ -74,6 +76,12 @@ function generateComposeFile(templatePath, devId, idNum, ports, infraDir, option
74
76
  const traefikConfig = typeof options.traefik === 'object'
75
77
  ? options.traefik
76
78
  : buildTraefikConfig(!!options.traefik);
79
+ const pgadminConfig = options.pgadmin && typeof options.pgadmin.enabled === 'boolean'
80
+ ? options.pgadmin
81
+ : { enabled: true };
82
+ const redisCommanderConfig = options.redisCommander && typeof options.redisCommander.enabled === 'boolean'
83
+ ? options.redisCommander
84
+ : { enabled: true };
77
85
  const composeContent = template({
78
86
  devId: devId,
79
87
  postgresPort: ports.postgres,
@@ -86,7 +94,9 @@ function generateComposeFile(templatePath, devId, idNum, ports, infraDir, option
86
94
  serversJsonPath: serversJsonPath,
87
95
  pgpassPath: pgpassPath,
88
96
  infraDir: infraDir,
89
- traefik: traefikConfig
97
+ traefik: traefikConfig,
98
+ pgadmin: pgadminConfig,
99
+ redisCommander: redisCommanderConfig
90
100
  });
91
101
  const composePath = path.join(infraDir, 'compose.yaml');
92
102
  fs.writeFileSync(composePath, composeContent);