@geekmidas/cli 1.5.0 → 1.6.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/{HostingerProvider-B9N-TKbp.mjs → HostingerProvider-402UdK89.mjs} +34 -1
- package/dist/HostingerProvider-402UdK89.mjs.map +1 -0
- package/dist/{HostingerProvider-DUV9-Tzg.cjs → HostingerProvider-BiXdHjiq.cjs} +34 -1
- package/dist/HostingerProvider-BiXdHjiq.cjs.map +1 -0
- package/dist/{Route53Provider-C8mS0zY6.mjs → Route53Provider-DbBo7Uz5.mjs} +53 -1
- package/dist/Route53Provider-DbBo7Uz5.mjs.map +1 -0
- package/dist/{Route53Provider-Bs7Arms9.cjs → Route53Provider-kfJ77LmL.cjs} +53 -1
- package/dist/Route53Provider-kfJ77LmL.cjs.map +1 -0
- package/dist/backup-provisioner-B5e-F6zX.cjs +164 -0
- package/dist/backup-provisioner-B5e-F6zX.cjs.map +1 -0
- package/dist/backup-provisioner-BIArpmTr.mjs +163 -0
- package/dist/backup-provisioner-BIArpmTr.mjs.map +1 -0
- package/dist/{config-ZQM1vBoz.cjs → config-6JHOwLCx.cjs} +30 -2
- package/dist/{config-ZQM1vBoz.cjs.map → config-6JHOwLCx.cjs.map} +1 -1
- package/dist/{config-DfCJ29PQ.mjs → config-DxASSNjr.mjs} +25 -3
- package/dist/{config-DfCJ29PQ.mjs.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 +15 -3
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -3
- package/dist/{dokploy-api-z0833e7r.mjs → dokploy-api-2ldYoN3i.mjs} +131 -1
- package/dist/dokploy-api-2ldYoN3i.mjs.map +1 -0
- package/dist/dokploy-api-C93pveuy.mjs +3 -0
- package/dist/dokploy-api-CbDh4o93.cjs +3 -0
- package/dist/{dokploy-api-CQvhV6Hd.cjs → dokploy-api-DLgvEQlr.cjs} +131 -1
- package/dist/dokploy-api-DLgvEQlr.cjs.map +1 -0
- package/dist/{index-C0SpUT9Y.d.mts → index-C-KxSGGK.d.mts} +133 -31
- package/dist/index-C-KxSGGK.d.mts.map +1 -0
- package/dist/{index-B58qjyBd.d.cts → index-Cyk2rTyj.d.cts} +132 -30
- package/dist/index-Cyk2rTyj.d.cts.map +1 -0
- package/dist/index.cjs +662 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +626 -116
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-BcSjLfWq.mjs → openapi-BYlyAbH3.mjs} +6 -5
- package/dist/openapi-BYlyAbH3.mjs.map +1 -0
- package/dist/{openapi-D6Hcfov0.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 +2 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/{types-B9UZ7fOG.d.mts → types-CZg5iUgD.d.mts} +1 -1
- package/dist/{types-B9UZ7fOG.d.mts.map → types-CZg5iUgD.d.mts.map} +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-BW2iU37P.mjs → workspace-9IQIjwkQ.mjs} +20 -4
- package/dist/workspace-9IQIjwkQ.mjs.map +1 -0
- package/dist/{workspace-2Do2YcGZ.cjs → workspace-D2ocAlpl.cjs} +20 -4
- package/dist/workspace-D2ocAlpl.cjs.map +1 -0
- package/examples/cron-example.ts +6 -6
- package/examples/function-example.ts +1 -1
- package/package.json +6 -3
- package/src/config.ts +44 -0
- package/src/deploy/__tests__/backup-provisioner.spec.ts +428 -0
- package/src/deploy/__tests__/createDnsProvider.spec.ts +23 -0
- package/src/deploy/__tests__/env-resolver.spec.ts +1 -1
- package/src/deploy/__tests__/undeploy.spec.ts +758 -0
- package/src/deploy/backup-provisioner.ts +316 -0
- package/src/deploy/dns/DnsProvider.ts +39 -1
- package/src/deploy/dns/HostingerProvider.ts +74 -0
- package/src/deploy/dns/Route53Provider.ts +81 -0
- package/src/deploy/dns/index.ts +25 -0
- package/src/deploy/dokploy-api.ts +237 -0
- package/src/deploy/index.ts +71 -13
- package/src/deploy/state.ts +171 -0
- package/src/deploy/undeploy.ts +407 -0
- package/src/dev/__tests__/index.spec.ts +490 -0
- package/src/dev/index.ts +313 -18
- package/src/generators/FunctionGenerator.ts +1 -1
- 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 +3 -3
- 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/src/workspace/schema.ts +26 -0
- package/src/workspace/types.ts +14 -37
- package/dist/HostingerProvider-B9N-TKbp.mjs.map +0 -1
- package/dist/HostingerProvider-DUV9-Tzg.cjs.map +0 -1
- package/dist/Route53Provider-Bs7Arms9.cjs.map +0 -1
- package/dist/Route53Provider-C8mS0zY6.mjs.map +0 -1
- package/dist/dokploy-api-CQvhV6Hd.cjs.map +0 -1
- package/dist/dokploy-api-CWc02yyg.cjs +0 -3
- package/dist/dokploy-api-DSJYNx88.mjs +0 -3
- package/dist/dokploy-api-z0833e7r.mjs.map +0 -1
- package/dist/index-B58qjyBd.d.cts.map +0 -1
- package/dist/index-C0SpUT9Y.d.mts.map +0 -1
- package/dist/openapi-BcSjLfWq.mjs.map +0 -1
- package/dist/openapi-D6Hcfov0.cjs.map +0 -1
- package/dist/workspace-2Do2YcGZ.cjs.map +0 -1
- package/dist/workspace-BW2iU37P.mjs.map +0 -1
|
@@ -0,0 +1,323 @@
|
|
|
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 { ensureTestDatabase, rewriteDatabaseUrlForTests } from '../index';
|
|
21
|
+
|
|
22
|
+
describe('rewriteDatabaseUrlForTests', () => {
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
vi.restoreAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should append _test to DATABASE_URL database name', () => {
|
|
32
|
+
const result = rewriteDatabaseUrlForTests({
|
|
33
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/myapp',
|
|
34
|
+
});
|
|
35
|
+
expect(result.DATABASE_URL).toBe(
|
|
36
|
+
'postgresql://app:secret@localhost:5432/myapp_test',
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle URL-encoded passwords', () => {
|
|
41
|
+
const result = rewriteDatabaseUrlForTests({
|
|
42
|
+
DATABASE_URL: 'postgresql://app:p%40ssw0rd@localhost:5432/myapp',
|
|
43
|
+
});
|
|
44
|
+
expect(result.DATABASE_URL).toBe(
|
|
45
|
+
'postgresql://app:p%40ssw0rd@localhost:5432/myapp_test',
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle non-default ports', () => {
|
|
50
|
+
const result = rewriteDatabaseUrlForTests({
|
|
51
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5433/myapp',
|
|
52
|
+
});
|
|
53
|
+
expect(result.DATABASE_URL).toBe(
|
|
54
|
+
'postgresql://app:secret@localhost:5433/myapp_test',
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should not double-append _test if already present', () => {
|
|
59
|
+
const result = rewriteDatabaseUrlForTests({
|
|
60
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/myapp_test',
|
|
61
|
+
});
|
|
62
|
+
expect(result.DATABASE_URL).toBe(
|
|
63
|
+
'postgresql://app:secret@localhost:5432/myapp_test',
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should rewrite all keys containing DATABASE_URL', () => {
|
|
68
|
+
const result = rewriteDatabaseUrlForTests({
|
|
69
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/app',
|
|
70
|
+
API_DATABASE_URL: 'postgresql://app:secret@localhost:5432/api',
|
|
71
|
+
});
|
|
72
|
+
expect(result.DATABASE_URL).toBe(
|
|
73
|
+
'postgresql://app:secret@localhost:5432/app_test',
|
|
74
|
+
);
|
|
75
|
+
expect(result.API_DATABASE_URL).toBe(
|
|
76
|
+
'postgresql://app:secret@localhost:5432/api_test',
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should skip non-DATABASE_URL keys', () => {
|
|
81
|
+
const result = rewriteDatabaseUrlForTests({
|
|
82
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/app',
|
|
83
|
+
REDIS_URL: 'redis://:secret@localhost:6379',
|
|
84
|
+
API_KEY: 'sk-12345',
|
|
85
|
+
});
|
|
86
|
+
expect(result.DATABASE_URL).toBe(
|
|
87
|
+
'postgresql://app:secret@localhost:5432/app_test',
|
|
88
|
+
);
|
|
89
|
+
expect(result.REDIS_URL).toBe('redis://:secret@localhost:6379');
|
|
90
|
+
expect(result.API_KEY).toBe('sk-12345');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should skip invalid URLs gracefully', () => {
|
|
94
|
+
const result = rewriteDatabaseUrlForTests({
|
|
95
|
+
DATABASE_URL: 'not-a-url',
|
|
96
|
+
});
|
|
97
|
+
expect(result.DATABASE_URL).toBe('not-a-url');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should skip URLs without a database name', () => {
|
|
101
|
+
const result = rewriteDatabaseUrlForTests({
|
|
102
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/',
|
|
103
|
+
});
|
|
104
|
+
expect(result.DATABASE_URL).toBe('postgresql://app:secret@localhost:5432/');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return empty object for empty input', () => {
|
|
108
|
+
const result = rewriteDatabaseUrlForTests({});
|
|
109
|
+
expect(result).toEqual({});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should not mutate the original object', () => {
|
|
113
|
+
const original = {
|
|
114
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/app',
|
|
115
|
+
};
|
|
116
|
+
const result = rewriteDatabaseUrlForTests(original);
|
|
117
|
+
expect(original.DATABASE_URL).toBe(
|
|
118
|
+
'postgresql://app:secret@localhost:5432/app',
|
|
119
|
+
);
|
|
120
|
+
expect(result).not.toBe(original);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('ensureTestDatabase', () => {
|
|
125
|
+
beforeAll(() => {
|
|
126
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
afterAll(() => {
|
|
130
|
+
vi.restoreAllMocks();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should do nothing when DATABASE_URL is missing', async () => {
|
|
134
|
+
// Should resolve without error
|
|
135
|
+
await ensureTestDatabase({});
|
|
136
|
+
await ensureTestDatabase({ REDIS_URL: 'redis://localhost:6379' });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should do nothing when database name is empty', async () => {
|
|
140
|
+
await ensureTestDatabase({
|
|
141
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/',
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should not throw when postgres is unreachable', async () => {
|
|
146
|
+
// Use a port that's almost certainly not running postgres
|
|
147
|
+
await ensureTestDatabase({
|
|
148
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:59999/test_db',
|
|
149
|
+
});
|
|
150
|
+
// Should log a warning but not throw
|
|
151
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
152
|
+
expect.stringContaining('Could not ensure test database'),
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('port rewriting + test database pipeline', () => {
|
|
158
|
+
let testDir: string;
|
|
159
|
+
|
|
160
|
+
beforeAll(() => {
|
|
161
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
afterAll(() => {
|
|
165
|
+
vi.restoreAllMocks();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
testDir = join(tmpdir(), `gkm-pipeline-test-${Date.now()}`);
|
|
170
|
+
mkdirSync(testDir, { recursive: true });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
afterEach(() => {
|
|
174
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should rewrite ports then append _test to DATABASE_URL', async () => {
|
|
178
|
+
// 1. Write docker-compose.yml with port mappings
|
|
179
|
+
writeFileSync(
|
|
180
|
+
join(testDir, 'docker-compose.yml'),
|
|
181
|
+
`
|
|
182
|
+
services:
|
|
183
|
+
postgres:
|
|
184
|
+
image: postgres:17
|
|
185
|
+
ports:
|
|
186
|
+
- '\${POSTGRES_HOST_PORT:-5432}:5432'
|
|
187
|
+
redis:
|
|
188
|
+
image: redis:7
|
|
189
|
+
ports:
|
|
190
|
+
- '\${REDIS_HOST_PORT:-6379}:6379'
|
|
191
|
+
`,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// 2. Save port state (simulating gkm dev having resolved ports)
|
|
195
|
+
await savePortState(testDir, {
|
|
196
|
+
POSTGRES_HOST_PORT: 5433,
|
|
197
|
+
REDIS_HOST_PORT: 6380,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// 3. Simulate secrets loaded from encrypted store
|
|
201
|
+
let secrets: Record<string, string> = {
|
|
202
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/myapp',
|
|
203
|
+
REDIS_URL: 'redis://:secret@localhost:6379',
|
|
204
|
+
BETTER_AUTH_SECRET: 'auth-secret-123',
|
|
205
|
+
BETTER_AUTH_URL: 'http://localhost:3002',
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// 4. Apply port rewriting (what gkm test/exec does)
|
|
209
|
+
const composePath = join(testDir, 'docker-compose.yml');
|
|
210
|
+
const mappings = parseComposePortMappings(composePath);
|
|
211
|
+
const ports = await loadPortState(testDir);
|
|
212
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
213
|
+
dockerEnv: {},
|
|
214
|
+
ports,
|
|
215
|
+
mappings,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Ports should be rewritten
|
|
219
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
220
|
+
'postgresql://app:secret@localhost:5433/myapp',
|
|
221
|
+
);
|
|
222
|
+
expect(secrets.REDIS_URL).toBe('redis://:secret@localhost:6380');
|
|
223
|
+
// Non-docker URLs should be untouched
|
|
224
|
+
expect(secrets.BETTER_AUTH_URL).toBe('http://localhost:3002');
|
|
225
|
+
expect(secrets.BETTER_AUTH_SECRET).toBe('auth-secret-123');
|
|
226
|
+
|
|
227
|
+
// 5. Apply test database rewriting (gkm test only)
|
|
228
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
229
|
+
|
|
230
|
+
// DATABASE_URL should now have both port + _test suffix
|
|
231
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
232
|
+
'postgresql://app:secret@localhost:5433/myapp_test',
|
|
233
|
+
);
|
|
234
|
+
// Other URLs unchanged
|
|
235
|
+
expect(secrets.REDIS_URL).toBe('redis://:secret@localhost:6380');
|
|
236
|
+
expect(secrets.BETTER_AUTH_URL).toBe('http://localhost:3002');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should handle no saved port state gracefully', async () => {
|
|
240
|
+
writeFileSync(
|
|
241
|
+
join(testDir, 'docker-compose.yml'),
|
|
242
|
+
`
|
|
243
|
+
services:
|
|
244
|
+
postgres:
|
|
245
|
+
image: postgres:17
|
|
246
|
+
ports:
|
|
247
|
+
- '\${POSTGRES_HOST_PORT:-5432}:5432'
|
|
248
|
+
`,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// No saved port state — gkm dev hasn't run yet
|
|
252
|
+
let secrets: Record<string, string> = {
|
|
253
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/myapp',
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const composePath = join(testDir, 'docker-compose.yml');
|
|
257
|
+
const mappings = parseComposePortMappings(composePath);
|
|
258
|
+
const ports = await loadPortState(testDir);
|
|
259
|
+
|
|
260
|
+
// No ports saved — rewrite should be a no-op
|
|
261
|
+
if (Object.keys(ports).length > 0) {
|
|
262
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
263
|
+
dockerEnv: {},
|
|
264
|
+
ports,
|
|
265
|
+
mappings,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Still apply test database suffix
|
|
270
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
271
|
+
|
|
272
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
273
|
+
'postgresql://app:secret@localhost:5432/myapp_test',
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should handle worker template with rabbitmq ports', async () => {
|
|
278
|
+
writeFileSync(
|
|
279
|
+
join(testDir, 'docker-compose.yml'),
|
|
280
|
+
`
|
|
281
|
+
services:
|
|
282
|
+
postgres:
|
|
283
|
+
image: postgres:17
|
|
284
|
+
ports:
|
|
285
|
+
- '\${POSTGRES_HOST_PORT:-5432}:5432'
|
|
286
|
+
rabbitmq:
|
|
287
|
+
image: rabbitmq:3-management
|
|
288
|
+
ports:
|
|
289
|
+
- '\${RABBITMQ_HOST_PORT:-5672}:5672'
|
|
290
|
+
- '\${RABBITMQ_MGMT_PORT:-15672}:15672'
|
|
291
|
+
`,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
await savePortState(testDir, {
|
|
295
|
+
POSTGRES_HOST_PORT: 5432,
|
|
296
|
+
RABBITMQ_HOST_PORT: 5673,
|
|
297
|
+
RABBITMQ_MGMT_PORT: 15672,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
let secrets: Record<string, string> = {
|
|
301
|
+
DATABASE_URL: 'postgresql://app:secret@localhost:5432/myapp',
|
|
302
|
+
RABBITMQ_URL: 'amqp://app:secret@localhost:5672',
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const mappings = parseComposePortMappings(
|
|
306
|
+
join(testDir, 'docker-compose.yml'),
|
|
307
|
+
);
|
|
308
|
+
const ports = await loadPortState(testDir);
|
|
309
|
+
secrets = rewriteUrlsWithPorts(secrets, {
|
|
310
|
+
dockerEnv: {},
|
|
311
|
+
ports,
|
|
312
|
+
mappings,
|
|
313
|
+
});
|
|
314
|
+
secrets = rewriteDatabaseUrlForTests(secrets);
|
|
315
|
+
|
|
316
|
+
// Postgres port unchanged, but _test suffix added
|
|
317
|
+
expect(secrets.DATABASE_URL).toBe(
|
|
318
|
+
'postgresql://app:secret@localhost:5432/myapp_test',
|
|
319
|
+
);
|
|
320
|
+
// RabbitMQ port rewritten
|
|
321
|
+
expect(secrets.RABBITMQ_URL).toBe('amqp://app:secret@localhost:5673');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
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 {
|
|
21
|
+
getDependencyEnvVars,
|
|
22
|
+
normalizeWorkspace,
|
|
23
|
+
} from '../../workspace/index';
|
|
24
|
+
import {
|
|
25
|
+
COMPOSE_FULL,
|
|
26
|
+
createFullstackSecrets,
|
|
27
|
+
createFullstackWorkspace,
|
|
28
|
+
mapSecretsForApp,
|
|
29
|
+
} from './__fixtures__/workspace';
|
|
30
|
+
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterAll(() => {
|
|
36
|
+
vi.restoreAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('web (frontend) app context', () => {
|
|
40
|
+
let testDir: string;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
testDir = join(tmpdir(), `gkm-web-test-${Date.now()}`);
|
|
44
|
+
mkdirSync(testDir, { recursive: true });
|
|
45
|
+
writeFileSync(join(testDir, 'docker-compose.yml'), COMPOSE_FULL);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should inject both NEXT_PUBLIC_ and server-side dependency URLs', () => {
|
|
53
|
+
const workspace = createFullstackWorkspace();
|
|
54
|
+
const depEnv = getDependencyEnvVars(workspace, 'web');
|
|
55
|
+
|
|
56
|
+
expect(depEnv).toEqual({
|
|
57
|
+
API_URL: 'http://localhost:3000',
|
|
58
|
+
NEXT_PUBLIC_API_URL: 'http://localhost:3000',
|
|
59
|
+
AUTH_URL: 'http://localhost:3002',
|
|
60
|
+
NEXT_PUBLIC_AUTH_URL: 'http://localhost:3002',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should not rewrite dependency URLs with docker ports (app ports are fixed)', async () => {
|
|
65
|
+
await savePortState(testDir, {
|
|
66
|
+
POSTGRES_HOST_PORT: 5433,
|
|
67
|
+
REDIS_HOST_PORT: 6380,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const workspace = createFullstackWorkspace();
|
|
71
|
+
const depEnv = getDependencyEnvVars(workspace, 'web');
|
|
72
|
+
|
|
73
|
+
// Dependency URLs use app ports (3000, 3002), not docker service ports
|
|
74
|
+
// Port rewriting should not affect them
|
|
75
|
+
expect(depEnv.API_URL).toBe('http://localhost:3000');
|
|
76
|
+
expect(depEnv.AUTH_URL).toBe('http://localhost:3002');
|
|
77
|
+
expect(depEnv.NEXT_PUBLIC_API_URL).toBe('http://localhost:3000');
|
|
78
|
+
expect(depEnv.NEXT_PUBLIC_AUTH_URL).toBe('http://localhost:3002');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should not have DATABASE_URL (frontend does not access database)', () => {
|
|
82
|
+
// Frontend gets secrets mapped without an app-specific DATABASE_URL
|
|
83
|
+
const secrets = mapSecretsForApp(createFullstackSecrets(), 'web');
|
|
84
|
+
|
|
85
|
+
// No WEB_DATABASE_URL exists, so DATABASE_URL stays as default (api's)
|
|
86
|
+
// In practice, frontend apps don't use DATABASE_URL at all
|
|
87
|
+
const workspace = createFullstackWorkspace();
|
|
88
|
+
const depEnv = getDependencyEnvVars(workspace, 'web');
|
|
89
|
+
|
|
90
|
+
// depEnv should not contain DATABASE_URL
|
|
91
|
+
expect(depEnv.DATABASE_URL).toBeUndefined();
|
|
92
|
+
expect(depEnv.NEXT_PUBLIC_DATABASE_URL).toBeUndefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle workspace with single dependency', () => {
|
|
96
|
+
const workspace = normalizeWorkspace(
|
|
97
|
+
{
|
|
98
|
+
apps: {
|
|
99
|
+
api: {
|
|
100
|
+
type: 'backend',
|
|
101
|
+
path: 'apps/api',
|
|
102
|
+
port: 3000,
|
|
103
|
+
routes: './src/endpoints/**/*.ts',
|
|
104
|
+
dependencies: [],
|
|
105
|
+
},
|
|
106
|
+
web: {
|
|
107
|
+
type: 'frontend',
|
|
108
|
+
path: 'apps/web',
|
|
109
|
+
port: 3001,
|
|
110
|
+
framework: 'nextjs',
|
|
111
|
+
dependencies: ['api'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
'/project',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const depEnv = getDependencyEnvVars(workspace, 'web');
|
|
119
|
+
|
|
120
|
+
expect(depEnv).toEqual({
|
|
121
|
+
API_URL: 'http://localhost:3000',
|
|
122
|
+
NEXT_PUBLIC_API_URL: 'http://localhost:3000',
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle workspace with many dependencies', () => {
|
|
127
|
+
const workspace = normalizeWorkspace(
|
|
128
|
+
{
|
|
129
|
+
apps: {
|
|
130
|
+
api: {
|
|
131
|
+
type: 'backend',
|
|
132
|
+
path: 'apps/api',
|
|
133
|
+
port: 3000,
|
|
134
|
+
routes: './src/endpoints/**/*.ts',
|
|
135
|
+
dependencies: [],
|
|
136
|
+
},
|
|
137
|
+
auth: {
|
|
138
|
+
type: 'backend',
|
|
139
|
+
path: 'apps/auth',
|
|
140
|
+
port: 3002,
|
|
141
|
+
entry: './src/index.ts',
|
|
142
|
+
dependencies: [],
|
|
143
|
+
},
|
|
144
|
+
payments: {
|
|
145
|
+
type: 'backend',
|
|
146
|
+
path: 'apps/payments',
|
|
147
|
+
port: 3003,
|
|
148
|
+
routes: './src/endpoints/**/*.ts',
|
|
149
|
+
dependencies: [],
|
|
150
|
+
},
|
|
151
|
+
web: {
|
|
152
|
+
type: 'frontend',
|
|
153
|
+
path: 'apps/web',
|
|
154
|
+
port: 3001,
|
|
155
|
+
framework: 'nextjs',
|
|
156
|
+
dependencies: ['api', 'auth', 'payments'],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
'/project',
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const depEnv = getDependencyEnvVars(workspace, 'web');
|
|
164
|
+
|
|
165
|
+
expect(depEnv).toEqual({
|
|
166
|
+
API_URL: 'http://localhost:3000',
|
|
167
|
+
NEXT_PUBLIC_API_URL: 'http://localhost:3000',
|
|
168
|
+
AUTH_URL: 'http://localhost:3002',
|
|
169
|
+
NEXT_PUBLIC_AUTH_URL: 'http://localhost:3002',
|
|
170
|
+
PAYMENTS_URL: 'http://localhost:3003',
|
|
171
|
+
NEXT_PUBLIC_PAYMENTS_URL: 'http://localhost:3003',
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should not rewrite NEXT_PUBLIC_ URLs via port rewriting', async () => {
|
|
176
|
+
await savePortState(testDir, {
|
|
177
|
+
POSTGRES_HOST_PORT: 5433,
|
|
178
|
+
REDIS_HOST_PORT: 6380,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const workspace = createFullstackWorkspace();
|
|
182
|
+
const depEnv = getDependencyEnvVars(workspace, 'web');
|
|
183
|
+
|
|
184
|
+
// Simulate what gkm exec/test does: merge secrets + dep URLs, then rewrite
|
|
185
|
+
const combined: Record<string, string> = {
|
|
186
|
+
...createFullstackSecrets(),
|
|
187
|
+
...depEnv,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const mappings = parseComposePortMappings(
|
|
191
|
+
join(testDir, 'docker-compose.yml'),
|
|
192
|
+
);
|
|
193
|
+
const ports = await loadPortState(testDir);
|
|
194
|
+
const rewritten = rewriteUrlsWithPorts(combined, {
|
|
195
|
+
dockerEnv: {},
|
|
196
|
+
ports,
|
|
197
|
+
mappings,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Docker service URLs rewritten
|
|
201
|
+
expect(rewritten.DATABASE_URL).toContain(':5433/');
|
|
202
|
+
expect(rewritten.REDIS_URL).toContain(':6380');
|
|
203
|
+
|
|
204
|
+
// App dependency URLs NOT rewritten (3000 and 3002 are not docker ports)
|
|
205
|
+
expect(rewritten.API_URL).toBe('http://localhost:3000');
|
|
206
|
+
expect(rewritten.AUTH_URL).toBe('http://localhost:3002');
|
|
207
|
+
expect(rewritten.NEXT_PUBLIC_API_URL).toBe('http://localhost:3000');
|
|
208
|
+
expect(rewritten.NEXT_PUBLIC_AUTH_URL).toBe('http://localhost:3002');
|
|
209
|
+
});
|
|
210
|
+
});
|