@vue-skuilder/common 0.1.6 → 0.1.7

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.
@@ -0,0 +1,84 @@
1
+ import { EventEmitter } from 'events';
2
+ export interface CouchDBConfig {
3
+ mode: 'blank' | 'test-data' | 'custom';
4
+ port: number;
5
+ containerName?: string;
6
+ image?: string;
7
+ username?: string;
8
+ password?: string;
9
+ dataVolume?: string;
10
+ configFile?: string;
11
+ maxStartupWaitMs?: number;
12
+ }
13
+ export interface CouchDBManagerOptions {
14
+ onLog?: (message: string) => void;
15
+ onError?: (error: string) => void;
16
+ }
17
+ export declare class CouchDBManager extends EventEmitter {
18
+ private config;
19
+ private options;
20
+ private isStarting;
21
+ constructor(config: CouchDBConfig, options?: CouchDBManagerOptions);
22
+ private log;
23
+ private error;
24
+ /**
25
+ * Check if Docker is available
26
+ */
27
+ checkDockerAvailable(): Promise<boolean>;
28
+ /**
29
+ * Check if container exists
30
+ */
31
+ private containerExists;
32
+ /**
33
+ * Check if container is running
34
+ */
35
+ private isContainerRunning;
36
+ /**
37
+ * Stop and remove existing container
38
+ */
39
+ private cleanupExistingContainer;
40
+ /**
41
+ * Build Docker run command based on configuration
42
+ */
43
+ private buildDockerCommand;
44
+ /**
45
+ * Wait for CouchDB to be ready
46
+ */
47
+ private waitForReady;
48
+ /**
49
+ * Start CouchDB container
50
+ */
51
+ start(): Promise<void>;
52
+ /**
53
+ * Stop CouchDB container
54
+ */
55
+ stop(): Promise<void>;
56
+ /**
57
+ * Remove CouchDB container and volumes
58
+ */
59
+ remove(): Promise<void>;
60
+ /**
61
+ * Get container status
62
+ */
63
+ getStatus(): Promise<'running' | 'stopped' | 'missing'>;
64
+ /**
65
+ * Get connection URL for this CouchDB instance
66
+ */
67
+ getConnectionUrl(): string;
68
+ /**
69
+ * Get connection details for applications
70
+ */
71
+ getConnectionDetails(): {
72
+ protocol: string;
73
+ host: string;
74
+ port: number;
75
+ username: string;
76
+ password: string;
77
+ url: string;
78
+ };
79
+ /**
80
+ * Configure CORS settings for browser access
81
+ */
82
+ private configureCORS;
83
+ }
84
+ //# sourceMappingURL=CouchDBManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CouchDBManager.d.ts","sourceRoot":"","sources":["../../src/docker/CouchDBManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,aAAa,EAAE,OAAO,GAAE,qBAA0B;IAsBtE,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,KAAK;IAIb;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAS9C;;OAEG;YACW,eAAe;IAY7B;;OAEG;YACW,kBAAkB;IAYhC;;OAEG;YACW,wBAAwB;IAqBtC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAoC1B;;OAEG;YACW,YAAY;IAsB1B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwC5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB7B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAY7D;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,oBAAoB;;;;;;;;IAWpB;;OAEG;YACW,aAAa;CA8C5B"}
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CouchDBManager = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const events_1 = require("events");
6
+ class CouchDBManager extends events_1.EventEmitter {
7
+ config;
8
+ options;
9
+ isStarting = false;
10
+ constructor(config, options = {}) {
11
+ super();
12
+ // Set defaults
13
+ this.config = {
14
+ mode: config.mode,
15
+ port: config.port,
16
+ containerName: config.containerName || `skuilder-couch-${config.port}`,
17
+ image: config.image || 'couchdb:3.4.3',
18
+ username: config.username || 'admin',
19
+ password: config.password || 'password',
20
+ dataVolume: config.dataVolume || '',
21
+ configFile: config.configFile || '',
22
+ maxStartupWaitMs: config.maxStartupWaitMs || 30000,
23
+ };
24
+ this.options = {
25
+ onLog: options.onLog || (() => { }),
26
+ onError: options.onError || (() => { }),
27
+ };
28
+ }
29
+ log(message) {
30
+ this.options.onLog(message);
31
+ }
32
+ error(message) {
33
+ this.options.onError(message);
34
+ }
35
+ /**
36
+ * Check if Docker is available
37
+ */
38
+ async checkDockerAvailable() {
39
+ try {
40
+ (0, child_process_1.execSync)('docker --version', { stdio: 'pipe' });
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ /**
48
+ * Check if container exists
49
+ */
50
+ async containerExists() {
51
+ try {
52
+ const result = (0, child_process_1.execSync)(`docker ps -a -q -f name=^${this.config.containerName}$`, { stdio: 'pipe' }).toString().trim();
53
+ return result.length > 0;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Check if container is running
61
+ */
62
+ async isContainerRunning() {
63
+ try {
64
+ const result = (0, child_process_1.execSync)(`docker ps -q -f name=^${this.config.containerName}$`, { stdio: 'pipe' }).toString().trim();
65
+ return result.length > 0;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ /**
72
+ * Stop and remove existing container
73
+ */
74
+ async cleanupExistingContainer() {
75
+ if (await this.containerExists()) {
76
+ this.log(`Found existing container '${this.config.containerName}'. Cleaning up...`);
77
+ try {
78
+ if (await this.isContainerRunning()) {
79
+ this.log('Stopping container...');
80
+ (0, child_process_1.execSync)(`docker stop ${this.config.containerName}`, { stdio: 'pipe' });
81
+ this.log('Container stopped.');
82
+ }
83
+ this.log('Removing container...');
84
+ (0, child_process_1.execSync)(`docker rm ${this.config.containerName}`, { stdio: 'pipe' });
85
+ this.log('Container removed.');
86
+ }
87
+ catch (error) {
88
+ this.error(`Error during cleanup: ${error instanceof Error ? error.message : String(error)}`);
89
+ throw error;
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * Build Docker run command based on configuration
95
+ */
96
+ buildDockerCommand() {
97
+ const cmd = [
98
+ 'run', '-d',
99
+ '--name', this.config.containerName,
100
+ '-p', `${this.config.port}:5984`,
101
+ '-e', `COUCHDB_USER=${this.config.username}`,
102
+ '-e', `COUCHDB_PASSWORD=${this.config.password}`,
103
+ ];
104
+ // Add volume mounts based on mode
105
+ switch (this.config.mode) {
106
+ case 'test-data':
107
+ if (this.config.dataVolume) {
108
+ cmd.push('-v', `${this.config.dataVolume}:/opt/couchdb/data`);
109
+ }
110
+ if (this.config.configFile) {
111
+ cmd.push('-v', `${this.config.configFile}:/opt/couchdb/etc/local.ini`);
112
+ }
113
+ break;
114
+ case 'custom':
115
+ if (this.config.dataVolume) {
116
+ cmd.push('-v', `${this.config.dataVolume}:/opt/couchdb/data`);
117
+ }
118
+ if (this.config.configFile) {
119
+ cmd.push('-v', `${this.config.configFile}:/opt/couchdb/etc/local.ini`);
120
+ }
121
+ break;
122
+ case 'blank':
123
+ // No volume mounts for blank mode - clean slate
124
+ break;
125
+ }
126
+ cmd.push(this.config.image);
127
+ return cmd;
128
+ }
129
+ /**
130
+ * Wait for CouchDB to be ready
131
+ */
132
+ async waitForReady() {
133
+ const maxAttempts = Math.floor(this.config.maxStartupWaitMs / 1000);
134
+ let attempts = 0;
135
+ return new Promise((resolve, reject) => {
136
+ const checkInterval = setInterval(() => {
137
+ attempts++;
138
+ try {
139
+ (0, child_process_1.execSync)(`curl -s http://localhost:${this.config.port}/`, { stdio: 'pipe' });
140
+ clearInterval(checkInterval);
141
+ this.log('CouchDB is ready!');
142
+ resolve();
143
+ }
144
+ catch {
145
+ if (attempts >= maxAttempts) {
146
+ clearInterval(checkInterval);
147
+ reject(new Error('CouchDB failed to start within timeout period'));
148
+ }
149
+ }
150
+ }, 1000);
151
+ });
152
+ }
153
+ /**
154
+ * Start CouchDB container
155
+ */
156
+ async start() {
157
+ if (this.isStarting) {
158
+ throw new Error('CouchDB is already starting');
159
+ }
160
+ this.isStarting = true;
161
+ try {
162
+ // Check Docker availability
163
+ if (!(await this.checkDockerAvailable())) {
164
+ throw new Error('Docker is not available or not running');
165
+ }
166
+ // Clean up any existing container
167
+ await this.cleanupExistingContainer();
168
+ // Build and execute Docker command
169
+ const dockerCmd = this.buildDockerCommand();
170
+ this.log(`Starting CouchDB container: docker ${dockerCmd.join(' ')}`);
171
+ (0, child_process_1.execSync)(`docker ${dockerCmd.join(' ')}`, { stdio: 'pipe' });
172
+ this.log('Container started successfully');
173
+ // Wait for CouchDB to be ready
174
+ this.log('Waiting for CouchDB to be ready...');
175
+ await this.waitForReady();
176
+ // Configure CORS for browser access
177
+ await this.configureCORS();
178
+ this.emit('ready');
179
+ }
180
+ catch (error) {
181
+ this.error(`Failed to start CouchDB: ${error instanceof Error ? error.message : String(error)}`);
182
+ this.emit('error', error);
183
+ throw error;
184
+ }
185
+ finally {
186
+ this.isStarting = false;
187
+ }
188
+ }
189
+ /**
190
+ * Stop CouchDB container
191
+ */
192
+ async stop() {
193
+ try {
194
+ if (await this.isContainerRunning()) {
195
+ this.log('Stopping CouchDB container...');
196
+ (0, child_process_1.execSync)(`docker stop ${this.config.containerName}`, { stdio: 'pipe' });
197
+ this.log('CouchDB stopped');
198
+ this.emit('stopped');
199
+ }
200
+ else {
201
+ this.log('CouchDB container is not running');
202
+ }
203
+ }
204
+ catch (error) {
205
+ this.error(`Failed to stop CouchDB: ${error instanceof Error ? error.message : String(error)}`);
206
+ throw error;
207
+ }
208
+ }
209
+ /**
210
+ * Remove CouchDB container and volumes
211
+ */
212
+ async remove() {
213
+ try {
214
+ await this.stop();
215
+ if (await this.containerExists()) {
216
+ this.log('Removing CouchDB container...');
217
+ (0, child_process_1.execSync)(`docker rm ${this.config.containerName}`, { stdio: 'pipe' });
218
+ this.log('CouchDB container removed');
219
+ this.emit('removed');
220
+ }
221
+ }
222
+ catch (error) {
223
+ this.error(`Failed to remove CouchDB: ${error instanceof Error ? error.message : String(error)}`);
224
+ throw error;
225
+ }
226
+ }
227
+ /**
228
+ * Get container status
229
+ */
230
+ async getStatus() {
231
+ if (!(await this.containerExists())) {
232
+ return 'missing';
233
+ }
234
+ if (await this.isContainerRunning()) {
235
+ return 'running';
236
+ }
237
+ return 'stopped';
238
+ }
239
+ /**
240
+ * Get connection URL for this CouchDB instance
241
+ */
242
+ getConnectionUrl() {
243
+ return `http://localhost:${this.config.port}`;
244
+ }
245
+ /**
246
+ * Get connection details for applications
247
+ */
248
+ getConnectionDetails() {
249
+ return {
250
+ protocol: 'http',
251
+ host: 'localhost',
252
+ port: this.config.port,
253
+ username: this.config.username,
254
+ password: this.config.password,
255
+ url: this.getConnectionUrl(),
256
+ };
257
+ }
258
+ /**
259
+ * Configure CORS settings for browser access
260
+ */
261
+ async configureCORS() {
262
+ try {
263
+ this.log('Configuring CORS for browser access...');
264
+ const baseUrl = `http://localhost:${this.config.port}`;
265
+ const auth = `${this.config.username}:${this.config.password}`;
266
+ // Enable CORS with comprehensive PouchDB-compatible headers
267
+ // Note: CouchDB requires BOTH chttpd/enable_cors AND cors section settings
268
+ const corsCommands = [
269
+ // First: Enable CORS globally in chttpd section (CRITICAL!)
270
+ `curl -X PUT "${baseUrl}/_node/_local/_config/chttpd/enable_cors" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
271
+ // Then: Configure CORS settings in cors section
272
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/enable" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
273
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/origins" -d '"*"' -H "Content-Type: application/json" -u "${auth}"`,
274
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/methods" -d '"GET,POST,PUT,DELETE,OPTIONS,HEAD"' -H "Content-Type: application/json" -u "${auth}"`,
275
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/headers" -d '"accept,authorization,content-type,origin,referer,x-csrf-token,cache-control,if-none-match,x-requested-with,pragma,expires,x-couch-request-id,x-couch-update-newrev"' -H "Content-Type: application/json" -u "${auth}"`,
276
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/credentials" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
277
+ ];
278
+ for (const cmd of corsCommands) {
279
+ try {
280
+ const result = (0, child_process_1.execSync)(cmd, { stdio: 'pipe' }).toString();
281
+ this.log(`CORS command success: ${result.trim()}`);
282
+ }
283
+ catch (error) {
284
+ this.error(`CORS command failed: ${cmd} - ${error instanceof Error ? error.message : String(error)}`);
285
+ throw error;
286
+ }
287
+ }
288
+ // Verify CORS configuration took effect
289
+ try {
290
+ this.log('Verifying CORS configuration...');
291
+ const verifyCmd = `curl -s "${baseUrl}/_node/_local/_config/cors" -u "${auth}"`;
292
+ const corsSettings = (0, child_process_1.execSync)(verifyCmd, { stdio: 'pipe' }).toString();
293
+ this.log(`CORS verification result: ${corsSettings.trim()}`);
294
+ }
295
+ catch (error) {
296
+ this.log(`Warning: CORS verification failed: ${error instanceof Error ? error.message : String(error)}`);
297
+ }
298
+ this.log('CORS configuration completed');
299
+ }
300
+ catch (error) {
301
+ this.log(`Warning: CORS configuration failed: ${error instanceof Error ? error.message : String(error)}`);
302
+ // Don't throw - CORS failure shouldn't prevent container startup
303
+ }
304
+ }
305
+ }
306
+ exports.CouchDBManager = CouchDBManager;
307
+ //# sourceMappingURL=CouchDBManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CouchDBManager.js","sourceRoot":"","sources":["../../src/docker/CouchDBManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,303 @@
1
+ import { execSync } from 'child_process';
2
+ import { EventEmitter } from 'events';
3
+ export class CouchDBManager extends EventEmitter {
4
+ config;
5
+ options;
6
+ isStarting = false;
7
+ constructor(config, options = {}) {
8
+ super();
9
+ // Set defaults
10
+ this.config = {
11
+ mode: config.mode,
12
+ port: config.port,
13
+ containerName: config.containerName || `skuilder-couch-${config.port}`,
14
+ image: config.image || 'couchdb:3.4.3',
15
+ username: config.username || 'admin',
16
+ password: config.password || 'password',
17
+ dataVolume: config.dataVolume || '',
18
+ configFile: config.configFile || '',
19
+ maxStartupWaitMs: config.maxStartupWaitMs || 30000,
20
+ };
21
+ this.options = {
22
+ onLog: options.onLog || (() => { }),
23
+ onError: options.onError || (() => { }),
24
+ };
25
+ }
26
+ log(message) {
27
+ this.options.onLog(message);
28
+ }
29
+ error(message) {
30
+ this.options.onError(message);
31
+ }
32
+ /**
33
+ * Check if Docker is available
34
+ */
35
+ async checkDockerAvailable() {
36
+ try {
37
+ execSync('docker --version', { stdio: 'pipe' });
38
+ return true;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ /**
45
+ * Check if container exists
46
+ */
47
+ async containerExists() {
48
+ try {
49
+ const result = execSync(`docker ps -a -q -f name=^${this.config.containerName}$`, { stdio: 'pipe' }).toString().trim();
50
+ return result.length > 0;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ /**
57
+ * Check if container is running
58
+ */
59
+ async isContainerRunning() {
60
+ try {
61
+ const result = execSync(`docker ps -q -f name=^${this.config.containerName}$`, { stdio: 'pipe' }).toString().trim();
62
+ return result.length > 0;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ /**
69
+ * Stop and remove existing container
70
+ */
71
+ async cleanupExistingContainer() {
72
+ if (await this.containerExists()) {
73
+ this.log(`Found existing container '${this.config.containerName}'. Cleaning up...`);
74
+ try {
75
+ if (await this.isContainerRunning()) {
76
+ this.log('Stopping container...');
77
+ execSync(`docker stop ${this.config.containerName}`, { stdio: 'pipe' });
78
+ this.log('Container stopped.');
79
+ }
80
+ this.log('Removing container...');
81
+ execSync(`docker rm ${this.config.containerName}`, { stdio: 'pipe' });
82
+ this.log('Container removed.');
83
+ }
84
+ catch (error) {
85
+ this.error(`Error during cleanup: ${error instanceof Error ? error.message : String(error)}`);
86
+ throw error;
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * Build Docker run command based on configuration
92
+ */
93
+ buildDockerCommand() {
94
+ const cmd = [
95
+ 'run', '-d',
96
+ '--name', this.config.containerName,
97
+ '-p', `${this.config.port}:5984`,
98
+ '-e', `COUCHDB_USER=${this.config.username}`,
99
+ '-e', `COUCHDB_PASSWORD=${this.config.password}`,
100
+ ];
101
+ // Add volume mounts based on mode
102
+ switch (this.config.mode) {
103
+ case 'test-data':
104
+ if (this.config.dataVolume) {
105
+ cmd.push('-v', `${this.config.dataVolume}:/opt/couchdb/data`);
106
+ }
107
+ if (this.config.configFile) {
108
+ cmd.push('-v', `${this.config.configFile}:/opt/couchdb/etc/local.ini`);
109
+ }
110
+ break;
111
+ case 'custom':
112
+ if (this.config.dataVolume) {
113
+ cmd.push('-v', `${this.config.dataVolume}:/opt/couchdb/data`);
114
+ }
115
+ if (this.config.configFile) {
116
+ cmd.push('-v', `${this.config.configFile}:/opt/couchdb/etc/local.ini`);
117
+ }
118
+ break;
119
+ case 'blank':
120
+ // No volume mounts for blank mode - clean slate
121
+ break;
122
+ }
123
+ cmd.push(this.config.image);
124
+ return cmd;
125
+ }
126
+ /**
127
+ * Wait for CouchDB to be ready
128
+ */
129
+ async waitForReady() {
130
+ const maxAttempts = Math.floor(this.config.maxStartupWaitMs / 1000);
131
+ let attempts = 0;
132
+ return new Promise((resolve, reject) => {
133
+ const checkInterval = setInterval(() => {
134
+ attempts++;
135
+ try {
136
+ execSync(`curl -s http://localhost:${this.config.port}/`, { stdio: 'pipe' });
137
+ clearInterval(checkInterval);
138
+ this.log('CouchDB is ready!');
139
+ resolve();
140
+ }
141
+ catch {
142
+ if (attempts >= maxAttempts) {
143
+ clearInterval(checkInterval);
144
+ reject(new Error('CouchDB failed to start within timeout period'));
145
+ }
146
+ }
147
+ }, 1000);
148
+ });
149
+ }
150
+ /**
151
+ * Start CouchDB container
152
+ */
153
+ async start() {
154
+ if (this.isStarting) {
155
+ throw new Error('CouchDB is already starting');
156
+ }
157
+ this.isStarting = true;
158
+ try {
159
+ // Check Docker availability
160
+ if (!(await this.checkDockerAvailable())) {
161
+ throw new Error('Docker is not available or not running');
162
+ }
163
+ // Clean up any existing container
164
+ await this.cleanupExistingContainer();
165
+ // Build and execute Docker command
166
+ const dockerCmd = this.buildDockerCommand();
167
+ this.log(`Starting CouchDB container: docker ${dockerCmd.join(' ')}`);
168
+ execSync(`docker ${dockerCmd.join(' ')}`, { stdio: 'pipe' });
169
+ this.log('Container started successfully');
170
+ // Wait for CouchDB to be ready
171
+ this.log('Waiting for CouchDB to be ready...');
172
+ await this.waitForReady();
173
+ // Configure CORS for browser access
174
+ await this.configureCORS();
175
+ this.emit('ready');
176
+ }
177
+ catch (error) {
178
+ this.error(`Failed to start CouchDB: ${error instanceof Error ? error.message : String(error)}`);
179
+ this.emit('error', error);
180
+ throw error;
181
+ }
182
+ finally {
183
+ this.isStarting = false;
184
+ }
185
+ }
186
+ /**
187
+ * Stop CouchDB container
188
+ */
189
+ async stop() {
190
+ try {
191
+ if (await this.isContainerRunning()) {
192
+ this.log('Stopping CouchDB container...');
193
+ execSync(`docker stop ${this.config.containerName}`, { stdio: 'pipe' });
194
+ this.log('CouchDB stopped');
195
+ this.emit('stopped');
196
+ }
197
+ else {
198
+ this.log('CouchDB container is not running');
199
+ }
200
+ }
201
+ catch (error) {
202
+ this.error(`Failed to stop CouchDB: ${error instanceof Error ? error.message : String(error)}`);
203
+ throw error;
204
+ }
205
+ }
206
+ /**
207
+ * Remove CouchDB container and volumes
208
+ */
209
+ async remove() {
210
+ try {
211
+ await this.stop();
212
+ if (await this.containerExists()) {
213
+ this.log('Removing CouchDB container...');
214
+ execSync(`docker rm ${this.config.containerName}`, { stdio: 'pipe' });
215
+ this.log('CouchDB container removed');
216
+ this.emit('removed');
217
+ }
218
+ }
219
+ catch (error) {
220
+ this.error(`Failed to remove CouchDB: ${error instanceof Error ? error.message : String(error)}`);
221
+ throw error;
222
+ }
223
+ }
224
+ /**
225
+ * Get container status
226
+ */
227
+ async getStatus() {
228
+ if (!(await this.containerExists())) {
229
+ return 'missing';
230
+ }
231
+ if (await this.isContainerRunning()) {
232
+ return 'running';
233
+ }
234
+ return 'stopped';
235
+ }
236
+ /**
237
+ * Get connection URL for this CouchDB instance
238
+ */
239
+ getConnectionUrl() {
240
+ return `http://localhost:${this.config.port}`;
241
+ }
242
+ /**
243
+ * Get connection details for applications
244
+ */
245
+ getConnectionDetails() {
246
+ return {
247
+ protocol: 'http',
248
+ host: 'localhost',
249
+ port: this.config.port,
250
+ username: this.config.username,
251
+ password: this.config.password,
252
+ url: this.getConnectionUrl(),
253
+ };
254
+ }
255
+ /**
256
+ * Configure CORS settings for browser access
257
+ */
258
+ async configureCORS() {
259
+ try {
260
+ this.log('Configuring CORS for browser access...');
261
+ const baseUrl = `http://localhost:${this.config.port}`;
262
+ const auth = `${this.config.username}:${this.config.password}`;
263
+ // Enable CORS with comprehensive PouchDB-compatible headers
264
+ // Note: CouchDB requires BOTH chttpd/enable_cors AND cors section settings
265
+ const corsCommands = [
266
+ // First: Enable CORS globally in chttpd section (CRITICAL!)
267
+ `curl -X PUT "${baseUrl}/_node/_local/_config/chttpd/enable_cors" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
268
+ // Then: Configure CORS settings in cors section
269
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/enable" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
270
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/origins" -d '"*"' -H "Content-Type: application/json" -u "${auth}"`,
271
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/methods" -d '"GET,POST,PUT,DELETE,OPTIONS,HEAD"' -H "Content-Type: application/json" -u "${auth}"`,
272
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/headers" -d '"accept,authorization,content-type,origin,referer,x-csrf-token,cache-control,if-none-match,x-requested-with,pragma,expires,x-couch-request-id,x-couch-update-newrev"' -H "Content-Type: application/json" -u "${auth}"`,
273
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/credentials" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
274
+ ];
275
+ for (const cmd of corsCommands) {
276
+ try {
277
+ const result = execSync(cmd, { stdio: 'pipe' }).toString();
278
+ this.log(`CORS command success: ${result.trim()}`);
279
+ }
280
+ catch (error) {
281
+ this.error(`CORS command failed: ${cmd} - ${error instanceof Error ? error.message : String(error)}`);
282
+ throw error;
283
+ }
284
+ }
285
+ // Verify CORS configuration took effect
286
+ try {
287
+ this.log('Verifying CORS configuration...');
288
+ const verifyCmd = `curl -s "${baseUrl}/_node/_local/_config/cors" -u "${auth}"`;
289
+ const corsSettings = execSync(verifyCmd, { stdio: 'pipe' }).toString();
290
+ this.log(`CORS verification result: ${corsSettings.trim()}`);
291
+ }
292
+ catch (error) {
293
+ this.log(`Warning: CORS verification failed: ${error instanceof Error ? error.message : String(error)}`);
294
+ }
295
+ this.log('CORS configuration completed');
296
+ }
297
+ catch (error) {
298
+ this.log(`Warning: CORS configuration failed: ${error instanceof Error ? error.message : String(error)}`);
299
+ // Don't throw - CORS failure shouldn't prevent container startup
300
+ }
301
+ }
302
+ }
303
+ //# sourceMappingURL=CouchDBManager.js.map
@@ -0,0 +1,3 @@
1
+ export { CouchDBManager } from './CouchDBManager.js';
2
+ export type { CouchDBConfig, CouchDBManagerOptions } from './CouchDBManager.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/docker/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CouchDBManager = void 0;
4
+ var CouchDBManager_js_1 = require("./CouchDBManager.js");
5
+ Object.defineProperty(exports, "CouchDBManager", { enumerable: true, get: function () { return CouchDBManager_js_1.CouchDBManager; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/docker/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { CouchDBManager } from './CouchDBManager.mjs';
2
+ //# sourceMappingURL=index.js.map
package/dist/index.js CHANGED
@@ -28,4 +28,6 @@ __exportStar(require("./bulkImport/types.js"), exports);
28
28
  __exportStar(require("./interfaces/index.js"), exports);
29
29
  // enums
30
30
  __exportStar(require("./enums/index.js"), exports);
31
+ // docker utilities (Node.js only - not exported in main index for browser compatibility)
32
+ // Use explicit import: import { CouchDBManager } from '@vue-skuilder/common/docker'
31
33
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AAExB,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AAEtC,aAAa;AACb,cAAc,uBAAuB,CAAC;AAEtC,QAAQ;AACR,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AAExB,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AAEtC,aAAa;AACb,cAAc,uBAAuB,CAAC;AAEtC,QAAQ;AACR,cAAc,kBAAkB,CAAC;AAEjC,yFAAyF;AACzF,oFAAoF"}
package/dist/index.mjs CHANGED
@@ -12,4 +12,6 @@ export * from './bulkImport/types.mjs';
12
12
  export * from './interfaces/index.mjs';
13
13
  // enums
14
14
  export * from './enums/index.mjs';
15
+ // docker utilities (Node.js only - not exported in main index for browser compatibility)
16
+ // Use explicit import: import { CouchDBManager } from '@vue-skuilder/common/docker'
15
17
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.6",
6
+ "version": "0.1.7",
7
7
  "type": "module",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.mjs",
@@ -13,6 +13,11 @@
13
13
  "types": "./dist/index.d.ts",
14
14
  "import": "./dist/index.mjs",
15
15
  "require": "./dist/index.js"
16
+ },
17
+ "./docker": {
18
+ "types": "./dist/docker/index.d.ts",
19
+ "import": "./dist/docker/index.mjs",
20
+ "require": "./dist/docker/index.js"
16
21
  }
17
22
  },
18
23
  "scripts": {
@@ -0,0 +1,352 @@
1
+ import { execSync } from 'child_process';
2
+ import { EventEmitter } from 'events';
3
+
4
+ export interface CouchDBConfig {
5
+ mode: 'blank' | 'test-data' | 'custom';
6
+ port: number;
7
+ containerName?: string;
8
+ image?: string;
9
+ username?: string;
10
+ password?: string;
11
+ dataVolume?: string;
12
+ configFile?: string;
13
+ maxStartupWaitMs?: number;
14
+ }
15
+
16
+ export interface CouchDBManagerOptions {
17
+ onLog?: (message: string) => void;
18
+ onError?: (error: string) => void;
19
+ }
20
+
21
+ export class CouchDBManager extends EventEmitter {
22
+ private config: Required<CouchDBConfig>;
23
+ private options: CouchDBManagerOptions;
24
+ private isStarting = false;
25
+
26
+ constructor(config: CouchDBConfig, options: CouchDBManagerOptions = {}) {
27
+ super();
28
+
29
+ // Set defaults
30
+ this.config = {
31
+ mode: config.mode,
32
+ port: config.port,
33
+ containerName: config.containerName || `skuilder-couch-${config.port}`,
34
+ image: config.image || 'couchdb:3.4.3',
35
+ username: config.username || 'admin',
36
+ password: config.password || 'password',
37
+ dataVolume: config.dataVolume || '',
38
+ configFile: config.configFile || '',
39
+ maxStartupWaitMs: config.maxStartupWaitMs || 30000,
40
+ };
41
+
42
+ this.options = {
43
+ onLog: options.onLog || (() => {}),
44
+ onError: options.onError || (() => {}),
45
+ };
46
+ }
47
+
48
+ private log(message: string): void {
49
+ this.options.onLog!(message);
50
+ }
51
+
52
+ private error(message: string): void {
53
+ this.options.onError!(message);
54
+ }
55
+
56
+ /**
57
+ * Check if Docker is available
58
+ */
59
+ async checkDockerAvailable(): Promise<boolean> {
60
+ try {
61
+ execSync('docker --version', { stdio: 'pipe' });
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Check if container exists
70
+ */
71
+ private async containerExists(): Promise<boolean> {
72
+ try {
73
+ const result = execSync(
74
+ `docker ps -a -q -f name=^${this.config.containerName}$`,
75
+ { stdio: 'pipe' }
76
+ ).toString().trim();
77
+ return result.length > 0;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Check if container is running
85
+ */
86
+ private async isContainerRunning(): Promise<boolean> {
87
+ try {
88
+ const result = execSync(
89
+ `docker ps -q -f name=^${this.config.containerName}$`,
90
+ { stdio: 'pipe' }
91
+ ).toString().trim();
92
+ return result.length > 0;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Stop and remove existing container
100
+ */
101
+ private async cleanupExistingContainer(): Promise<void> {
102
+ if (await this.containerExists()) {
103
+ this.log(`Found existing container '${this.config.containerName}'. Cleaning up...`);
104
+
105
+ try {
106
+ if (await this.isContainerRunning()) {
107
+ this.log('Stopping container...');
108
+ execSync(`docker stop ${this.config.containerName}`, { stdio: 'pipe' });
109
+ this.log('Container stopped.');
110
+ }
111
+
112
+ this.log('Removing container...');
113
+ execSync(`docker rm ${this.config.containerName}`, { stdio: 'pipe' });
114
+ this.log('Container removed.');
115
+ } catch (error: unknown) {
116
+ this.error(`Error during cleanup: ${error instanceof Error ? error.message : String(error)}`);
117
+ throw error;
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Build Docker run command based on configuration
124
+ */
125
+ private buildDockerCommand(): string[] {
126
+ const cmd = [
127
+ 'run', '-d',
128
+ '--name', this.config.containerName,
129
+ '-p', `${this.config.port}:5984`,
130
+ '-e', `COUCHDB_USER=${this.config.username}`,
131
+ '-e', `COUCHDB_PASSWORD=${this.config.password}`,
132
+ ];
133
+
134
+ // Add volume mounts based on mode
135
+ switch (this.config.mode) {
136
+ case 'test-data':
137
+ if (this.config.dataVolume) {
138
+ cmd.push('-v', `${this.config.dataVolume}:/opt/couchdb/data`);
139
+ }
140
+ if (this.config.configFile) {
141
+ cmd.push('-v', `${this.config.configFile}:/opt/couchdb/etc/local.ini`);
142
+ }
143
+ break;
144
+ case 'custom':
145
+ if (this.config.dataVolume) {
146
+ cmd.push('-v', `${this.config.dataVolume}:/opt/couchdb/data`);
147
+ }
148
+ if (this.config.configFile) {
149
+ cmd.push('-v', `${this.config.configFile}:/opt/couchdb/etc/local.ini`);
150
+ }
151
+ break;
152
+ case 'blank':
153
+ // No volume mounts for blank mode - clean slate
154
+ break;
155
+ }
156
+
157
+ cmd.push(this.config.image);
158
+ return cmd;
159
+ }
160
+
161
+ /**
162
+ * Wait for CouchDB to be ready
163
+ */
164
+ private async waitForReady(): Promise<void> {
165
+ const maxAttempts = Math.floor(this.config.maxStartupWaitMs / 1000);
166
+ let attempts = 0;
167
+
168
+ return new Promise((resolve, reject) => {
169
+ const checkInterval = setInterval(() => {
170
+ attempts++;
171
+ try {
172
+ execSync(`curl -s http://localhost:${this.config.port}/`, { stdio: 'pipe' });
173
+ clearInterval(checkInterval);
174
+ this.log('CouchDB is ready!');
175
+ resolve();
176
+ } catch {
177
+ if (attempts >= maxAttempts) {
178
+ clearInterval(checkInterval);
179
+ reject(new Error('CouchDB failed to start within timeout period'));
180
+ }
181
+ }
182
+ }, 1000);
183
+ });
184
+ }
185
+
186
+ /**
187
+ * Start CouchDB container
188
+ */
189
+ async start(): Promise<void> {
190
+ if (this.isStarting) {
191
+ throw new Error('CouchDB is already starting');
192
+ }
193
+
194
+ this.isStarting = true;
195
+
196
+ try {
197
+ // Check Docker availability
198
+ if (!(await this.checkDockerAvailable())) {
199
+ throw new Error('Docker is not available or not running');
200
+ }
201
+
202
+ // Clean up any existing container
203
+ await this.cleanupExistingContainer();
204
+
205
+ // Build and execute Docker command
206
+ const dockerCmd = this.buildDockerCommand();
207
+ this.log(`Starting CouchDB container: docker ${dockerCmd.join(' ')}`);
208
+
209
+ execSync(`docker ${dockerCmd.join(' ')}`, { stdio: 'pipe' });
210
+ this.log('Container started successfully');
211
+
212
+ // Wait for CouchDB to be ready
213
+ this.log('Waiting for CouchDB to be ready...');
214
+ await this.waitForReady();
215
+
216
+ // Configure CORS for browser access
217
+ await this.configureCORS();
218
+
219
+ this.emit('ready');
220
+ } catch (error: unknown) {
221
+ this.error(`Failed to start CouchDB: ${error instanceof Error ? error.message : String(error)}`);
222
+ this.emit('error', error);
223
+ throw error;
224
+ } finally {
225
+ this.isStarting = false;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Stop CouchDB container
231
+ */
232
+ async stop(): Promise<void> {
233
+ try {
234
+ if (await this.isContainerRunning()) {
235
+ this.log('Stopping CouchDB container...');
236
+ execSync(`docker stop ${this.config.containerName}`, { stdio: 'pipe' });
237
+ this.log('CouchDB stopped');
238
+ this.emit('stopped');
239
+ } else {
240
+ this.log('CouchDB container is not running');
241
+ }
242
+ } catch (error: unknown) {
243
+ this.error(`Failed to stop CouchDB: ${error instanceof Error ? error.message : String(error)}`);
244
+ throw error;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Remove CouchDB container and volumes
250
+ */
251
+ async remove(): Promise<void> {
252
+ try {
253
+ await this.stop();
254
+
255
+ if (await this.containerExists()) {
256
+ this.log('Removing CouchDB container...');
257
+ execSync(`docker rm ${this.config.containerName}`, { stdio: 'pipe' });
258
+ this.log('CouchDB container removed');
259
+ this.emit('removed');
260
+ }
261
+ } catch (error: unknown) {
262
+ this.error(`Failed to remove CouchDB: ${error instanceof Error ? error.message : String(error)}`);
263
+ throw error;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Get container status
269
+ */
270
+ async getStatus(): Promise<'running' | 'stopped' | 'missing'> {
271
+ if (!(await this.containerExists())) {
272
+ return 'missing';
273
+ }
274
+
275
+ if (await this.isContainerRunning()) {
276
+ return 'running';
277
+ }
278
+
279
+ return 'stopped';
280
+ }
281
+
282
+ /**
283
+ * Get connection URL for this CouchDB instance
284
+ */
285
+ getConnectionUrl(): string {
286
+ return `http://localhost:${this.config.port}`;
287
+ }
288
+
289
+ /**
290
+ * Get connection details for applications
291
+ */
292
+ getConnectionDetails() {
293
+ return {
294
+ protocol: 'http',
295
+ host: 'localhost',
296
+ port: this.config.port,
297
+ username: this.config.username,
298
+ password: this.config.password,
299
+ url: this.getConnectionUrl(),
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Configure CORS settings for browser access
305
+ */
306
+ private async configureCORS(): Promise<void> {
307
+ try {
308
+ this.log('Configuring CORS for browser access...');
309
+
310
+ const baseUrl = `http://localhost:${this.config.port}`;
311
+ const auth = `${this.config.username}:${this.config.password}`;
312
+
313
+ // Enable CORS with comprehensive PouchDB-compatible headers
314
+ // Note: CouchDB requires BOTH chttpd/enable_cors AND cors section settings
315
+ const corsCommands = [
316
+ // First: Enable CORS globally in chttpd section (CRITICAL!)
317
+ `curl -X PUT "${baseUrl}/_node/_local/_config/chttpd/enable_cors" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
318
+ // Then: Configure CORS settings in cors section
319
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/enable" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
320
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/origins" -d '"*"' -H "Content-Type: application/json" -u "${auth}"`,
321
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/methods" -d '"GET,POST,PUT,DELETE,OPTIONS,HEAD"' -H "Content-Type: application/json" -u "${auth}"`,
322
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/headers" -d '"accept,authorization,content-type,origin,referer,x-csrf-token,cache-control,if-none-match,x-requested-with,pragma,expires,x-couch-request-id,x-couch-update-newrev"' -H "Content-Type: application/json" -u "${auth}"`,
323
+ `curl -X PUT "${baseUrl}/_node/_local/_config/cors/credentials" -d '"true"' -H "Content-Type: application/json" -u "${auth}"`,
324
+ ];
325
+
326
+ for (const cmd of corsCommands) {
327
+ try {
328
+ const result = execSync(cmd, { stdio: 'pipe' }).toString();
329
+ this.log(`CORS command success: ${result.trim()}`);
330
+ } catch (error: unknown) {
331
+ this.error(`CORS command failed: ${cmd} - ${error instanceof Error ? error.message : String(error)}`);
332
+ throw error;
333
+ }
334
+ }
335
+
336
+ // Verify CORS configuration took effect
337
+ try {
338
+ this.log('Verifying CORS configuration...');
339
+ const verifyCmd = `curl -s "${baseUrl}/_node/_local/_config/cors" -u "${auth}"`;
340
+ const corsSettings = execSync(verifyCmd, { stdio: 'pipe' }).toString();
341
+ this.log(`CORS verification result: ${corsSettings.trim()}`);
342
+ } catch (error: unknown) {
343
+ this.log(`Warning: CORS verification failed: ${error instanceof Error ? error.message : String(error)}`);
344
+ }
345
+
346
+ this.log('CORS configuration completed');
347
+ } catch (error: unknown) {
348
+ this.log(`Warning: CORS configuration failed: ${error instanceof Error ? error.message : String(error)}`);
349
+ // Don't throw - CORS failure shouldn't prevent container startup
350
+ }
351
+ }
352
+ }
@@ -0,0 +1,2 @@
1
+ export { CouchDBManager } from './CouchDBManager.js';
2
+ export type { CouchDBConfig, CouchDBManagerOptions } from './CouchDBManager.js';
package/src/index.ts CHANGED
@@ -15,3 +15,6 @@ export * from './interfaces/index.js';
15
15
 
16
16
  // enums
17
17
  export * from './enums/index.js';
18
+
19
+ // docker utilities (Node.js only - not exported in main index for browser compatibility)
20
+ // Use explicit import: import { CouchDBManager } from '@vue-skuilder/common/docker'