@aifabrix/builder 2.31.1 → 2.32.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 (118) hide show
  1. package/README.md +9 -9
  2. package/integration/hubspot/README.md +2 -2
  3. package/integration/hubspot/hubspot-deploy-company.json +17 -14
  4. package/integration/hubspot/hubspot-deploy-contact.json +19 -16
  5. package/integration/hubspot/hubspot-deploy-deal.json +21 -18
  6. package/lib/api/types/datasources.types.js +31 -5
  7. package/lib/api/types/wizard.types.js +142 -0
  8. package/lib/api/wizard.api.js +177 -0
  9. package/lib/{app-config.js → app/config.js} +4 -4
  10. package/lib/{app-deploy.js → app/deploy.js} +8 -8
  11. package/lib/app/display.js +90 -0
  12. package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
  13. package/lib/{app-down.js → app/down.js} +4 -4
  14. package/lib/app/helpers.js +218 -0
  15. package/lib/app/index.js +298 -0
  16. package/lib/{app-list.js → app/list.js} +6 -6
  17. package/lib/{app-push.js → app/push.js} +4 -4
  18. package/lib/{app-readme.js → app/readme.js} +34 -13
  19. package/lib/{app-register.js → app/register.js} +9 -9
  20. package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
  21. package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
  22. package/lib/{app-run.js → app/run.js} +6 -6
  23. package/lib/{build.js → build/index.js} +59 -32
  24. package/lib/build/package.json +7 -0
  25. package/lib/cli.js +245 -179
  26. package/lib/commands/app.js +3 -3
  27. package/lib/commands/datasource.js +4 -4
  28. package/lib/commands/login-credentials.js +209 -0
  29. package/lib/commands/login-device.js +254 -0
  30. package/lib/commands/login.js +67 -378
  31. package/lib/commands/logout.js +1 -1
  32. package/lib/commands/secrets-set.js +1 -1
  33. package/lib/commands/secure.js +2 -2
  34. package/lib/commands/wizard.js +498 -0
  35. package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
  36. package/lib/{config.js → core/config.js} +28 -26
  37. package/lib/{diff.js → core/diff.js} +157 -72
  38. package/lib/{secrets.js → core/secrets.js} +86 -49
  39. package/lib/{templates.js → core/templates-env.js} +14 -222
  40. package/lib/core/templates.js +279 -0
  41. package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
  42. package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
  43. package/lib/datasource/list.js +223 -0
  44. package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
  45. package/lib/{deployer.js → deployment/deployer.js} +48 -18
  46. package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
  47. package/lib/{push.js → deployment/push.js} +1 -1
  48. package/lib/external-system/deploy-helpers.js +145 -0
  49. package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
  50. package/lib/external-system/download-helpers.js +114 -0
  51. package/lib/{external-system-download.js → external-system/download.js} +92 -135
  52. package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
  53. package/lib/external-system/test-auth.js +40 -0
  54. package/lib/external-system/test-execution.js +84 -0
  55. package/lib/external-system/test-helpers.js +109 -0
  56. package/lib/{external-system-test.js → external-system/test.js} +174 -192
  57. package/lib/{generator-builders.js → generator/builders.js} +87 -10
  58. package/lib/{generator-external.js → generator/external.js} +115 -52
  59. package/lib/{github-generator.js → generator/github.js} +116 -15
  60. package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
  61. package/lib/{generator.js → generator/index.js} +49 -22
  62. package/lib/{generator-split.js → generator/split.js} +108 -55
  63. package/lib/generator/wizard-prompts.js +357 -0
  64. package/lib/generator/wizard.js +490 -0
  65. package/lib/{infra.js → infrastructure/index.js} +49 -22
  66. package/lib/schema/external-datasource.schema.json +145 -133
  67. package/lib/schema/external-system.schema.json +42 -0
  68. package/lib/utils/api.js +9 -5
  69. package/lib/utils/app-register-api.js +60 -32
  70. package/lib/utils/app-register-auth.js +172 -47
  71. package/lib/utils/app-register-config.js +130 -59
  72. package/lib/utils/app-run-containers.js +29 -8
  73. package/lib/utils/build-helpers.js +1 -1
  74. package/lib/utils/cli-utils.js +78 -30
  75. package/lib/utils/compose-generator.js +145 -65
  76. package/lib/utils/config-paths.js +2 -0
  77. package/lib/utils/deployment-errors.js +1 -1
  78. package/lib/utils/device-code.js +99 -41
  79. package/lib/utils/env-config-loader.js +1 -1
  80. package/lib/utils/env-copy.js +21 -18
  81. package/lib/utils/env-endpoints.js +115 -67
  82. package/lib/utils/env-map.js +13 -14
  83. package/lib/utils/env-ports.js +45 -25
  84. package/lib/utils/env-template.js +84 -42
  85. package/lib/utils/error-formatter.js +26 -9
  86. package/lib/utils/error-formatters/error-parser.js +90 -4
  87. package/lib/utils/error-formatters/http-status-errors.js +54 -17
  88. package/lib/utils/error-formatters/network-errors.js +103 -26
  89. package/lib/utils/external-system-display.js +184 -90
  90. package/lib/utils/external-system-validators.js +164 -42
  91. package/lib/utils/file-upload.js +109 -0
  92. package/lib/utils/health-check.js +199 -83
  93. package/lib/utils/infra-containers.js +1 -1
  94. package/lib/utils/infra-status.js +66 -15
  95. package/lib/utils/local-secrets.js +45 -25
  96. package/lib/utils/paths.js +45 -33
  97. package/lib/utils/schema-loader.js +42 -25
  98. package/lib/utils/schema-resolver.js +123 -74
  99. package/lib/utils/secrets-encryption.js +62 -25
  100. package/lib/utils/secrets-helpers.js +126 -63
  101. package/lib/utils/secrets-path.js +1 -1
  102. package/lib/utils/secrets-url.js +1 -1
  103. package/lib/utils/token-manager-refresh.js +181 -0
  104. package/lib/utils/token-manager.js +76 -123
  105. package/lib/utils/variable-transformer.js +154 -77
  106. package/lib/utils/yaml-preserve.js +41 -47
  107. package/lib/{template-validator.js → validation/template.js} +54 -23
  108. package/lib/{validate.js → validation/validate.js} +205 -125
  109. package/lib/{validator.js → validation/validator.js} +58 -39
  110. package/package.json +31 -2
  111. package/templates/external-system/deploy.ps1.hbs +34 -0
  112. package/templates/external-system/deploy.sh.hbs +34 -0
  113. package/templates/external-system/external-datasource.json.hbs +31 -12
  114. package/lib/app.js +0 -467
  115. package/lib/datasource-list.js +0 -141
  116. /package/lib/{app-prompts.js → app/prompts.js} +0 -0
  117. /package/lib/{env-reader.js → core/env-reader.js} +0 -0
  118. /package/lib/{key-generator.js → core/key-generator.js} +0 -0
