@aifabrix/builder 2.0.0 → 2.0.3

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 (61) hide show
  1. package/README.md +5 -3
  2. package/bin/aifabrix.js +9 -3
  3. package/jest.config.integration.js +30 -0
  4. package/lib/app-config.js +157 -0
  5. package/lib/app-deploy.js +233 -82
  6. package/lib/app-dockerfile.js +112 -0
  7. package/lib/app-prompts.js +244 -0
  8. package/lib/app-push.js +172 -0
  9. package/lib/app-run.js +235 -144
  10. package/lib/app.js +208 -274
  11. package/lib/audit-logger.js +2 -0
  12. package/lib/build.js +177 -125
  13. package/lib/cli.js +76 -86
  14. package/lib/commands/app.js +414 -0
  15. package/lib/commands/login.js +304 -0
  16. package/lib/config.js +78 -0
  17. package/lib/deployer.js +225 -81
  18. package/lib/env-reader.js +45 -30
  19. package/lib/generator.js +308 -191
  20. package/lib/github-generator.js +67 -7
  21. package/lib/infra.js +156 -61
  22. package/lib/push.js +105 -10
  23. package/lib/schema/application-schema.json +30 -2
  24. package/lib/schema/env-config.yaml +9 -1
  25. package/lib/schema/infrastructure-schema.json +589 -0
  26. package/lib/secrets.js +229 -24
  27. package/lib/template-validator.js +205 -0
  28. package/lib/templates.js +305 -170
  29. package/lib/utils/api.js +329 -0
  30. package/lib/utils/cli-utils.js +97 -0
  31. package/lib/utils/compose-generator.js +185 -0
  32. package/lib/utils/docker-build.js +173 -0
  33. package/lib/utils/dockerfile-utils.js +131 -0
  34. package/lib/utils/environment-checker.js +125 -0
  35. package/lib/utils/error-formatter.js +61 -0
  36. package/lib/utils/health-check.js +187 -0
  37. package/lib/utils/logger.js +53 -0
  38. package/lib/utils/template-helpers.js +223 -0
  39. package/lib/utils/variable-transformer.js +271 -0
  40. package/lib/validator.js +27 -112
  41. package/package.json +14 -10
  42. package/templates/README.md +75 -3
  43. package/templates/applications/keycloak/Dockerfile +36 -0
  44. package/templates/applications/keycloak/env.template +32 -0
  45. package/templates/applications/keycloak/rbac.yaml +37 -0
  46. package/templates/applications/keycloak/variables.yaml +56 -0
  47. package/templates/applications/miso-controller/Dockerfile +125 -0
  48. package/templates/applications/miso-controller/env.template +129 -0
  49. package/templates/applications/miso-controller/rbac.yaml +214 -0
  50. package/templates/applications/miso-controller/variables.yaml +56 -0
  51. package/templates/github/release.yaml.hbs +5 -26
  52. package/templates/github/steps/npm.hbs +24 -0
  53. package/templates/infra/compose.yaml +6 -6
  54. package/templates/python/docker-compose.hbs +19 -12
  55. package/templates/python/main.py +80 -0
  56. package/templates/python/requirements.txt +4 -0
  57. package/templates/typescript/Dockerfile.hbs +2 -2
  58. package/templates/typescript/docker-compose.hbs +19 -12
  59. package/templates/typescript/index.ts +116 -0
  60. package/templates/typescript/package.json +26 -0
  61. package/templates/typescript/tsconfig.json +24 -0
package/lib/validator.js CHANGED
@@ -14,6 +14,9 @@ const path = require('path');
14
14
  const yaml = require('js-yaml');
15
15
  const Ajv = require('ajv');
16
16
  const applicationSchema = require('./schema/application-schema.json');
17
+ const { transformVariablesForValidation } = require('./utils/variable-transformer');
18
+ const { checkEnvironment } = require('./utils/environment-checker');
19
+ const { formatValidationErrors } = require('./utils/error-formatter');
17
20
 
