@aifabrix/builder 2.22.1 → 2.31.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 (65) hide show
  1. package/jest.config.coverage.js +37 -0
  2. package/lib/api/pipeline.api.js +10 -9
  3. package/lib/app-deploy.js +36 -14
  4. package/lib/app-list.js +191 -71
  5. package/lib/app-prompts.js +77 -26
  6. package/lib/app-readme.js +123 -5
  7. package/lib/app-rotate-secret.js +101 -57
  8. package/lib/app-run-helpers.js +200 -172
  9. package/lib/app-run.js +137 -68
  10. package/lib/audit-logger.js +8 -7
  11. package/lib/build.js +161 -250
  12. package/lib/cli.js +73 -65
  13. package/lib/commands/login.js +45 -31
  14. package/lib/commands/logout.js +181 -0
  15. package/lib/commands/secrets-set.js +2 -2
  16. package/lib/commands/secure.js +61 -26
  17. package/lib/config.js +79 -45
  18. package/lib/datasource-deploy.js +89 -29
  19. package/lib/deployer.js +164 -129
  20. package/lib/diff.js +63 -21
  21. package/lib/environment-deploy.js +36 -19
  22. package/lib/external-system-deploy.js +134 -66
  23. package/lib/external-system-download.js +244 -171
  24. package/lib/external-system-test.js +199 -164
  25. package/lib/generator-external.js +145 -72
  26. package/lib/generator-helpers.js +49 -17
  27. package/lib/generator-split.js +105 -58
  28. package/lib/infra.js +101 -131
  29. package/lib/schema/application-schema.json +895 -896
  30. package/lib/schema/env-config.yaml +11 -4
  31. package/lib/template-validator.js +13 -4
  32. package/lib/utils/api.js +8 -8
  33. package/lib/utils/app-register-auth.js +36 -18
  34. package/lib/utils/app-run-containers.js +140 -0
  35. package/lib/utils/auth-headers.js +6 -6
  36. package/lib/utils/build-copy.js +60 -2
  37. package/lib/utils/build-helpers.js +94 -0
  38. package/lib/utils/cli-utils.js +177 -76
  39. package/lib/utils/compose-generator.js +12 -2
  40. package/lib/utils/config-tokens.js +151 -9
  41. package/lib/utils/deployment-errors.js +137 -69
  42. package/lib/utils/deployment-validation-helpers.js +103 -0
  43. package/lib/utils/docker-build.js +57 -0
  44. package/lib/utils/dockerfile-utils.js +13 -3
  45. package/lib/utils/env-copy.js +163 -94
  46. package/lib/utils/env-map.js +226 -86
  47. package/lib/utils/environment-checker.js +2 -2
  48. package/lib/utils/error-formatters/network-errors.js +0 -1
  49. package/lib/utils/external-system-display.js +14 -19
  50. package/lib/utils/external-system-env-helpers.js +107 -0
  51. package/lib/utils/external-system-test-helpers.js +144 -0
  52. package/lib/utils/health-check.js +10 -8
  53. package/lib/utils/infra-status.js +123 -0
  54. package/lib/utils/local-secrets.js +3 -2
  55. package/lib/utils/paths.js +228 -49
  56. package/lib/utils/schema-loader.js +125 -57
  57. package/lib/utils/token-manager.js +10 -7
  58. package/lib/utils/yaml-preserve.js +55 -16
  59. package/lib/validate.js +87 -89
  60. package/package.json +4 -4
  61. package/scripts/ci-fix.sh +19 -0
  62. package/scripts/ci-simulate.sh +19 -0
  63. package/templates/applications/miso-controller/test.yaml +1 -0
  64. package/templates/python/Dockerfile.hbs +8 -45
  65. package/templates/typescript/Dockerfile.hbs +8 -42
package/lib/validate.js CHANGED
@@ -19,6 +19,81 @@ const { formatValidationErrors } = require('./utils/error-formatter');
19
19
  const { detectAppType } = require('./utils/paths');