@@ -0,0 +1,490 @@
1
+ /**
2
+ * @fileoverview Wizard file generator - saves dataplane-generated configurations to files
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const fs = require('fs').promises;
8
+ const path = require('path');
9
+ const yaml = require('js-yaml');
10
+ const Handlebars = require('handlebars');
11
+ const chalk = require('chalk');
12
+ const logger = require('../utils/logger');
13
+ const { generateExternalSystemApplicationSchema } = require('./external');
14
+
15
+ /**
16
+ * Generate files from dataplane-generated wizard configurations
17
+ * @async
18
+ * @function generateWizardFiles
19
+ * @param {string} appName - Application name
20
+ * @param {Object} systemConfig - System configuration from dataplane
21
+ * @param {Object[]} datasourceConfigs - Array of datasource configurations from dataplane
22
+ * @param {string} systemKey - System key (from dataplane or derived)
23
+ * @returns {Promise<Object>} Object with generated file paths
24
+ * @throws {Error} If file generation fails
25
+ */
26
+ /**
27
+ * Writes system JSON file
28
+ * @async
29
+ * @function writeSystemJsonFile
30
+ * @param {string} appPath - Application path
31
+ * @param {string} finalSystemKey - Final system key
32
+ * @param {Object} systemConfig - System configuration
33
+ * @returns {Promise<string>} System file path
34
+ */
35
+ async function writeSystemJsonFile(appPath, finalSystemKey, systemConfig) {
36
+ const systemFileName = `${finalSystemKey}-deploy.json`;
37
+ const systemFilePath = path.join(appPath, systemFileName);
38
+ await fs.writeFile(systemFilePath, JSON.stringify(systemConfig, null, 2), 'utf8');
39
+ logger.log(chalk.green(`✓ Generated system file: ${systemFileName}`));
40
+ return systemFilePath;
41
+ }
42
+
43
+ /**
44
+ * Writes datasource JSON files
45
+ * @async
46
+ * @function writeDatasourceJsonFiles
47
+ * @param {string} appPath - Application path
48
+ * @param {string} finalSystemKey - Final system key
49
+ * @param {Object[]} datasourceConfigs - Array of datasource configurations
50
+ * @returns {Promise<string[]>} Array of datasource file names
51
+ */
52
+ async function writeDatasourceJsonFiles(appPath, finalSystemKey, datasourceConfigs) {
53
+ const datasourceFileNames = [];
54
+ for (const datasourceConfig of datasourceConfigs) {
55
+ const entityType = datasourceConfig.entityType || datasourceConfig.entityKey || datasourceConfig.key?.split('-').pop() || 'default';
56
+ const datasourceFileName = `${finalSystemKey}-deploy-${entityType}.json`;
57
+ const datasourceFilePath = path.join(appPath, datasourceFileName);
58
+ await fs.writeFile(datasourceFilePath, JSON.stringify(datasourceConfig, null, 2), 'utf8');
59
+ datasourceFileNames.push(datasourceFileName);
60
+ logger.log(chalk.green(`✓ Generated datasource file: ${datasourceFileName}`));
61
+ }
62
+ return datasourceFileNames;
63
+ }
64
+
65
+ /**
66
+ * Generates all configuration files
67
+ * @async
68
+ * @function generateConfigFilesForWizard
69
+ * @param {Object} params - Parameters object
70
+ * @param {string} params.appPath - Application path
71
+ * @param {string} params.appName - Application name
72
+ * @param {string} params.finalSystemKey - Final system key
73
+ * @param {string} params.systemFileName - System file name
74
+ * @param {string[]} params.datasourceFileNames - Array of datasource file names
75
+ * @param {Object} params.systemConfig - System configuration
76
+ * @param {Object[]} params.datasourceConfigs - Array of datasource configurations
77
+ * @param {string} [params.aiGeneratedReadme] - Optional AI-generated README content
78
+ * @returns {Promise<Object>} Object with file paths
79
+ */
80
+ async function generateConfigFilesForWizard(params) {
81
+ const { appPath, appName, finalSystemKey, systemFileName, datasourceFileNames, systemConfig, datasourceConfigs, aiGeneratedReadme } = params;
82
+
83
+ // Generate or update variables.yaml with externalIntegration block
84
+ await generateOrUpdateVariablesYaml({
85
+ appPath,
86
+ appName,
87
+ systemKey: finalSystemKey,
88
+ systemFileName,
89
+ datasourceFileNames,
90
+ systemConfig
91
+ });
92
+
93
+ // Generate env.template with authentication variables
94
+ await generateEnvTemplate(appPath, systemConfig);
95
+
96
+ // Generate README.md (use AI-generated content if available)
97
+ await generateReadme(appPath, appName, finalSystemKey, systemConfig, datasourceConfigs, aiGeneratedReadme);
98
+
99
+ // Generate deployment scripts
100
+ const deployScripts = await generateDeployScripts(appPath, finalSystemKey, systemFileName, datasourceFileNames);
101
+
102
+ // Generate application-schema.json
103
+ const applicationSchema = await generateExternalSystemApplicationSchema(appName);
104
+ const applicationSchemaPath = path.join(appPath, 'application-schema.json');
105
+ await fs.writeFile(applicationSchemaPath, JSON.stringify(applicationSchema, null, 2), 'utf8');
106
+ logger.log(chalk.green('✓ Generated application-schema.json'));
107
+
108
+ return {
109
+ variablesPath: path.join(appPath, 'variables.yaml'),
110
+ envTemplatePath: path.join(appPath, 'env.template'),
111
+ readmePath: path.join(appPath, 'README.md'),
112
+ applicationSchemaPath,
113
+ ...deployScripts
114
+ };
115
+ }
116
+
117
+ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, options = {}) {
118
+ try {
119
+ const { aiGeneratedReadme } = options || {};
120
+ // Determine app path (integration directory for external systems)
121
+ const appPath = path.join(process.cwd(), 'integration', appName);
122
+
123
+ // Create directory if it doesn't exist
124
+ await fs.mkdir(appPath, { recursive: true });
125
+
126
+ // Extract system key from config if not provided
127
+ const finalSystemKey = systemKey || systemConfig.key || appName;
128
+
129
+ // Write system and datasource JSON files
130
+ const systemFilePath = await writeSystemJsonFile(appPath, finalSystemKey, systemConfig);
131
+ const datasourceFileNames = await writeDatasourceJsonFiles(appPath, finalSystemKey, datasourceConfigs);
132
+
133
+ // Generate configuration files
134
+ const systemFileName = `${finalSystemKey}-deploy.json`;
135
+ const configFiles = await generateConfigFilesForWizard({
136
+ appPath,
137
+ appName,
138
+ finalSystemKey,
139
+ systemFileName,
140
+ datasourceFileNames,
141
+ systemConfig,
142
+ datasourceConfigs,
143
+ aiGeneratedReadme
144
+ });
145
+
146
+ return {
147
+ appPath,
148
+ systemFilePath,
149
+ datasourceFilePaths: datasourceFileNames.map(name => path.join(appPath, name)),
150
+ ...configFiles
151
+ };
152
+ } catch (error) {
153
+ throw new Error(`Failed to generate wizard files: ${error.message}`);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Generate or update variables.yaml with externalIntegration block
159
+ * @async
160
+ * @function generateOrUpdateVariablesYaml
161
+ * @param {Object} params - Parameters object
162
+ * @param {string} params.appPath - Application directory path
163
+ * @param {string} params.appName - Application name
164
+ * @param {string} params.systemKey - System key
165
+ * @param {string} params.systemFileName - System file name
166
+ * @param {string[]} params.datasourceFileNames - Array of datasource file names
167
+ * @param {Object} params.systemConfig - System configuration
168
+ * @throws {Error} If generation fails
169
+ */
170
+ async function generateOrUpdateVariablesYaml(params) {
171
+ const { appPath, appName, systemFileName, datasourceFileNames, systemConfig } = params;
172
+ try {
173
+ const variablesPath = path.join(appPath, 'variables.yaml');
174
+ let variables = {};
175
+
176
+ // Try to read existing variables.yaml
177
+ try {
178
+ const existingContent = await fs.readFile(variablesPath, 'utf8');
179
+ variables = yaml.load(existingContent) || {};
180
+ } catch (error) {
181
+ // File doesn't exist, create new one
182
+ if (error.code !== 'ENOENT') {
183
+ throw error;
184
+ }
185
+ }
186
+
187
+ // Set basic app info if not present
188
+ if (!variables.app) {
189
+ variables.app = {
190
+ key: appName,
191
+ displayName: systemConfig.displayName || appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
192
+ description: systemConfig.description || `External system integration for ${appName}`,
193
+ type: 'external'
194
+ };
195
+ }
196
+
197
+ // Set deployment config if not present
198
+ if (!variables.deployment) {
199
+ variables.deployment = {
200
+ controllerUrl: '',
201
+ environment: 'dev'
202
+ };
203
+ }
204
+
205
+ // Add or update externalIntegration block
206
+ variables.externalIntegration = {
207
+ schemaBasePath: './',
208
+ systems: [systemFileName],
209
+ dataSources: datasourceFileNames,
210
+ autopublish: true,
211
+ version: systemConfig.version || '1.0.0'
212
+ };
213
+
214
+ await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }), 'utf8');
215
+ logger.log(chalk.green('✓ Generated/updated variables.yaml'));
216
+ } catch (error) {
217
+ throw new Error(`Failed to generate variables.yaml: ${error.message}`);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Adds API key authentication lines to env template
223
+ * @function addApiKeyAuthLines
224
+ * @param {Array<string>} lines - Lines array to append to
225
+ */
226
+ function addApiKeyAuthLines(lines) {
227
+ lines.push('# API Key Authentication');
228
+ lines.push('API_KEY=kv://secrets/api-key');
229
+ lines.push('');
230
+ }
231
+
232
+ /**
233
+ * Adds OAuth2 authentication lines to env template
234
+ * @function addOAuth2AuthLines
235
+ * @param {Array<string>} lines - Lines array to append to
236
+ * @param {Object} auth - Authentication configuration
237
+ */
238
+ function addOAuth2AuthLines(lines, auth) {
239
+ lines.push('# OAuth2 Authentication');
240
+ lines.push('CLIENT_ID=kv://secrets/client-id');
241
+ lines.push('CLIENT_SECRET=kv://secrets/client-secret');
242
+ lines.push('AUTH_URL=kv://secrets/auth-url');
243
+ lines.push('TOKEN_URL=kv://secrets/token-url');
244
+ if (auth.scope) {
245
+ lines.push(`SCOPE=${auth.scope}`);
246
+ }
247
+ lines.push('');
248
+ }
249
+
250
+ /**
251
+ * Adds bearer token authentication lines to env template
252
+ * @function addBearerTokenAuthLines
253
+ * @param {Array<string>} lines - Lines array to append to
254
+ */
255
+ function addBearerTokenAuthLines(lines) {
256
+ lines.push('# Bearer Token Authentication');
257
+ lines.push('BEARER_TOKEN=kv://secrets/bearer-token');
258
+ lines.push('');
259
+ }
260
+
261
+ /**
262
+ * Adds basic authentication lines to env template
263
+ * @function addBasicAuthLines
264
+ * @param {Array<string>} lines - Lines array to append to
265
+ */
266
+ function addBasicAuthLines(lines) {
267
+ lines.push('# Basic Authentication');
268
+ lines.push('USERNAME=kv://secrets/username');
269
+ lines.push('PASSWORD=kv://secrets/password');
270
+ lines.push('');
271
+ }
272
+
273
+ /**
274
+ * Adds authentication lines based on auth type
275
+ * @function addAuthenticationLines
276
+ * @param {Array<string>} lines - Lines array to append to
277
+ * @param {Object} auth - Authentication configuration
278
+ * @param {string} authType - Authentication type
279
+ */
280
+ function addAuthenticationLines(lines, auth, authType) {
281
+ if (authType === 'apikey' || authType === 'apiKey') {
282
+ addApiKeyAuthLines(lines);
283
+ } else if (authType === 'oauth2' || authType === 'oauth') {
284
+ addOAuth2AuthLines(lines, auth);
285
+ } else if (authType === 'bearer' || authType === 'token') {
286
+ addBearerTokenAuthLines(lines);
287
+ } else if (authType === 'basic') {
288
+ addBasicAuthLines(lines);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Adds base URL lines if present
294
+ * @function addBaseUrlLines
295
+ * @param {Array<string>} lines - Lines array to append to
296
+ * @param {Object} systemConfig - System configuration
297
+ */
298
+ function addBaseUrlLines(lines, systemConfig) {
299
+ if (systemConfig.baseUrl || systemConfig.baseURL) {
300
+ lines.push('# API Base URL');
301
+ lines.push(`BASE_URL=${systemConfig.baseUrl || systemConfig.baseURL}`);
302
+ lines.push('');
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Generate env.template with authentication variables
308
+ * @async
309
+ * @function generateEnvTemplate
310
+ * @param {string} appPath - Application directory path
311
+ * @param {Object} systemConfig - System configuration
312
+ * @throws {Error} If generation fails
313
+ */
314
+ async function generateEnvTemplate(appPath, systemConfig) {
315
+ try {
316
+ const envTemplatePath = path.join(appPath, 'env.template');
317
+ const lines = ['# Environment variables for external system integration', ''];
318
+
319
+ // Extract authentication variables from system config
320
+ const auth = systemConfig.authentication || systemConfig.auth || {};
321
+ const authType = auth.type || auth.authType || 'apikey';
322
+
323
+ addAuthenticationLines(lines, auth, authType);
324
+ addBaseUrlLines(lines, systemConfig);
325
+
326
+ await fs.writeFile(envTemplatePath, lines.join('\n'), 'utf8');
327
+ logger.log(chalk.green('✓ Generated env.template'));
328
+ } catch (error) {
329
+ throw new Error(`Failed to generate env.template: ${error.message}`);
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Generate deployment scripts (deploy.sh and deploy.ps1) from templates
335
+ * @async
336
+ * @function generateDeployScripts
337
+ * @param {string} appPath - Application directory path
338
+ * @param {string} systemKey - System key
339
+ * @param {string} systemFileName - System file name
340
+ * @param {string[]} datasourceFileNames - Array of datasource file names
341
+ * @returns {Promise<Object>} Object with script file paths
342
+ * @throws {Error} If generation fails
343
+ */
344
+ async function generateDeployScripts(appPath, systemKey, systemFileName, datasourceFileNames) {
345
+ try {
346
+ const allJsonFiles = [systemFileName, ...datasourceFileNames];
347
+
348
+ // Load and compile deploy.sh template
349
+ const deployShTemplatePath = path.join(__dirname, '..', '..', 'templates', 'external-system', 'deploy.sh.hbs');
350
+ const deployShTemplateContent = await fs.readFile(deployShTemplatePath, 'utf8');
351
+ const deployShTemplate = Handlebars.compile(deployShTemplateContent);
352
+
353
+ // Generate deploy.sh
354
+ const deployShPath = path.join(appPath, 'deploy.sh');
355
+ const deployShContent = deployShTemplate({
356
+ systemKey,
357
+ allJsonFiles,
358
+ datasourceFileNames
359
+ });
360
+ await fs.writeFile(deployShPath, deployShContent, 'utf8');
361
+ await fs.chmod(deployShPath, 0o755); // Make executable
362
+ logger.log(chalk.green('✓ Generated deploy.sh'));
363
+
364
+ // Load and compile deploy.ps1 template
365
+ const deployPs1TemplatePath = path.join(__dirname, '..', '..', 'templates', 'external-system', 'deploy.ps1.hbs');
366
+ const deployPs1TemplateContent = await fs.readFile(deployPs1TemplatePath, 'utf8');
367
+ const deployPs1Template = Handlebars.compile(deployPs1TemplateContent);
368
+
369
+ // Generate deploy.ps1
370
+ const deployPs1Path = path.join(appPath, 'deploy.ps1');
371
+ const deployPs1Content = deployPs1Template({
372
+ systemKey,
373
+ allJsonFiles,
374
+ datasourceFileNames
375
+ });
376
+ await fs.writeFile(deployPs1Path, deployPs1Content, 'utf8');
377
+ logger.log(chalk.green('✓ Generated deploy.ps1'));
378
+
379
+ return {
380
+ deployShPath,
381
+ deployPs1Path
382
+ };
383
+ } catch (error) {
384
+ throw new Error(`Failed to generate deployment scripts: ${error.message}`);
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Generate README.md with basic documentation
390
+ * @async
391
+ * @function generateReadme
392
+ * @param {string} appPath - Application directory path
393
+ * @param {string} appName - Application name
394
+ * @param {string} systemKey - System key
395
+ * @param {Object} systemConfig - System configuration
396
+ * @param {Object[]} datasourceConfigs - Array of datasource configurations
397
+ * @param {string} [aiGeneratedContent] - Optional AI-generated README content from dataplane
398
+ * @throws {Error} If generation fails
399
+ */
400
+ async function generateReadme(appPath, appName, systemKey, systemConfig, datasourceConfigs, aiGeneratedContent) {
401
+ try {
402
+ const readmePath = path.join(appPath, 'README.md');
403
+
404
+ // Use AI-generated content if available, otherwise generate basic README
405
+ if (aiGeneratedContent) {
406
+ await fs.writeFile(readmePath, aiGeneratedContent, 'utf8');
407
+ logger.log(chalk.green('✓ Generated README.md (AI-generated from dataplane)'));
408
+ return;
409
+ }
410
+
411
+ const displayName = systemConfig.displayName || appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
412
+ const description = systemConfig.description || `External system integration for ${appName}`;
413
+
414
+ const lines = [
415
+ `# ${displayName}`,
416
+ '',
417
+ description,
418
+ '',
419
+ '## Overview',
420
+ '',
421
+ 'This integration was created using the AI Fabrix wizard.',
422
+ '',
423
+ '## Files',
424
+ '',
425
+ `- \`${systemKey}-deploy.json\` - External system configuration`,
426
+ ...datasourceConfigs.map((ds, index) => {
427
+ const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || `datasource${index + 1}`;
428
+ return `- \`${systemKey}-deploy-${entityType}.json\` - Datasource configuration`;
429
+ }),
430
+ '- `variables.yaml` - Application variables and external integration configuration',
431
+ '- `env.template` - Environment variable template',
432
+ '- `application-schema.json` - Single deployment file',
433
+ '- `deploy.sh` - Bash deployment script',
434
+ '- `deploy.ps1` - PowerShell deployment script',
435
+ '',
436
+ '## Deployment',
437
+ '',
438
+ '### Using Deployment Scripts',
439
+ '',
440
+ 'You can deploy using the provided scripts:',
441
+ '',
442
+ '**Bash (Linux/macOS):**',
443
+ '```bash',
444
+ './deploy.sh',
445
+ '```',
446
+ '',
447
+ '**PowerShell (Windows):**',
448
+ '```powershell',
449
+ '.\\deploy.ps1',
450
+ '```',
451
+ '',
452
+ 'The scripts support environment variables:',
453
+ '- `ENVIRONMENT` - Environment key (default: dev)',
454
+ '- `CONTROLLER` - Controller URL (default: http://localhost:3000)',
455
+ '- `RUN_TESTS` - Set to "true" to run integration tests after deployment',
456
+ '',
457
+ '**Example:**',
458
+ '```bash',
459
+ 'ENVIRONMENT=prod CONTROLLER=https://controller.example.com ./deploy.sh',
460
+ '```',
461
+ '',
462
+ '### Using CLI Directly',
463
+ '',
464
+ 'To deploy this external system:',
465
+ '',
466
+ '```bash',
467
+ `aifabrix deploy ${appName}`,
468
+ '```',
469
+ '',
470
+ '## Configuration',
471
+ '',
472
+ 'Update the environment variables in `env.template` and set the values in your secrets store.',
473
+ '',
474
+ '## Documentation',
475
+ '',
476
+ 'For more information, see the [External Systems Documentation](../../docs/external-systems.md).'
477
+ ];
478
+
479
+ await fs.writeFile(readmePath, lines.join('\n'), 'utf8');
480
+ logger.log(chalk.green('✓ Generated README.md'));
481
+ } catch (error) {
482
+ throw new Error(`Failed to generate README.md: ${error.message}`);
483
+ }
484
+ }
485
+
486
+ module.exports = {
487
+ generateWizardFiles,
488
+ generateDeployScripts
489
+ };
490
+
@@ -14,14 +14,14 @@ const { promisify } = require('util');
14
14
  const path = require('path');
15
15
  const fs = require('fs');
16
16
  const handlebars = require('handlebars');
17
- const secrets = require('./secrets');
18
- const config = require('./config');
19
- const devConfig = require('./utils/dev-config');
20
- const logger = require('./utils/logger');
21
- const containerUtils = require('./utils/infra-containers');
22
- const dockerUtils = require('./utils/docker');
23
- const paths = require('./utils/paths');
24
- const statusHelpers = require('./utils/infra-status');
17
+ const secrets = require('../core/secrets');
18
+ const config = require('../core/config');
19
+ const devConfig = require('../utils/dev-config');
20
+ const logger = require('../utils/logger');
21
+ const containerUtils = require('../utils/infra-containers');
22
+ const dockerUtils = require('../utils/docker');
23
+ const paths = require('../utils/paths');
24
+ const statusHelpers = require('../utils/infra-status');
25
25
 
26
26
  // Register Handlebars helper for equality check
27
27
  // Handles both strict equality and numeric string comparisons
@@ -238,7 +238,14 @@ async function copyPgAdminConfig(pgadminContainerName, serversJsonPath, pgpassPa
238
238
  }
239
239
  }
240
240
 
241
- async function startInfra(developerId = null) {
241
+ /**
242
+ * Prepares infrastructure environment
243
+ * @async
244
+ * @function prepareInfrastructureEnvironment
245
+ * @param {string|number|null} developerId - Developer ID
246
+ * @returns {Promise<Object>} Prepared environment configuration
247
+ */
248
+ async function prepareInfrastructureEnvironment(developerId) {
242
249
  await checkDockerAvailability();
243
250
  const adminSecretsPath = await ensureAdminSecrets();
244
251
 
@@ -255,6 +262,38 @@ async function startInfra(developerId = null) {
255
262
  // Prepare infrastructure directory
256
263
  const { infraDir } = prepareInfraDirectory(devId, adminSecretsPath);
257
264
 
265
+ return { devId, idNum, ports, templatePath, infraDir, adminSecretsPath };
266
+ }
267
+
268
+ /**
269
+ * Starts Docker services and configures pgAdmin
270
+ * @async
271
+ * @function startDockerServicesAndConfigure
272
+ * @param {string} composePath - Compose file path
273
+ * @param {string} devId - Developer ID
274
+ * @param {number} idNum - Developer ID number
275
+ * @param {string} adminSecretsPath - Admin secrets path
276
+ * @param {string} infraDir - Infrastructure directory
277
+ */
278
+ async function startDockerServicesAndConfigure(composePath, devId, idNum, adminSecretsPath, infraDir) {
279
+ // Start Docker services
280
+ const projectName = getInfraProjectName(devId);
281
+ await startDockerServices(composePath, projectName, adminSecretsPath, infraDir);
282
+
283
+ // Copy pgAdmin4 config files
284
+ const pgadminContainerName = idNum === 0 ? 'aifabrix-pgadmin' : `aifabrix-dev${devId}-pgadmin`;
285
+ const serversJsonPath = path.join(infraDir, 'servers.json');
286
+ const pgpassPath = path.join(infraDir, 'pgpass');
287
+ await copyPgAdminConfig(pgadminContainerName, serversJsonPath, pgpassPath);
288
+
289
+ // Wait for services to be healthy
290
+ await waitForServices(devId);
291
+ logger.log('All services are healthy and ready');
292
+ }
293
+
294
+ async function startInfra(developerId = null) {
295
+ const { devId, idNum, ports, templatePath, infraDir, adminSecretsPath } = await prepareInfrastructureEnvironment(developerId);
296
+
258
297
  // Register Handlebars helper
259
298
  registerHandlebarsHelper();
260
299
 
@@ -262,19 +301,7 @@ async function startInfra(developerId = null) {
262
301
  const composePath = generateComposeFile(templatePath, devId, idNum, ports, infraDir);
263
302
 
264
303
  try {
265
- // Start Docker services
266
- const projectName = getInfraProjectName(devId);
267
- await startDockerServices(composePath, projectName, adminSecretsPath, infraDir);
268
-
269
- // Copy pgAdmin4 config files
270
- const pgadminContainerName = idNum === 0 ? 'aifabrix-pgadmin' : `aifabrix-dev${devId}-pgadmin`;
271
- const serversJsonPath = path.join(infraDir, 'servers.json');
272
- const pgpassPath = path.join(infraDir, 'pgpass');
273
- await copyPgAdminConfig(pgadminContainerName, serversJsonPath, pgpassPath);
274
-
275
- // Wait for services to be healthy
276
- await waitForServices(devId);
277
- logger.log('All services are healthy and ready');
304
+ await startDockerServicesAndConfigure(composePath, devId, idNum, adminSecretsPath, infraDir);
278
305
  } finally {
279
306
  // Keep the compose file for stop commands
280
307
  }