@aifabrix/builder 2.41.0 → 2.42.1

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 (142) hide show
  1. package/.cursor/rules/docs-rules.mdc +30 -0
  2. package/README.md +2 -2
  3. package/integration/hubspot/README.md +11 -5
  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/jest.config.manual.js +2 -1
  16. package/lib/api/external-test.api.js +111 -0
  17. package/lib/api/index.js +42 -19
  18. package/lib/api/pipeline.api.js +66 -120
  19. package/lib/api/types/pipeline.types.js +37 -0
  20. package/lib/api/wizard-platform.api.js +61 -0
  21. package/lib/api/wizard.api.js +36 -2
  22. package/lib/app/config.js +23 -11
  23. package/lib/app/index.js +5 -3
  24. package/lib/app/prompts.js +46 -31
  25. package/lib/app/readme.js +11 -4
  26. package/lib/app/run-env-compose.js +64 -1
  27. package/lib/app/run-helpers.js +1 -1
  28. package/lib/app/show-display.js +1 -1
  29. package/lib/cli/setup-app.js +45 -14
  30. package/lib/cli/setup-credential-deployment.js +31 -6
  31. package/lib/cli/setup-dev.js +27 -0
  32. package/lib/cli/setup-environment.js +12 -4
  33. package/lib/cli/setup-external-system.js +19 -4
  34. package/lib/cli/setup-infra.js +54 -14
  35. package/lib/cli/setup-utility.js +117 -21
  36. package/lib/commands/auth-config.js +22 -12
  37. package/lib/commands/credential-env.js +162 -0
  38. package/lib/commands/credential-list.js +17 -22
  39. package/lib/commands/credential-push.js +96 -0
  40. package/lib/commands/datasource.js +77 -6
  41. package/lib/commands/dev-init.js +39 -1
  42. package/lib/commands/repair-auth-config.js +99 -0
  43. package/lib/commands/repair-datasource-keys.js +208 -0
  44. package/lib/commands/repair-datasource.js +235 -0
  45. package/lib/commands/repair-env-template.js +348 -0
  46. package/lib/commands/repair-internal.js +85 -0
  47. package/lib/commands/repair-rbac.js +158 -0
  48. package/lib/commands/repair.js +518 -0
  49. package/lib/commands/secrets-set.js +6 -0
  50. package/lib/commands/test-e2e-external.js +165 -0
  51. package/lib/commands/up-dataplane.js +90 -6
  52. package/lib/commands/upload.js +71 -40
  53. package/lib/commands/wizard-core-helpers.js +230 -5
  54. package/lib/commands/wizard-core.js +68 -29
  55. package/lib/commands/wizard-dataplane.js +1 -1
  56. package/lib/commands/wizard-entity-selection.js +43 -0
  57. package/lib/commands/wizard-headless.js +49 -5
  58. package/lib/commands/wizard-helpers.js +7 -3
  59. package/lib/commands/wizard.js +93 -64
  60. package/lib/core/config.js +7 -1
  61. package/lib/core/secrets.js +33 -12
  62. package/lib/datasource/deploy.js +12 -3
  63. package/lib/datasource/test-e2e.js +219 -0
  64. package/lib/datasource/test-integration.js +154 -0
  65. package/lib/deployment/deployer.js +7 -5
  66. package/lib/external-system/download-helpers.js +3 -1
  67. package/lib/external-system/download.js +182 -204
  68. package/lib/external-system/generator.js +204 -56
  69. package/lib/external-system/test-execution.js +2 -1
  70. package/lib/external-system/test-system-level.js +73 -0
  71. package/lib/external-system/test.js +51 -18
  72. package/lib/generator/external-controller-manifest.js +29 -2
  73. package/lib/generator/external-schema-utils.js +4 -2
  74. package/lib/generator/external.js +10 -3
  75. package/lib/generator/index.js +4 -1
  76. package/lib/generator/split-readme.js +1 -0
  77. package/lib/generator/split-variables.js +7 -1
  78. package/lib/generator/split.js +194 -54
  79. package/lib/generator/wizard-prompts-secondary.js +326 -0
  80. package/lib/generator/wizard-prompts.js +105 -106
  81. package/lib/generator/wizard-readme.js +91 -0
  82. package/lib/generator/wizard.js +180 -179
  83. package/lib/infrastructure/compose.js +11 -1
  84. package/lib/infrastructure/index.js +11 -3
  85. package/lib/infrastructure/services.js +22 -11
  86. package/lib/schema/application-schema.json +8 -5
  87. package/lib/schema/external-datasource.schema.json +49 -26
  88. package/lib/schema/external-system.schema.json +82 -6
  89. package/lib/schema/wizard-config.schema.json +23 -1
  90. package/lib/utils/api.js +38 -10
  91. package/lib/utils/auth-headers.js +8 -7
  92. package/lib/utils/compose-generator.js +1 -1
  93. package/lib/utils/compose-handlebars-helpers.js +11 -0
  94. package/lib/utils/config-format-preference.js +51 -0
  95. package/lib/utils/config-format.js +36 -0
  96. package/lib/utils/configuration-env-resolver.js +179 -0
  97. package/lib/utils/credential-display.js +83 -0
  98. package/lib/utils/credential-secrets-env.js +115 -25
  99. package/lib/utils/dataplane-pipeline-warning.js +28 -0
  100. package/lib/utils/deployment-validation-helpers.js +4 -4
  101. package/lib/utils/dev-ca-install.js +139 -0
  102. package/lib/utils/env-copy.js +23 -3
  103. package/lib/utils/error-formatters/http-status-errors.js +0 -1
  104. package/lib/utils/error-formatters/permission-errors.js +0 -1
  105. package/lib/utils/error-formatters/validation-errors.js +0 -1
  106. package/lib/utils/external-readme.js +89 -30
  107. package/lib/utils/external-system-display.js +59 -1
  108. package/lib/utils/external-system-test-helpers.js +21 -8
  109. package/lib/utils/external-system-validators.js +3 -0
  110. package/lib/utils/file-upload.js +20 -50
  111. package/lib/utils/help-builder.js +1 -0
  112. package/lib/utils/infra-status.js +50 -44
  113. package/lib/utils/local-secrets.js +5 -5
  114. package/lib/utils/paths.js +85 -4
  115. package/lib/utils/secrets-canonical.js +93 -0
  116. package/lib/utils/secrets-generator.js +20 -0
  117. package/lib/utils/secrets-helpers.js +75 -89
  118. package/lib/utils/test-log-writer.js +56 -0
  119. package/lib/utils/token-manager.js +24 -32
  120. package/lib/validation/env-template-auth.js +157 -0
  121. package/lib/validation/env-template-kv.js +41 -0
  122. package/lib/validation/external-manifest-validator.js +25 -0
  123. package/lib/validation/external-system-auth-rules.js +86 -0
  124. package/lib/validation/validate-batch.js +149 -0
  125. package/lib/validation/validate-datasource-keys-api.js +33 -0
  126. package/lib/validation/validate-display.js +94 -16
  127. package/lib/validation/validate.js +25 -12
  128. package/lib/validation/validator.js +7 -9
  129. package/lib/validation/wizard-datasource-validation.js +50 -0
  130. package/package.json +7 -2
  131. package/templates/applications/dataplane/application.yaml +1 -1
  132. package/templates/applications/dataplane/env.template +5 -5
  133. package/templates/applications/dataplane/rbac.yaml +2 -2
  134. package/templates/applications/miso-controller/env.template +1 -1
  135. package/templates/external-system/README.md.hbs +75 -22
  136. package/templates/external-system/deploy.js.hbs +4 -2
  137. package/templates/external-system/external-datasource.yaml.hbs +217 -0
  138. package/templates/external-system/external-system.json.hbs +1 -18
  139. package/templates/infra/compose.yaml.hbs +6 -0
  140. package/templates/python/docker-compose.hbs +4 -4
  141. package/templates/typescript/docker-compose.hbs +4 -4
  142. 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, securityKeyToVar, isValidKvPath } = 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,20 +117,21 @@ 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);
