@aifabrix/builder 2.41.0 → 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 (138) hide show
  1. package/.cursor/rules/docs-rules.mdc +30 -0
  2. package/README.md +1 -1
  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/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 +34 -1
  22. package/lib/app/config.js +23 -11
  23. package/lib/app/index.js +3 -1
  24. package/lib/app/prompts.js +44 -29
  25. package/lib/app/readme.js +8 -3
  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 +42 -11
  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/credential-env.js +162 -0
  37. package/lib/commands/credential-list.js +17 -22
  38. package/lib/commands/credential-push.js +96 -0
  39. package/lib/commands/datasource.js +77 -6
  40. package/lib/commands/dev-init.js +39 -1
  41. package/lib/commands/repair-auth-config.js +99 -0
  42. package/lib/commands/repair-datasource-keys.js +208 -0
  43. package/lib/commands/repair-datasource.js +235 -0
  44. package/lib/commands/repair-env-template.js +348 -0
  45. package/lib/commands/repair-internal.js +85 -0
  46. package/lib/commands/repair-rbac.js +158 -0
  47. package/lib/commands/repair.js +507 -0
  48. package/lib/commands/test-e2e-external.js +165 -0
  49. package/lib/commands/upload.js +71 -40
  50. package/lib/commands/wizard-core-helpers.js +226 -4
  51. package/lib/commands/wizard-core.js +67 -29
  52. package/lib/commands/wizard-dataplane.js +1 -1
  53. package/lib/commands/wizard-entity-selection.js +43 -0
  54. package/lib/commands/wizard-headless.js +44 -5
  55. package/lib/commands/wizard-helpers.js +7 -3
  56. package/lib/commands/wizard.js +86 -64
  57. package/lib/core/config.js +7 -1
  58. package/lib/core/secrets.js +33 -12
  59. package/lib/datasource/deploy.js +12 -3
  60. package/lib/datasource/test-e2e.js +219 -0
  61. package/lib/datasource/test-integration.js +154 -0
  62. package/lib/deployment/deployer.js +7 -5
  63. package/lib/external-system/download.js +182 -204
  64. package/lib/external-system/generator.js +204 -56
  65. package/lib/external-system/test-execution.js +2 -1
  66. package/lib/external-system/test-system-level.js +73 -0
  67. package/lib/external-system/test.js +51 -18
  68. package/lib/generator/external-controller-manifest.js +29 -2
  69. package/lib/generator/external-schema-utils.js +1 -1
  70. package/lib/generator/external.js +10 -3
  71. package/lib/generator/index.js +4 -1
  72. package/lib/generator/split-readme.js +1 -0
  73. package/lib/generator/split-variables.js +7 -1
  74. package/lib/generator/split.js +194 -54
  75. package/lib/generator/wizard-prompts-secondary.js +294 -0
  76. package/lib/generator/wizard-prompts.js +105 -106
  77. package/lib/generator/wizard-readme.js +88 -0
  78. package/lib/generator/wizard.js +147 -158
  79. package/lib/infrastructure/compose.js +11 -1
  80. package/lib/infrastructure/index.js +11 -3
  81. package/lib/infrastructure/services.js +22 -11
  82. package/lib/schema/application-schema.json +8 -5
  83. package/lib/schema/external-datasource.schema.json +49 -26
  84. package/lib/schema/external-system.schema.json +82 -6
  85. package/lib/schema/wizard-config.schema.json +16 -0
  86. package/lib/utils/api.js +38 -10
  87. package/lib/utils/auth-headers.js +8 -7
  88. package/lib/utils/compose-generator.js +1 -1
  89. package/lib/utils/compose-handlebars-helpers.js +11 -0
  90. package/lib/utils/config-format-preference.js +51 -0
  91. package/lib/utils/config-format.js +36 -0
  92. package/lib/utils/configuration-env-resolver.js +179 -0
  93. package/lib/utils/credential-display.js +83 -0
  94. package/lib/utils/credential-secrets-env.js +115 -25
  95. package/lib/utils/dataplane-pipeline-warning.js +28 -0
  96. package/lib/utils/deployment-validation-helpers.js +4 -4
  97. package/lib/utils/dev-ca-install.js +139 -0
  98. package/lib/utils/env-copy.js +23 -3
  99. package/lib/utils/error-formatters/http-status-errors.js +0 -1
  100. package/lib/utils/error-formatters/permission-errors.js +0 -1
  101. package/lib/utils/error-formatters/validation-errors.js +0 -1
  102. package/lib/utils/external-readme.js +56 -29
  103. package/lib/utils/external-system-display.js +59 -1
  104. package/lib/utils/external-system-test-helpers.js +21 -8
  105. package/lib/utils/external-system-validators.js +3 -0
  106. package/lib/utils/file-upload.js +20 -50
  107. package/lib/utils/help-builder.js +1 -0
  108. package/lib/utils/infra-status.js +50 -44
  109. package/lib/utils/local-secrets.js +5 -5
  110. package/lib/utils/paths.js +85 -4
  111. package/lib/utils/secrets-canonical.js +93 -0
  112. package/lib/utils/secrets-generator.js +20 -0
  113. package/lib/utils/secrets-helpers.js +75 -89
  114. package/lib/utils/test-log-writer.js +56 -0
  115. package/lib/utils/token-manager.js +24 -32
  116. package/lib/validation/env-template-auth.js +157 -0
  117. package/lib/validation/env-template-kv.js +41 -0
  118. package/lib/validation/external-manifest-validator.js +25 -0
  119. package/lib/validation/external-system-auth-rules.js +86 -0
  120. package/lib/validation/validate-batch.js +149 -0
  121. package/lib/validation/validate-datasource-keys-api.js +33 -0
  122. package/lib/validation/validate-display.js +94 -16
  123. package/lib/validation/validate.js +25 -12
  124. package/lib/validation/validator.js +7 -9
  125. package/lib/validation/wizard-datasource-validation.js +50 -0
  126. package/package.json +7 -2
  127. package/templates/applications/dataplane/application.yaml +1 -1
  128. package/templates/applications/dataplane/env.template +5 -5
  129. package/templates/applications/dataplane/rbac.yaml +2 -2
  130. package/templates/applications/miso-controller/env.template +1 -1
  131. package/templates/external-system/README.md.hbs +65 -25
  132. package/templates/external-system/deploy.js.hbs +4 -2
  133. package/templates/external-system/external-datasource.yaml.hbs +217 -0
  134. package/templates/external-system/external-system.json.hbs +1 -18
  135. package/templates/infra/compose.yaml.hbs +6 -0
  136. package/templates/python/docker-compose.hbs +4 -4
  137. package/templates/typescript/docker-compose.hbs +4 -4
  138. 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,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,88 @@ async function generateOrUpdateVariablesYaml(params) {
213
281
  configPath = resolveApplicationConfigPath(appPath);
214
282
  variables = loadConfigFile(configPath) || {};
215
283
  } catch {
216
- configPath = path.join(appPath, 'application.yaml');
217
- }
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';
284
+ configPath = path.join(appPath, `application${ext}`);
230
285
  }
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
300
  /**
258
- * Adds API key authentication lines to env template
259
- * @function addApiKeyAuthLines
301
+ * Adds API key authentication lines with KV_* convention
260
302
  * @param {Array<string>} lines - Lines array to append to
303
+ * @param {string} prefix - KV prefix (e.g. HUBSPOT)
261
304
  */