18
21
  /**
19
22
  * Validates variables.yaml file against application schema
@@ -49,9 +52,12 @@ async function validateVariables(appName) {
49
52
  throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
50
53
  }
51
54
 
55
+ // Transform nested structure to flat schema format
56
+ const transformed = transformVariablesForValidation(variables, appName);
57
+
52
58
  const ajv = new Ajv({ allErrors: true, strict: false });
53
59
  const validate = ajv.compile(applicationSchema);
54
- const valid = validate(variables);
60
+ const valid = validate(transformed);
55
61
 
56
62
  return {
57
63
  valid,
@@ -209,125 +215,33 @@ async function validateEnvTemplate(appName) {
209
215
  }
210
216
 
211
217
  /**
212
- * Checks the development environment for common issues
213
- * Validates Docker, ports, secrets, and other requirements
218
+ * Validates deployment JSON against application schema
219
+ * Ensures generated aifabrix-deploy.json matches the schema structure
214
220
  *
215
- * @async
216
- * @function checkEnvironment
217
- * @returns {Promise<Object>} Environment check result
218
- * @throws {Error} If critical issues are found
221
+ * @function validateDeploymentJson
222
+ * @param {Object} deployment - Deployment JSON object to validate
223
+ * @returns {Object} Validation result with errors
219
224
  *
220
225
  * @example
221
- * const result = await checkEnvironment();
222
- * // Returns: { docker: 'ok', ports: 'ok', secrets: 'missing', recommendations: [...] }
226
+ * const result = validateDeploymentJson(deployment);
227
+ * // Returns: { valid: true, errors: [] }
223
228
  */