119
135
 
120
136
  const envTemplatePath = path.join(appPath, 'env.template');
121
137
  try {
@@ -126,16 +142,25 @@ async function generateConfigFilesForWizard(params) {
126
142
  }
127
143
 
128
144
  // Generate README.md (use AI-generated content if available)
129
- 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
+ });
130
154
 
131
155
  // Generate deployment scripts
132
156
  const deployScripts = await generateDeployScripts(appPath, finalSystemKey, systemFileName, datasourceFileNames);
133
157
 
134
158
  // Generate deployment manifest (<systemKey>-deploy.json) using controller format
135
- const { generateControllerManifest } = require('./external-controller-manifest');
159
+ const { generateControllerManifest, toDeployJsonShape } = require('./external-controller-manifest');
136
160
  const manifest = await generateControllerManifest(appName, { appPath });
161
+ const deployJson = toDeployJsonShape(manifest);
137
162
  const deployManifestPath = path.join(appPath, `${finalSystemKey}-deploy.json`);
138
- await fs.writeFile(deployManifestPath, JSON.stringify(manifest, null, 2), 'utf8');
163
+ await fs.writeFile(deployManifestPath, JSON.stringify(deployJson, null, 2), 'utf8');
139
164
  logger.log(chalk.green(`✓ Generated deployment manifest: ${finalSystemKey}-deploy.json`));