20
20
  const logger = require('./utils/logger');
21
21
 
22
+ /**
23
+ * Validates a file path (detects type and validates)
24
+ * @async
25
+ * @param {string} filePath - Path to file
26
+ * @returns {Promise<Object>} Validation result
27
+ */
28
+ async function validateFilePath(filePath) {
29
+ if (!fs.existsSync(filePath)) {
30
+ throw new Error(`File not found: ${filePath}`);
31
+ }
32
+
33
+ const content = fs.readFileSync(filePath, 'utf8');
34
+ const type = detectSchemaType(filePath, content);
35
+
36
+ return await validateExternalFile(filePath, type);
37
+ }
38
+
39
+ /**
40
+ * Validates external files for an application
41
+ * @async
42
+ * @param {string} appName - Application name
43
+ * @returns {Promise<Array>} Array of validation results
44
+ */
45
+ async function validateExternalFilesForApp(appName) {
46
+ const files = await resolveExternalFiles(appName);
47
+ const validations = [];
48
+
49
+ for (const file of files) {
50
+ const result = await validateExternalFile(file.path, file.type);
51
+ validations.push({
52
+ file: file.fileName,
53
+ path: file.path,
54
+ type: file.type,
55
+ ...result
56
+ });
57
+ }
58
+
59
+ return validations;
60
+ }
61
+
62
+ /**
63
+ * Aggregates validation results from multiple sources
64
+ * @param {Object} appValidation - Application validation result
65
+ * @param {Array} externalValidations - External file validation results
66
+ * @param {Object|null} rbacValidation - RBAC validation result (optional)
67
+ * @returns {Object} Aggregated validation result
68
+ */
69
+ function aggregateValidationResults(appValidation, externalValidations, rbacValidation) {
70
+ const allErrors = [...(appValidation.errors || [])];
71
+ const allWarnings = [...(appValidation.warnings || [])];
72
+
73
+ if (rbacValidation && !rbacValidation.valid) {
74
+ allErrors.push(...(rbacValidation.errors || []));
75
+ allWarnings.push(...(rbacValidation.warnings || []));
76
+ }
77
+
78
+ externalValidations.forEach(validation => {
79
+ if (!validation.valid) {
80
+ allErrors.push(`External ${validation.type} file "${validation.file}": ${validation.errors.join(', ')}`);
81
+ }
82
+ if (validation.warnings && validation.warnings.length > 0) {
83
+ allWarnings.push(`External ${validation.type} file "${validation.file}": ${validation.warnings.join(', ')}`);
84
+ }
85
+ });
86
+
87
+ return {
88
+ valid: appValidation.valid && (!rbacValidation || rbacValidation.valid) && externalValidations.every(v => v.valid),
89
+ application: appValidation,
90
+ externalFiles: externalValidations,
91
+ rbac: rbacValidation,
92
+ errors: allErrors,
93
+ warnings: allWarnings
94
+ };
95
+ }
96
+
22
97
  /**
23
98
  * Validates a single external file against its schema
24
99
  *
@@ -47,9 +122,11 @@ async function validateExternalFile(filePath, type) {
47
122
  }
48
123
 
49
124
  let validate;
50
- if (type === 'system') {
125
+ // Normalize type: handle both 'external-system'/'external-datasource' and 'system'/'datasource'
126
+ const normalizedType = type === 'external-system' ? 'system' : (type === 'external-datasource' ? 'datasource' : type);
127
+ if (normalizedType === 'system') {
51
128
  validate = loadExternalSystemSchema();
52
- } else if (type === 'datasource') {
129
+ } else if (normalizedType === 'datasource') {
53
130
  validate = loadExternalDataSourceSchema();
54
131
  } else {
55
132
  throw new Error(`Unknown file type: ${type}`);
@@ -61,7 +138,7 @@ async function validateExternalFile(filePath, type) {
61
138
  const warnings = [];
62
139
 
63
140
  // Additional validation for external system files: check role references in permissions
64
- if (type === 'system' && parsed.permissions && Array.isArray(parsed.permissions)) {
141
+ if (normalizedType === 'system' && parsed.permissions && Array.isArray(parsed.permissions)) {
65
142
  const roles = parsed.roles || [];
66
143
  const roleValues = new Set(roles.map(r => r.value));
67
144
 
@@ -79,7 +156,9 @@ async function validateExternalFile(filePath, type) {
79
156
  return {
80
157
  valid: errors.length === 0,
81
158
  errors,
82
- warnings
159
+ warnings,
160
+ file: filePath,
161
+ type: type
83
162
  };
84
163
  }
85
164
 
@@ -106,45 +185,7 @@ async function validateAppOrFile(appOrFile) {
106
185
  const isFilePath = fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile();
107
186
 
108
187
  if (isFilePath) {
109
- // Validate single file
110
- const schemaType = detectSchemaType(appOrFile);
111
- let result;
112
-
113
- if (schemaType === 'application') {
114
- // For application files, we'd need to transform them first
115
- // For now, just validate JSON structure
116
- const content = fs.readFileSync(appOrFile, 'utf8');
117
- try {
118
- JSON.parse(content);
119
- } catch (error) {
120
- return {
121
- valid: false,
122
- errors: [`Invalid JSON syntax: ${error.message}`],
123
- warnings: []
124
- };
125
- }
126
- // Note: Full application validation requires variables.yaml transformation
127
- // This is a simplified validation for direct JSON files
128
- result = {
129
- valid: true,
130
- errors: [],
131
- warnings: ['Application file validation is simplified. Use app name for full validation.']
132
- };
133
- } else if (schemaType === 'external-system') {
134
- result = await validateExternalFile(appOrFile, 'system');
135
- } else if (schemaType === 'external-datasource') {
136
- result = await validateExternalFile(appOrFile, 'datasource');
137
- } else {
138
- throw new Error(`Unknown schema type: ${schemaType}`);
139
- }
140
-
141
- return {
142
- valid: result.valid,
143
- file: appOrFile,
144
- type: schemaType,
145
- errors: result.errors,
146
- warnings: result.warnings
147
- };
188
+ return await validateFilePath(appOrFile);
148
189
  }
149
190
 
150
191
  // Treat as app name
@@ -204,54 +245,11 @@ async function validateAppOrFile(appOrFile) {
204
245
  };
205
246
  }
206
247
 
207
- // Resolve and validate external files
208
- const externalFiles = await resolveExternalFiles(appName);
209
- const externalValidations = [];
210
-
211
- for (const fileInfo of externalFiles) {
212
- try {
213
- const validation = await validateExternalFile(fileInfo.path, fileInfo.type);
214
- externalValidations.push({
215
- file: fileInfo.fileName,
216
- path: fileInfo.path,
217
- type: fileInfo.type,
218
- ...validation
219
- });
220
- } catch (error) {
221
- externalValidations.push({
222
- file: fileInfo.fileName,
223
- path: fileInfo.path,
224
- type: fileInfo.type,
225
- valid: false,
226
- errors: [error.message],
227
- warnings: []
228
- });
229
- }
230
- }
248
+ // Validate external files
249
+ const externalValidations = await validateExternalFilesForApp(appName);
231
250
 
232
251
  // Aggregate results
233
- const rbacErrors = rbacValidation ? rbacValidation.errors : [];
234
- const rbacWarnings = rbacValidation ? rbacValidation.warnings : [];
235
- const allValid = appValidation.valid && externalValidations.every(v => v.valid) && (!rbacValidation || rbacValidation.valid);
236
- const allErrors = [
237
- ...appValidation.errors,
238
- ...externalValidations.flatMap(v => v.errors.map(e => `${v.file}: ${e}`)),
239
- ...rbacErrors.map(e => `rbac.yaml: ${e}`)
240
- ];
241
- const allWarnings = [
242
- ...appValidation.warnings,
243
- ...externalValidations.flatMap(v => v.warnings),
244
- ...rbacWarnings
245
- ];
246
-
247
- return {
248
- valid: allValid,
249
- application: appValidation,
250
- externalFiles: externalValidations,
251
- rbac: rbacValidation,
252
- errors: allErrors,
253
- warnings: allWarnings
254
- };
252
+ return aggregateValidationResults(appValidation, externalValidations, rbacValidation);
255
253
  }
256
254
 
257
255
  /**
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.22.1",
3
+ "version": "2.31.0",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
7
  "aifabrix": "bin/aifabrix.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "jest --coverage",
10
+ "test": "node tests/scripts/test-wrapper.js",
11
+ "test:coverage": "jest --config jest.config.coverage.js --coverage --runInBand",
11
12
  "test:watch": "jest --watch",
12
- "test:ci": "jest --ci --coverage --watchAll=false",
13
13
  "test:integration": "jest --config jest.config.integration.js --runInBand",
14
14
  "test:integration:python": "cross-env TEST_LANGUAGE=python jest --config jest.config.integration.js --runInBand",
15
15
  "test:integration:typescript": "cross-env TEST_LANGUAGE=typescript jest --config jest.config.integration.js --runInBand",
@@ -17,7 +17,7 @@
17
17
  "lint:fix": "eslint . --ext .js --fix",
18
18
  "lint:ci": "eslint . --ext .js --format json --output-file eslint-report.json",
19
19
  "dev": "node bin/aifabrix.js",
20
- "build": "npm run lint && npm run test:ci",
20
+ "build": "npm run lint && npm run test",
21
21
  "pack": "npm run build && npm pack",
22
22
  "validate": "npm run build",
23
23
  "prepublishOnly": "npm run validate",
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+ # CI Fix Script Wrapper
3
+ # This script is a wrapper that calls the actual CI fix script
4
+ # located in tests/scripts/ci-fix.sh
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ ACTUAL_SCRIPT="$SCRIPT_DIR/../tests/scripts/ci-fix.sh"
8
+
9
+ if [ ! -f "$ACTUAL_SCRIPT" ]; then
10
+ echo "Error: CI fix script not found at $ACTUAL_SCRIPT" >&2
11
+ exit 1
12
+ fi
13
+
14
+ # Make sure the script is executable
15
+ chmod +x "$ACTUAL_SCRIPT" 2>/dev/null || true
16
+
17
+ # Execute the actual script
18
+ exec "$ACTUAL_SCRIPT" "$@"
19
+
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+ # CI Simulation Script Wrapper
3
+ # This script is a wrapper that calls the actual CI simulation script
4
+ # located in tests/scripts/ci-simulate.sh
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ ACTUAL_SCRIPT="$SCRIPT_DIR/../tests/scripts/ci-simulate.sh"
8
+
9
+ if [ ! -f "$ACTUAL_SCRIPT" ]; then
10
+ echo "Error: CI simulation script not found at $ACTUAL_SCRIPT" >&2
11
+ exit 1
12
+ fi
13
+
14
+ # Make sure the script is executable
15
+ chmod +x "$ACTUAL_SCRIPT" 2>/dev/null || true
16
+
17
+ # Execute the actual script
18
+ exec "$ACTUAL_SCRIPT" "$@"
19
+
@@ -0,0 +1 @@
1
+ test content
@@ -1,49 +1,12 @@
1
- # Python Dockerfile Template
2
- # Generated by AI Fabrix Builder SDK
3
- # Base image: Python 3.11 Alpine for optimal size and security
4
-
5
1
  FROM python:3.11-alpine
6
-
7
- # Set working directory
8
2
  WORKDIR /app
9
-
10
- # Install system dependencies
11
- RUN apk add --no-cache \
12
- dumb-init \
13
- curl \
14
- gcc \
15
- musl-dev \
16
- libffi-dev \
17
- && rm -rf /var/cache/apk/*
18
-
19
- # Copy requirements first for better layer caching
20
- COPY requirements*.txt ./
21
-
22
- # Install Python dependencies
3
+ COPY {{appSourcePath}}requirements*.txt ./
23
4
  RUN pip install --no-cache-dir -r requirements.txt
24
-
25
- # Copy application code
26
- COPY . .
27
-
28
- # Create non-root user for security
29
- RUN addgroup -g 1001 -S python && \
30
- adduser -S python -u 1001
31
-
32
- # Change ownership of app directory
33
- RUN chown -R python:python /app
34
- USER python
35
-
36
- # Expose application port
37
- # Template variable: {{port}} will be replaced with actual port number
5
+ COPY {{appSourcePath}} .
38
6
  EXPOSE {{port}}
39
-
40
- # Health check
41
- # Template variables: {{healthCheck.interval}} and {{healthCheck.path}} will be replaced
42
- HEALTHCHECK --interval={{healthCheck.interval}}s --timeout=3s --start-period=5s --retries=3 \
43
- CMD curl -f http://localhost:{{port}}{{healthCheck.path}} || exit 1
44
-
45
- # Use dumb-init to handle signals properly
46
- ENTRYPOINT ["dumb-init", "--"]
47
-
48
- # Start application
49
- CMD {{#if startupCommand}}["{{startupCommand}}"]{{else}}["python", "main.py"]{{/if}}
7
+ {{#if healthCheck}}
8
+ HEALTHCHECK --interval={{healthCheck.interval}}s CMD curl -f http://localhost:{{port}}{{healthCheck.path}} || exit 1
9
+ {{/if}}
10
+ {{#if startupCommand}}
11
+ CMD {{startupCommand}}
12
+ {{/if}}
@@ -1,46 +1,12 @@
1
- # TypeScript/Node.js Dockerfile Template
2
- # Generated by AI Fabrix Builder SDK
3
- # Base image: Node 20 Alpine for optimal size and security
4
-
5
1
  FROM node:20-alpine
6
-
7
- # Set working directory
8
2
  WORKDIR /app
9
-
10
- # Install system dependencies
11
- RUN apk add --no-cache \
12
- dumb-init \
13
- curl \
14
- && rm -rf /var/cache/apk/*
15
-
16
- # Copy package files first for better layer caching
17
- COPY package*.json ./
18
-
19
- # Install dependencies (including devDependencies for ts-node)
3
+ COPY {{appSourcePath}}package*.json ./
20
4
  RUN npm install && npm cache clean --force
21
-
22
- # Copy application code
23
- COPY . .
24
-
25
- # Create non-root user for security
26
- RUN addgroup -g 1001 -S nodejs && \
27
- adduser -S nextjs -u 1001
28
-
29
- # Change ownership of app directory
30
- RUN chown -R nextjs:nodejs /app
31
- USER nextjs
32
-
33
- # Expose application port
34
- # Template variable: {{port}} will be replaced with actual port number
5
+ COPY {{appSourcePath}} .
35
6
  EXPOSE {{port}}
36
-
37
- # Health check
38
- # Template variables: {{healthCheck.interval}} and {{healthCheck.path}} will be replaced
39
- HEALTHCHECK --interval={{healthCheck.interval}}s --timeout=3s --start-period=5s --retries=3 \
40
- CMD curl -f http://localhost:{{port}}{{healthCheck.path}} || exit 1
41
-
42
- # Use dumb-init to handle signals properly
43
- ENTRYPOINT ["dumb-init", "--"]
44
-
45
- # Start application
46
- CMD {{#if startupCommand}}["{{startupCommand}}"]{{else}}["npm", "start"]{{/if}}
7
+ {{#if healthCheck}}
8
+ HEALTHCHECK --interval={{healthCheck.interval}}s CMD curl -f http://localhost:{{port}}{{healthCheck.path}} || exit 1
9
+ {{/if}}
10
+ {{#if startupCommand}}
11
+ CMD {{startupCommand}}
12
+ {{/if}}