@simplens/onboard 1.0.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 (94) hide show
  1. package/README.md +214 -0
  2. package/dist/__tests__/errors.test.d.ts +2 -0
  3. package/dist/__tests__/errors.test.d.ts.map +1 -0
  4. package/dist/__tests__/errors.test.js +125 -0
  5. package/dist/__tests__/errors.test.js.map +1 -0
  6. package/dist/__tests__/utils.test.d.ts +2 -0
  7. package/dist/__tests__/utils.test.d.ts.map +1 -0
  8. package/dist/__tests__/utils.test.js +105 -0
  9. package/dist/__tests__/utils.test.js.map +1 -0
  10. package/dist/__tests__/validators.test.d.ts +2 -0
  11. package/dist/__tests__/validators.test.d.ts.map +1 -0
  12. package/dist/__tests__/validators.test.js +148 -0
  13. package/dist/__tests__/validators.test.js.map +1 -0
  14. package/dist/config/constants.d.ts +69 -0
  15. package/dist/config/constants.d.ts.map +1 -0
  16. package/dist/config/constants.js +79 -0
  17. package/dist/config/constants.js.map +1 -0
  18. package/dist/config/index.d.ts +2 -0
  19. package/dist/config/index.d.ts.map +1 -0
  20. package/dist/config/index.js +2 -0
  21. package/dist/config/index.js.map +1 -0
  22. package/dist/env-config.d.ts +33 -0
  23. package/dist/env-config.d.ts.map +1 -0
  24. package/dist/env-config.js +285 -0
  25. package/dist/env-config.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +153 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/infra.d.ts +31 -0
  31. package/dist/infra.d.ts.map +1 -0
  32. package/dist/infra.js +267 -0
  33. package/dist/infra.js.map +1 -0
  34. package/dist/plugins.d.ts +35 -0
  35. package/dist/plugins.d.ts.map +1 -0
  36. package/dist/plugins.js +164 -0
  37. package/dist/plugins.js.map +1 -0
  38. package/dist/services.d.ts +52 -0
  39. package/dist/services.d.ts.map +1 -0
  40. package/dist/services.js +158 -0
  41. package/dist/services.js.map +1 -0
  42. package/dist/templates.d.ts +3 -0
  43. package/dist/templates.d.ts.map +1 -0
  44. package/dist/templates.js +202 -0
  45. package/dist/templates.js.map +1 -0
  46. package/dist/types/domain.d.ts +119 -0
  47. package/dist/types/domain.d.ts.map +1 -0
  48. package/dist/types/domain.js +5 -0
  49. package/dist/types/domain.js.map +1 -0
  50. package/dist/types/errors.d.ts +69 -0
  51. package/dist/types/errors.d.ts.map +1 -0
  52. package/dist/types/errors.js +129 -0
  53. package/dist/types/errors.js.map +1 -0
  54. package/dist/types/index.d.ts +3 -0
  55. package/dist/types/index.d.ts.map +1 -0
  56. package/dist/types/index.js +3 -0
  57. package/dist/types/index.js.map +1 -0
  58. package/dist/utils/index.d.ts +2 -0
  59. package/dist/utils/index.d.ts.map +1 -0
  60. package/dist/utils/index.js +2 -0
  61. package/dist/utils/index.js.map +1 -0
  62. package/dist/utils/logger.d.ts +54 -0
  63. package/dist/utils/logger.d.ts.map +1 -0
  64. package/dist/utils/logger.js +92 -0
  65. package/dist/utils/logger.js.map +1 -0
  66. package/dist/utils.d.ts +32 -0
  67. package/dist/utils.d.ts.map +1 -0
  68. package/dist/utils.js +79 -0
  69. package/dist/utils.js.map +1 -0
  70. package/dist/validators.d.ts +93 -0
  71. package/dist/validators.d.ts.map +1 -0
  72. package/dist/validators.js +180 -0
  73. package/dist/validators.js.map +1 -0
  74. package/package.json +45 -0
  75. package/src/__tests__/errors.test.ts +187 -0
  76. package/src/__tests__/utils.test.ts +142 -0
  77. package/src/__tests__/validators.test.ts +195 -0
  78. package/src/config/constants.ts +86 -0
  79. package/src/config/index.ts +1 -0
  80. package/src/env-config.ts +320 -0
  81. package/src/index.ts +203 -0
  82. package/src/infra.ts +300 -0
  83. package/src/plugins.ts +190 -0
  84. package/src/services.ts +190 -0
  85. package/src/templates.ts +203 -0
  86. package/src/types/domain.ts +127 -0
  87. package/src/types/errors.ts +173 -0
  88. package/src/types/index.ts +2 -0
  89. package/src/utils/index.ts +1 -0
  90. package/src/utils/logger.ts +118 -0
  91. package/src/utils.ts +105 -0
  92. package/src/validators.ts +192 -0
  93. package/tsconfig.json +19 -0
  94. package/vitest.config.ts +20 -0