140
165
 
141
166
  return {
@@ -151,24 +176,40 @@ async function prepareWizardContext(appName, systemConfig, datasourceConfigs) {
151
176
  const appPath = path.join(process.cwd(), 'integration', appName);
152
177
  await fs.mkdir(appPath, { recursive: true });
153
178
  const finalSystemKey = appName;
179
+ const originalSystemKey = systemConfig.key || finalSystemKey;
154
180
  const appDisplayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
155
181
  const updatedSystemConfig = { ...systemConfig, key: finalSystemKey, displayName: appDisplayName };
182
+ const originalPrefix = `${originalSystemKey}-`;
156
183
  const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
157
- const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || 'default';
158
- const keySegment = toKeySegment(entityType);
159
- const entityDisplayName = entityType.charAt(0).toUpperCase() + entityType.slice(1).replace(/-/g, ' ');
160
- 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}` };
161
196
  });
162
197
  return { appPath, finalSystemKey, updatedSystemConfig, updatedDatasourceConfigs, appDisplayName };
163
198
  }
164
199
 
165
200
  async function generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, options = {}) {
166
201
  try {
167
- const { aiGeneratedReadme } = options || {};
202
+ const { aiGeneratedReadme, format } = options || {};
203
+ const fmt = format === 'json' ? 'json' : 'yaml';
204
+ const ext = FORMAT_EXT[fmt] || '.yaml';
168
205
  const { appPath, finalSystemKey, updatedSystemConfig, updatedDatasourceConfigs } = await prepareWizardContext(appName, systemConfig, datasourceConfigs);
169
- const systemFilePath = await writeSystemYamlFile(appPath, finalSystemKey, updatedSystemConfig);
170
- const datasourceFileNames = await writeDatasourceYamlFiles(appPath, finalSystemKey, updatedDatasourceConfigs);
171
- 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}`;
172
213
  const configFiles = await generateConfigFilesForWizard({
173
214
  appPath,
174
215
  appName,
@@ -177,7 +218,8 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
177
218
  datasourceFileNames,
178
219
  systemConfig: updatedSystemConfig,
179
220
  datasourceConfigs: updatedDatasourceConfigs,
180
- aiGeneratedReadme
221
+ aiGeneratedReadme,
222
+ format: fmt
181
223
  });
182
224
  return {
183
225
  appPath,
@@ -190,22 +232,48 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
190
232
  }
191
233
  }
