@geekmidas/cli 1.10.4 → 1.10.5
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/CHANGELOG.md +6 -0
- package/dist/index.cjs +16 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +16 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/dev/__tests__/index.spec.ts +35 -9
- package/src/dev/index.ts +18 -1
- package/src/docker/__tests__/compose.spec.ts +7 -5
- package/src/docker/compose.ts +4 -4
- package/src/init/generators/docker.ts +4 -4
- package/src/test/__tests__/index.spec.ts +62 -34
- package/src/test/index.ts +0 -41
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.5",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"tsx": "~4.20.3",
|
|
58
58
|
"yaml": "~2.8.2",
|
|
59
59
|
"@geekmidas/constructs": "~3.0.2",
|
|
60
|
-
"@geekmidas/
|
|
61
|
-
"@geekmidas/schema": "~1.0.0",
|
|
60
|
+
"@geekmidas/errors": "~1.0.0",
|
|
62
61
|
"@geekmidas/logger": "~1.0.0",
|
|
63
|
-
"@geekmidas/
|
|
62
|
+
"@geekmidas/schema": "~1.0.0",
|
|
63
|
+
"@geekmidas/envkit": "~1.0.3"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -1257,10 +1257,11 @@ describe('rewriteUrlsWithPorts', () => {
|
|
|
1257
1257
|
containerPort: 5672,
|
|
1258
1258
|
};
|
|
1259
1259
|
|
|
1260
|
-
it('should rewrite DATABASE_URL with resolved postgres port', () => {
|
|
1260
|
+
it('should rewrite DATABASE_URL with resolved postgres port and hostname', () => {
|
|
1261
1261
|
const secrets = {
|
|
1262
1262
|
DATABASE_URL: 'postgresql://app:pass@postgres:5432/mydb',
|
|
1263
1263
|
POSTGRES_PORT: '5432',
|
|
1264
|
+
POSTGRES_HOST: 'postgres',
|
|
1264
1265
|
SOME_OTHER: 'value',
|
|
1265
1266
|
};
|
|
1266
1267
|
const result = rewriteUrlsWithPorts(secrets, {
|
|
@@ -1269,9 +1270,10 @@ describe('rewriteUrlsWithPorts', () => {
|
|
|
1269
1270
|
mappings: [pgMapping],
|
|
1270
1271
|
});
|
|
1271
1272
|
expect(result.DATABASE_URL).toBe(
|
|
1272
|
-
'postgresql://app:pass@
|
|
1273
|
+
'postgresql://app:pass@localhost:5433/mydb',
|
|
1273
1274
|
);
|
|
1274
1275
|
expect(result.POSTGRES_PORT).toBe('5433');
|
|
1276
|
+
expect(result.POSTGRES_HOST).toBe('localhost');
|
|
1275
1277
|
expect(result.SOME_OTHER).toBe('value');
|
|
1276
1278
|
});
|
|
1277
1279
|
|
|
@@ -1293,18 +1295,20 @@ describe('rewriteUrlsWithPorts', () => {
|
|
|
1293
1295
|
);
|
|
1294
1296
|
});
|
|
1295
1297
|
|
|
1296
|
-
it('should rewrite REDIS_URL and
|
|
1298
|
+
it('should rewrite REDIS_URL, REDIS_PORT, and REDIS_HOST', () => {
|
|
1297
1299
|
const secrets = {
|
|
1298
1300
|
REDIS_URL: 'redis://:pass@redis:6379',
|
|
1299
1301
|
REDIS_PORT: '6379',
|
|
1302
|
+
REDIS_HOST: 'redis',
|
|
1300
1303
|
};
|
|
1301
1304
|
const result = rewriteUrlsWithPorts(secrets, {
|
|
1302
1305
|
dockerEnv: { REDIS_HOST_PORT: '6380' },
|
|
1303
1306
|
ports: { REDIS_HOST_PORT: 6380 },
|
|
1304
1307
|
mappings: [redisMapping],
|
|
1305
1308
|
});
|
|
1306
|
-
expect(result.REDIS_URL).toBe('redis://:pass@
|
|
1309
|
+
expect(result.REDIS_URL).toBe('redis://:pass@localhost:6380');
|
|
1307
1310
|
expect(result.REDIS_PORT).toBe('6380');
|
|
1311
|
+
expect(result.REDIS_HOST).toBe('localhost');
|
|
1308
1312
|
});
|
|
1309
1313
|
|
|
1310
1314
|
it('should rewrite RABBITMQ_URL and RABBITMQ_PORT', () => {
|
|
@@ -1317,7 +1321,7 @@ describe('rewriteUrlsWithPorts', () => {
|
|
|
1317
1321
|
ports: { RABBITMQ_HOST_PORT: 5673 },
|
|
1318
1322
|
mappings: [rmqMapping],
|
|
1319
1323
|
});
|
|
1320
|
-
expect(result.RABBITMQ_URL).toBe('amqp://app:pass@
|
|
1324
|
+
expect(result.RABBITMQ_URL).toBe('amqp://app:pass@localhost:5673/%2F');
|
|
1321
1325
|
expect(result.RABBITMQ_PORT).toBe('5673');
|
|
1322
1326
|
});
|
|
1323
1327
|
|
|
@@ -1336,26 +1340,48 @@ describe('rewriteUrlsWithPorts', () => {
|
|
|
1336
1340
|
ports: { POSTGRES_HOST_PORT: 5433, REDIS_HOST_PORT: 6380 },
|
|
1337
1341
|
mappings: [pgMapping, redisMapping],
|
|
1338
1342
|
});
|
|
1339
|
-
expect(result.DATABASE_URL).
|
|
1343
|
+
expect(result.DATABASE_URL).toBe(
|
|
1344
|
+
'postgresql://app:pass@localhost:5433/mydb',
|
|
1345
|
+
);
|
|
1340
1346
|
expect(result.POSTGRES_PORT).toBe('5433');
|
|
1341
|
-
expect(result.REDIS_URL).
|
|
1347
|
+
expect(result.REDIS_URL).toBe('redis://:pass@localhost:6380');
|
|
1342
1348
|
expect(result.REDIS_PORT).toBe('6380');
|
|
1343
1349
|
});
|
|
1344
1350
|
|
|
1345
|
-
it('should
|
|
1351
|
+
it('should rewrite hostnames even when ports are defaults', () => {
|
|
1346
1352
|
const secrets = {
|
|
1347
1353
|
DATABASE_URL: 'postgresql://app:pass@postgres:5432/mydb',
|
|
1348
1354
|
POSTGRES_PORT: '5432',
|
|
1355
|
+
POSTGRES_HOST: 'postgres',
|
|
1349
1356
|
};
|
|
1350
1357
|
const result = rewriteUrlsWithPorts(secrets, {
|
|
1351
1358
|
dockerEnv: { POSTGRES_HOST_PORT: '5432' },
|
|
1352
1359
|
ports: { POSTGRES_HOST_PORT: 5432 },
|
|
1353
1360
|
mappings: [pgMapping],
|
|
1354
1361
|
});
|
|
1355
|
-
expect(result.DATABASE_URL).toBe(
|
|
1362
|
+
expect(result.DATABASE_URL).toBe(
|
|
1363
|
+
'postgresql://app:pass@localhost:5432/mydb',
|
|
1364
|
+
);
|
|
1365
|
+
expect(result.POSTGRES_HOST).toBe('localhost');
|
|
1356
1366
|
expect(result.POSTGRES_PORT).toBe('5432');
|
|
1357
1367
|
});
|
|
1358
1368
|
|
|
1369
|
+
it('should not rewrite _HOST vars that are already localhost', () => {
|
|
1370
|
+
const secrets = {
|
|
1371
|
+
DATABASE_URL: 'postgresql://app:pass@localhost:5432/mydb',
|
|
1372
|
+
POSTGRES_HOST: 'localhost',
|
|
1373
|
+
};
|
|
1374
|
+
const result = rewriteUrlsWithPorts(secrets, {
|
|
1375
|
+
dockerEnv: { POSTGRES_HOST_PORT: '5432' },
|
|
1376
|
+
ports: { POSTGRES_HOST_PORT: 5432 },
|
|
1377
|
+
mappings: [pgMapping],
|
|
1378
|
+
});
|
|
1379
|
+
expect(result.DATABASE_URL).toBe(
|
|
1380
|
+
'postgresql://app:pass@localhost:5432/mydb',
|
|
1381
|
+
);
|
|
1382
|
+
expect(result.POSTGRES_HOST).toBe('localhost');
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1359
1385
|
it('should return empty for no mappings', () => {
|
|
1360
1386
|
const result = rewriteUrlsWithPorts(
|
|
1361
1387
|
{},
|
package/src/dev/index.ts
CHANGED
|
@@ -354,7 +354,10 @@ export function rewriteUrlsWithPorts(
|
|
|
354
354
|
|
|
355
355
|
// Build a map of defaultPort → resolvedPort for all changed ports
|
|
356
356
|
const portReplacements: { defaultPort: number; resolvedPort: number }[] = [];
|
|
357
|
+
// Collect Docker service names for hostname rewriting
|
|
358
|
+
const serviceNames = new Set<string>();
|
|
357
359
|
for (const mapping of mappings) {
|
|
360
|
+
serviceNames.add(mapping.service);
|
|
358
361
|
const resolved = ports[mapping.envVar];
|
|
359
362
|
if (resolved !== undefined) {
|
|
360
363
|
portReplacements.push({
|
|
@@ -364,6 +367,14 @@ export function rewriteUrlsWithPorts(
|
|
|
364
367
|
}
|
|
365
368
|
}
|
|
366
369
|
|
|
370
|
+
// Rewrite _HOST env vars that use Docker service names
|
|
371
|
+
for (const [key, value] of Object.entries(result)) {
|
|
372
|
+
if (!key.endsWith('_HOST')) continue;
|
|
373
|
+
if (serviceNames.has(value)) {
|
|
374
|
+
result[key] = 'localhost';
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
367
378
|
// Rewrite _PORT env vars whose values match a default port
|
|
368
379
|
for (const [key, value] of Object.entries(result)) {
|
|
369
380
|
if (!key.endsWith('_PORT')) continue;
|
|
@@ -374,11 +385,17 @@ export function rewriteUrlsWithPorts(
|
|
|
374
385
|
}
|
|
375
386
|
}
|
|
376
387
|
|
|
377
|
-
// Rewrite URLs
|
|
388
|
+
// Rewrite URLs: replace Docker service hostnames with localhost and fix ports
|
|
378
389
|
for (const [key, value] of Object.entries(result)) {
|
|
379
390
|
if (!key.endsWith('_URL') && key !== 'DATABASE_URL') continue;
|
|
380
391
|
|
|
381
392
|
let rewritten = value;
|
|
393
|
+
for (const name of serviceNames) {
|
|
394
|
+
rewritten = rewritten.replace(
|
|
395
|
+
new RegExp(`@${name}:`, 'g'),
|
|
396
|
+
'@localhost:',
|
|
397
|
+
);
|
|
398
|
+
}
|
|
382
399
|
for (const { defaultPort, resolvedPort } of portReplacements) {
|
|
383
400
|
rewritten = replacePortInUrl(rewritten, defaultPort, resolvedPort);
|
|
384
401
|
}
|
|
@@ -122,14 +122,14 @@ describe('generateDockerCompose', () => {
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
describe('postgres service', () => {
|
|
125
|
-
it('should add DATABASE_URL environment variable', () => {
|
|
125
|
+
it('should add DATABASE_URL environment variable with credential interpolation', () => {
|
|
126
126
|
const yaml = generateDockerCompose({
|
|
127
127
|
...baseOptions,
|
|
128
128
|
services: { postgres: true },
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
expect(yaml).toContain(
|
|
132
|
-
'- DATABASE_URL=${DATABASE_URL:-postgresql
|
|
132
|
+
'- DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-app}}',
|
|
133
133
|
);
|
|
134
134
|
});
|
|
135
135
|
|
|
@@ -185,13 +185,15 @@ describe('generateDockerCompose', () => {
|
|
|
185
185
|
expect(yaml).toContain('postgres_data:');
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
it('should include postgres healthcheck', () => {
|
|
188
|
+
it('should include postgres healthcheck using POSTGRES_USER', () => {
|
|
189
189
|
const yaml = generateDockerCompose({
|
|
190
190
|
...baseOptions,
|
|
191
191
|
services: { postgres: true },
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
-
expect(yaml).toContain(
|
|
194
|
+
expect(yaml).toContain(
|
|
195
|
+
'test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]',
|
|
196
|
+
);
|
|
195
197
|
});
|
|
196
198
|
|
|
197
199
|
it('should add depends_on for postgres', () => {
|
|
@@ -801,7 +803,7 @@ describe('generateWorkspaceCompose', () => {
|
|
|
801
803
|
const yaml = generateWorkspaceCompose(workspace);
|
|
802
804
|
|
|
803
805
|
expect(yaml).toContain(
|
|
804
|
-
'DATABASE_URL=${DATABASE_URL:-postgresql
|
|
806
|
+
'DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-app}}',
|
|
805
807
|
);
|
|
806
808
|
});
|
|
807
809
|
|
package/src/docker/compose.ts
CHANGED
|
@@ -105,7 +105,7 @@ services:
|
|
|
105
105
|
|
|
106
106
|
// Add environment variables based on services
|
|
107
107
|
if (serviceMap.has('postgres')) {
|
|
108
|
-
yaml += ` - DATABASE_URL=\${DATABASE_URL:-postgresql
|
|
108
|
+
yaml += ` - DATABASE_URL=\${DATABASE_URL:-postgresql://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:-postgres}@postgres:5432/\${POSTGRES_DB:-app}}
|
|
109
109
|
`;
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -156,7 +156,7 @@ services:
|
|
|
156
156
|
volumes:
|
|
157
157
|
- postgres_data:/var/lib/postgresql/data
|
|
158
158
|
healthcheck:
|
|
159
|
-
test: ["CMD-SHELL", "pg_isready -U
|
|
159
|
+
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
|
|
160
160
|
interval: 5s
|
|
161
161
|
timeout: 5s
|
|
162
162
|
retries: 5
|
|
@@ -335,7 +335,7 @@ services:
|
|
|
335
335
|
volumes:
|
|
336
336
|
- postgres_data:/var/lib/postgresql/data
|
|
337
337
|
healthcheck:
|
|
338
|
-
test: ["CMD-SHELL", "pg_isready -U
|
|
338
|
+
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
|
|
339
339
|
interval: 5s
|
|
340
340
|
timeout: 5s
|
|
341
341
|
retries: 5
|
|
@@ -480,7 +480,7 @@ function generateAppService(
|
|
|
480
480
|
// Add infrastructure service URLs for backend apps
|
|
481
481
|
if (app.type === 'backend') {
|
|
482
482
|
if (hasPostgres) {
|
|
483
|
-
yaml += ` - DATABASE_URL=\${DATABASE_URL:-postgresql
|
|
483
|
+
yaml += ` - DATABASE_URL=\${DATABASE_URL:-postgresql://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:-postgres}@postgres:5432/\${POSTGRES_DB:-app}}
|
|
484
484
|
`;
|
|
485
485
|
}
|
|
486
486
|
if (hasRedis) {
|
|
@@ -46,15 +46,15 @@ export function generateDockerFiles(
|
|
|
46
46
|
container_name: ${options.name}-postgres
|
|
47
47
|
restart: unless-stopped${envFile}
|
|
48
48
|
environment:
|
|
49
|
-
POSTGRES_USER: postgres
|
|
50
|
-
POSTGRES_PASSWORD: postgres
|
|
51
|
-
POSTGRES_DB:
|
|
49
|
+
POSTGRES_USER: \${POSTGRES_USER:-postgres}
|
|
50
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
|
|
51
|
+
POSTGRES_DB: \${POSTGRES_DB:-${options.name.replace(/-/g, '_')}_dev}
|
|
52
52
|
ports:
|
|
53
53
|
- '\${POSTGRES_HOST_PORT:-5432}:5432'
|
|
54
54
|
volumes:
|
|
55
55
|
- postgres_data:/var/lib/postgresql/data${initVolume}
|
|
56
56
|
healthcheck:
|
|
57
|
-
test: ['CMD-SHELL', 'pg_isready -U
|
|
57
|
+
test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER']
|
|
58
58
|
interval: 5s
|
|
59
59
|
timeout: 5s
|
|
60
60
|
retries: 5`);
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
rewriteUrlsWithPorts,
|
|
25
25
|
savePortState,
|
|
26
26
|
} from '../../dev/index';
|
|
27
|
-
import {
|
|
27
|
+
import { rewriteDatabaseUrlForTests } from '../index';
|
|
28
28
|
|
|
29
29
|
describe('rewriteDatabaseUrlForTests', () => {
|
|
30
30
|
beforeAll(() => {
|
|
@@ -128,39 +128,6 @@ describe('rewriteDatabaseUrlForTests', () => {
|
|
|
128
128
|
});
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
describe('ensureTestDatabase', () => {
|
|
132
|
-
beforeAll(() => {
|
|
133
|
-
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
afterAll(() => {
|
|
137
|
-
vi.restoreAllMocks();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should do nothing when DATABASE_URL is missing', async () => {
|
|
141
|
-
// Should resolve without error
|
|
142
|
-
await ensureTestDatabase({});
|
|
143
|
-
await ensureTestDatabase({ REDIS_URL: 'redis://localhost:6379' });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should do nothing when database name is empty', async () => {
|
|
147
|
-
await ensureTestDatabase({
|
|
148
|
-
DATABASE_URL: 'postgresql://app:secret@localhost:5432/',
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should not throw when postgres is unreachable', async () => {
|
|
153
|
-
// Use a port that's almost certainly not running postgres
|
|
154
|
-
await ensureTestDatabase({
|
|
155
|
-
DATABASE_URL: 'postgresql://app:secret@localhost:59999/test_db',
|
|
156
|
-
});
|
|
157
|
-
// Should log a warning but not throw
|
|
158
|
-
expect(console.log).toHaveBeenCalledWith(
|
|
159
|
-
expect.stringContaining('Could not ensure test database'),
|
|
160
|
-
);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
131
|
describe('port rewriting + test database pipeline', () => {
|
|
165
132
|
let testDir: string;
|
|
166
133
|
|
|
@@ -359,4 +326,65 @@ services:
|
|
|
359
326
|
// RabbitMQ port rewritten
|
|
360
327
|
expect(secrets.RABBITMQ_URL).toBe('amqp://app:secret@localhost:5673');
|
|
361
328
|
});
|
|
329
|
+
|
|
330
|
+
it('should preserve root postgres credentials through the pipeline', async () => {
|
|
331
|
+
writeFileSync(
|
|
332
|
+
join(testDir, 'docker-compose.yml'),
|
|
333
|
+
`
|
|
334
|
+
services:
|
|
335
|
+
postgres:
|
|
336
|
+
image: postgres:17
|
|
337
|
+
ports:
|
|
338
|
+
- '\${POSTGRES_HOST_PORT:-5432}:5432'
|
|
339
|
+
`,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
await savePortState(testDir, {
|
|
343
|
+
POSTGRES_HOST_PORT: 5434,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Simulate toEmbeddableSecrets output for a workspace with app-specific users
|
|
347
|
+
let secrets: Record<string, string> = {
|
|
348
|
+
DATABASE_URL: 'postgresql://app:rootpass@postgres:5432/app',
|
|
349
|
+
API_DATABASE_URL: 'postgresql://api:apipass@localhost:5432/myproject_dev',
|
|
350
|
+
AUTH_DATABASE_URL:
|
|
351
|
+
'postgresql://auth:authpass@localhost:5432/myproject_dev',
|
|
352
|
+
POSTGRES_USER: 'app',
|
|
353
|
+
POSTGRES_PASSWORD: 'rootpass',
|
|
354
|
+
POSTGRES_DB: 'app',
|
|
355
|
+
POSTGRES_HOST: 'postgres',
|
|
356
|
+
POSTGRES_PORT: '5432',
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Apply port + host rewriting
|
|
360
|
+
const mappings = parseComposePortMappings(
|
|
361
|
+
join(testDir, 'docker-compose.yml'),
|
|
362
|
+
);
|
|
363
|
+
const ports = await loadPortState(testDir);
|
|
364
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
365
|
+
dockerEnv: {},
|
|
366
|
+
ports,
|
|
367
|
+
mappings,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Apply test database suffix
|
|
371
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
372
|
+
|
|
373
|
+
// Root credentials should be present and rewritten to localhost
|
|
374
|
+
expect(secrets.POSTGRES_USER).toBe('app');
|
|
375
|
+
expect(secrets.POSTGRES_PASSWORD).toBe('rootpass');
|
|
376
|
+
expect(secrets.POSTGRES_HOST).toBe('localhost');
|
|
377
|
+
expect(secrets.POSTGRES_PORT).toBe('5434');
|
|
378
|
+
|
|
379
|
+
// All DATABASE_URLs should have localhost, resolved port, and _test suffix
|
|
380
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
381
|
+
'postgresql://app:rootpass@localhost:5434/app_test',
|
|
382
|
+
);
|
|
383
|
+
expect(secrets.API_DATABASE_URL).toBe(
|
|
384
|
+
'postgresql://api:apipass@localhost:5434/myproject_dev_test',
|
|
385
|
+
);
|
|
386
|
+
expect(secrets.AUTH_DATABASE_URL).toBe(
|
|
387
|
+
'postgresql://auth:authpass@localhost:5434/myproject_dev_test',
|
|
388
|
+
);
|
|
389
|
+
});
|
|
362
390
|
});
|
package/src/test/index.ts
CHANGED
|
@@ -81,7 +81,6 @@ export async function testCommand(options: TestOptions = {}): Promise<void> {
|
|
|
81
81
|
|
|
82
82
|
// 4. Use a separate test database (append _test suffix)
|
|
83
83
|
secretsEnv = rewriteDatabaseUrlForTests(secretsEnv);
|
|
84
|
-
await ensureTestDatabase(secretsEnv);
|
|
85
84
|
|
|
86
85
|
// 5. Load workspace config + dependency URLs + sniff env vars
|
|
87
86
|
let dependencyEnv: Record<string, string> = {};
|
|
@@ -225,43 +224,3 @@ export function rewriteDatabaseUrlForTests(
|
|
|
225
224
|
|
|
226
225
|
return result;
|
|
227
226
|
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Ensure the test database exists by connecting to the default database
|
|
231
|
-
* and running CREATE DATABASE IF NOT EXISTS.
|
|
232
|
-
* @internal Exported for testing
|
|
233
|
-
*/
|
|
234
|
-
export async function ensureTestDatabase(
|
|
235
|
-
env: Record<string, string>,
|
|
236
|
-
): Promise<void> {
|
|
237
|
-
const databaseUrl = env.DATABASE_URL;
|
|
238
|
-
if (!databaseUrl) return;
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
const url = new URL(databaseUrl);
|
|
242
|
-
const testDbName = url.pathname.slice(1);
|
|
243
|
-
if (!testDbName) return;
|
|
244
|
-
|
|
245
|
-
// Connect to the default 'postgres' database to create the test database
|
|
246
|
-
url.pathname = '/postgres';
|
|
247
|
-
const { default: pg } = await import('pg');
|
|
248
|
-
const client = new pg.Client({ connectionString: url.toString() });
|
|
249
|
-
await client.connect();
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
await client.query(`CREATE DATABASE "${testDbName}"`);
|
|
253
|
-
console.log(` 📦 Created test database "${testDbName}"`);
|
|
254
|
-
} catch (err: unknown) {
|
|
255
|
-
// 42P04 = database already exists — that's fine
|
|
256
|
-
if ((err as { code?: string }).code !== '42P04') throw err;
|
|
257
|
-
} finally {
|
|
258
|
-
await client.end();
|
|
259
|
-
}
|
|
260
|
-
} catch (err) {
|
|
261
|
-
// Don't fail test startup if we can't create the database
|
|
262
|
-
// (e.g., postgres not running yet, will fail later with a clear error)
|
|
263
|
-
console.log(
|
|
264
|
-
` ⚠️ Could not ensure test database: ${(err as Error).message}`,
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|