262
- function addApiKeyAuthLines(lines) {
305
+ function addApiKeyAuthLines(lines, prefix) {
263
306
  lines.push('# API Key Authentication');
264
- lines.push('API_KEY=kv://secrets/api-key');
307
+ lines.push(`KV_${prefix}_APIKEY=`);
265
308
  lines.push('');
266
309
  }
267
310
 
268
311
  /**
269
- * Adds OAuth2 authentication lines to env template
270
- * @function addOAuth2AuthLines
312
+ * Adds OAuth2 authentication lines with KV_* convention
271
313
  * @param {Array<string>} lines - Lines array to append to
272
314
  * @param {Object} auth - Authentication configuration
315
+ * @param {string} prefix - KV prefix
273
316
  */
274
- function addOAuth2AuthLines(lines, auth) {
317
+ function addOAuth2AuthLines(lines, auth, prefix) {
275
318
  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}`);
282
- }
319
+ lines.push(`KV_${prefix}_CLIENTID=`);
320
+ lines.push(`KV_${prefix}_CLIENTSECRET=`);
321
+ if (auth.scope) lines.push(`SCOPE=${auth.scope}`);
283
322
  lines.push('');
284
323
  }
285
324
 
286
325
  /**
287
- * Adds bearer token authentication lines to env template
288
- * @function addBearerTokenAuthLines
326
+ * Adds bearer token authentication lines with KV_* convention
289
327
  * @param {Array<string>} lines - Lines array to append to
328
+ * @param {string} prefix - KV prefix
290
329
  */
291
- function addBearerTokenAuthLines(lines) {
330
+ function addBearerTokenAuthLines(lines, prefix) {
292
331
  lines.push('# Bearer Token Authentication');
293
- lines.push('BEARER_TOKEN=kv://secrets/bearer-token');
332
+ lines.push(`KV_${prefix}_BEARERTOKEN=`);
294
333
  lines.push('');
295
334
  }
296
335
 
297
336
  /**
298
- * Adds basic authentication lines to env template
299
- * @function addBasicAuthLines
337
+ * Adds basic authentication lines with KV_* convention
300
338
  * @param {Array<string>} lines - Lines array to append to
339
+ * @param {string} prefix - KV prefix
301
340
  */
302
- function addBasicAuthLines(lines) {
341
+ function addBasicAuthLines(lines, prefix) {
303
342
  lines.push('# Basic Authentication');
304
- lines.push('USERNAME=kv://secrets/username');
305
- lines.push('PASSWORD=kv://secrets/password');
343
+ lines.push(`KV_${prefix}_USERNAME=`);
344
+ lines.push(`KV_${prefix}_PASSWORD=`);
306
345
  lines.push('');
307
346
  }
308
347
 
309
348
  /**
310
- * Adds authentication lines based on auth type
311
- * @function addAuthenticationLines
349
+ * Adds authentication lines based on auth type. Uses KV_<APPKEY>_<VAR> convention.
312
350
  * @param {Array<string>} lines - Lines array to append to
313
351
  * @param {Object} auth - Authentication configuration
314
352
  * @param {string} authType - Authentication type
353
+ * @param {string} systemKey - System key (e.g. hubspot) for KV_ prefix
315
354
  */
316
- function addAuthenticationLines(lines, auth, authType) {
355
+ function addAuthenticationLines(lines, auth, authType, systemKey) {
356
+ const prefix = systemKeyToKvPrefix(systemKey);
357
+ if (!prefix) return;
317
358
  if (authType === 'apikey' || authType === 'apiKey') {
318
- addApiKeyAuthLines(lines);
359
+ addApiKeyAuthLines(lines, prefix);
319
360
  } else if (authType === 'oauth2' || authType === 'oauth') {
320
- addOAuth2AuthLines(lines, auth);
361
+ addOAuth2AuthLines(lines, auth || {}, prefix);
321
362
  } else if (authType === 'bearer' || authType === 'token') {
322
- addBearerTokenAuthLines(lines);
363
+ addBearerTokenAuthLines(lines, prefix);
323
364
  } else if (authType === 'basic') {
324
- addBasicAuthLines(lines);
365
+ addBasicAuthLines(lines, prefix);
325
366
  }
326
367
  }
327
368
 
@@ -340,23 +381,24 @@ function addBaseUrlLines(lines, systemConfig) {
340
381
  }
341
382
 
342
383
  /**
343
- * Generate env.template with authentication variables
384
+ * Generate env.template with KV_* authentication variables
344
385
  * @async
345
386
  * @function generateEnvTemplate
346
387
  * @param {string} appPath - Application directory path
347
- * @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)
348
390
  * @throws {Error} If generation fails
349
391
  */
350
- async function generateEnvTemplate(appPath, systemConfig) {
392
+ async function generateEnvTemplate(appPath, systemConfig, finalSystemKey) {
351
393
  try {
352
394
  const envTemplatePath = path.join(appPath, 'env.template');
353
- 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)', ''];
354
397
 
355
- // Extract authentication variables from system config
356
- const auth = systemConfig.authentication || systemConfig.auth || {};
398
+ const auth = systemConfig?.authentication || systemConfig?.auth || {};
357
399
  const authType = auth.type || auth.authType || 'apikey';
358
400
 
359
- addAuthenticationLines(lines, auth, authType);
401
+ addAuthenticationLines(lines, auth, authType, systemKey);
360
402
  addBaseUrlLines(lines, systemConfig);
361
403
 
362
404
  await fs.writeFile(envTemplatePath, lines.join('\n'), 'utf8');
@@ -401,59 +443,6 @@ async function generateDeployScripts(appPath, systemKey, systemFileName, datasou
401
443
  }
402
444
  }
403
445
 
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
446
  module.exports = {
458
447
  generateWizardFiles,
459
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);
@@ -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
  }
@@ -123,7 +123,7 @@ function cleanupRunFiles(runEnvPath, pgpassRunPath) {
123
123
  }
124
124
 
125
125
  /**
126
- * Starts Docker services and configures pgAdmin.
126
+ * Starts Docker services and configures pgAdmin (when enabled).
127
127
  * Writes decrypted admin secrets to a temporary .env in infra dir, runs compose, then deletes the file (ISO 27K).
128
128
  *
129
129
  * @async
@@ -132,11 +132,13 @@ function cleanupRunFiles(runEnvPath, pgpassRunPath) {
132
132
  * @param {string} devId - Developer ID
133
133
  * @param {number} idNum - Developer ID number
134
134
  * @param {string} infraDir - Infrastructure directory
135
+ * @param {Object} [opts] - Options (pgadmin, redisCommander, traefik)
135
136
  */
136
- async function startDockerServicesAndConfigure(composePath, devId, idNum, infraDir) {
137
+ async function startDockerServicesAndConfigure(composePath, devId, idNum, infraDir, opts = {}) {
137
138
  let runEnvPath;
138
139
  let pgpassRunPath;
139
140
  let adminObj;
141
+ const { pgadmin = true, redisCommander = true, traefik = false } = opts;
140
142
  try {
141
143
  ({ adminObj, runEnvPath } = await prepareRunEnv(infraDir));
142
144
  } catch (err) {
@@ -146,8 +148,10 @@ async function startDockerServicesAndConfigure(composePath, devId, idNum, infraD
146
148
  try {
147
149
  const projectName = getInfraProjectName(devId);
148
150
  await startDockerServices(composePath, projectName, runEnvPath, infraDir);
149
- pgpassRunPath = await writePgpassAndCopyPgAdminConfig(infraDir, adminObj, devId, idNum);
150
- await waitForServices(devId);
151
+ if (pgadmin) {
152
+ pgpassRunPath = await writePgpassAndCopyPgAdminConfig(infraDir, adminObj, devId, idNum);
153
+ }
154
+ await waitForServices(devId, { pgadmin, redisCommander, traefik });
151
155
  logger.log('All services are healthy and ready');
152
156
  } finally {
153
157
  cleanupRunFiles(runEnvPath, pgpassRunPath);
@@ -157,14 +161,16 @@ async function startDockerServicesAndConfigure(composePath, devId, idNum, infraD
157
161
  /**
158
162
  * Waits for services to be healthy
159
163
  * @private
160
- * @param {number} [devId] - Developer ID (optional, will be loaded from config if not provided)
164
+ * @param {number|string|null} [devId] - Developer ID (optional, will be loaded from config if not provided)
165
+ * @param {Object} [opts] - Options (pgadmin, redisCommander, traefik) - which optional services to expect
161
166
  */
162
- async function waitForServices(devId = null) {
167
+ async function waitForServices(devId = null, opts = {}) {
163
168
  const maxAttempts = 30;
164
169
  const delay = 2000; // 2 seconds
170
+ const { pgadmin = true, redisCommander = true, traefik = false } = opts;
165
171
 
166
172
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
167
- const health = await checkInfraHealth(devId);
173
+ const health = await checkInfraHealth(devId, { pgadmin, redisCommander, traefik });
168
174
  const allHealthy = Object.values(health).every(status => status === 'healthy');
169
175
 
170
176
  if (allHealthy) {
@@ -182,15 +188,17 @@ async function waitForServices(devId = null) {
182
188
 
183
189
  /**
184
190
  * Checks if infrastructure services are running
185
- * Validates that all required services are healthy and accessible
191
+ * Validates that all expected services are healthy and accessible
186
192
  *
187
193
  * @async
188
194
  * @function checkInfraHealth
189
195
  * @param {number|string|null} [devId] - Developer ID (null = use current)
190
196
  * @param {Object} [options] - Options
191
197
  * @param {boolean} [options.strict=false] - When true, only consider current dev's containers (no fallback to dev 0); use for up-miso and status consistency
198
+ * @param {boolean} [options.pgadmin=true] - Include pgAdmin in health check
199
+ * @param {boolean} [options.redisCommander=true] - Include Redis Commander in health check
200
+ * @param {boolean} [options.traefik=false] - Include Traefik in health check
192
201
  * @returns {Promise<Object>} Health status of each service
193
- * @throws {Error} If health check fails
194
202
  *
195
203
  * @example
196
204
  * const health = await checkInfraHealth();
@@ -199,7 +207,10 @@ async function waitForServices(devId = null) {
199
207
  async function checkInfraHealth(devId = null, options = {}) {
200
208
  const developerId = devId || await config.getDeveloperId();
201
209
  const servicesWithHealthCheck = ['postgres', 'redis'];
202
- const servicesWithoutHealthCheck = ['pgadmin', 'redis-commander'];
210
+ const servicesWithoutHealthCheck = [];
211
+ if (options.pgadmin !== false) servicesWithoutHealthCheck.push('pgadmin');
212
+ if (options.redisCommander !== false) servicesWithoutHealthCheck.push('redis-commander');
213
+ if (options.traefik === true) servicesWithoutHealthCheck.push('traefik');
203
214
  const health = {};
204
215
  const lookupOptions = options.strict ? { strict: true } : {};
205
216
 
@@ -208,7 +219,7 @@ async function checkInfraHealth(devId = null, options = {}) {
208
219
  health[service] = await containerUtils.checkServiceWithHealthCheck(service, developerId, lookupOptions);
209
220
  }
210
221
 
211
- // Check if services without health checks are running
222
+ // Check if optional services without health checks are running
212
223
  for (const service of servicesWithoutHealthCheck) {
213
224
  health[service] = await containerUtils.checkServiceWithoutHealthCheck(service, developerId, lookupOptions);
214
225
  }