192
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
+
193
259
  /**
194
- * Generate or update application.yaml with externalIntegration block
260
+ * Generate or update application config with externalIntegration block
195
261
  * @async
196
262
  * @function generateOrUpdateVariablesYaml
197
263
  * @param {Object} params - Parameters object
198
264
  * @param {string} params.appPath - Application directory path
199
265
  * @param {string} params.appName - Application name
200
- * @param {string} params.systemKey - System key
201
266
  * @param {string} params.systemFileName - System file name
202
267
  * @param {string[]} params.datasourceFileNames - Array of datasource file names
203
268
  * @param {Object} params.systemConfig - System configuration
269
+ * @param {string} [params.format] - Output format: 'yaml' (default) or 'json'
204
270
  * @returns {Promise<string>} Path to application config file
205
271
  * @throws {Error} If generation fails
206
272
  */
207
273
  async function generateOrUpdateVariablesYaml(params) {
208
- 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';
209
277
  let configPath;
210
278
  let variables = {};
211
279
  try {
@@ -213,115 +281,100 @@ async function generateOrUpdateVariablesYaml(params) {
213
281
  configPath = resolveApplicationConfigPath(appPath);
214
282
  variables = loadConfigFile(configPath) || {};
215
283
  } catch {
216
- configPath = path.join(appPath, 'application.yaml');
284
+ configPath = path.join(appPath, `application${ext}`);
217
285
  }
218
-
219
- // Set basic app info if not present
220
- if (!variables.app) {
221
- variables.app = {
222
- key: appName,
223
- displayName: systemConfig.displayName || appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
224
- description: systemConfig.description || `External system integration for ${appName}`,
225
- type: 'external',
226
- version: '1.0.0'
227
- };
228
- } else {
229
- variables.app.version = variables.app.version || '1.0.0';
230
- }
231
-
232
- // Set deployment config if not present
233
- if (!variables.deployment) {
234
- variables.deployment = {
235
- controllerUrl: '',
236
- environment: 'dev'
237
- };
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);
238
292
  }
239
-
240
- // Add or update externalIntegration block
241
- variables.externalIntegration = {
242
- schemaBasePath: './',
243
- systems: [systemFileName],
244
- dataSources: datasourceFileNames,
245
- autopublish: true,
246
- version: systemConfig.version || '1.0.0'
247
- };
248
-
249
- writeConfigFile(configPath, variables);
250
- logger.log(chalk.green('✓ Generated/updated application.yaml'));
251
- return configPath;
293
+ logger.log(chalk.green(`✓ Generated/updated application${ext}`));
294
+ return targetPath;
252
295
  } catch (error) {
253
296
  throw new Error(`Failed to generate application config: ${error.message}`);
254
297
  }
255
298
  }
256
299
 