@@ -0,0 +1,190 @@
1
+ import inquirer from 'inquirer';
2
+ import { execa } from 'execa';
3
+ import ora from 'ora';
4
+ import { logInfo, logSuccess, logError, logWarning } from './utils.js';
5
+ import chalk from 'chalk';
6
+ import { HEALTH_CHECK, SERVICE_PORTS, getServiceURL } from './config/constants.js';
7
+
8
+ /**
9
+ * Prompts user whether to start the services immediately after setup.
10
+ *
11
+ * @returns `true` if user wants to start services, `false` otherwise
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * if (await promptStartServices()) {
16
+ * await startInfraServices(targetDir);
17
+ * }
18
+ * ```
19
+ */
20
+ export async function promptStartServices(): Promise<boolean> {
21
+ const answer = await inquirer.prompt<{ start: boolean }>([
22
+ {
23
+ type: 'confirm',
24
+ name: 'start',
25
+ message: 'Do you want to start the services now?',
26
+ default: true,
27
+ },
28
+ ]);
29
+
30
+ return answer.start;
31
+ }
32
+
33
+ /**
34
+ * Starts infrastructure services using docker-compose.
35
+ * Runs `docker-compose -f docker-compose.infra.yaml up -d` in the target directory.
36
+ *
37
+ * @param targetDir - Directory containing docker-compose.infra.yaml
38
+ * @throws Error if docker-compose command fails
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * await startInfraServices('/opt/simplens');
43
+ * ```
44
+ */
45
+ export async function startInfraServices(targetDir: string): Promise<void> {
46
+ logInfo('Starting infrastructure services...');
47
+
48
+ const spinner = ora('Starting docker-compose.infra.yaml...').start();
49
+
50
+ try {
51
+ await execa(
52
+ 'docker-compose',
53
+ ['-f', 'docker-compose.infra.yaml', 'up', '-d'],
54
+ { cwd: targetDir }
55
+ );
56
+ spinner.succeed('Infrastructure services started');
57
+ } catch (error: any) {
58
+ spinner.fail('Failed to start infrastructure services');
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Waits for infrastructure services to become healthy.
65
+ * Polls Docker health checks for up to 60 seconds (30 retries Ɨ 2s).
66
+ *
67
+ * @param targetDir - Directory where services are running
68
+ *
69
+ * @remarks
70
+ * Checks for healthy containers running MongoDB or Redis.
71
+ * Configuration: 30 max retries, 2000ms delay between retries.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * await startInfraServices(targetDir);
76
+ * await waitForInfraHealth(targetDir); // Wait for services to be ready
77
+ * ```
78
+ */
79
+ export async function waitForInfraHealth(targetDir: string): Promise<void> {
80
+ logInfo('Waiting for infrastructure services to be healthy...');
81
+
82
+ const spinner = ora('Checking service health...').start();
83
+
84
+ // Wait for mongo, redis health checks
85
+ const maxRetries = HEALTH_CHECK.MAX_RETRIES;
86
+ const retryDelay = HEALTH_CHECK.RETRY_DELAY_MS;
87
+
88
+ for (let i = 0; i < maxRetries; i++) {
89
+ try {
90
+ // Check if containers are healthy
91
+ const { stdout } = await execa('docker', ['ps', '--filter', 'health=healthy', '--format', '{{.Names}}']);
92
+ const healthyContainers = stdout.split('\n').filter(Boolean);
93
+
94
+ // Check for critical services
95
+ const hasMongoOrRedis = healthyContainers.some(name =>
96
+ name.includes('mongo') || name.includes('redis')
97
+ );
98
+
99
+ if (hasMongoOrRedis) {
100
+ spinner.succeed('Infrastructure services are healthy');
101
+ return;
102
+ }
103
+
104
+ spinner.text = `Waiting for services... (${i + 1}/${maxRetries})`;
105
+ await sleep(retryDelay);
106
+ } catch (error) {
107
+ spinner.text = `Checking health... (${i + 1}/${maxRetries})`;
108
+ await sleep(retryDelay);
109
+ }
110
+ }
111
+
112
+ spinner.warn('Health check timed out, but services may still be starting');
113
+ logWarning('You may need to wait a bit longer for all services to be ready.');
114
+ }
115
+
116
+ /**
117
+ * Start application services
118
+ */
119
+ export async function startAppServices(targetDir: string): Promise<void> {
120
+ logInfo('Starting application services...');
121
+
122
+ const spinner = ora('Starting docker-compose.yaml...').start();
123
+
124
+ try {
125
+ await execa(
126
+ 'docker-compose',
127
+ ['up', '-d'],
128
+ { cwd: targetDir }
129
+ );
130
+ spinner.succeed('Application services started');
131
+ } catch (error: any) {
132
+ spinner.fail('Failed to start application services');
133
+ throw error;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Display service status and URLs
139
+ */
140
+ export async function displayServiceStatus(): Promise<void> {
141
+ console.log('\n' + chalk.green('═'.repeat(60)));
142
+ console.log(chalk.green.bold(' āœ… Services Started Successfully!'));
143
+ console.log(chalk.green('═'.repeat(60)) + '\n');
144
+
145
+ try {
146
+ // Get running containers
147
+ const { stdout } = await execa('docker', ['ps', '--format', '{{.Names}}']);
148
+ const containers = stdout.split('\n').filter(Boolean);
149
+
150
+ console.log(chalk.cyan.bold('šŸ”— Access URLs:\n'));
151
+
152
+ // Display URLs for known services
153
+ if (containers.some(c => c.includes('api'))) {
154
+ console.log(` ${chalk.bold('API Server:')} ${chalk.underline(getServiceURL('API'))}`);
155
+ console.log(` ${chalk.bold('API Health:')} ${chalk.underline(getServiceURL('API') + '/health')}\n`);
156
+ }
157
+
158
+ if (containers.some(c => c.includes('dashboard'))) {
159
+ console.log(` ${chalk.bold('Dashboard:')} ${chalk.underline(getServiceURL('DASHBOARD'))}\n`);
160
+ }
161
+
162
+ if (containers.some(c => c.includes('kafka-ui'))) {
163
+ console.log(` ${chalk.bold('Kafka UI:')} ${chalk.underline(getServiceURL('KAFKA_UI'))}\n`);
164
+ }
165
+
166
+ if (containers.some(c => c.includes('grafana'))) {
167
+ console.log(` ${chalk.bold('Grafana:')} ${chalk.underline(getServiceURL('GRAFANA'))}`);
168
+ console.log(` ${chalk.gray('(default login: admin/admin)')}\n`);
169
+ }
170
+
171
+ console.log(chalk.cyan.bold('šŸ“¦ Running Containers:\n'));
172
+ for (const container of containers) {
173
+ console.log(` ${chalk.green('āœ“')} ${container}`);
174
+ }
175
+
176
+ console.log('\n' + chalk.cyan('To view logs:') + ' docker-compose logs -f');
177
+ console.log(chalk.cyan('To stop services:') + ' docker-compose down\n');
178
+ console.log(chalk.green('═'.repeat(60)) + '\n');
179
+
180
+ } catch (error) {
181
+ logWarning('Could not fetch container status');
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Helper: Sleep for specified milliseconds
187
+ */
188
+ function sleep(ms: number): Promise<void> {
189
+ return new Promise(resolve => setTimeout(resolve, ms));
190
+ }
@@ -0,0 +1,203 @@
1
+ // Docker Compose templates as constants
2
+
3
+ export const INFRA_COMPOSE_TEMPLATE = `# ============================================
4
+ # INFRA_HOST: Set this in .env to your machine's IP/hostname when running
5
+ # infrastructure on a separate system from application services.
6
+ # Default: host.docker.internal (for same-system deployment)
7
+ # ============================================
8
+
9
+ services:
10
+ # ============================================
11
+ # Infrastructure Services
12
+ # ============================================
13
+ mongo:
14
+ image: mongo:7.0
15
+ container_name: mongo
16
+ command: [ "--replSet", "rs0", "--bind_ip_all", "--port", "27017" ]
17
+ ports:
18
+ - 27017:27017
19
+ extra_hosts:
20
+ - "host.docker.internal:host-gateway"
21
+ healthcheck:
22
+ test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'{{INFRA_HOST}}:27017'}]}) }" | mongosh --port 27017 --quiet
23
+ interval: 5s
24
+ timeout: 30s
25
+ start_period: 0s
26
+ start_interval: 1s
27
+ retries: 30
28
+ volumes:
29
+ - "mongo_data:/data/db"
30
+ - "mongo_config:/data/configdb"
31
+
32
+ kafka:
33
+ image: apache/kafka-native
34
+ container_name: kafka
35
+ ports:
36
+ - "9092:9092"
37
+ environment:
38
+ # Configure listeners for both docker and host communication
39
+ KAFKA_LISTENERS: CONTROLLER://localhost:9091,HOST://0.0.0.0:9092,DOCKER://0.0.0.0:9093
40
+ KAFKA_ADVERTISED_LISTENERS: HOST://{{INFRA_HOST}}:9092,DOCKER://kafka:9093
41
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,DOCKER:PLAINTEXT,HOST:PLAINTEXT
42
+
43
+ # Settings required for KRaft mode
44
+ KAFKA_NODE_ID: 1
45
+ KAFKA_PROCESS_ROLES: broker,controller
46
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
47
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9091
48
+
49
+ # Listener to use for broker-to-broker communication
50
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER
51
+
52
+ # Required for a single node cluster
53
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
54
+
55
+ # Disable auto-topic creation - API server will create topics with correct partitions
56
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
57
+ volumes:
58
+ - "kafka_data:/var/lib/kafka/data"
59
+
60
+ kafka-ui:
61
+ image: kafbat/kafka-ui:main
62
+ container_name: kafka-ui
63
+ ports:
64
+ - 8080:8080
65
+ environment:
66
+ DYNAMIC_CONFIG_ENABLED: "true"
67
+ KAFKA_CLUSTERS_0_NAME: local
68
+ KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9093
69
+ depends_on:
70
+ - kafka
71
+
72
+ redis:
73
+ image: redis:7-alpine
74
+ container_name: redis
75
+ ports:
76
+ - "6379:6379"
77
+ command: redis-server --appendonly yes
78
+ volumes:
79
+ - "redis_data:/data"
80
+ healthcheck:
81
+ test: [ "CMD", "redis-cli", "ping" ]
82
+ interval: 5s
83
+ timeout: 3s
84
+ retries: 5
85
+
86
+ # ============================================
87
+ # Observability Services
88
+ # ============================================
89
+
90
+ loki:
91
+ image: grafana/loki:2.9.0
92
+ container_name: loki
93
+ ports:
94
+ - "3100:3100"
95
+ command: -config.file=/etc/loki/local-config.yaml
96
+ volumes:
97
+ - "loki_data:/loki"
98
+ healthcheck:
99
+ test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3100/ready || exit 1" ]
100
+ interval: 10s
101
+ timeout: 5s
102
+ retries: 5
103
+
104
+ grafana:
105
+ image: grafana/grafana:10.2.0
106
+ container_name: grafana
107
+ ports:
108
+ - "3001:3000"
109
+ environment:
110
+ - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
111
+ - GF_AUTH_ANONYMOUS_ENABLED=true
112
+ - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
113
+ - GF_SECURITY_ADMIN_PASSWORD=admin
114
+ volumes:
115
+ - "grafana_data:/var/lib/grafana"
116
+ depends_on:
117
+ loki:
118
+ condition: service_healthy
119
+
120
+ volumes:
121
+ mongo_data:
122
+ mongo_config:
123
+ kafka_data:
124
+ redis_data:
125
+ loki_data:
126
+ grafana_data:
127
+ `;
128
+
129
+ export const APP_COMPOSE_TEMPLATE = `services:
130
+ api:
131
+ image: ghcr.io/simplenotificationsystem/simplens-core:latest
132
+ container_name: api
133
+ ports:
134
+ - 3000:3000
135
+ env_file:
136
+ - .env
137
+ volumes:
138
+ - plugin-data:/app/.plugins
139
+ - ./simplens.config.yaml:/app/simplens.config.yaml:ro
140
+ command: [ "node", "dist/api/server.js" ]
141
+ restart: unless-stopped
142
+ healthcheck:
143
+ test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))" ]
144
+ interval: 30s
145
+ timeout: 10s
146
+ retries: 3
147
+ start_period: 10s
148
+
149
+ worker:
150
+ image: ghcr.io/simplenotificationsystem/simplens-core:latest
151
+ env_file:
152
+ - .env
153
+ command: [ "node", "dist/workers/worker.js" ]
154
+ restart: unless-stopped
155
+
156
+ notification_processor:
157
+ image: ghcr.io/simplenotificationsystem/simplens-core:latest
158
+ env_file:
159
+ - .env
160
+ volumes:
161
+ - plugin-data:/app/.plugins
162
+ - ./simplens.config.yaml:/app/simplens.config.yaml:ro
163
+ command: [ "node", "dist/processors/unified/unified.processor.js" ]
164
+ depends_on:
165
+ api:
166
+ condition: service_healthy
167
+ restart: unless-stopped
168
+
169
+ delayed_processor:
170
+ image: ghcr.io/simplenotificationsystem/simplens-core:latest
171
+ env_file:
172
+ - .env
173
+ command: [ "node", "dist/processors/delayed/delayed.processor.js" ]
174
+ restart: unless-stopped
175
+
176
+ recovery:
177
+ image: ghcr.io/simplenotificationsystem/simplens-core:latest
178
+ env_file:
179
+ - .env
180
+ command: [ "node", "dist/workers/recovery/recovery.service.js" ]
181
+ restart: unless-stopped
182
+
183
+ dashboard:
184
+ image: ghcr.io/simplenotificationsystem/simplens-dashboard:latest
185
+ ports:
186
+ - 3002:3002
187
+ container_name: dashboard
188
+ env_file:
189
+ - .env
190
+ environment:
191
+ PORT: \${DASHBOARD_PORT:-3002}
192
+ API_BASE_URL: http://api:\${PORT:-3000}
193
+ WEBHOOK_HOST: dashboard
194
+ WEBHOOK_PORT: \${DASHBOARD_PORT:-3002}
195
+ restart: unless-stopped
196
+
197
+ volumes:
198
+ plugin-data:
199
+
200
+ networks:
201
+ default:
202
+ name: simplens
203
+ `;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Domain type definitions for the onboarding system
3
+ */
4
+
5
+ /**
6
+ * Plugin information returned by config-gen
7
+ */
8
+ export interface PluginInfo {
9
+ /** Package name (e.g., '@simplens/nodemailer-gmail') */
10
+ package: string;
11
+ /** Display name */
12
+ name: string;
13
+ /** Description of what the plugin does */
14
+ description: string;
15
+ }
16
+
17
+ /**
18
+ * Infrastructure service option
19
+ */
20
+ export interface InfraService {
21
+ /** Display name (e.g., 'MongoDB (Database)') */
22
+ name: string;
23
+ /** Service identifier (e.g., 'mongo') */
24
+ value: string;
25
+ /** Whether checked by default */
26
+ checked: boolean;
27
+ }
28
+
29
+ /**
30
+ * Environment variable definition
31
+ */
32
+ export interface EnvVariable {
33
+ /** Environment variable key */
34
+ key: string;
35
+ /** Current or default value */
36
+ value: string;
37
+ /** Optional description/comment */
38
+ description?: string;
39
+ /** Whether this variable is required */
40
+ required: boolean;
41
+ }
42
+
43
+ /**
44
+ * Setup options provided via CLI or prompts
45
+ */
46
+ export interface SetupOptions {
47
+ /** Whether to setup infrastructure services */
48
+ infra: boolean;
49
+ /** Environment configuration mode */
50
+ envMode: 'default' | 'interactive';
51
+ /** Target directory for setup */
52
+ targetDir: string;
53
+ }
54
+
55
+ /**
56
+ * Service definition for docker-compose generation
57
+ */
58
+ export interface ServiceDefinition {
59
+ /** Service identifier */
60
+ id: string;
61
+ /** Display name */
62
+ name: string;
63
+ /** Docker compose YAML block */
64
+ dockerCompose: string;
65
+ /** Required volumes */
66
+ volumes: string[];
67
+ /** Service dependencies */
68
+ dependencies?: string[];
69
+ /** Whether service has health check */
70
+ healthCheck?: boolean;
71
+ }
72
+
73
+ /**
74
+ * SimplensConfig YAML structure
75
+ */
76
+ export interface SimplensConfig {
77
+ providers: ProviderConfig[];
78
+ }
79
+
80
+ /**
81
+ * Provider configuration in simplens.config.yaml
82
+ */
83
+ export interface ProviderConfig {
84
+ /** Package name */
85
+ package: string;
86
+ /** Provider ID */
87
+ id: string;
88
+ /** Required credentials as key-value pairs */
89
+ credentials: Record<string, string>;
90
+ /** Optional configuration */
91
+ optionalConfig?: Record<string, string | number | boolean>;
92
+ }
93
+
94
+ /**
95
+ * Docker compose file structure (simplified)
96
+ */
97
+ export interface DockerComposeFile {
98
+ services: Record<string, DockerService>;
99
+ volumes?: Record<string, DockerVolume>;
100
+ networks?: Record<string, DockerNetwork>;
101
+ }
102
+
103
+ export interface DockerService {
104
+ image: string;
105
+ container_name?: string;
106
+ ports?: string[];
107
+ environment?: Record<string, string> | string[];
108
+ volumes?: string[];
109
+ command?: string | string[];
110
+ depends_on?: string[] | Record<string, { condition: string }>;
111
+ healthcheck?: {
112
+ test: string | string[];
113
+ interval?: string;
114
+ timeout?: string;
115
+ retries?: number;
116
+ start_period?: string;
117
+ };
118
+ }
119
+
120
+ export interface DockerVolume {
121
+ driver?: string;
122
+ driver_opts?: Record<string, string>;
123
+ }
124
+
125
+ export interface DockerNetwork {
126
+ driver?: string;
127
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Custom error types for better error handling and troubleshooting
3
+ */
4
+
5
+ /**
6
+ * Base error class for all onboarding errors
7
+ */
8
+ export class OnboardingError extends Error {
9
+ constructor(
10
+ public readonly code: string,
11
+ message: string,
12
+ public readonly troubleshooting?: string
13
+ ) {
14
+ super(message);
15
+ this.name = 'OnboardingError';
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Docker-related errors
22
+ */
23
+ export class DockerError extends OnboardingError {
24
+ constructor(message: string, troubleshooting?: string) {
25
+ super('DOCKER_ERROR', message, troubleshooting);
26
+ this.name = 'DockerError';
27
+ }
28
+ }
29
+
30
+ export class DockerNotInstalledError extends DockerError {
31
+ constructor() {
32
+ super(
33
+ 'Docker is not installed on this system',
34
+ 'Please install Docker from: https://docs.docker.com/get-docker/'
35
+ );
36
+ this.name = 'DockerNotInstalledError';
37
+ }
38
+ }
39
+
40
+ export class DockerNotRunningError extends DockerError {
41
+ constructor() {
42
+ super(
43
+ 'Docker daemon is not running',
44
+ 'Please start Docker Desktop or Docker daemon, then try again'
45
+ );
46
+ this.name = 'DockerNotRunningError';
47
+ }
48
+ }
49
+
50
+ export class DockerPermissionError extends DockerError {
51
+ constructor() {
52
+ super(
53
+ 'Permission denied when accessing Docker',
54
+ 'Try running with sudo or add your user to the docker group:\n' +
55
+ ' sudo usermod -aG docker $USER\n' +
56
+ ' Then log out and log back in'
57
+ );
58
+ this.name = 'DockerPermissionError';
59
+ }
60
+ }
61
+
62
+ export class DockerComposeError extends DockerError {
63
+ constructor(operation: string, details?: string) {
64
+ super(
65
+ `Failed to ${operation} with docker-compose`,
66
+ details || 'Check docker-compose logs for more details:\n docker-compose logs'
67
+ );
68
+ this.name = 'DockerComposeError';
69
+ }
70
+ }
71
+
72
+ /**
73
+ * File system errors
74
+ */
75
+ export class FileSystemError extends OnboardingError {
76
+ constructor(message: string, public readonly path: string, troubleshooting?: string) {
77
+ super('FILESYSTEM_ERROR', message, troubleshooting);
78
+ this.name = 'FileSystemError';
79
+ }
80
+ }
81
+
82
+ export class DirectoryNotWritableError extends FileSystemError {
83
+ constructor(path: string) {
84
+ super(
85
+ `Directory is not writable: ${path}`,
86
+ path,
87
+ 'Check directory permissions or choose a different directory'
88
+ );
89
+ this.name = 'DirectoryNotWritableError';
90
+ }
91
+ }
92
+
93
+ export class FileNotFoundError extends FileSystemError {
94
+ constructor(path: string) {
95
+ super(
96
+ `File not found: ${path}`,
97
+ path,
98
+ 'Ensure the file exists or check the path'
99
+ );
100
+ this.name = 'FileNotFoundError';
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Configuration errors
106
+ */
107
+ export class ConfigurationError extends OnboardingError {
108
+ constructor(message: string, troubleshooting?: string) {
109
+ super('CONFIG_ERROR', message, troubleshooting);
110
+ this.name = 'ConfigurationError';
111
+ }
112
+ }
113
+
114
+ export class InvalidEnvironmentValueError extends ConfigurationError {
115
+ constructor(key: string, value: string, expectedFormat: string) {
116
+ super(
117
+ `Invalid value for ${key}: ${value}`,
118
+ `Expected format: ${expectedFormat}`
119
+ );
120
+ this.name = 'InvalidEnvironmentValueError';
121
+ }
122
+ }
123
+
124
+ export class PluginConfigurationError extends ConfigurationError {
125
+ constructor(pluginName: string, details: string) {
126
+ super(
127
+ `Failed to configure plugin ${pluginName}`,
128
+ details
129
+ );
130
+ this.name = 'PluginConfigurationError';
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Service health errors
136
+ */
137
+ export class ServiceHealthError extends OnboardingError {
138
+ constructor(serviceName: string, timeout: number) {
139
+ super(
140
+ 'SERVICE_HEALTH_ERROR',
141
+ `Service '${serviceName}' did not become healthy within ${timeout}ms`,
142
+ `Check service logs:\n docker-compose logs ${serviceName}\n\n` +
143
+ 'Or check container status:\n docker ps -a'
144
+ );
145
+ this.name = 'ServiceHealthError';
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Type guard to check if an error is an OnboardingError
151
+ */
152
+ export function isOnboardingError(error: unknown): error is OnboardingError {
153
+ return error instanceof OnboardingError;
154
+ }
155
+
156
+ /**
157
+ * Format error for user display
158
+ */
159
+ export function formatErrorForUser(error: unknown): string {
160
+ if (isOnboardingError(error)) {
161
+ let message = `āŒ ${error.message}`;
162
+ if (error.troubleshooting) {
163
+ message += `\n\nšŸ’” Troubleshooting:\n${error.troubleshooting}`;
164
+ }
165
+ return message;
166
+ }
167
+
168
+ if (error instanceof Error) {
169
+ return `āŒ Unexpected error: ${error.message}`;
170
+ }
171
+
172
+ return `āŒ An unknown error occurred`;
173
+ }
@@ -0,0 +1,2 @@
1
+ export * from './errors.js';
2
+ export * from './domain.js';
@@ -0,0 +1 @@
1
+ export * from './logger.js';