@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.
- package/dist/docker/CouchDBManager.d.ts +84 -0
- package/dist/docker/CouchDBManager.d.ts.map +1 -0
- package/dist/docker/CouchDBManager.js +307 -0
- package/dist/docker/CouchDBManager.js.map +1 -0
- package/dist/docker/CouchDBManager.mjs +303 -0
- package/dist/docker/index.d.ts +3 -0
- package/dist/docker/index.d.ts.map +1 -0
- package/dist/docker/index.js +6 -0
- package/dist/docker/index.js.map +1 -0
- package/dist/docker/index.mjs +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -0
- package/package.json +6 -1
- package/src/docker/CouchDBManager.ts +352 -0
- package/src/docker/index.ts +2 -0
- package/src/index.ts +3 -0
|
@@ -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 @@
|
|
|
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"}
|
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
|
+
"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
|
+
}
|
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'
|