257
- /**
258
- * Adds API key authentication lines to env template
259
- * @function addApiKeyAuthLines
260
- * @param {Array<string>} lines - Lines array to append to
261
- */
262
- function addApiKeyAuthLines(lines) {
263
- lines.push('# API Key Authentication');
264
- lines.push('API_KEY=kv://secrets/api-key');
265
- lines.push('');
300
+ /** Path-style kv value: kv://systemKey/key (camelCase key). */
301
+ function toPathStyleKv(systemKey, key) {
302
+ return `kv://${systemKey}/${key}`;
266
303
  }
267
304
 
268
305
  /**
269
- * Adds OAuth2 authentication lines to env template
270
- * @function addOAuth2AuthLines
306
+ * Adds env.template lines from authentication.security (path-style kv:// values).
271
307
  * @param {Array<string>} lines - Lines array to append to
272
- * @param {Object} auth - Authentication configuration
308
+ * @param {Object} security - authentication.security object
309
+ * @param {string} systemKey - System key for path namespace
310
+ * @param {string} prefix - KV prefix (e.g. HUBSPOT)
273
311
  */
274
- function addOAuth2AuthLines(lines, auth) {
275
- lines.push('# OAuth2 Authentication');
276
- lines.push('CLIENT_ID=kv://secrets/client-id');
277
- lines.push('CLIENT_SECRET=kv://secrets/client-secret');
278
- lines.push('AUTH_URL=kv://secrets/auth-url');
279
- lines.push('TOKEN_URL=kv://secrets/token-url');
280
- if (auth.scope) {
281
- lines.push(`SCOPE=${auth.scope}`);
312
+ function addLinesFromSecurity(lines, security, systemKey, prefix) {
313
+ if (!security || typeof security !== 'object') return;
314
+ for (const key of Object.keys(security)) {
315
+ const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
316
+ let value = security[key];
317
+ if (typeof value === 'string' && value.trim().startsWith('kv://') && isValidKvPath(value.trim())) {
318
+ value = value.trim();
319
+ } else {
320
+ value = toPathStyleKv(systemKey, key);
321
+ }
322
+ lines.push(`${envName}=${value}`);
282
323
  }
283
- lines.push('');
324
+ if (Object.keys(security).length > 0) lines.push('');
284
325
  }
285
326
 
286
- /**
287
- * Adds bearer token authentication lines to env template
288
- * @function addBearerTokenAuthLines
289
- * @param {Array<string>} lines - Lines array to append to
290
- */
291
- function addBearerTokenAuthLines(lines) {
292
- lines.push('# Bearer Token Authentication');
293
- lines.push('BEARER_TOKEN=kv://secrets/bearer-token');
294
- lines.push('');
295
- }
327
+ /** Fallback security keys by auth method (camelCase) for path-style kv://. */
328
+ const FALLBACK_SECURITY_BY_AUTH = {
329
+ oauth2: ['clientId', 'clientSecret'],
330
+ oauth: ['clientId', 'clientSecret'],
331
+ aad: ['clientId', 'clientSecret'],
332
+ apikey: ['apiKey'],
333
+ apiKey: ['apiKey'],
334
+ basic: ['username', 'password'],
335
+ queryParam: ['paramValue'],
336
+ oidc: [],
337
+ hmac: ['signingSecret'],
338
+ bearer: ['bearerToken'],
339
+ token: ['bearerToken'],
340
+ none: []
341
+ };
296
342
 
297
343
  /**
298
- * Adds basic authentication lines to env template
299
- * @function addBasicAuthLines
344
+ * Adds auth lines by type with path-style kv:// values (fallback when security is absent).
300
345
  * @param {Array<string>} lines - Lines array to append to
346
+ * @param {string} authType - Authentication type
347
+ * @param {string} systemKey - System key
348
+ * @param {string} prefix - KV prefix
301
349
  */
302
- function addBasicAuthLines(lines) {
303
- lines.push('# Basic Authentication');
304
- lines.push('USERNAME=kv://secrets/username');
305
- lines.push('PASSWORD=kv://secrets/password');
350
+ function addFallbackAuthLines(lines, authType, systemKey, prefix) {
351
+ const keys = FALLBACK_SECURITY_BY_AUTH[authType] || FALLBACK_SECURITY_BY_AUTH.apikey;
352
+ if (keys.length === 0) return;
353
+ const labels = { oauth2: 'OAuth2', aad: 'Azure AD', apikey: 'API Key', basic: 'Basic', queryParam: 'Query Param', hmac: 'HMAC', bearer: 'Bearer Token' };
354
+ const label = labels[authType] || authType;
355
+ lines.push(`# ${label} Authentication`);
356
+ for (const key of keys) {
357
+ lines.push(`KV_${prefix}_${securityKeyToVar(key)}=${toPathStyleKv(systemKey, key)}`);
358
+ }
306
359
  lines.push('');
307
360
  }
308
361
 
309
362
  /**
310
- * Adds authentication lines based on auth type
311
- * @function addAuthenticationLines
363
+ * Adds authentication lines: from authentication.security when present, else by auth type with path-style kv://.
312
364
  * @param {Array<string>} lines - Lines array to append to
313
- * @param {Object} auth - Authentication configuration
314
- * @param {string} authType - Authentication type
365
+ * @param {Object} auth - Authentication configuration (may have security, type/method)
366
+ * @param {string} authType - Normalized auth type
367
+ * @param {string} systemKey - System key for KV path namespace
315
368
  */
316
- function addAuthenticationLines(lines, auth, authType) {
317
- if (authType === 'apikey' || authType === 'apiKey') {
318
- addApiKeyAuthLines(lines);
319
- } else if (authType === 'oauth2' || authType === 'oauth') {
320
- addOAuth2AuthLines(lines, auth);
321
- } else if (authType === 'bearer' || authType === 'token') {
322
- addBearerTokenAuthLines(lines);
323
- } else if (authType === 'basic') {
324
- addBasicAuthLines(lines);
369
+ function addAuthenticationLines(lines, auth, authType, systemKey) {
370
+ const prefix = systemKeyToKvPrefix(systemKey);
371
+ if (!prefix) return;
372
+ const security = auth?.security;
373
+ if (security && typeof security === 'object' && Object.keys(security).length > 0) {
374
+ lines.push('# Authentication (from security)');
375
+ addLinesFromSecurity(lines, security, systemKey, prefix);
376
+ } else {
377
+ addFallbackAuthLines(lines, authType, systemKey, prefix);
325
378
  }
326
379
  }
327
380
 
@@ -340,23 +393,24 @@ function addBaseUrlLines(lines, systemConfig) {
340
393
  }
341
394
 
342
395
  /**
343
- * Generate env.template with authentication variables
396
+ * Generate env.template with KV_* authentication variables
344
397
  * @async
345
398
  * @function generateEnvTemplate
346
399
  * @param {string} appPath - Application directory path
347
- * @param {Object} systemConfig - System configuration
400
+ * @param {Object} systemConfig - System configuration (must have key for systemKey)
401
+ * @param {string} [finalSystemKey] - Final system key for KV_ prefix (default: systemConfig.key)
348
402
  * @throws {Error} If generation fails
349
403
  */
350
- async function generateEnvTemplate(appPath, systemConfig) {
404
+ async function generateEnvTemplate(appPath, systemConfig, finalSystemKey) {
351
405
  try {
352
406
  const envTemplatePath = path.join(appPath, 'env.template');
353
- const lines = ['# Environment variables for external system integration', ''];
407
+ const systemKey = finalSystemKey || systemConfig?.key;
408
+ const lines = ['# Environment variables for external system integration', '# Use KV_* for credential push (aifabrix credential push)', ''];
354
409
 
355
- // Extract authentication variables from system config
356
- const auth = systemConfig.authentication || systemConfig.auth || {};
357
- const authType = auth.type || auth.authType || 'apikey';
410
+ const auth = systemConfig?.authentication || systemConfig?.auth || {};
411
+ const authType = (auth.type || auth.method || auth.authType || 'apikey').toLowerCase();
358
412
 
359
- addAuthenticationLines(lines, auth, authType);
413
+ addAuthenticationLines(lines, auth, authType, systemKey);
360
414
  addBaseUrlLines(lines, systemConfig);
361
415
 
362
416
  await fs.writeFile(envTemplatePath, lines.join('\n'), 'utf8');
@@ -401,59 +455,6 @@ async function generateDeployScripts(appPath, systemKey, systemFileName, datasou
401
455
  }
402
456
  }
403
457
 
404
- /**
405
- * Generate README.md with basic documentation
406
- * @async
407
- * @function generateReadme
408
- * @param {string} appPath - Application directory path
409
- * @param {string} appName - Application name
410
- * @param {string} systemKey - System key
411
- * @param {Object} systemConfig - System configuration
412
- * @param {Object[]} datasourceConfigs - Array of datasource configurations
413
- * @param {string} [aiGeneratedContent] - Optional AI-generated README content from dataplane
414
- * @throws {Error} If generation fails
415
- */
416
- async function generateReadme(appPath, appName, systemKey, systemConfig, datasourceConfigs, aiGeneratedContent) {
417
- try {
418
- const readmePath = path.join(appPath, 'README.md');
419
-
420
- // Use AI-generated content if available, otherwise generate basic README
421
- if (aiGeneratedContent) {
422
- await fs.writeFile(readmePath, aiGeneratedContent, 'utf8');
423
- logger.log(chalk.green('✓ Generated README.md (AI-generated from dataplane)'));
424
- return;
425
- }
426
-
427
- const datasources = (Array.isArray(datasourceConfigs) ? datasourceConfigs : []).map((ds, index) => {
428
- const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || `datasource${index + 1}`;
429
- const keySegment = toKeySegment(entityType);
430
- const datasourceKey = ds.key || `${systemKey}-${keySegment}`;
431
- const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${systemKey}-`)
432
- ? datasourceKey.substring(systemKey.length + 1)
433
- : keySegment;
434
- return {
435
- entityType,
436
- displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
437
- fileName: `${systemKey}-datasource-${datasourceKeyOnly}.yaml`
438
- };
439
- });
440
-
441
- const readmeContent = generateExternalReadmeContent({
442
- appName,
443
- systemKey,
444
- systemType: systemConfig.type || systemConfig.systemType,
445
- displayName: systemConfig.displayName,
446
- description: systemConfig.description,
447
- datasources
448
- });
449
-
450
- await fs.writeFile(readmePath, readmeContent, 'utf8');
451
- logger.log(chalk.green('✓ Generated README.md (template)'));
452
- } catch (error) {
453
- throw new Error(`Failed to generate README.md: ${error.message}`);
454
- }
455
- }
456
-
457
458
  module.exports = {
458
459
  generateWizardFiles,
459
460
  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);
@@ -89,7 +89,7 @@ async function prepareInfrastructureEnvironment(developerId, options = {}) {
89
89
  */
90
90
  async function startInfra(developerId = null, options = {}) {
91
91
  const { devId, idNum, ports, templatePath, infraDir } = await prepareInfrastructureEnvironment(developerId, options);
92
- const { traefik = false } = options;
92
+ const { traefik = false, pgadmin = true, redisCommander = true } = options;
93
93
  const traefikConfig = buildTraefikConfig(traefik);
94
94
  const validation = validateTraefikConfig(traefikConfig);
95
95
  if (!validation.valid) {
@@ -100,10 +100,18 @@ async function startInfra(developerId = null, options = {}) {
100
100
  registerHandlebarsHelper();
101
101
 
102
102
  // Generate compose file
103
- const composePath = generateComposeFile(templatePath, devId, idNum, ports, infraDir, { traefik: traefikConfig });
103
+ const composePath = generateComposeFile(templatePath, devId, idNum, ports, infraDir, {
104
+ traefik: traefikConfig,
105
+ pgadmin: { enabled: !!pgadmin },
106
+ redisCommander: { enabled: !!redisCommander }
107
+ });
104
108
 
105
109
  try {
106
- await startDockerServicesAndConfigure(composePath, devId, idNum, infraDir);
110
+ await startDockerServicesAndConfigure(composePath, devId, idNum, infraDir, {
111
+ pgadmin: !!pgadmin,
112
+ redisCommander: !!redisCommander,
113
+ traefik: !!traefik
114
+ });
107
115
  } finally {
108
116
  // Keep the compose file for stop commands
109
117
  }