@geekmidas/cli 0.54.0 → 1.0.1
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 +23 -0
- package/README.md +26 -5
- package/dist/CachedStateProvider-D73dCqfH.cjs +60 -0
- package/dist/CachedStateProvider-D73dCqfH.cjs.map +1 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs +54 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs.map +1 -0
- package/dist/CachedStateProvider-D_uISMmJ.cjs +3 -0
- package/dist/CachedStateProvider-OiFUGr7p.mjs +3 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs +210 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs.map +1 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs +210 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs.map +1 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs +43 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs.map +1 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs +42 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs.map +1 -0
- package/dist/Route53Provider-CpRIqu69.cjs +157 -0
- package/dist/Route53Provider-CpRIqu69.cjs.map +1 -0
- package/dist/Route53Provider-KUAX3vz9.mjs +156 -0
- package/dist/Route53Provider-KUAX3vz9.mjs.map +1 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs +53 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs.map +1 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs +52 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs.map +1 -0
- package/dist/{bundler-DGry2vaR.mjs → bundler-BqTN5Dj5.mjs} +3 -3
- package/dist/{bundler-DGry2vaR.mjs.map → bundler-BqTN5Dj5.mjs.map} +1 -1
- package/dist/{bundler-BB-kETMd.cjs → bundler-tHLLwYuU.cjs} +3 -3
- package/dist/{bundler-BB-kETMd.cjs.map → bundler-tHLLwYuU.cjs.map} +1 -1
- package/dist/{config-HYiM3iQJ.cjs → config-BGeJsW1r.cjs} +2 -2
- package/dist/{config-HYiM3iQJ.cjs.map → config-BGeJsW1r.cjs.map} +1 -1
- package/dist/{config-C3LSBNSl.mjs → config-C6awcFBx.mjs} +2 -2
- package/dist/{config-C3LSBNSl.mjs.map → config-C6awcFBx.mjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/credentials-C8DWtnMY.cjs +174 -0
- package/dist/credentials-C8DWtnMY.cjs.map +1 -0
- package/dist/credentials-DT1dSxIx.mjs +126 -0
- package/dist/credentials-DT1dSxIx.mjs.map +1 -0
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs → dokploy-api-7k3t7_zd.mjs} +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs.map → dokploy-api-7k3t7_zd.mjs.map} +1 -1
- package/dist/dokploy-api-CHa8G51l.mjs +3 -0
- package/dist/{dokploy-api-YD8WCQfW.cjs → dokploy-api-CQvhV6Hd.cjs} +1 -1
- package/dist/{dokploy-api-YD8WCQfW.cjs.map → dokploy-api-CQvhV6Hd.cjs.map} +1 -1
- package/dist/dokploy-api-CWc02yyg.cjs +3 -0
- package/dist/{encryption-DaCB_NmS.cjs → encryption-BE0UOb8j.cjs} +1 -1
- package/dist/{encryption-DaCB_NmS.cjs.map → encryption-BE0UOb8j.cjs.map} +1 -1
- package/dist/{encryption-Biq0EZ4m.cjs → encryption-Cv3zips0.cjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs → encryption-JtMsiGNp.mjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
- package/dist/encryption-UUmaWAmz.mjs +3 -0
- package/dist/{index-pOA56MWT.d.cts → index-B5rGIc4g.d.cts} +553 -196
- package/dist/index-B5rGIc4g.d.cts.map +1 -0
- package/dist/{index-A70abJ1m.d.mts → index-KFEbMIRa.d.mts} +554 -197
- package/dist/index-KFEbMIRa.d.mts.map +1 -0
- package/dist/index.cjs +2265 -658
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2242 -635
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-C3C-BzIZ.mjs → openapi-BMFmLnX6.mjs} +51 -7
- package/dist/openapi-BMFmLnX6.mjs.map +1 -0
- package/dist/{openapi-D7WwlpPF.cjs → openapi-D1KXv2Ml.cjs} +51 -7
- package/dist/openapi-D1KXv2Ml.cjs.map +1 -0
- package/dist/{openapi-react-query-C_MxpBgF.cjs → openapi-react-query-BeXvk-wa.cjs} +1 -1
- package/dist/{openapi-react-query-C_MxpBgF.cjs.map → openapi-react-query-BeXvk-wa.cjs.map} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs → openapi-react-query-DGEkD39r.mjs} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs.map → openapi-react-query-DGEkD39r.mjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +2 -2
- package/dist/openapi.mjs +3 -3
- package/dist/{storage-Dhst7BhI.mjs → storage-BMW6yLu3.mjs} +1 -1
- package/dist/{storage-Dhst7BhI.mjs.map → storage-BMW6yLu3.mjs.map} +1 -1
- package/dist/{storage-fOR8dMu5.cjs → storage-C7pmBq1u.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs → storage-CoCNe0Pt.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs.map → storage-CoCNe0Pt.cjs.map} +1 -1
- package/dist/{storage-DNj_I11J.mjs → storage-D8XzjVaO.mjs} +1 -1
- package/dist/{types-BtGL-8QS.d.mts → types-BldpmqQX.d.mts} +1 -1
- package/dist/{types-BtGL-8QS.d.mts.map → types-BldpmqQX.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-CaVW6j2q.cjs → workspace-BFRUOOrh.cjs} +309 -25
- package/dist/workspace-BFRUOOrh.cjs.map +1 -0
- package/dist/{workspace-DLFRaDc-.mjs → workspace-DAxG3_H2.mjs} +309 -25
- package/dist/workspace-DAxG3_H2.mjs.map +1 -0
- package/package.json +14 -8
- package/scripts/sync-versions.ts +86 -0
- package/src/build/__tests__/handler-templates.spec.ts +115 -47
- package/src/deploy/CachedStateProvider.ts +86 -0
- package/src/deploy/LocalStateProvider.ts +57 -0
- package/src/deploy/SSMStateProvider.ts +93 -0
- package/src/deploy/StateProvider.ts +171 -0
- package/src/deploy/__tests__/CachedStateProvider.spec.ts +228 -0
- package/src/deploy/__tests__/HostingerProvider.spec.ts +347 -0
- package/src/deploy/__tests__/LocalStateProvider.spec.ts +126 -0
- package/src/deploy/__tests__/Route53Provider.spec.ts +402 -0
- package/src/deploy/__tests__/SSMStateProvider.spec.ts +177 -0
- package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +1 -3
- package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +28 -19
- package/src/deploy/__tests__/createDnsProvider.spec.ts +172 -0
- package/src/deploy/__tests__/createStateProvider.spec.ts +116 -0
- package/src/deploy/__tests__/dns-orchestration.spec.ts +192 -0
- package/src/deploy/__tests__/dns-verification.spec.ts +2 -2
- package/src/deploy/__tests__/env-resolver.spec.ts +37 -15
- package/src/deploy/__tests__/sniffer.spec.ts +4 -20
- package/src/deploy/__tests__/state.spec.ts +13 -5
- package/src/deploy/dns/DnsProvider.ts +163 -0
- package/src/deploy/dns/HostingerProvider.ts +100 -0
- package/src/deploy/dns/Route53Provider.ts +256 -0
- package/src/deploy/dns/index.ts +257 -165
- package/src/deploy/env-resolver.ts +12 -5
- package/src/deploy/index.ts +16 -13
- package/src/deploy/sniffer-envkit-patch.ts +3 -1
- package/src/deploy/sniffer-routes-worker.ts +104 -0
- package/src/deploy/sniffer.ts +77 -55
- package/src/deploy/state-commands.ts +274 -0
- package/src/dev/__tests__/entry.spec.ts +8 -2
- package/src/dev/__tests__/index.spec.ts +1 -3
- package/src/dev/index.ts +9 -3
- package/src/docker/__tests__/templates.spec.ts +3 -1
- package/src/index.ts +88 -0
- package/src/init/__tests__/generators.spec.ts +273 -0
- package/src/init/__tests__/init.spec.ts +3 -3
- package/src/init/generators/auth.ts +1 -0
- package/src/init/generators/config.ts +2 -0
- package/src/init/generators/models.ts +6 -1
- package/src/init/generators/monorepo.ts +3 -0
- package/src/init/generators/ui.ts +1472 -0
- package/src/init/generators/web.ts +134 -87
- package/src/init/index.ts +22 -3
- package/src/init/templates/api.ts +109 -3
- package/src/init/versions.ts +25 -53
- package/src/openapi.ts +99 -13
- package/src/workspace/__tests__/schema.spec.ts +107 -0
- package/src/workspace/schema.ts +314 -4
- package/src/workspace/types.ts +22 -36
- package/dist/dokploy-api-CItuaWTq.mjs +0 -3
- package/dist/dokploy-api-DBNE8MDt.cjs +0 -3
- package/dist/encryption-CQXBZGkt.mjs +0 -3
- package/dist/index-A70abJ1m.d.mts.map +0 -1
- package/dist/index-pOA56MWT.d.cts.map +0 -1
- package/dist/openapi-C3C-BzIZ.mjs.map +0 -1
- package/dist/openapi-D7WwlpPF.cjs.map +0 -1
- package/dist/workspace-CaVW6j2q.cjs.map +0 -1
- package/dist/workspace-DLFRaDc-.mjs.map +0 -1
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subprocess worker for route-based app sniffing.
|
|
3
|
+
*
|
|
4
|
+
* This script is executed in a subprocess with tsx loader,
|
|
5
|
+
* which handles TypeScript compilation and path alias resolution.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node --import tsx ./sniffer-routes-worker.ts "/path/to/app" "./src/endpoints/**\/*.ts"
|
|
9
|
+
*
|
|
10
|
+
* Output (JSON to stdout):
|
|
11
|
+
* { "envVars": ["PORT", "DATABASE_URL", ...], "error": null }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Construct } from '@geekmidas/constructs';
|
|
15
|
+
import { Cron } from '@geekmidas/constructs/crons';
|
|
16
|
+
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
17
|
+
import { Function as GkmFunction } from '@geekmidas/constructs/functions';
|
|
18
|
+
import { Subscriber } from '@geekmidas/constructs/subscribers';
|
|
19
|
+
import fg from 'fast-glob';
|
|
20
|
+
|
|
21
|
+
// Get args from command line
|
|
22
|
+
const appPathArg = process.argv[2];
|
|
23
|
+
const routesPatternArg = process.argv[3];
|
|
24
|
+
|
|
25
|
+
if (!appPathArg || !routesPatternArg) {
|
|
26
|
+
console.log(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
envVars: [],
|
|
29
|
+
error: 'App path and routes pattern are required',
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// After validation, these are guaranteed to be strings
|
|
36
|
+
const appPath: string = appPathArg;
|
|
37
|
+
const routesPattern: string = routesPatternArg;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if a value is a gkm construct.
|
|
41
|
+
*/
|
|
42
|
+
function isConstruct(value: unknown): value is Construct {
|
|
43
|
+
return (
|
|
44
|
+
Endpoint.isEndpoint(value) ||
|
|
45
|
+
GkmFunction.isFunction(value) ||
|
|
46
|
+
Cron.isCron(value) ||
|
|
47
|
+
Subscriber.isSubscriber(value)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Main sniffing function
|
|
53
|
+
*/
|
|
54
|
+
async function sniff(): Promise<void> {
|
|
55
|
+
const envVars = new Set<string>();
|
|
56
|
+
let error: string | null = null;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Find all route files matching the pattern
|
|
60
|
+
const files = await fg(routesPattern, {
|
|
61
|
+
cwd: appPath,
|
|
62
|
+
absolute: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Import each route file and find constructs
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
try {
|
|
68
|
+
const module = await import(file);
|
|
69
|
+
|
|
70
|
+
// Check all exports for constructs
|
|
71
|
+
for (const [, exportValue] of Object.entries(module)) {
|
|
72
|
+
if (isConstruct(exportValue)) {
|
|
73
|
+
try {
|
|
74
|
+
const constructEnvVars = await exportValue.getEnvironment();
|
|
75
|
+
constructEnvVars.forEach((v) => envVars.add(v));
|
|
76
|
+
} catch {
|
|
77
|
+
// Individual construct may fail, continue with others
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// Log import errors but continue with other files
|
|
83
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
84
|
+
console.error(`[sniffer] Failed to import ${file}: ${msg}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {
|
|
88
|
+
error = e instanceof Error ? e.message : String(e);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Output result as JSON (last line of stdout)
|
|
92
|
+
console.log(JSON.stringify({ envVars: Array.from(envVars).sort(), error }));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle unhandled rejections
|
|
96
|
+
process.on('unhandledRejection', () => {
|
|
97
|
+
// Silently ignore - we only care about env var capture
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Run the sniffer
|
|
101
|
+
sniff().catch((e) => {
|
|
102
|
+
console.log(JSON.stringify({ envVars: [], error: e.message || String(e) }));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
package/src/deploy/sniffer.ts
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { dirname, resolve } from 'node:path';
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
-
import type { Construct } from '@geekmidas/constructs';
|
|
6
|
-
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
7
|
-
import { Function as GkmFunction } from '@geekmidas/constructs/functions';
|
|
8
|
-
import { Cron } from '@geekmidas/constructs/crons';
|
|
9
|
-
import { Subscriber } from '@geekmidas/constructs/subscribers';
|
|
10
6
|
import type { SniffResult } from '@geekmidas/envkit/sniffer';
|
|
11
|
-
import fg from 'fast-glob';
|
|
12
7
|
import type { NormalizedAppConfig } from '../workspace/types.js';
|
|
13
8
|
|
|
14
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
10
|
const __dirname = dirname(__filename);
|
|
16
11
|
|
|
17
12
|
/**
|
|
18
|
-
*
|
|
13
|
+
* Resolve the tsx package path from the CLI package's dependencies.
|
|
14
|
+
* This ensures tsx is available regardless of whether the target project has it installed.
|
|
19
15
|
*/
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
GkmFunction.isFunction(value) ||
|
|
24
|
-
Cron.isCron(value) ||
|
|
25
|
-
Subscriber.isSubscriber(value)
|
|
26
|
-
);
|
|
16
|
+
function resolveTsxPath(): string {
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
return require.resolve('tsx');
|
|
27
19
|
}
|
|
28
20
|
|
|
29
21
|
/**
|
|
@@ -269,7 +261,9 @@ async function sniffEntryFile(
|
|
|
269
261
|
*
|
|
270
262
|
* Route-based apps have endpoints, functions, crons, and subscribers that
|
|
271
263
|
* use services. Each service's register() method accesses environment variables.
|
|
272
|
-
*
|
|
264
|
+
*
|
|
265
|
+
* This runs in a subprocess with tsx loader to properly handle TypeScript
|
|
266
|
+
* compilation and path alias resolution (e.g., `src/...` imports).
|
|
273
267
|
*
|
|
274
268
|
* @param routes - Glob pattern(s) for route files
|
|
275
269
|
* @param appPath - The app's path relative to workspace (e.g., 'apps/api')
|
|
@@ -282,53 +276,81 @@ async function sniffRouteFiles(
|
|
|
282
276
|
workspacePath: string,
|
|
283
277
|
): Promise<EntrySniffResult> {
|
|
284
278
|
const fullAppPath = resolve(workspacePath, appPath);
|
|
285
|
-
const
|
|
279
|
+
const workerPath = resolveSnifferFile('sniffer-routes-worker');
|
|
280
|
+
const tsxPath = resolveTsxPath();
|
|
281
|
+
|
|
282
|
+
// Convert array of patterns to first pattern (worker handles glob internally)
|
|
283
|
+
const routesArray = Array.isArray(routes) ? routes : [routes];
|
|
284
|
+
const pattern = routesArray[0];
|
|
285
|
+
if (!pattern) {
|
|
286
|
+
return { envVars: [], error: new Error('No route patterns provided') };
|
|
287
|
+
}
|
|
286
288
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
return new Promise((resolvePromise) => {
|
|
290
|
+
const child = spawn(
|
|
291
|
+
'node',
|
|
292
|
+
['--import', tsxPath, workerPath, fullAppPath, pattern],
|
|
293
|
+
{
|
|
294
|
+
cwd: fullAppPath,
|
|
295
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
296
|
+
env: {
|
|
297
|
+
...process.env,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
);
|
|
289
301
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
302
|
+
let stdout = '';
|
|
303
|
+
let stderr = '';
|
|
304
|
+
|
|
305
|
+
child.stdout.on('data', (data) => {
|
|
306
|
+
stdout += data.toString();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
child.stderr.on('data', (data) => {
|
|
310
|
+
stderr += data.toString();
|
|
295
311
|
});
|
|
296
312
|
|
|
297
|
-
|
|
298
|
-
|
|
313
|
+
child.on('close', (code) => {
|
|
314
|
+
// Log any stderr output (import errors, etc.)
|
|
315
|
+
if (stderr) {
|
|
316
|
+
stderr
|
|
317
|
+
.split('\n')
|
|
318
|
+
.filter((line) => line.trim())
|
|
319
|
+
.forEach((line) => console.warn(line));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Try to parse the JSON output from the worker
|
|
299
323
|
try {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
} catch (e) {
|
|
310
|
-
// Individual construct may fail, continue with others
|
|
311
|
-
console.warn(
|
|
312
|
-
`[sniffer] Failed to get environment for construct in ${file}: ${e instanceof Error ? e.message : String(e)}`,
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
324
|
+
// Find the last JSON object in stdout (worker may emit other output)
|
|
325
|
+
const jsonMatch = stdout.match(/\{[^{}]*"envVars"[^{}]*\}[^{]*$/);
|
|
326
|
+
if (jsonMatch) {
|
|
327
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
328
|
+
resolvePromise({
|
|
329
|
+
envVars: result.envVars || [],
|
|
330
|
+
error: result.error ? new Error(result.error) : undefined,
|
|
331
|
+
});
|
|
332
|
+
return;
|
|
316
333
|
}
|
|
317
|
-
} catch
|
|
318
|
-
//
|
|
319
|
-
console.warn(
|
|
320
|
-
`[sniffer] Failed to import ${file}: ${e instanceof Error ? e.message : String(e)}`,
|
|
321
|
-
);
|
|
334
|
+
} catch {
|
|
335
|
+
// JSON parse failed
|
|
322
336
|
}
|
|
323
|
-
}
|
|
324
|
-
} catch (e) {
|
|
325
|
-
error = e instanceof Error ? e : new Error(String(e));
|
|
326
|
-
}
|
|
327
337
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
338
|
+
// If we couldn't parse the output, return empty with error info
|
|
339
|
+
resolvePromise({
|
|
340
|
+
envVars: [],
|
|
341
|
+
error: new Error(
|
|
342
|
+
`Failed to sniff route files (exit code ${code}): ${stderr || stdout || 'No output'}`,
|
|
343
|
+
),
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
child.on('error', (err) => {
|
|
348
|
+
resolvePromise({
|
|
349
|
+
envVars: [],
|
|
350
|
+
error: err,
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
332
354
|
}
|
|
333
355
|
|
|
334
356
|
/**
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Management CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for managing deployment state across local and remote providers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { loadWorkspaceConfig } from '../config';
|
|
8
|
+
import { CachedStateProvider } from './CachedStateProvider';
|
|
9
|
+
import { createStateProvider } from './StateProvider';
|
|
10
|
+
import type { DokployStageState } from './state';
|
|
11
|
+
|
|
12
|
+
export interface StateCommandOptions {
|
|
13
|
+
stage: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pull state from remote to local.
|
|
18
|
+
* `gkm state:pull --stage=<stage>`
|
|
19
|
+
*/
|
|
20
|
+
export async function statePullCommand(
|
|
21
|
+
options: StateCommandOptions,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
24
|
+
|
|
25
|
+
if (!workspace.state || workspace.state.provider === 'local') {
|
|
26
|
+
console.error('No remote state provider configured.');
|
|
27
|
+
console.error('Add a remote provider in gkm.config.ts:');
|
|
28
|
+
console.error(' state: { provider: "ssm", region: "us-east-1" }');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const provider = await createStateProvider({
|
|
33
|
+
config: workspace.state,
|
|
34
|
+
workspaceRoot: workspace.root,
|
|
35
|
+
workspaceName: workspace.name,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!(provider instanceof CachedStateProvider)) {
|
|
39
|
+
console.error('State provider does not support pull operation.');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`Pulling state for stage: ${options.stage}...`);
|
|
44
|
+
const state = await provider.pull(options.stage);
|
|
45
|
+
|
|
46
|
+
if (state) {
|
|
47
|
+
console.log('State pulled successfully.');
|
|
48
|
+
printStateSummary(state);
|
|
49
|
+
} else {
|
|
50
|
+
console.log('No remote state found for this stage.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Push local state to remote.
|
|
56
|
+
* `gkm state:push --stage=<stage>`
|
|
57
|
+
*/
|
|
58
|
+
export async function statePushCommand(
|
|
59
|
+
options: StateCommandOptions,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
62
|
+
|
|
63
|
+
if (!workspace.state || workspace.state.provider === 'local') {
|
|
64
|
+
console.error('No remote state provider configured.');
|
|
65
|
+
console.error('Add a remote provider in gkm.config.ts:');
|
|
66
|
+
console.error(' state: { provider: "ssm", region: "us-east-1" }');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const provider = await createStateProvider({
|
|
71
|
+
config: workspace.state,
|
|
72
|
+
workspaceRoot: workspace.root,
|
|
73
|
+
workspaceName: workspace.name,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!(provider instanceof CachedStateProvider)) {
|
|
77
|
+
console.error('State provider does not support push operation.');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(`Pushing state for stage: ${options.stage}...`);
|
|
82
|
+
const state = await provider.push(options.stage);
|
|
83
|
+
|
|
84
|
+
if (state) {
|
|
85
|
+
console.log('State pushed successfully.');
|
|
86
|
+
printStateSummary(state);
|
|
87
|
+
} else {
|
|
88
|
+
console.log('No local state found for this stage.');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Show current state.
|
|
94
|
+
* `gkm state:show --stage=<stage>`
|
|
95
|
+
*/
|
|
96
|
+
export async function stateShowCommand(
|
|
97
|
+
options: StateCommandOptions & { json?: boolean },
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
100
|
+
|
|
101
|
+
const provider = await createStateProvider({
|
|
102
|
+
config: workspace.state,
|
|
103
|
+
workspaceRoot: workspace.root,
|
|
104
|
+
workspaceName: workspace.name,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const state = await provider.read(options.stage);
|
|
108
|
+
|
|
109
|
+
if (!state) {
|
|
110
|
+
console.log(`No state found for stage: ${options.stage}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (options.json) {
|
|
115
|
+
console.log(JSON.stringify(state, null, 2));
|
|
116
|
+
} else {
|
|
117
|
+
printStateDetails(state);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compare local and remote state.
|
|
123
|
+
* `gkm state:diff --stage=<stage>`
|
|
124
|
+
*/
|
|
125
|
+
export async function stateDiffCommand(
|
|
126
|
+
options: StateCommandOptions,
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
129
|
+
|
|
130
|
+
if (!workspace.state || workspace.state.provider === 'local') {
|
|
131
|
+
console.error('No remote state provider configured.');
|
|
132
|
+
console.error('Diff requires a remote provider to compare against.');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const provider = await createStateProvider({
|
|
137
|
+
config: workspace.state,
|
|
138
|
+
workspaceRoot: workspace.root,
|
|
139
|
+
workspaceName: workspace.name,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!(provider instanceof CachedStateProvider)) {
|
|
143
|
+
console.error('State provider does not support diff operation.');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`Comparing state for stage: ${options.stage}...\n`);
|
|
148
|
+
const { local, remote } = await provider.diff(options.stage);
|
|
149
|
+
|
|
150
|
+
if (!local && !remote) {
|
|
151
|
+
console.log('No state found (local or remote).');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!local) {
|
|
156
|
+
console.log('Local: (none)');
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`Local: Last deployed ${local.lastDeployedAt}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!remote) {
|
|
162
|
+
console.log('Remote: (none)');
|
|
163
|
+
} else {
|
|
164
|
+
console.log(`Remote: Last deployed ${remote.lastDeployedAt}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log('');
|
|
168
|
+
|
|
169
|
+
// Compare applications
|
|
170
|
+
const localApps = local?.applications ?? {};
|
|
171
|
+
const remoteApps = remote?.applications ?? {};
|
|
172
|
+
const allApps = new Set([
|
|
173
|
+
...Object.keys(localApps),
|
|
174
|
+
...Object.keys(remoteApps),
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
if (allApps.size > 0) {
|
|
178
|
+
console.log('Applications:');
|
|
179
|
+
for (const app of allApps) {
|
|
180
|
+
const localId = localApps[app];
|
|
181
|
+
const remoteId = remoteApps[app];
|
|
182
|
+
|
|
183
|
+
if (localId === remoteId) {
|
|
184
|
+
console.log(` ${app}: ${localId ?? '(none)'}`);
|
|
185
|
+
} else if (!localId) {
|
|
186
|
+
console.log(` ${app}: (none) -> ${remoteId} [REMOTE ONLY]`);
|
|
187
|
+
} else if (!remoteId) {
|
|
188
|
+
console.log(` ${app}: ${localId} -> (none) [LOCAL ONLY]`);
|
|
189
|
+
} else {
|
|
190
|
+
console.log(
|
|
191
|
+
` ${app}: ${localId} (local) != ${remoteId} (remote) [MISMATCH]`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Compare services
|
|
198
|
+
const localServices = local?.services ?? {};
|
|
199
|
+
const remoteServices = remote?.services ?? {};
|
|
200
|
+
|
|
201
|
+
if (
|
|
202
|
+
Object.keys(localServices).length > 0 ||
|
|
203
|
+
Object.keys(remoteServices).length > 0
|
|
204
|
+
) {
|
|
205
|
+
console.log('\nServices:');
|
|
206
|
+
const serviceKeys = new Set([
|
|
207
|
+
...Object.keys(localServices),
|
|
208
|
+
...Object.keys(remoteServices),
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
for (const key of serviceKeys) {
|
|
212
|
+
const localVal = localServices[key as keyof typeof localServices];
|
|
213
|
+
const remoteVal = remoteServices[key as keyof typeof remoteServices];
|
|
214
|
+
|
|
215
|
+
if (localVal === remoteVal) {
|
|
216
|
+
console.log(` ${key}: ${localVal ?? '(none)'}`);
|
|
217
|
+
} else {
|
|
218
|
+
console.log(
|
|
219
|
+
` ${key}: ${localVal ?? '(none)'} (local) != ${remoteVal ?? '(none)'} (remote)`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function printStateSummary(state: DokployStageState): void {
|
|
227
|
+
const appCount = Object.keys(state.applications).length;
|
|
228
|
+
const hasPostgres = !!state.services.postgresId;
|
|
229
|
+
const hasRedis = !!state.services.redisId;
|
|
230
|
+
|
|
231
|
+
console.log(` Stage: ${state.stage}`);
|
|
232
|
+
console.log(` Applications: ${appCount}`);
|
|
233
|
+
console.log(` Postgres: ${hasPostgres ? 'configured' : 'none'}`);
|
|
234
|
+
console.log(` Redis: ${hasRedis ? 'configured' : 'none'}`);
|
|
235
|
+
console.log(` Last deployed: ${state.lastDeployedAt}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function printStateDetails(state: DokployStageState): void {
|
|
239
|
+
console.log(`Stage: ${state.stage}`);
|
|
240
|
+
console.log(`Environment ID: ${state.environmentId}`);
|
|
241
|
+
console.log(`Last Deployed: ${state.lastDeployedAt}`);
|
|
242
|
+
console.log('');
|
|
243
|
+
|
|
244
|
+
console.log('Applications:');
|
|
245
|
+
const apps = Object.entries(state.applications);
|
|
246
|
+
if (apps.length === 0) {
|
|
247
|
+
console.log(' (none)');
|
|
248
|
+
} else {
|
|
249
|
+
for (const [name, id] of apps) {
|
|
250
|
+
console.log(` ${name}: ${id}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
console.log('');
|
|
254
|
+
|
|
255
|
+
console.log('Services:');
|
|
256
|
+
if (!state.services.postgresId && !state.services.redisId) {
|
|
257
|
+
console.log(' (none)');
|
|
258
|
+
} else {
|
|
259
|
+
if (state.services.postgresId) {
|
|
260
|
+
console.log(` Postgres: ${state.services.postgresId}`);
|
|
261
|
+
}
|
|
262
|
+
if (state.services.redisId) {
|
|
263
|
+
console.log(` Redis: ${state.services.redisId}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (state.dnsVerified && Object.keys(state.dnsVerified).length > 0) {
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log('DNS Verified:');
|
|
270
|
+
for (const [hostname, info] of Object.entries(state.dnsVerified)) {
|
|
271
|
+
console.log(` ${hostname}: ${info.serverIp} (${info.verifiedAt})`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -8,7 +8,10 @@ describe('findSecretsRoot', () => {
|
|
|
8
8
|
let testDir: string;
|
|
9
9
|
|
|
10
10
|
beforeEach(async () => {
|
|
11
|
-
testDir = join(
|
|
11
|
+
testDir = join(
|
|
12
|
+
tmpdir(),
|
|
13
|
+
`gkm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
14
|
+
);
|
|
12
15
|
await mkdir(testDir, { recursive: true });
|
|
13
16
|
});
|
|
14
17
|
|
|
@@ -61,7 +64,10 @@ describe('createEntryWrapper', () => {
|
|
|
61
64
|
let testDir: string;
|
|
62
65
|
|
|
63
66
|
beforeEach(async () => {
|
|
64
|
-
testDir = join(
|
|
67
|
+
testDir = join(
|
|
68
|
+
tmpdir(),
|
|
69
|
+
`gkm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
70
|
+
);
|
|
65
71
|
await mkdir(testDir, { recursive: true });
|
|
66
72
|
});
|
|
67
73
|
|
|
@@ -1049,9 +1049,7 @@ describe('loadSecretsForApp', () => {
|
|
|
1049
1049
|
|
|
1050
1050
|
const secrets = await loadSecretsForApp(testDir);
|
|
1051
1051
|
|
|
1052
|
-
expect(secrets.DATABASE_URL).toBe(
|
|
1053
|
-
'postgresql://localhost/developmentdb',
|
|
1054
|
-
);
|
|
1052
|
+
expect(secrets.DATABASE_URL).toBe('postgresql://localhost/developmentdb');
|
|
1055
1053
|
});
|
|
1056
1054
|
|
|
1057
1055
|
it('should return empty object if no secrets exist', async () => {
|
package/src/dev/index.ts
CHANGED
|
@@ -340,7 +340,9 @@ export async function devCommand(options: DevOptions): Promise<void> {
|
|
|
340
340
|
secretsRoot = appConfig.workspaceRoot;
|
|
341
341
|
workspaceAppName = appConfig.appName;
|
|
342
342
|
workspaceAppPort = appConfig.app.port;
|
|
343
|
-
logger.log(
|
|
343
|
+
logger.log(
|
|
344
|
+
`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`,
|
|
345
|
+
);
|
|
344
346
|
|
|
345
347
|
// Check if app has an entry point (non-gkm app like better-auth)
|
|
346
348
|
if (appConfig.app.entry) {
|
|
@@ -1387,7 +1389,9 @@ export async function prepareEntryCredentials(options: {
|
|
|
1387
1389
|
appName = appConfig.appName;
|
|
1388
1390
|
} catch (error) {
|
|
1389
1391
|
// Not in a workspace - use defaults
|
|
1390
|
-
logger.log(
|
|
1392
|
+
logger.log(
|
|
1393
|
+
`⚠️ Could not load workspace config: ${(error as Error).message}`,
|
|
1394
|
+
);
|
|
1391
1395
|
secretsRoot = findSecretsRoot(cwd);
|
|
1392
1396
|
appName = getAppNameFromCwd(cwd) ?? undefined;
|
|
1393
1397
|
}
|
|
@@ -1457,7 +1461,9 @@ async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
|
1457
1461
|
logger.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1458
1462
|
|
|
1459
1463
|
if (Object.keys(credentials).length > 1) {
|
|
1460
|
-
logger.log(
|
|
1464
|
+
logger.log(
|
|
1465
|
+
`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`,
|
|
1466
|
+
);
|
|
1461
1467
|
}
|
|
1462
1468
|
|
|
1463
1469
|
// Create wrapper entry that injects secrets before importing user's file
|
|
@@ -614,7 +614,9 @@ describe('docker templates', () => {
|
|
|
614
614
|
const dockerfile = generateEntryDockerfile(baseOptions);
|
|
615
615
|
|
|
616
616
|
expect(dockerfile).toContain('import { createRequire } from "module"');
|
|
617
|
-
expect(dockerfile).toContain(
|
|
617
|
+
expect(dockerfile).toContain(
|
|
618
|
+
'const require = createRequire(import.meta.url)',
|
|
619
|
+
);
|
|
618
620
|
});
|
|
619
621
|
|
|
620
622
|
it('should output to dist/index.mjs', () => {
|