@geekmidas/cli 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/{config-BYn5yUt5.cjs → config-6JHOwLCx.cjs} +30 -2
- package/dist/{config-dLNQIvDR.mjs.map → config-6JHOwLCx.cjs.map} +1 -1
- package/dist/{config-dLNQIvDR.mjs → config-DxASSNjr.mjs} +25 -3
- package/dist/{config-BYn5yUt5.cjs.map → config-DxASSNjr.mjs.map} +1 -1
- package/dist/config.cjs +3 -2
- package/dist/config.d.cts +14 -2
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +14 -2
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -3
- package/dist/{index-Bj5VNxEL.d.mts → index-C-KxSGGK.d.mts} +2 -2
- package/dist/{index-Ba21_lNt.d.cts.map → index-C-KxSGGK.d.mts.map} +1 -1
- package/dist/{index-Ba21_lNt.d.cts → index-Cyk2rTyj.d.cts} +2 -2
- package/dist/{index-Bj5VNxEL.d.mts.map → index-Cyk2rTyj.d.cts.map} +1 -1
- package/dist/index.cjs +555 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +519 -103
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CMTyaIJJ.mjs → openapi-BYlyAbH3.mjs} +6 -5
- package/dist/openapi-BYlyAbH3.mjs.map +1 -0
- package/dist/{openapi-CqblwJZ4.cjs → openapi-CnvwSRDU.cjs} +6 -5
- package/dist/openapi-CnvwSRDU.cjs.map +1 -0
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -0
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -0
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +1 -1
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-Dy8k7Wru.mjs → workspace-9IQIjwkQ.mjs} +5 -3
- package/dist/workspace-9IQIjwkQ.mjs.map +1 -0
- package/dist/{workspace-DIMnYaYt.cjs → workspace-D2ocAlpl.cjs} +5 -3
- package/dist/workspace-D2ocAlpl.cjs.map +1 -0
- package/package.json +11 -10
- package/src/config.ts +44 -0
- package/src/dev/__tests__/index.spec.ts +490 -0
- package/src/dev/index.ts +313 -18
- package/src/generators/Generator.ts +4 -1
- package/src/init/__tests__/generators.spec.ts +167 -18
- package/src/init/__tests__/init.spec.ts +66 -3
- package/src/init/generators/auth.ts +6 -5
- package/src/init/generators/config.ts +49 -7
- package/src/init/generators/docker.ts +8 -8
- package/src/init/generators/index.ts +1 -0
- package/src/init/generators/models.ts +3 -5
- package/src/init/generators/package.ts +4 -0
- package/src/init/generators/test.ts +133 -0
- package/src/init/generators/ui.ts +13 -12
- package/src/init/generators/web.ts +9 -8
- package/src/init/index.ts +2 -0
- package/src/init/templates/api.ts +6 -6
- package/src/init/templates/minimal.ts +2 -2
- package/src/init/templates/worker.ts +2 -2
- package/src/init/versions.ts +4 -4
- package/src/openapi.ts +6 -2
- package/src/test/__tests__/__fixtures__/workspace.ts +104 -0
- package/src/test/__tests__/api.spec.ts +199 -0
- package/src/test/__tests__/auth.spec.ts +162 -0
- package/src/test/__tests__/index.spec.ts +323 -0
- package/src/test/__tests__/web.spec.ts +210 -0
- package/src/test/index.ts +165 -14
- package/src/workspace/__tests__/index.spec.ts +3 -0
- package/src/workspace/index.ts +4 -2
- package/dist/openapi-CMTyaIJJ.mjs.map +0 -1
- package/dist/openapi-CqblwJZ4.cjs.map +0 -1
- package/dist/workspace-DIMnYaYt.cjs.map +0 -1
- package/dist/workspace-Dy8k7Wru.mjs.map +0 -1
|
@@ -126,7 +126,7 @@ export type AppEvents =
|
|
|
126
126
|
path: 'src/events/publisher.ts',
|
|
127
127
|
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
128
128
|
import { Publisher, type EventPublisher } from '@geekmidas/events';
|
|
129
|
-
import type { AppEvents } from './types.
|
|
129
|
+
import type { AppEvents } from './types.ts';
|
|
130
130
|
|
|
131
131
|
export const eventsPublisherService = {
|
|
132
132
|
serviceName: 'events' as const,
|
|
@@ -155,7 +155,7 @@ export const eventsPublisherService = {
|
|
|
155
155
|
{
|
|
156
156
|
path: 'src/subscribers/user-events.ts',
|
|
157
157
|
content: `import { s } from '@geekmidas/constructs/subscribers';
|
|
158
|
-
import { eventsPublisherService } from '
|
|
158
|
+
import { eventsPublisherService } from '~/events/publisher.ts';
|
|
159
159
|
|
|
160
160
|
export const userEventsSubscriber = s
|
|
161
161
|
.publisher(eventsPublisherService)
|
package/src/init/versions.ts
CHANGED
|
@@ -30,18 +30,18 @@ export const GEEKMIDAS_VERSIONS = {
|
|
|
30
30
|
'@geekmidas/audit': '~1.0.0',
|
|
31
31
|
'@geekmidas/auth': '~1.0.0',
|
|
32
32
|
'@geekmidas/cache': '~1.0.0',
|
|
33
|
-
'@geekmidas/client': '~
|
|
33
|
+
'@geekmidas/client': '~2.0.0',
|
|
34
34
|
'@geekmidas/cloud': '~1.0.0',
|
|
35
|
-
'@geekmidas/constructs': '~1.0
|
|
35
|
+
'@geekmidas/constructs': '~1.1.0',
|
|
36
36
|
'@geekmidas/db': '~1.0.0',
|
|
37
37
|
'@geekmidas/emailkit': '~1.0.0',
|
|
38
|
-
'@geekmidas/envkit': '~1.0.
|
|
38
|
+
'@geekmidas/envkit': '~1.0.3',
|
|
39
39
|
'@geekmidas/errors': '~1.0.0',
|
|
40
40
|
'@geekmidas/events': '~1.0.0',
|
|
41
41
|
'@geekmidas/logger': '~1.0.0',
|
|
42
42
|
'@geekmidas/rate-limit': '~1.0.0',
|
|
43
43
|
'@geekmidas/schema': '~1.0.0',
|
|
44
|
-
'@geekmidas/services': '~1.0.
|
|
44
|
+
'@geekmidas/services': '~1.0.1',
|
|
45
45
|
'@geekmidas/storage': '~1.0.0',
|
|
46
46
|
'@geekmidas/studio': '~1.0.0',
|
|
47
47
|
'@geekmidas/telescope': '~1.0.0',
|
package/src/openapi.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function resolveOpenApiConfig(
|
|
|
52
52
|
*/
|
|
53
53
|
export async function generateOpenApi(
|
|
54
54
|
config: GkmConfig,
|
|
55
|
-
options: { silent?: boolean } = {},
|
|
55
|
+
options: { silent?: boolean; bustCache?: boolean } = {},
|
|
56
56
|
): Promise<{ outputPath: string; endpointCount: number } | null> {
|
|
57
57
|
const logger = options.silent ? { log: () => {} } : console;
|
|
58
58
|
const openApiConfig = resolveOpenApiConfig(config);
|
|
@@ -62,7 +62,11 @@ export async function generateOpenApi(
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const endpointGenerator = new EndpointGenerator();
|
|
65
|
-
const loadedEndpoints = await endpointGenerator.load(
|
|
65
|
+
const loadedEndpoints = await endpointGenerator.load(
|
|
66
|
+
config.routes,
|
|
67
|
+
undefined,
|
|
68
|
+
options.bustCache,
|
|
69
|
+
);
|
|
66
70
|
|
|
67
71
|
if (loadedEndpoints.length === 0) {
|
|
68
72
|
logger.log('No valid endpoints found for OpenAPI generation');
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { normalizeWorkspace } from '../../../workspace/index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simulates the secrets that gkm init generates for a fullstack workspace.
|
|
5
|
+
* In production these come from the encrypted store via toEmbeddableSecrets.
|
|
6
|
+
*/
|
|
7
|
+
export function createFullstackSecrets(): Record<string, string> {
|
|
8
|
+
return {
|
|
9
|
+
NODE_ENV: 'development',
|
|
10
|
+
PORT: '3000',
|
|
11
|
+
LOG_LEVEL: 'debug',
|
|
12
|
+
JWT_SECRET: 'dev-jwt-secret',
|
|
13
|
+
// Per-app database URLs (fullstack workspace)
|
|
14
|
+
API_DATABASE_URL: 'postgresql://api:api-pass@localhost:5432/my-saas',
|
|
15
|
+
AUTH_DATABASE_URL: 'postgresql://auth:auth-pass@localhost:5432/my-saas',
|
|
16
|
+
API_DB_PASSWORD: 'api-pass',
|
|
17
|
+
AUTH_DB_PASSWORD: 'auth-pass',
|
|
18
|
+
// Auth service secrets
|
|
19
|
+
AUTH_PORT: '3002',
|
|
20
|
+
AUTH_URL: 'http://localhost:3002',
|
|
21
|
+
BETTER_AUTH_SECRET: 'better-auth-secret-123',
|
|
22
|
+
BETTER_AUTH_URL: 'http://localhost:3002',
|
|
23
|
+
BETTER_AUTH_TRUSTED_ORIGINS: 'http://localhost:3000,http://localhost:3001',
|
|
24
|
+
// Service credentials
|
|
25
|
+
POSTGRES_USER: 'api',
|
|
26
|
+
POSTGRES_PASSWORD: 'api-pass',
|
|
27
|
+
POSTGRES_DB: 'my-saas',
|
|
28
|
+
POSTGRES_HOST: 'localhost',
|
|
29
|
+
POSTGRES_PORT: '5432',
|
|
30
|
+
REDIS_PASSWORD: 'redis-pass',
|
|
31
|
+
REDIS_HOST: 'localhost',
|
|
32
|
+
REDIS_PORT: '6379',
|
|
33
|
+
// URLs
|
|
34
|
+
DATABASE_URL: 'postgresql://api:api-pass@localhost:5432/my-saas',
|
|
35
|
+
REDIS_URL: 'redis://:redis-pass@localhost:6379',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createFullstackWorkspace(): ReturnType<
|
|
40
|
+
typeof normalizeWorkspace
|
|
41
|
+
> {
|
|
42
|
+
return normalizeWorkspace(
|
|
43
|
+
{
|
|
44
|
+
apps: {
|
|
45
|
+
api: {
|
|
46
|
+
type: 'backend',
|
|
47
|
+
path: 'apps/api',
|
|
48
|
+
port: 3000,
|
|
49
|
+
routes: './src/endpoints/**/*.ts',
|
|
50
|
+
dependencies: [],
|
|
51
|
+
},
|
|
52
|
+
auth: {
|
|
53
|
+
type: 'backend',
|
|
54
|
+
path: 'apps/auth',
|
|
55
|
+
port: 3002,
|
|
56
|
+
entry: './src/index.ts',
|
|
57
|
+
dependencies: [],
|
|
58
|
+
},
|
|
59
|
+
web: {
|
|
60
|
+
type: 'frontend',
|
|
61
|
+
path: 'apps/web',
|
|
62
|
+
port: 3001,
|
|
63
|
+
framework: 'nextjs',
|
|
64
|
+
dependencies: ['api', 'auth'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
'/project',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Simulates what loadSecretsForApp does for a specific app:
|
|
74
|
+
* maps {APP}_DATABASE_URL -> DATABASE_URL.
|
|
75
|
+
*/
|
|
76
|
+
export function mapSecretsForApp(
|
|
77
|
+
secrets: Record<string, string>,
|
|
78
|
+
appName: string,
|
|
79
|
+
): Record<string, string> {
|
|
80
|
+
const prefix = appName.toUpperCase();
|
|
81
|
+
const mapped = { ...secrets };
|
|
82
|
+
const appDbUrl = secrets[`${prefix}_DATABASE_URL`];
|
|
83
|
+
if (appDbUrl) {
|
|
84
|
+
mapped.DATABASE_URL = appDbUrl;
|
|
85
|
+
}
|
|
86
|
+
return mapped;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const COMPOSE_FULL = `
|
|
90
|
+
services:
|
|
91
|
+
postgres:
|
|
92
|
+
image: postgres:17
|
|
93
|
+
ports:
|
|
94
|
+
- '\${POSTGRES_HOST_PORT:-5432}:5432'
|
|
95
|
+
redis:
|
|
96
|
+
image: redis:7
|
|
97
|
+
ports:
|
|
98
|
+
- '\${REDIS_HOST_PORT:-6379}:6379'
|
|
99
|
+
mailpit:
|
|
100
|
+
image: axllent/mailpit
|
|
101
|
+
ports:
|
|
102
|
+
- '\${MAILPIT_SMTP_PORT:-1025}:1025'
|
|
103
|
+
- '\${MAILPIT_UI_PORT:-8025}:8025'
|
|
104
|
+
`;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
afterAll,
|
|
6
|
+
afterEach,
|
|
7
|
+
beforeAll,
|
|
8
|
+
beforeEach,
|
|
9
|
+
describe,
|
|
10
|
+
expect,
|
|
11
|
+
it,
|
|
12
|
+
vi,
|
|
13
|
+
} from 'vitest';
|
|
14
|
+
import {
|
|
15
|
+
loadPortState,
|
|
16
|
+
parseComposePortMappings,
|
|
17
|
+
rewriteUrlsWithPorts,
|
|
18
|
+
savePortState,
|
|
19
|
+
} from '../../dev/index';
|
|
20
|
+
import { getDependencyEnvVars } from '../../workspace/index';
|
|
21
|
+
import { rewriteDatabaseUrlForTests } from '../index';
|
|
22
|
+
import {
|
|
23
|
+
COMPOSE_FULL,
|
|
24
|
+
createFullstackSecrets,
|
|
25
|
+
createFullstackWorkspace,
|
|
26
|
+
mapSecretsForApp,
|
|
27
|
+
} from './__fixtures__/workspace';
|
|
28
|
+
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('api app context', () => {
|
|
38
|
+
let testDir: string;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
testDir = join(tmpdir(), `gkm-api-test-${Date.now()}`);
|
|
42
|
+
mkdirSync(testDir, { recursive: true });
|
|
43
|
+
writeFileSync(join(testDir, 'docker-compose.yml'), COMPOSE_FULL);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should resolve DATABASE_URL from API_DATABASE_URL', () => {
|
|
51
|
+
const apiSecrets = mapSecretsForApp(createFullstackSecrets(), 'api');
|
|
52
|
+
|
|
53
|
+
expect(apiSecrets.DATABASE_URL).toBe(
|
|
54
|
+
'postgresql://api:api-pass@localhost:5432/my-saas',
|
|
55
|
+
);
|
|
56
|
+
expect(apiSecrets.API_DATABASE_URL).toBe(
|
|
57
|
+
'postgresql://api:api-pass@localhost:5432/my-saas',
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should rewrite DATABASE_URL and REDIS_URL when ports are remapped', async () => {
|
|
62
|
+
await savePortState(testDir, {
|
|
63
|
+
POSTGRES_HOST_PORT: 5433,
|
|
64
|
+
REDIS_HOST_PORT: 6380,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'api');
|
|
68
|
+
|
|
69
|
+
const mappings = parseComposePortMappings(
|
|
70
|
+
join(testDir, 'docker-compose.yml'),
|
|
71
|
+
);
|
|
72
|
+
const ports = await loadPortState(testDir);
|
|
73
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
74
|
+
dockerEnv: {},
|
|
75
|
+
ports,
|
|
76
|
+
mappings,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
80
|
+
'postgresql://api:api-pass@localhost:5433/my-saas',
|
|
81
|
+
);
|
|
82
|
+
expect(secrets.API_DATABASE_URL).toBe(
|
|
83
|
+
'postgresql://api:api-pass@localhost:5433/my-saas',
|
|
84
|
+
);
|
|
85
|
+
expect(secrets.REDIS_URL).toBe('redis://:redis-pass@localhost:6380');
|
|
86
|
+
// AUTH_URL is an app URL, not a docker service
|
|
87
|
+
expect(secrets.AUTH_URL).toBe('http://localhost:3002');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should rewrite only postgres when redis port unchanged', async () => {
|
|
91
|
+
await savePortState(testDir, {
|
|
92
|
+
POSTGRES_HOST_PORT: 5433,
|
|
93
|
+
REDIS_HOST_PORT: 6379,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'api');
|
|
97
|
+
|
|
98
|
+
const mappings = parseComposePortMappings(
|
|
99
|
+
join(testDir, 'docker-compose.yml'),
|
|
100
|
+
);
|
|
101
|
+
const ports = await loadPortState(testDir);
|
|
102
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
103
|
+
dockerEnv: {},
|
|
104
|
+
ports,
|
|
105
|
+
mappings,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
109
|
+
'postgresql://api:api-pass@localhost:5433/my-saas',
|
|
110
|
+
);
|
|
111
|
+
// Redis port unchanged — URL stays the same
|
|
112
|
+
expect(secrets.REDIS_URL).toBe('redis://:redis-pass@localhost:6379');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should apply full pipeline for gkm test in api context', async () => {
|
|
116
|
+
await savePortState(testDir, {
|
|
117
|
+
POSTGRES_HOST_PORT: 5433,
|
|
118
|
+
REDIS_HOST_PORT: 6380,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'api');
|
|
122
|
+
|
|
123
|
+
const mappings = parseComposePortMappings(
|
|
124
|
+
join(testDir, 'docker-compose.yml'),
|
|
125
|
+
);
|
|
126
|
+
const ports = await loadPortState(testDir);
|
|
127
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
128
|
+
dockerEnv: {},
|
|
129
|
+
ports,
|
|
130
|
+
mappings,
|
|
131
|
+
});
|
|
132
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
133
|
+
|
|
134
|
+
// Port rewrite + _test suffix
|
|
135
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
136
|
+
'postgresql://api:api-pass@localhost:5433/my-saas_test',
|
|
137
|
+
);
|
|
138
|
+
expect(secrets.API_DATABASE_URL).toBe(
|
|
139
|
+
'postgresql://api:api-pass@localhost:5433/my-saas_test',
|
|
140
|
+
);
|
|
141
|
+
// AUTH_DATABASE_URL also gets both rewrites
|
|
142
|
+
expect(secrets.AUTH_DATABASE_URL).toBe(
|
|
143
|
+
'postgresql://auth:auth-pass@localhost:5433/my-saas_test',
|
|
144
|
+
);
|
|
145
|
+
// Non-database URLs unaffected
|
|
146
|
+
expect(secrets.REDIS_URL).toBe('redis://:redis-pass@localhost:6380');
|
|
147
|
+
expect(secrets.AUTH_URL).toBe('http://localhost:3002');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should use default ports when no port state saved', async () => {
|
|
151
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'api');
|
|
152
|
+
|
|
153
|
+
const mappings = parseComposePortMappings(
|
|
154
|
+
join(testDir, 'docker-compose.yml'),
|
|
155
|
+
);
|
|
156
|
+
const ports = await loadPortState(testDir);
|
|
157
|
+
|
|
158
|
+
if (Object.keys(ports).length > 0) {
|
|
159
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
160
|
+
dockerEnv: {},
|
|
161
|
+
ports,
|
|
162
|
+
mappings,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
166
|
+
|
|
167
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
168
|
+
'postgresql://api:api-pass@localhost:5432/my-saas_test',
|
|
169
|
+
);
|
|
170
|
+
expect(secrets.REDIS_URL).toBe('redis://:redis-pass@localhost:6379');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should not inject dependency URLs for api app (no dependencies)', () => {
|
|
174
|
+
const workspace = createFullstackWorkspace();
|
|
175
|
+
const depEnv = getDependencyEnvVars(workspace, 'api');
|
|
176
|
+
|
|
177
|
+
expect(depEnv).toEqual({});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should keep BETTER_AUTH vars available for api to call auth service', async () => {
|
|
181
|
+
await savePortState(testDir, { POSTGRES_HOST_PORT: 5433 });
|
|
182
|
+
|
|
183
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'api');
|
|
184
|
+
|
|
185
|
+
const mappings = parseComposePortMappings(
|
|
186
|
+
join(testDir, 'docker-compose.yml'),
|
|
187
|
+
);
|
|
188
|
+
const ports = await loadPortState(testDir);
|
|
189
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
190
|
+
dockerEnv: {},
|
|
191
|
+
ports,
|
|
192
|
+
mappings,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// API app may need AUTH_URL to call auth service internally
|
|
196
|
+
expect(secrets.AUTH_URL).toBe('http://localhost:3002');
|
|
197
|
+
expect(secrets.BETTER_AUTH_URL).toBe('http://localhost:3002');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
afterAll,
|
|
6
|
+
afterEach,
|
|
7
|
+
beforeAll,
|
|
8
|
+
beforeEach,
|
|
9
|
+
describe,
|
|
10
|
+
expect,
|
|
11
|
+
it,
|
|
12
|
+
vi,
|
|
13
|
+
} from 'vitest';
|
|
14
|
+
import {
|
|
15
|
+
loadPortState,
|
|
16
|
+
parseComposePortMappings,
|
|
17
|
+
rewriteUrlsWithPorts,
|
|
18
|
+
savePortState,
|
|
19
|
+
} from '../../dev/index';
|
|
20
|
+
import { getDependencyEnvVars } from '../../workspace/index';
|
|
21
|
+
import { rewriteDatabaseUrlForTests } from '../index';
|
|
22
|
+
import {
|
|
23
|
+
COMPOSE_FULL,
|
|
24
|
+
createFullstackSecrets,
|
|
25
|
+
createFullstackWorkspace,
|
|
26
|
+
mapSecretsForApp,
|
|
27
|
+
} from './__fixtures__/workspace';
|
|
28
|
+
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('auth app context', () => {
|
|
38
|
+
let testDir: string;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
testDir = join(tmpdir(), `gkm-auth-test-${Date.now()}`);
|
|
42
|
+
mkdirSync(testDir, { recursive: true });
|
|
43
|
+
writeFileSync(join(testDir, 'docker-compose.yml'), COMPOSE_FULL);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should resolve DATABASE_URL from AUTH_DATABASE_URL', () => {
|
|
51
|
+
const authSecrets = mapSecretsForApp(createFullstackSecrets(), 'auth');
|
|
52
|
+
|
|
53
|
+
expect(authSecrets.DATABASE_URL).toBe(
|
|
54
|
+
'postgresql://auth:auth-pass@localhost:5432/my-saas',
|
|
55
|
+
);
|
|
56
|
+
expect(authSecrets.AUTH_DATABASE_URL).toBe(
|
|
57
|
+
'postgresql://auth:auth-pass@localhost:5432/my-saas',
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should preserve BETTER_AUTH_* secrets untouched', () => {
|
|
62
|
+
const authSecrets = mapSecretsForApp(createFullstackSecrets(), 'auth');
|
|
63
|
+
|
|
64
|
+
expect(authSecrets.BETTER_AUTH_SECRET).toBe('better-auth-secret-123');
|
|
65
|
+
expect(authSecrets.BETTER_AUTH_URL).toBe('http://localhost:3002');
|
|
66
|
+
expect(authSecrets.BETTER_AUTH_TRUSTED_ORIGINS).toBe(
|
|
67
|
+
'http://localhost:3000,http://localhost:3001',
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should rewrite auth DATABASE_URL ports when postgres is remapped', async () => {
|
|
72
|
+
await savePortState(testDir, {
|
|
73
|
+
POSTGRES_HOST_PORT: 5433,
|
|
74
|
+
REDIS_HOST_PORT: 6379,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'auth');
|
|
78
|
+
|
|
79
|
+
const mappings = parseComposePortMappings(
|
|
80
|
+
join(testDir, 'docker-compose.yml'),
|
|
81
|
+
);
|
|
82
|
+
const ports = await loadPortState(testDir);
|
|
83
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
84
|
+
dockerEnv: {},
|
|
85
|
+
ports,
|
|
86
|
+
mappings,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
90
|
+
'postgresql://auth:auth-pass@localhost:5433/my-saas',
|
|
91
|
+
);
|
|
92
|
+
expect(secrets.AUTH_DATABASE_URL).toBe(
|
|
93
|
+
'postgresql://auth:auth-pass@localhost:5433/my-saas',
|
|
94
|
+
);
|
|
95
|
+
// BETTER_AUTH_URL is an app port (3002), not a docker service port
|
|
96
|
+
expect(secrets.BETTER_AUTH_URL).toBe('http://localhost:3002');
|
|
97
|
+
expect(secrets.BETTER_AUTH_SECRET).toBe('better-auth-secret-123');
|
|
98
|
+
expect(secrets.BETTER_AUTH_TRUSTED_ORIGINS).toBe(
|
|
99
|
+
'http://localhost:3000,http://localhost:3001',
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should apply _test suffix for gkm test in auth context', async () => {
|
|
104
|
+
await savePortState(testDir, { POSTGRES_HOST_PORT: 5433 });
|
|
105
|
+
|
|
106
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'auth');
|
|
107
|
+
|
|
108
|
+
const mappings = parseComposePortMappings(
|
|
109
|
+
join(testDir, 'docker-compose.yml'),
|
|
110
|
+
);
|
|
111
|
+
const ports = await loadPortState(testDir);
|
|
112
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
113
|
+
dockerEnv: {},
|
|
114
|
+
ports,
|
|
115
|
+
mappings,
|
|
116
|
+
});
|
|
117
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
118
|
+
|
|
119
|
+
// Port rewrite + _test suffix
|
|
120
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
121
|
+
'postgresql://auth:auth-pass@localhost:5433/my-saas_test',
|
|
122
|
+
);
|
|
123
|
+
expect(secrets.AUTH_DATABASE_URL).toBe(
|
|
124
|
+
'postgresql://auth:auth-pass@localhost:5433/my-saas_test',
|
|
125
|
+
);
|
|
126
|
+
// API_DATABASE_URL also gets _test (same container, different user)
|
|
127
|
+
expect(secrets.API_DATABASE_URL).toBe(
|
|
128
|
+
'postgresql://api:api-pass@localhost:5433/my-saas_test',
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should use default ports when no port state saved', async () => {
|
|
133
|
+
let secrets = mapSecretsForApp(createFullstackSecrets(), 'auth');
|
|
134
|
+
|
|
135
|
+
const mappings = parseComposePortMappings(
|
|
136
|
+
join(testDir, 'docker-compose.yml'),
|
|
137
|
+
);
|
|
138
|
+
const ports = await loadPortState(testDir);
|
|
139
|
+
|
|
140
|
+
// No saved state — rewriting is a no-op
|
|
141
|
+
if (Object.keys(ports).length > 0) {
|
|
142
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
143
|
+
dockerEnv: {},
|
|
144
|
+
ports,
|
|
145
|
+
mappings,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
149
|
+
|
|
150
|
+
// Default port preserved, only _test suffix added
|
|
151
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
152
|
+
'postgresql://auth:auth-pass@localhost:5432/my-saas_test',
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should not inject dependency URLs for auth app (no dependencies)', () => {
|
|
157
|
+
const workspace = createFullstackWorkspace();
|
|
158
|
+
const depEnv = getDependencyEnvVars(workspace, 'auth');
|
|
159
|
+
|
|
160
|
+
expect(depEnv).toEqual({});
|
|
161
|
+
});
|
|
162
|
+
});
|