224
- async function checkDocker() {
225
- try {
226
- const { exec } = require('child_process');
227
- const { promisify } = require('util');
228
- const execAsync = promisify(exec);
229
-
230
- await execAsync('docker --version');
231
- await execAsync('docker-compose --version');
232
- return 'ok';
233
- } catch (error) {
234
- return 'error';
235
- }
236
- }
237
-
238
- async function checkPorts() {
239
- const requiredPorts = [5432, 6379, 5050, 8081];
240
- const netstat = require('net');
241
- let portIssues = 0;
242
-
243
- for (const port of requiredPorts) {
244
- try {
245
- await new Promise((resolve, reject) => {
246
- const server = netstat.createServer();
247
- server.listen(port, () => {
248
- server.close(resolve);
249
- });
250
- server.on('error', reject);
251
- });
252
- } catch (error) {
253
- portIssues++;
254
- }
255
- }
256
-
257
- return portIssues === 0 ? 'ok' : 'warning';
258
- }
259
-
260
- function checkSecrets() {
261
- const os = require('os');
262
- const secretsPath = path.join(os.homedir(), '.aifabrix', 'secrets.yaml');
263
- return fs.existsSync(secretsPath) ? 'ok' : 'missing';
264
- }
265
-
266
- async function checkEnvironment() {
267
- const result = {
268
- docker: 'unknown',
269
- ports: 'unknown',
270
- secrets: 'unknown',
271
- recommendations: []
272
- };
273
-
274
- // Check Docker
275
- result.docker = await checkDocker();
276
- if (result.docker === 'error') {
277
- result.recommendations.push('Install Docker and Docker Compose');
278
- }
279
-
280
- // Check ports
281
- result.ports = await checkPorts();
282
- if (result.ports === 'warning') {
283
- result.recommendations.push('Some required ports (5432, 6379, 5050, 8081) are in use');
284
- }
285
-
286
- // Check secrets
287
- result.secrets = checkSecrets();
288
- if (result.secrets === 'missing') {
289
- result.recommendations.push('Create secrets file: ~/.aifabrix/secrets.yaml');
229
+ function validateDeploymentJson(deployment) {
230
+ if (!deployment || typeof deployment !== 'object') {
231
+ return {
232
+ valid: false,
233
+ errors: ['Deployment must be an object']
234
+ };
290
235
  }
291
236
 
292
- return result;
293
- }
237
+ const ajv = new Ajv({ allErrors: true, strict: false });
238
+ const validate = ajv.compile(applicationSchema);
239
+ const valid = validate(deployment);
294
240
 
295
- /**
296
- * Formats validation errors into developer-friendly messages
297
- * Converts technical schema errors into actionable advice
298
- *
299
- * @function formatValidationErrors
300
- * @param {Array} errors - Raw validation errors from Ajv
301
- * @returns {Array} Formatted error messages
302
- *
303
- * @example
304
- * const messages = formatValidationErrors(ajvErrors);
305
- * // Returns: ['Port must be between 1 and 65535', 'Missing required field: displayName']
306
- */
307
- function formatSingleError(error) {
308
- const path = error.instancePath ? error.instancePath.slice(1) : 'root';
309
- const field = path ? `Field "${path}"` : 'Configuration';
310
-
311
- const errorMessages = {
312
- required: `${field}: Missing required property "${error.params.missingProperty}"`,
313
- type: `${field}: Expected ${error.params.type}, got ${typeof error.data}`,
314
- minimum: `${field}: Value must be at least ${error.params.limit}`,
315
- maximum: `${field}: Value must be at most ${error.params.limit}`,
316
- minLength: `${field}: Must be at least ${error.params.limit} characters`,
317
- maxLength: `${field}: Must be at most ${error.params.limit} characters`,
318
- pattern: `${field}: Invalid format`,
319
- enum: `${field}: Must be one of: ${error.params.allowedValues?.join(', ') || 'unknown'}`
241
+ return {
242
+ valid,
243
+ errors: valid ? [] : formatValidationErrors(validate.errors)
320
244
  };
321
-
322
- return errorMessages[error.keyword] || `${field}: ${error.message}`;
323
- }
324
-
325
- function formatValidationErrors(errors) {
326
- if (!Array.isArray(errors)) {
327
- return ['Unknown validation error'];
328
- }
329
-
330
- return errors.map(formatSingleError);
331
245
  }
332
246
 
333
247
  /**
@@ -371,6 +285,7 @@ module.exports = {
371
285
  validateVariables,
372
286
  validateRbac,
373
287
  validateEnvTemplate,
288
+ validateDeploymentJson,
374
289
  checkEnvironment,
375
290
  formatValidationErrors,
376
291
  validateApplication
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -10,15 +10,18 @@
10
10
  "test": "jest --coverage",
11
11
  "test:watch": "jest --watch",
12
12
  "test:ci": "jest --ci --coverage --watchAll=false",
13
+ "test:integration": "jest --config jest.config.integration.js --runInBand",
14
+ "test:integration:python": "cross-env TEST_LANGUAGE=python jest --config jest.config.integration.js --runInBand",
15
+ "test:integration:typescript": "cross-env TEST_LANGUAGE=typescript jest --config jest.config.integration.js --runInBand",
13
16
  "lint": "eslint . --ext .js",
14
17
  "lint:fix": "eslint . --ext .js --fix",
15
18
  "lint:ci": "eslint . --ext .js --format json --output-file eslint-report.json",
16
19
  "dev": "node bin/aifabrix.js",
17
20
  "build": "npm run lint && npm run test:ci",
21
+ "pack": "npm run build && npm pack",
18
22
  "validate": "npm run build",
19
23
  "prepublishOnly": "npm run validate",
20
- "precommit": "npm run lint:fix && npm run test",
21
- "posttest": "npm run lint"
24
+ "precommit": "npm run lint:fix && npm run test"
22
25
  },
23
26
  "keywords": [
24
27
  "aifabrix",
@@ -34,19 +37,20 @@
34
37
  "node": ">=18.0.0"
35
38
  },
36
39
  "dependencies": {
37
- "commander": "^11.1.0",
38
- "js-yaml": "^4.1.0",
39
40
  "ajv": "^8.12.0",
40
- "handlebars": "^4.7.8",
41
41
  "axios": "^1.6.0",
42
42
  "chalk": "^4.1.2",
43
- "ora": "^5.4.1",
44
- "inquirer": "^8.2.5"
43
+ "commander": "^11.1.0",
44
+ "handlebars": "^4.7.8",
45
+ "inquirer": "^8.2.5",
46
+ "js-yaml": "^4.1.0",
47
+ "ora": "^5.4.1"
45
48
  },
46
49
  "devDependencies": {
47
- "jest": "^29.7.0",
50
+ "@types/node": "^20.10.0",
51
+ "cross-env": "^10.1.0",
48
52
  "eslint": "^8.55.0",
49
- "@types/node": "^20.10.0"
53
+ "jest": "^29.7.0"
50
54
  },
51
55
  "repository": {
52
56
  "type": "git",
@@ -1,15 +1,56 @@
1
1
  # AI Fabrix Builder Templates
2
2
 
3
- These are Handlebars (.hbs) template files that generate Docker files for AI Fabrix applications. They should NOT be linted as Dockerfiles since they contain template variables that will be replaced during generation.
3
+ These are Handlebars (.hbs) template files that generate Docker files and application configurations for AI Fabrix applications. They should NOT be linted as Dockerfiles since they contain template variables that will be replaced during generation.
4
4
 
5
- ## Template Files
5
+ ## Template Structure
6
+
7
+ The templates directory is organized as follows:
8
+
9
+ ### Application Templates (for `--template` flag)
10
+
11
+ Application templates are folder-based and located under `templates/applications/`. When you use `--template <name>`, the tool looks for `templates/applications/<name>/` and copies all files from that folder to `builder/<app>/`.
12
+
13
+ **Example:**
14
+ - `templates/applications/miso-controller/` - Miso Controller application template
15
+ - `templates/applications/keycloak/` - Keycloak application template
16
+
17
+ **Template Validation:**
18
+ - Template folder must exist in `templates/applications/<name>/`
19
+ - Template folder must contain at least one file
20
+ - Hidden files (starting with `.`) are skipped
21
+ - If a template includes a `Dockerfile`, it will be copied to `builder/<app>/Dockerfile` along with other files
22
+
23
+ ### Language Templates
6
24
 
7
25
  - `python/Dockerfile.hbs` - Python application Dockerfile template
8
26
  - `python/docker-compose.hbs` - Python application docker-compose template
9
27
  - `typescript/Dockerfile.hbs` - TypeScript/Node.js application Dockerfile template
10
28
  - `typescript/docker-compose.hbs` - TypeScript/Node.js application docker-compose template
29
+
30
+ ### Infrastructure Templates
31
+
11
32
  - `infra/compose.yaml` - Infrastructure services docker-compose template
12
33
 
34
+ ### GitHub Workflow Templates
35
+
36
+ - `github/ci.yaml.hbs` - Continuous Integration workflow
37
+ - `github/pr-checks.yaml.hbs` - Pull Request checks workflow
38
+ - `github/release.yaml.hbs` - Release and publish workflow
39
+ - `github/test.yaml.hbs` - Test workflow
40
+
41
+ ### GitHub Workflow Step Templates (for `--github-steps` flag)
42
+
43
+ Extra workflow steps are located in `templates/github/steps/`. When you use `--github-steps <steps>`, the tool loads step templates from `templates/github/steps/{step}.hbs` and includes them in the generated workflows.
44
+
45
+ **Example:**
46
+ - `github/steps/npm.hbs` - NPM publishing step
47
+ - `github/steps/test.hbs` - Custom test step
48
+
49
+ **Step Templates:**
50
+ - Step templates are Handlebars templates that generate workflow job definitions
51
+ - They receive the same context as the main workflow templates
52
+ - Step templates are rendered and included in workflow files based on the `githubSteps` array
53
+
13
54
  ## Template Variables
14
55
 
15
56
  ### Application Configuration
@@ -34,7 +75,38 @@ These are Handlebars (.hbs) template files that generate Docker files for AI Fab
34
75
 
35
76
  ## Usage
36
77
 
37
- These templates are processed by the AI Fabrix Builder SDK based on the application schema defined in `variables.yaml`. The generated files will be valid Docker files after template processing.
78
+ ### Application Templates
79
+
80
+ Use `--template <name>` when creating an application:
81
+
82
+ ```bash
83
+ aifabrix create myapp --template miso-controller --port 3000
84
+ ```
85
+
86
+ This validates that `templates/applications/miso-controller/` exists and copies all files (including Dockerfile if present) from it to `builder/myapp/`.
87
+
88
+ ### GitHub Workflow Steps
89
+
90
+ Use `--github-steps <steps>` when creating an application with GitHub workflows:
91
+
92
+ ```bash
93
+ aifabrix create myapp --github --github-steps npm
94
+ ```
95
+
96
+ This loads step templates from:
97
+ - `templates/github/steps/npm.hbs`
98
+
99
+ And includes them in the generated workflow files (e.g., `release.yaml`).
100
+
101
+ **Currently available step templates:**
102
+ - `npm.hbs` - Adds NPM publishing job to release workflow
103
+
104
+ **Creating custom step templates:**
105
+ Create your own step templates in `templates/github/steps/{your-step}.hbs` and reference them in `--github-steps`.
106
+
107
+ ### Language and Infrastructure Templates
108
+
109
+ These templates are processed automatically by the AI Fabrix Builder SDK based on the application schema defined in `variables.yaml`. The generated files will be valid Docker files after template processing.
38
110
 
39
111
  ## VS Code Configuration
40
112
 
@@ -0,0 +1,36 @@
1
+ # Keycloak Identity and Access Management with Custom Themes
2
+ # This Dockerfile extends Keycloak 24.0 with custom themes
3
+
4
+ FROM quay.io/keycloak/keycloak:24.0
5
+
6
+ # Set working directory
7
+ WORKDIR /opt/keycloak
8
+
9
+ # Copy custom themes from share directory (only if themes/ folder exists)
10
+ USER root
11
+ # Use a shell script approach: copy themes if it exists, otherwise skip
12
+ # First, copy everything from build context to a staging area
13
+ COPY . /tmp/build-context/
14
+ # Then conditionally copy themes if the directory exists
15
+ RUN if [ -d "/tmp/build-context/themes" ] && [ "$(ls -A /tmp/build-context/themes)" ]; then \
16
+ cp -r /tmp/build-context/themes/* /opt/keycloak/themes/ && \
17
+ chown -R keycloak:keycloak /opt/keycloak/themes/ && \
18
+ echo "Themes copied successfully"; \
19
+ else \
20
+ echo "No themes directory found, skipping theme copy"; \
21
+ fi && \
22
+ rm -rf /tmp/build-context
23
+
24
+ # Build Keycloak with health checks enabled
25
+ # This enables the /health, /health/live, /health/ready, and /health/started endpoints
26
+ RUN /opt/keycloak/bin/kc.sh build --health-enabled=true
27
+
28
+ # Switch back to keycloak user
29
+ USER keycloak
30
+
31
+ # Keycloak runs on port 8080 internally, exposed as 8082
32
+ EXPOSE 8080
33
+
34
+ # Default Keycloak command (can be overridden in docker-compose.yaml)
35
+ # Health checks are enabled via the build step above
36
+ CMD ["start-dev"]
@@ -0,0 +1,32 @@
1
+ # Environment Variables Template
2
+ # Use kv:// references for secrets (resolved from .aifabrix/secrets.yaml)
3
+ # Use ${VAR} for environment-specific values
4
+
5
+ # =============================================================================
6
+ # APPLICATION ENVIRONMENT
7
+ # =============================================================================
8
+
9
+ KEYCLOAK_ADMIN=admin
10
+ KEYCLOAK_ADMIN_PASSWORD=kv://keycloak-admin-passwordKeyVault
11
+ KC_HOSTNAME_STRICT=false
12
+ KC_HTTP_ENABLED=true
13
+
14
+ # =============================================================================
15
+ # HEALTH CHECK CONFIGURATION
16
+ # =============================================================================
17
+ # Enable health check endpoints: /health, /health/live, /health/ready, /health/started
18
+
19
+ KC_HEALTH_ENABLED=true
20
+
21
+ # =============================================================================
22
+ # DATABASE CONFIGURATION
23
+ # =============================================================================
24
+ # Connects to postgres service in Docker network (postgres) or localhost (local)
25
+
26
+ KC_DB=postgres
27
+ KC_DB_URL_HOST=${DB_HOST}
28
+ KC_DB_URL_PORT=5432
29
+ KC_DB_URL_DATABASE=keycloak
30
+ KC_DB_USERNAME=keycloak_user
31
+ KC_DB_PASSWORD=kv://databases-keycloak-0-passwordKeyVault
32
+
@@ -0,0 +1,37 @@
1
+ roles:
2
+ - name: "AI Fabrix Admin"
3
+ value: "aifabrix-admin"
4
+ description: "Full access to all application features and configurations"
5
+
6
+ - name: "AI Fabrix User"
7
+ value: "aifabrix-user"
8
+ description: "Basic user access to the application"
9
+
10
+ - name: "AI Fabrix Developer"
11
+ value: "aifabrix-developer"
12
+ description: "Developer access for testing and debugging"
13
+
14
+ permissions:
15
+ - name: "myapp:read"
16
+ roles:
17
+ - "aifabrix-user"
18
+ - "aifabrix-admin"
19
+ - "aifabrix-developer"
20
+ description: "Read access to application data"
21
+
22
+ - name: "myapp:write"
23
+ roles:
24
+ - "aifabrix-admin"
25
+ - "aifabrix-developer"
26
+ description: "Create and edit application data"
27
+
28
+ - name: "myapp:delete"
29
+ roles:
30
+ - "aifabrix-admin"
31
+ description: "Delete application data"
32
+
33
+ - name: "myapp:admin"
34
+ roles:
35
+ - "aifabrix-admin"
36
+ description: "Administrative access to application configuration"
37
+
@@ -0,0 +1,56 @@
1
+ # Application Metadata
2
+ app:
3
+ key: keycloak
4
+ displayName: "AI Fabrix Keycloak"
5
+ description: "Identity and Access Management"
6
+ type: webapp
7
+
8
+ # Image Configuration
9
+ image:
10
+ name: aifabrix/keycloak
11
+ tag: "latest"
12
+ registry: devflowiseacr.azurecr.io
13
+ registryMode: acr
14
+
15
+ # Port Configuration
16
+ port: 8082
17
+
18
+ # Azure Requirements
19
+ requires:
20
+ database: true
21
+ databases:
22
+ - name: keycloak
23
+ redis: false
24
+ storage: false
25
+
26
+ # Health Check
27
+ healthCheck:
28
+ path: /health
29
+ interval: 30
30
+ probePath: /health
31
+ probeRequestType: GET
32
+ probeProtocol: Https
33
+ probeIntervalInSeconds: 120
34
+
35
+ # Authentication
36
+ authentication:
37
+ type: keycloak
38
+ enableSSO: true
39
+ requiredRoles: ["aifabrix-user"]
40
+ endpoints:
41
+ local: "http://localhost:8082/auth/callback"
42
+
43
+ # Build Configuration
44
+ build:
45
+ context: .. # Docker build context (relative to builder/)
46
+ dockerfile: builder/Dockerfile # Dockerfile name (empty = use template)
47
+ envOutputPath: .env # Copy .env to repo root for local dev
48
+ localPort: 8082 # Port for local development (different from Docker port)
49
+ containerPort: 8080 # Container port (different from local port)
50
+ language: typescript # Runtime language for template selection
51
+ secrets: # Path to secrets file (optional)
52
+
53
+ # Docker Compose
54
+ compose:
55
+ file: docker-compose.yaml
56
+ service: keycloak
@@ -0,0 +1,125 @@
1
+ # AI Fabrix Miso Controller - Optimized Dockerfile
2
+ FROM node:18-alpine
3
+
4
+ # Install only essential runtime dependencies
5
+ RUN apk add --no-cache \
6
+ curl \
7
+ wget \
8
+ openssl \
9
+ openssl-dev
10
+
11
+ # Install PNPM globally
12
+ RUN npm install -g pnpm
13
+
14
+ # Install tsconfig-paths globally for path resolution
15
+ RUN npm install -g tsconfig-paths
16
+
17
+ # Set working directory
18
+ WORKDIR /app
19
+
20
+ # Copy package files for dependency resolution
21
+ COPY package*.json ./
22
+ COPY pnpm-workspace.yaml ./
23
+ COPY packages/miso-azure/package*.json ./packages/miso-azure/
24
+ COPY packages/miso-controller/package*.json ./packages/miso-controller/
25
+ COPY packages/miso-controller/bin ./packages/miso-controller/bin
26
+ COPY packages/miso-ui/package*.json ./packages/miso-ui/
27
+
28
+ # Create .npmrc for hoisting to fix module resolution in Docker
29
+ RUN echo "shamefully-hoist=true" > .npmrc
30
+
31
+ # Install all dependencies (including dev for building)
32
+ RUN pnpm install
33
+
34
+ # Copy only the source code we need
35
+ COPY packages/miso-azure/src ./packages/miso-azure/src
36
+ COPY packages/miso-azure/tsconfig.json ./packages/miso-azure/
37
+ COPY packages/miso-controller/src ./packages/miso-controller/src
38
+ COPY packages/miso-controller/tsconfig.json ./packages/miso-controller/
39
+ COPY packages/miso-controller/tsconfig.docker.json ./packages/miso-controller/
40
+ COPY packages/miso-controller/openapi/openapi-complete.yaml ./packages/miso-controller/dist/openapi/
41
+ COPY packages/miso-controller/openapi/openapi-complete.json ./packages/miso-controller/dist/openapi/
42
+ COPY packages/miso-ui/src ./packages/miso-ui/src
43
+ COPY packages/miso-ui/tsconfig.json ./packages/miso-ui/
44
+ COPY packages/miso-ui/tsconfig.app.json ./packages/miso-ui/
45
+ COPY packages/miso-ui/tsconfig.node.json ./packages/miso-ui/
46
+ COPY packages/miso-ui/vite.config.ts ./packages/miso-ui/
47
+ COPY packages/miso-ui/index.html ./packages/miso-ui/
48
+
49
+ # Fix Rollup native module issue for Alpine Linux
50
+ RUN pnpm add @rollup/rollup-linux-x64-musl @types/express-serve-static-core --workspace-root
51
+
52
+ # Build packages
53
+ WORKDIR /app/packages/miso-azure
54
+ RUN pnpm run build
55
+
56
+ WORKDIR /app/packages/miso-ui
57
+ RUN pnpm run build
58
+
59
+ WORKDIR /app/packages/miso-controller
60
+ RUN pnpm run db:generate
61
+ RUN pnpm exec tsc -p tsconfig.docker.json || true
62
+ # Copy sensitive-fields.config.json to dist folder
63
+ RUN mkdir -p dist/src/services/logging && \
64
+ cp src/services/logging/sensitive-fields.config.json dist/src/services/logging/ || true
65
+
66
+ # Return to root to prune correctly (needed to keep workspace dependencies)
67
+ WORKDIR /app
68
+
69
+ # Remove source files and build artifacts, but preserve Prisma schema files and OpenAPI files
70
+ RUN mkdir -p packages/miso-controller/dist/database/prisma && \
71
+ cp packages/miso-controller/src/database/prisma/*.prisma packages/miso-controller/dist/database/prisma/ || true
72
+ RUN rm -rf packages/miso-azure/src packages/miso-controller/src packages/miso-ui/src
73
+ RUN rm -rf packages/miso-azure/tsconfig.json packages/miso-ui/tsconfig.json
74
+ RUN rm -rf packages/*/node_modules packages/*/.git packages/*/.github
75
+
76
+ # Reinstall to recreate symlinks after removing source files
77
+ # This ensures workspace dependencies are properly linked
78
+ RUN pnpm install --frozen-lockfile --filter "@aifabrix/miso-controller..." || pnpm install --frozen-lockfile
79
+
80
+ # Manually remove known dev dependencies
81
+ RUN rm -rf node_modules/markdownlint node_modules/markdownlint-cli node_modules/prettier
82
+ RUN rm -rf .pnpm/markdownlint* .pnpm/prettier*
83
+
84
+ # Create non-root user
85
+ RUN addgroup -g 1001 -S nodejs && \
86
+ adduser -S miso -u 1001
87
+
88
+ # Create necessary directories for mount points (with fallback to local storage)
89
+ RUN mkdir -p /mnt/data/logs /mnt/data/data /mnt/data/backup /mnt/data/config
90
+ RUN mkdir -p /app/data/logs /app/data/data /app/data/backup /app/data/config
91
+
92
+ # Change ownership of app directory and mount points
93
+ RUN chown -R miso:nodejs /app /mnt/data
94
+
95
+ # Switch to non-root user
96
+ USER miso
97
+
98
+ # Set working directory to controller
99
+ WORKDIR /app/packages/miso-controller
100
+
101
+ # Expose port
102
+ EXPOSE 3000
103
+
104
+ # Health check
105
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
106
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
107
+
108
+ # Set environment variable for tsconfig-paths
109
+ ENV TS_NODE_PROJECT=tsconfig.docker.json
110
+
111
+ # Set default data paths (can be overridden by environment variables)
112
+ ENV LOG_PATH=/mnt/data/logs
113
+ ENV DATA_PATH=/mnt/data/data
114
+ ENV BACKUP_PATH=/mnt/data/backup
115
+ ENV CONFIG_PATH=/mnt/data/config
116
+
117
+ # Create symlinks for fallback to local storage if mounts are not provided
118
+ RUN ln -sf /app/data/logs /mnt/data/logs || true
119
+ RUN ln -sf /app/data/data /mnt/data/data || true
120
+ RUN ln -sf /app/data/backup /mnt/data/backup || true
121
+ RUN ln -sf /app/data/config /mnt/data/config || true
122
+
123
+ # Start the application
124
+ CMD ["node", "-r", "tsconfig-paths/register", "dist/src/server.js"]
125
+