@geekmidas/cli 0.39.0 → 0.40.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/dist/{bundler-DQIuE3Kn.mjs → bundler-Db83tLti.mjs} +2 -2
- package/dist/{bundler-DQIuE3Kn.mjs.map → bundler-Db83tLti.mjs.map} +1 -1
- package/dist/{bundler-CyHg1v_T.cjs → bundler-DsXfFSCU.cjs} +2 -2
- package/dist/{bundler-CyHg1v_T.cjs.map → bundler-DsXfFSCU.cjs.map} +1 -1
- package/dist/{config-BC5n1a2D.mjs → config-C0b0jdmU.mjs} +2 -2
- package/dist/{config-BC5n1a2D.mjs.map → config-C0b0jdmU.mjs.map} +1 -1
- package/dist/{config-BAE9LFC1.cjs → config-xVZsRjN7.cjs} +2 -2
- package/dist/{config-BAE9LFC1.cjs.map → config-xVZsRjN7.cjs.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/dokploy-api-Bdmk5ImW.cjs +3 -0
- package/dist/{dokploy-api-C5czOZoc.cjs → dokploy-api-BdxOMH_V.cjs} +43 -1
- package/dist/{dokploy-api-C5czOZoc.cjs.map → dokploy-api-BdxOMH_V.cjs.map} +1 -1
- package/dist/{dokploy-api-B9qR2Yn1.mjs → dokploy-api-DWsqNjwP.mjs} +43 -1
- package/dist/{dokploy-api-B9qR2Yn1.mjs.map → dokploy-api-DWsqNjwP.mjs.map} +1 -1
- package/dist/dokploy-api-tZSZaHd9.mjs +3 -0
- package/dist/{encryption-JtMsiGNp.mjs → encryption-BC4MAODn.mjs} +1 -1
- package/dist/{encryption-JtMsiGNp.mjs.map → encryption-BC4MAODn.mjs.map} +1 -1
- package/dist/encryption-Biq0EZ4m.cjs +4 -0
- package/dist/encryption-CQXBZGkt.mjs +3 -0
- package/dist/{encryption-BAz0xQ1Q.cjs → encryption-DaCB_NmS.cjs} +13 -3
- package/dist/{encryption-BAz0xQ1Q.cjs.map → encryption-DaCB_NmS.cjs.map} +1 -1
- package/dist/{index-C7TkoYmt.d.mts → index-CXa3odEw.d.mts} +68 -7
- package/dist/index-CXa3odEw.d.mts.map +1 -0
- package/dist/{index-CpchsC9w.d.cts → index-E8Nu2Rxl.d.cts} +67 -6
- package/dist/index-E8Nu2Rxl.d.cts.map +1 -0
- package/dist/index.cjs +674 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +653 -101
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CjYeF-Tg.mjs → openapi-D3pA6FfZ.mjs} +2 -2
- package/dist/{openapi-CjYeF-Tg.mjs.map → openapi-D3pA6FfZ.mjs.map} +1 -1
- package/dist/{openapi-a-e3Y8WA.cjs → openapi-DhcCtKzM.cjs} +2 -2
- package/dist/{openapi-a-e3Y8WA.cjs.map → openapi-DhcCtKzM.cjs.map} +1 -1
- package/dist/{openapi-react-query-DvNpdDpM.cjs → openapi-react-query-C_MxpBgF.cjs} +1 -1
- package/dist/{openapi-react-query-DvNpdDpM.cjs.map → openapi-react-query-C_MxpBgF.cjs.map} +1 -1
- package/dist/{openapi-react-query-5rSortLH.mjs → openapi-react-query-ZoP9DPbY.mjs} +1 -1
- package/dist/{openapi-react-query-5rSortLH.mjs.map → openapi-react-query-ZoP9DPbY.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.mts +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/{types-K2uQJ-FO.d.mts → types-BtGL-8QS.d.mts} +1 -1
- package/dist/{types-K2uQJ-FO.d.mts.map → types-BtGL-8QS.d.mts.map} +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-My0A4IRO.cjs → workspace-BDAhr6Kb.cjs} +33 -4
- package/dist/{workspace-My0A4IRO.cjs.map → workspace-BDAhr6Kb.cjs.map} +1 -1
- package/dist/{workspace-DFJ3sWfY.mjs → workspace-D_6ZCaR_.mjs} +33 -4
- package/dist/{workspace-DFJ3sWfY.mjs.map → workspace-D_6ZCaR_.mjs.map} +1 -1
- package/package.json +5 -5
- package/src/deploy/__tests__/domain.spec.ts +231 -0
- package/src/deploy/__tests__/secrets.spec.ts +300 -0
- package/src/deploy/__tests__/sniffer.spec.ts +221 -0
- package/src/deploy/docker.ts +40 -11
- package/src/deploy/dokploy-api.ts +99 -0
- package/src/deploy/domain.ts +125 -0
- package/src/deploy/index.ts +366 -148
- package/src/deploy/secrets.ts +182 -0
- package/src/deploy/sniffer.ts +180 -0
- package/src/dev/index.ts +11 -0
- package/src/docker/index.ts +17 -2
- package/src/docker/templates.ts +171 -1
- package/src/init/versions.ts +2 -2
- package/src/workspace/index.ts +2 -0
- package/src/workspace/schema.ts +32 -6
- package/src/workspace/types.ts +64 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/dokploy-api-B0w17y4_.mjs +0 -3
- package/dist/dokploy-api-BnGeUqN4.cjs +0 -3
- package/dist/index-C7TkoYmt.d.mts.map +0 -1
- package/dist/index-CpchsC9w.d.cts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
-
const require_workspace = require('./workspace-
|
|
4
|
-
const require_config = require('./config-
|
|
5
|
-
const require_openapi = require('./openapi-
|
|
3
|
+
const require_workspace = require('./workspace-BDAhr6Kb.cjs');
|
|
4
|
+
const require_config = require('./config-xVZsRjN7.cjs');
|
|
5
|
+
const require_openapi = require('./openapi-DhcCtKzM.cjs');
|
|
6
6
|
const require_storage = require('./storage-BPRgh3DU.cjs');
|
|
7
|
-
const require_dokploy_api = require('./dokploy-api-
|
|
8
|
-
const
|
|
7
|
+
const require_dokploy_api = require('./dokploy-api-BdxOMH_V.cjs');
|
|
8
|
+
const require_encryption = require('./encryption-DaCB_NmS.cjs');
|
|
9
|
+
const require_openapi_react_query = require('./openapi-react-query-C_MxpBgF.cjs');
|
|
9
10
|
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
10
11
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
11
12
|
const commander = require_chunk.__toESM(require("commander"));
|
|
@@ -22,12 +23,13 @@ const __geekmidas_constructs_crons = require_chunk.__toESM(require("@geekmidas/c
|
|
|
22
23
|
const __geekmidas_constructs_functions = require_chunk.__toESM(require("@geekmidas/constructs/functions"));
|
|
23
24
|
const __geekmidas_constructs_subscribers = require_chunk.__toESM(require("@geekmidas/constructs/subscribers"));
|
|
24
25
|
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
26
|
+
const node_url = require_chunk.__toESM(require("node:url"));
|
|
25
27
|
const prompts = require_chunk.__toESM(require("prompts"));
|
|
26
28
|
const node_module = require_chunk.__toESM(require("node:module"));
|
|
27
29
|
|
|
28
30
|
//#region package.json
|
|
29
31
|
var name = "@geekmidas/cli";
|
|
30
|
-
var version = "0.
|
|
32
|
+
var version = "0.40.0";
|
|
31
33
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
32
34
|
var private$1 = false;
|
|
33
35
|
var type = "module";
|
|
@@ -226,7 +228,7 @@ const logger$10 = console;
|
|
|
226
228
|
* Validate Dokploy token by making a test API call
|
|
227
229
|
*/
|
|
228
230
|
async function validateDokployToken(endpoint, token) {
|
|
229
|
-
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-
|
|
231
|
+
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-Bdmk5ImW.cjs"));
|
|
230
232
|
const api = new DokployApi$1({
|
|
231
233
|
baseUrl: endpoint,
|
|
232
234
|
token
|
|
@@ -240,7 +242,7 @@ async function prompt$1(message, hidden = false) {
|
|
|
240
242
|
if (!process.stdin.isTTY) throw new Error("Interactive input required. Please provide --token option.");
|
|
241
243
|
if (hidden) {
|
|
242
244
|
process.stdout.write(message);
|
|
243
|
-
return new Promise((resolve$
|
|
245
|
+
return new Promise((resolve$3, reject) => {
|
|
244
246
|
let value = "";
|
|
245
247
|
const cleanup = () => {
|
|
246
248
|
process.stdin.setRawMode(false);
|
|
@@ -257,7 +259,7 @@ async function prompt$1(message, hidden = false) {
|
|
|
257
259
|
if (c === "\n" || c === "\r") {
|
|
258
260
|
cleanup();
|
|
259
261
|
process.stdout.write("\n");
|
|
260
|
-
resolve$
|
|
262
|
+
resolve$3(value);
|
|
261
263
|
} else if (c === "") {
|
|
262
264
|
cleanup();
|
|
263
265
|
process.stdout.write("\n");
|
|
@@ -892,15 +894,15 @@ function loadEnvFiles(envConfig, cwd = process.cwd()) {
|
|
|
892
894
|
* @internal Exported for testing
|
|
893
895
|
*/
|
|
894
896
|
async function isPortAvailable(port) {
|
|
895
|
-
return new Promise((resolve$
|
|
897
|
+
return new Promise((resolve$3) => {
|
|
896
898
|
const server = (0, node_net.createServer)();
|
|
897
899
|
server.once("error", (err) => {
|
|
898
|
-
if (err.code === "EADDRINUSE") resolve$
|
|
899
|
-
else resolve$
|
|
900
|
+
if (err.code === "EADDRINUSE") resolve$3(false);
|
|
901
|
+
else resolve$3(false);
|
|
900
902
|
});
|
|
901
903
|
server.once("listening", () => {
|
|
902
904
|
server.close();
|
|
903
|
-
resolve$
|
|
905
|
+
resolve$3(true);
|
|
904
906
|
});
|
|
905
907
|
server.listen(port);
|
|
906
908
|
});
|
|
@@ -1029,6 +1031,15 @@ async function devCommand(options) {
|
|
|
1029
1031
|
workspaceAppName = appConfig.appName;
|
|
1030
1032
|
workspaceAppPort = appConfig.app.port;
|
|
1031
1033
|
logger$8.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
1034
|
+
if (appConfig.app.entry) {
|
|
1035
|
+
logger$8.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1036
|
+
return entryDevCommand({
|
|
1037
|
+
...options,
|
|
1038
|
+
entry: appConfig.app.entry,
|
|
1039
|
+
port: workspaceAppPort,
|
|
1040
|
+
portExplicit: true
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1032
1043
|
} catch {
|
|
1033
1044
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
1034
1045
|
if (loadedConfig.type === "workspace") {
|
|
@@ -1491,7 +1502,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1491
1502
|
};
|
|
1492
1503
|
process.on("SIGINT", shutdown);
|
|
1493
1504
|
process.on("SIGTERM", shutdown);
|
|
1494
|
-
return new Promise((resolve$
|
|
1505
|
+
return new Promise((resolve$3, reject) => {
|
|
1495
1506
|
turboProcess.on("error", (error) => {
|
|
1496
1507
|
logger$8.error("❌ Turbo error:", error);
|
|
1497
1508
|
reject(error);
|
|
@@ -1499,7 +1510,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1499
1510
|
turboProcess.on("exit", (code) => {
|
|
1500
1511
|
if (endpointWatcher) endpointWatcher.close().catch(() => {});
|
|
1501
1512
|
if (code !== null && code !== 0) reject(new Error(`Turbo exited with code ${code}`));
|
|
1502
|
-
else resolve$
|
|
1513
|
+
else resolve$3();
|
|
1503
1514
|
});
|
|
1504
1515
|
});
|
|
1505
1516
|
}
|
|
@@ -1709,12 +1720,12 @@ var EntryRunner = class {
|
|
|
1709
1720
|
if (code !== null && code !== 0 && code !== 143) logger$8.error(`❌ Process exited with code ${code}`);
|
|
1710
1721
|
this.isRunning = false;
|
|
1711
1722
|
});
|
|
1712
|
-
await new Promise((resolve$
|
|
1723
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 500));
|
|
1713
1724
|
if (this.isRunning) logger$8.log(`\n🎉 Running at http://localhost:${this.port}`);
|
|
1714
1725
|
}
|
|
1715
1726
|
async restart() {
|
|
1716
1727
|
this.stopProcess();
|
|
1717
|
-
await new Promise((resolve$
|
|
1728
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 500));
|
|
1718
1729
|
await this.runProcess();
|
|
1719
1730
|
}
|
|
1720
1731
|
stop() {
|
|
@@ -1786,7 +1797,7 @@ var DevServer = class {
|
|
|
1786
1797
|
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$8.error(`❌ Server exited with code ${code}`);
|
|
1787
1798
|
this.isRunning = false;
|
|
1788
1799
|
});
|
|
1789
|
-
await new Promise((resolve$
|
|
1800
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 1e3));
|
|
1790
1801
|
if (this.isRunning) {
|
|
1791
1802
|
logger$8.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
|
|
1792
1803
|
if (this.enableOpenApi) logger$8.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
|
|
@@ -1821,7 +1832,7 @@ var DevServer = class {
|
|
|
1821
1832
|
let attempts = 0;
|
|
1822
1833
|
while (attempts < 30) {
|
|
1823
1834
|
if (await isPortAvailable(portToReuse)) break;
|
|
1824
|
-
await new Promise((resolve$
|
|
1835
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 100));
|
|
1825
1836
|
attempts++;
|
|
1826
1837
|
}
|
|
1827
1838
|
this.requestedPort = portToReuse;
|
|
@@ -1928,11 +1939,11 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1928
1939
|
NODE_OPTIONS: nodeOptions
|
|
1929
1940
|
}
|
|
1930
1941
|
});
|
|
1931
|
-
const exitCode = await new Promise((resolve$
|
|
1932
|
-
child.on("close", (code) => resolve$
|
|
1942
|
+
const exitCode = await new Promise((resolve$3) => {
|
|
1943
|
+
child.on("close", (code) => resolve$3(code ?? 0));
|
|
1933
1944
|
child.on("error", (error) => {
|
|
1934
1945
|
logger$8.error(`Failed to run command: ${error.message}`);
|
|
1935
|
-
resolve$
|
|
1946
|
+
resolve$3(1);
|
|
1936
1947
|
});
|
|
1937
1948
|
});
|
|
1938
1949
|
if (exitCode !== 0) process.exit(exitCode);
|
|
@@ -2111,7 +2122,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2111
2122
|
let masterKey;
|
|
2112
2123
|
if (context.production?.bundle && !skipBundle) {
|
|
2113
2124
|
logger$6.log(`\n📦 Bundling production server...`);
|
|
2114
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
2125
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DsXfFSCU.cjs"));
|
|
2115
2126
|
const allConstructs = [
|
|
2116
2127
|
...endpoints.map((e) => e.construct),
|
|
2117
2128
|
...functions.map((f) => f.construct),
|
|
@@ -2181,7 +2192,7 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2181
2192
|
try {
|
|
2182
2193
|
const turboCommand = getTurboCommand(pm);
|
|
2183
2194
|
logger$6.log(`Running: ${turboCommand}`);
|
|
2184
|
-
await new Promise((resolve$
|
|
2195
|
+
await new Promise((resolve$3, reject) => {
|
|
2185
2196
|
const child = (0, node_child_process.spawn)(turboCommand, {
|
|
2186
2197
|
shell: true,
|
|
2187
2198
|
cwd: workspace.root,
|
|
@@ -2192,7 +2203,7 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2192
2203
|
}
|
|
2193
2204
|
});
|
|
2194
2205
|
child.on("close", (code) => {
|
|
2195
|
-
if (code === 0) resolve$
|
|
2206
|
+
if (code === 0) resolve$3();
|
|
2196
2207
|
else reject(new Error(`Turbo build failed with exit code ${code}`));
|
|
2197
2208
|
});
|
|
2198
2209
|
child.on("error", (err) => {
|
|
@@ -3016,11 +3027,13 @@ function resolveDockerConfig$1(config) {
|
|
|
3016
3027
|
* @internal Exported for testing
|
|
3017
3028
|
*/
|
|
3018
3029
|
function generateNextjsDockerfile(options) {
|
|
3019
|
-
const { baseImage, port, appPath, turboPackage, packageManager } = options;
|
|
3030
|
+
const { baseImage, port, appPath, turboPackage, packageManager, publicUrlArgs = ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_AUTH_URL"] } = options;
|
|
3020
3031
|
const pm = getPmConfig(packageManager);
|
|
3021
3032
|
const installPm = pm.install ? `RUN ${pm.install}` : "";
|
|
3022
3033
|
const turboInstallCmd = getTurboInstallCmd(packageManager);
|
|
3023
3034
|
const turboCmd = packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo";
|
|
3035
|
+
const publicUrlArgDeclarations = publicUrlArgs.map((arg) => `ARG ${arg}=""`).join("\n");
|
|
3036
|
+
const publicUrlEnvDeclarations = publicUrlArgs.map((arg) => `ENV ${arg}=$${arg}`).join("\n");
|
|
3024
3037
|
return `# syntax=docker/dockerfile:1
|
|
3025
3038
|
# Next.js standalone Dockerfile with turbo prune optimization
|
|
3026
3039
|
|
|
@@ -3056,6 +3069,13 @@ FROM deps AS builder
|
|
|
3056
3069
|
|
|
3057
3070
|
WORKDIR /app
|
|
3058
3071
|
|
|
3072
|
+
# Build-time args for public API URLs (populated by gkm deploy)
|
|
3073
|
+
# These get baked into the Next.js build as public environment variables
|
|
3074
|
+
${publicUrlArgDeclarations}
|
|
3075
|
+
|
|
3076
|
+
# Convert ARGs to ENVs for Next.js build
|
|
3077
|
+
${publicUrlEnvDeclarations}
|
|
3078
|
+
|
|
3059
3079
|
# Copy pruned source
|
|
3060
3080
|
COPY --from=pruner /app/out/full/ ./
|
|
3061
3081
|
|
|
@@ -3146,9 +3166,20 @@ FROM deps AS builder
|
|
|
3146
3166
|
|
|
3147
3167
|
WORKDIR /app
|
|
3148
3168
|
|
|
3169
|
+
# Build-time args for encrypted secrets
|
|
3170
|
+
ARG GKM_ENCRYPTED_CREDENTIALS=""
|
|
3171
|
+
ARG GKM_CREDENTIALS_IV=""
|
|
3172
|
+
|
|
3149
3173
|
# Copy pruned source
|
|
3150
3174
|
COPY --from=pruner /app/out/full/ ./
|
|
3151
3175
|
|
|
3176
|
+
# Write encrypted credentials for gkm build to embed
|
|
3177
|
+
RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
|
|
3178
|
+
mkdir -p ${appPath}/.gkm && \
|
|
3179
|
+
echo "$GKM_ENCRYPTED_CREDENTIALS" > ${appPath}/.gkm/credentials.enc && \
|
|
3180
|
+
echo "$GKM_CREDENTIALS_IV" > ${appPath}/.gkm/credentials.iv; \
|
|
3181
|
+
fi
|
|
3182
|
+
|
|
3152
3183
|
# Build production server using gkm
|
|
3153
3184
|
RUN cd ${appPath} && ./node_modules/.bin/gkm build --provider server --production
|
|
3154
3185
|
|
|
@@ -3179,6 +3210,107 @@ ENTRYPOINT ["/sbin/tini", "--"]
|
|
|
3179
3210
|
CMD ["node", "server.mjs"]
|
|
3180
3211
|
`;
|
|
3181
3212
|
}
|
|
3213
|
+
/**
|
|
3214
|
+
* Generate a Dockerfile for apps with a custom entry point.
|
|
3215
|
+
* Uses tsdown to bundle the entry point into dist/index.mjs.
|
|
3216
|
+
* This is used for apps that don't use gkm routes (e.g., Better Auth servers).
|
|
3217
|
+
* @internal Exported for testing
|
|
3218
|
+
*/
|
|
3219
|
+
function generateEntryDockerfile(options) {
|
|
3220
|
+
const { baseImage, port, appPath, entry, turboPackage, packageManager, healthCheckPath = "/health" } = options;
|
|
3221
|
+
const pm = getPmConfig(packageManager);
|
|
3222
|
+
const installPm = pm.install ? `RUN ${pm.install}` : "";
|
|
3223
|
+
const turboInstallCmd = getTurboInstallCmd(packageManager);
|
|
3224
|
+
const turboCmd = packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo";
|
|
3225
|
+
return `# syntax=docker/dockerfile:1
|
|
3226
|
+
# Entry-based Dockerfile with turbo prune + tsdown bundling
|
|
3227
|
+
|
|
3228
|
+
# Stage 1: Prune monorepo
|
|
3229
|
+
FROM ${baseImage} AS pruner
|
|
3230
|
+
|
|
3231
|
+
WORKDIR /app
|
|
3232
|
+
|
|
3233
|
+
${installPm}
|
|
3234
|
+
|
|
3235
|
+
COPY . .
|
|
3236
|
+
|
|
3237
|
+
# Prune to only include necessary packages
|
|
3238
|
+
RUN ${turboCmd} prune ${turboPackage} --docker
|
|
3239
|
+
|
|
3240
|
+
# Stage 2: Install dependencies
|
|
3241
|
+
FROM ${baseImage} AS deps
|
|
3242
|
+
|
|
3243
|
+
WORKDIR /app
|
|
3244
|
+
|
|
3245
|
+
${installPm}
|
|
3246
|
+
|
|
3247
|
+
# Copy pruned lockfile and package.jsons
|
|
3248
|
+
COPY --from=pruner /app/out/${pm.lockfile} ./
|
|
3249
|
+
COPY --from=pruner /app/out/json/ ./
|
|
3250
|
+
|
|
3251
|
+
# Install dependencies
|
|
3252
|
+
RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
|
|
3253
|
+
${turboInstallCmd}
|
|
3254
|
+
|
|
3255
|
+
# Stage 3: Build with tsdown
|
|
3256
|
+
FROM deps AS builder
|
|
3257
|
+
|
|
3258
|
+
WORKDIR /app
|
|
3259
|
+
|
|
3260
|
+
# Build-time args for encrypted secrets
|
|
3261
|
+
ARG GKM_ENCRYPTED_CREDENTIALS=""
|
|
3262
|
+
ARG GKM_CREDENTIALS_IV=""
|
|
3263
|
+
|
|
3264
|
+
# Copy pruned source
|
|
3265
|
+
COPY --from=pruner /app/out/full/ ./
|
|
3266
|
+
|
|
3267
|
+
# Write encrypted credentials for tsdown to embed via define
|
|
3268
|
+
RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
|
|
3269
|
+
mkdir -p ${appPath}/.gkm && \
|
|
3270
|
+
echo "$GKM_ENCRYPTED_CREDENTIALS" > ${appPath}/.gkm/credentials.enc && \
|
|
3271
|
+
echo "$GKM_CREDENTIALS_IV" > ${appPath}/.gkm/credentials.iv; \
|
|
3272
|
+
fi
|
|
3273
|
+
|
|
3274
|
+
# Bundle entry point with tsdown (outputs to dist/index.mjs)
|
|
3275
|
+
# Use define to embed credentials if present
|
|
3276
|
+
RUN cd ${appPath} && \
|
|
3277
|
+
if [ -f .gkm/credentials.enc ]; then \
|
|
3278
|
+
CREDS=$(cat .gkm/credentials.enc) && \
|
|
3279
|
+
IV=$(cat .gkm/credentials.iv) && \
|
|
3280
|
+
npx tsdown ${entry} --outDir dist --format esm \
|
|
3281
|
+
--define __GKM_ENCRYPTED_CREDENTIALS__="'\\"$CREDS\\"'" \
|
|
3282
|
+
--define __GKM_CREDENTIALS_IV__="'\\"$IV\\"'"; \
|
|
3283
|
+
else \
|
|
3284
|
+
npx tsdown ${entry} --outDir dist --format esm; \
|
|
3285
|
+
fi
|
|
3286
|
+
|
|
3287
|
+
# Stage 4: Production
|
|
3288
|
+
FROM ${baseImage} AS runner
|
|
3289
|
+
|
|
3290
|
+
WORKDIR /app
|
|
3291
|
+
|
|
3292
|
+
RUN apk add --no-cache tini
|
|
3293
|
+
|
|
3294
|
+
RUN addgroup --system --gid 1001 nodejs && \\
|
|
3295
|
+
adduser --system --uid 1001 app
|
|
3296
|
+
|
|
3297
|
+
# Copy bundled output only (no node_modules needed - fully bundled)
|
|
3298
|
+
COPY --from=builder --chown=app:nodejs /app/${appPath}/dist/index.mjs ./
|
|
3299
|
+
|
|
3300
|
+
ENV NODE_ENV=production
|
|
3301
|
+
ENV PORT=${port}
|
|
3302
|
+
|
|
3303
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
|
|
3304
|
+
CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
|
|
3305
|
+
|
|
3306
|
+
USER app
|
|
3307
|
+
|
|
3308
|
+
EXPOSE ${port}
|
|
3309
|
+
|
|
3310
|
+
ENTRYPOINT ["/sbin/tini", "--"]
|
|
3311
|
+
CMD ["node", "index.mjs"]
|
|
3312
|
+
`;
|
|
3313
|
+
}
|
|
3182
3314
|
|
|
3183
3315
|
//#endregion
|
|
3184
3316
|
//#region src/docker/index.ts
|
|
@@ -3365,7 +3497,9 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3365
3497
|
const fullAppPath = (0, node_path.join)(workspace.root, appPath);
|
|
3366
3498
|
const turboPackage = getAppPackageName(fullAppPath) ?? appName;
|
|
3367
3499
|
const imageName = appName;
|
|
3368
|
-
|
|
3500
|
+
const hasEntry = !!app.entry;
|
|
3501
|
+
const buildType = hasEntry ? "entry" : app.type;
|
|
3502
|
+
logger$5.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
3369
3503
|
let dockerfile;
|
|
3370
3504
|
if (app.type === "frontend") dockerfile = generateNextjsDockerfile({
|
|
3371
3505
|
imageName,
|
|
@@ -3375,6 +3509,16 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3375
3509
|
turboPackage,
|
|
3376
3510
|
packageManager
|
|
3377
3511
|
});
|
|
3512
|
+
else if (app.entry) dockerfile = generateEntryDockerfile({
|
|
3513
|
+
imageName,
|
|
3514
|
+
baseImage: "node:22-alpine",
|
|
3515
|
+
port: app.port,
|
|
3516
|
+
appPath,
|
|
3517
|
+
entry: app.entry,
|
|
3518
|
+
turboPackage,
|
|
3519
|
+
packageManager,
|
|
3520
|
+
healthCheckPath: "/health"
|
|
3521
|
+
});
|
|
3378
3522
|
else dockerfile = generateBackendDockerfile({
|
|
3379
3523
|
imageName,
|
|
3380
3524
|
baseImage: "node:22-alpine",
|
|
@@ -3461,8 +3605,9 @@ function getImageRef(registry, imageName, tag) {
|
|
|
3461
3605
|
* Build Docker image
|
|
3462
3606
|
* @param imageRef - Full image reference (registry/name:tag)
|
|
3463
3607
|
* @param appName - Name of the app (used for Dockerfile.{appName} in workspaces)
|
|
3608
|
+
* @param buildArgs - Build arguments to pass to docker build
|
|
3464
3609
|
*/
|
|
3465
|
-
async function buildImage(imageRef, appName) {
|
|
3610
|
+
async function buildImage(imageRef, appName, buildArgs) {
|
|
3466
3611
|
logger$4.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
3467
3612
|
const cwd = process.cwd();
|
|
3468
3613
|
const lockfilePath = findLockfilePath(cwd);
|
|
@@ -3475,8 +3620,17 @@ async function buildImage(imageRef, appName) {
|
|
|
3475
3620
|
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
3476
3621
|
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
3477
3622
|
if (buildCwd !== cwd) logger$4.log(` Building from workspace root: ${buildCwd}`);
|
|
3623
|
+
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
3478
3624
|
try {
|
|
3479
|
-
|
|
3625
|
+
const cmd = [
|
|
3626
|
+
"DOCKER_BUILDKIT=1 docker build",
|
|
3627
|
+
"--platform linux/amd64",
|
|
3628
|
+
`-f ${dockerfilePath}`,
|
|
3629
|
+
`-t ${imageRef}`,
|
|
3630
|
+
buildArgsString,
|
|
3631
|
+
"."
|
|
3632
|
+
].filter(Boolean).join(" ");
|
|
3633
|
+
(0, node_child_process.execSync)(cmd, {
|
|
3480
3634
|
cwd: buildCwd,
|
|
3481
3635
|
stdio: "inherit",
|
|
3482
3636
|
env: {
|
|
@@ -3508,10 +3662,10 @@ async function pushImage(imageRef) {
|
|
|
3508
3662
|
* Deploy using Docker (build and optionally push image)
|
|
3509
3663
|
*/
|
|
3510
3664
|
async function deployDocker(options) {
|
|
3511
|
-
const { stage, tag, skipPush, masterKey, config } = options;
|
|
3665
|
+
const { stage, tag, skipPush, masterKey, config, buildArgs } = options;
|
|
3512
3666
|
const imageName = config.imageName;
|
|
3513
3667
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
3514
|
-
await buildImage(imageRef, config.appName);
|
|
3668
|
+
await buildImage(imageRef, config.appName, buildArgs);
|
|
3515
3669
|
if (!skipPush) if (!config.registry) logger$4.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
3516
3670
|
else await pushImage(imageRef);
|
|
3517
3671
|
logger$4.log("\n✅ Docker deployment ready!");
|
|
@@ -3632,6 +3786,81 @@ async function deployDokploy(options) {
|
|
|
3632
3786
|
};
|
|
3633
3787
|
}
|
|
3634
3788
|
|
|
3789
|
+
//#endregion
|
|
3790
|
+
//#region src/deploy/domain.ts
|
|
3791
|
+
/**
|
|
3792
|
+
* Resolve the hostname for an app based on stage configuration.
|
|
3793
|
+
*
|
|
3794
|
+
* Domain resolution priority:
|
|
3795
|
+
* 1. Explicit app.domain override (string or stage-specific)
|
|
3796
|
+
* 2. Default pattern based on app type:
|
|
3797
|
+
* - Main frontend app gets base domain (e.g., 'myapp.com')
|
|
3798
|
+
* - Other apps get prefixed domain (e.g., 'api.myapp.com')
|
|
3799
|
+
*
|
|
3800
|
+
* @param appName - The name of the app
|
|
3801
|
+
* @param app - The normalized app configuration
|
|
3802
|
+
* @param stage - The deployment stage (e.g., 'production', 'development')
|
|
3803
|
+
* @param dokployConfig - Dokploy workspace configuration with domain mappings
|
|
3804
|
+
* @param isMainFrontend - Whether this is the main frontend app
|
|
3805
|
+
* @returns The resolved hostname for the app
|
|
3806
|
+
* @throws Error if no domain configuration is found for the stage
|
|
3807
|
+
*/
|
|
3808
|
+
function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
|
|
3809
|
+
if (app.domain) {
|
|
3810
|
+
if (typeof app.domain === "string") return app.domain;
|
|
3811
|
+
if (app.domain[stage]) return app.domain[stage];
|
|
3812
|
+
}
|
|
3813
|
+
const baseDomain = dokployConfig?.domains?.[stage];
|
|
3814
|
+
if (!baseDomain) throw new Error(`No domain configured for stage "${stage}". Add deploy.dokploy.domains.${stage} to gkm.config.ts`);
|
|
3815
|
+
if (isMainFrontend) return baseDomain;
|
|
3816
|
+
return `${appName}.${baseDomain}`;
|
|
3817
|
+
}
|
|
3818
|
+
/**
|
|
3819
|
+
* Determine if an app is the "main" frontend (gets base domain).
|
|
3820
|
+
*
|
|
3821
|
+
* An app is considered the main frontend if:
|
|
3822
|
+
* 1. It's named 'web' and is a frontend type
|
|
3823
|
+
* 2. It's the first frontend app in the apps list
|
|
3824
|
+
*
|
|
3825
|
+
* @param appName - The name of the app to check
|
|
3826
|
+
* @param app - The app configuration
|
|
3827
|
+
* @param allApps - All apps in the workspace
|
|
3828
|
+
* @returns True if this is the main frontend app
|
|
3829
|
+
*/
|
|
3830
|
+
function isMainFrontendApp(appName, app, allApps) {
|
|
3831
|
+
if (app.type !== "frontend") return false;
|
|
3832
|
+
if (appName === "web") return true;
|
|
3833
|
+
for (const [name$1, a] of Object.entries(allApps)) if (a.type === "frontend") return name$1 === appName;
|
|
3834
|
+
return false;
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Generate public URL build args for a frontend app based on its dependencies.
|
|
3838
|
+
*
|
|
3839
|
+
* @param app - The frontend app configuration
|
|
3840
|
+
* @param deployedUrls - Map of app name to deployed public URL
|
|
3841
|
+
* @returns Array of build args like 'NEXT_PUBLIC_API_URL=https://api.example.com'
|
|
3842
|
+
*/
|
|
3843
|
+
function generatePublicUrlBuildArgs(app, deployedUrls) {
|
|
3844
|
+
const buildArgs = [];
|
|
3845
|
+
for (const dep of app.dependencies) {
|
|
3846
|
+
const publicUrl = deployedUrls[dep];
|
|
3847
|
+
if (publicUrl) {
|
|
3848
|
+
const envVarName = `NEXT_PUBLIC_${dep.toUpperCase()}_URL`;
|
|
3849
|
+
buildArgs.push(`${envVarName}=${publicUrl}`);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
return buildArgs;
|
|
3853
|
+
}
|
|
3854
|
+
/**
|
|
3855
|
+
* Get public URL arg names from app dependencies.
|
|
3856
|
+
*
|
|
3857
|
+
* @param app - The frontend app configuration
|
|
3858
|
+
* @returns Array of arg names like 'NEXT_PUBLIC_API_URL'
|
|
3859
|
+
*/
|
|
3860
|
+
function getPublicUrlArgNames(app) {
|
|
3861
|
+
return app.dependencies.map((dep) => `NEXT_PUBLIC_${dep.toUpperCase()}_URL`);
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3635
3864
|
//#endregion
|
|
3636
3865
|
//#region src/deploy/init.ts
|
|
3637
3866
|
const logger$2 = console;
|
|
@@ -3805,6 +4034,214 @@ async function deployListCommand(options) {
|
|
|
3805
4034
|
}
|
|
3806
4035
|
}
|
|
3807
4036
|
|
|
4037
|
+
//#endregion
|
|
4038
|
+
//#region src/deploy/secrets.ts
|
|
4039
|
+
/**
|
|
4040
|
+
* Filter secrets to only include the env vars that an app requires.
|
|
4041
|
+
*
|
|
4042
|
+
* @param stageSecrets - All secrets for the stage
|
|
4043
|
+
* @param sniffedEnv - The sniffed environment requirements for the app
|
|
4044
|
+
* @returns Filtered secrets with found/missing tracking
|
|
4045
|
+
*/
|
|
4046
|
+
function filterSecretsForApp(stageSecrets, sniffedEnv) {
|
|
4047
|
+
const allSecrets = require_storage.toEmbeddableSecrets(stageSecrets);
|
|
4048
|
+
const filtered = {};
|
|
4049
|
+
const found = [];
|
|
4050
|
+
const missing = [];
|
|
4051
|
+
for (const key of sniffedEnv.requiredEnvVars) if (key in allSecrets) {
|
|
4052
|
+
filtered[key] = allSecrets[key];
|
|
4053
|
+
found.push(key);
|
|
4054
|
+
} else missing.push(key);
|
|
4055
|
+
return {
|
|
4056
|
+
appName: sniffedEnv.appName,
|
|
4057
|
+
secrets: filtered,
|
|
4058
|
+
found: found.sort(),
|
|
4059
|
+
missing: missing.sort()
|
|
4060
|
+
};
|
|
4061
|
+
}
|
|
4062
|
+
/**
|
|
4063
|
+
* Encrypt filtered secrets for an app.
|
|
4064
|
+
* Generates an ephemeral master key that should be injected into Dokploy.
|
|
4065
|
+
*
|
|
4066
|
+
* @param filteredSecrets - The filtered secrets for the app
|
|
4067
|
+
* @returns Encrypted payload with master key
|
|
4068
|
+
*/
|
|
4069
|
+
function encryptSecretsForApp(filteredSecrets) {
|
|
4070
|
+
const payload = require_encryption.encryptSecrets(filteredSecrets.secrets);
|
|
4071
|
+
return {
|
|
4072
|
+
appName: filteredSecrets.appName,
|
|
4073
|
+
payload,
|
|
4074
|
+
masterKey: payload.masterKey,
|
|
4075
|
+
secretCount: Object.keys(filteredSecrets.secrets).length,
|
|
4076
|
+
missingSecrets: filteredSecrets.missing
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
/**
|
|
4080
|
+
* Filter and encrypt secrets for an app in one step.
|
|
4081
|
+
*
|
|
4082
|
+
* @param stageSecrets - All secrets for the stage
|
|
4083
|
+
* @param sniffedEnv - The sniffed environment requirements for the app
|
|
4084
|
+
* @returns Encrypted secrets with master key
|
|
4085
|
+
*/
|
|
4086
|
+
function prepareSecretsForApp(stageSecrets, sniffedEnv) {
|
|
4087
|
+
const filtered = filterSecretsForApp(stageSecrets, sniffedEnv);
|
|
4088
|
+
return encryptSecretsForApp(filtered);
|
|
4089
|
+
}
|
|
4090
|
+
/**
|
|
4091
|
+
* Prepare secrets for multiple apps.
|
|
4092
|
+
*
|
|
4093
|
+
* @param stageSecrets - All secrets for the stage
|
|
4094
|
+
* @param sniffedApps - Map of app name to sniffed environment
|
|
4095
|
+
* @returns Map of app name to encrypted secrets
|
|
4096
|
+
*/
|
|
4097
|
+
function prepareSecretsForAllApps(stageSecrets, sniffedApps) {
|
|
4098
|
+
const results = /* @__PURE__ */ new Map();
|
|
4099
|
+
for (const [appName, sniffedEnv] of sniffedApps) if (sniffedEnv.requiredEnvVars.length > 0) {
|
|
4100
|
+
const encrypted = prepareSecretsForApp(stageSecrets, sniffedEnv);
|
|
4101
|
+
results.set(appName, encrypted);
|
|
4102
|
+
}
|
|
4103
|
+
return results;
|
|
4104
|
+
}
|
|
4105
|
+
/**
|
|
4106
|
+
* Generate a report on secrets preparation.
|
|
4107
|
+
*/
|
|
4108
|
+
function generateSecretsReport(encryptedApps, sniffedApps) {
|
|
4109
|
+
const appsWithSecrets = [];
|
|
4110
|
+
const appsWithoutSecrets = [];
|
|
4111
|
+
const appsWithMissingSecrets = [];
|
|
4112
|
+
for (const [appName, sniffedEnv] of sniffedApps) {
|
|
4113
|
+
if (sniffedEnv.requiredEnvVars.length === 0) {
|
|
4114
|
+
appsWithoutSecrets.push(appName);
|
|
4115
|
+
continue;
|
|
4116
|
+
}
|
|
4117
|
+
const encrypted = encryptedApps.get(appName);
|
|
4118
|
+
if (encrypted) {
|
|
4119
|
+
appsWithSecrets.push(appName);
|
|
4120
|
+
if (encrypted.missingSecrets.length > 0) appsWithMissingSecrets.push({
|
|
4121
|
+
appName,
|
|
4122
|
+
missing: encrypted.missingSecrets
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
return {
|
|
4127
|
+
totalApps: sniffedApps.size,
|
|
4128
|
+
appsWithSecrets: appsWithSecrets.sort(),
|
|
4129
|
+
appsWithoutSecrets: appsWithoutSecrets.sort(),
|
|
4130
|
+
appsWithMissingSecrets
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
//#endregion
|
|
4135
|
+
//#region src/deploy/sniffer.ts
|
|
4136
|
+
/**
|
|
4137
|
+
* Get required environment variables for an app.
|
|
4138
|
+
*
|
|
4139
|
+
* Detection strategy:
|
|
4140
|
+
* - Frontend apps: Returns empty (no server secrets)
|
|
4141
|
+
* - Apps with `requiredEnv`: Uses explicit list from config
|
|
4142
|
+
* - Apps with `envParser`: Runs SnifferEnvironmentParser to detect usage
|
|
4143
|
+
* - Apps with neither: Returns empty
|
|
4144
|
+
*
|
|
4145
|
+
* This function handles "fire and forget" async operations gracefully,
|
|
4146
|
+
* capturing errors and unhandled rejections without failing the build.
|
|
4147
|
+
*
|
|
4148
|
+
* @param app - The normalized app configuration
|
|
4149
|
+
* @param appName - The name of the app
|
|
4150
|
+
* @param workspacePath - Absolute path to the workspace root
|
|
4151
|
+
* @param options - Optional configuration for sniffing behavior
|
|
4152
|
+
* @returns The sniffed environment with required variables
|
|
4153
|
+
*/
|
|
4154
|
+
async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
|
|
4155
|
+
const { logWarnings = true } = options;
|
|
4156
|
+
if (app.type === "frontend") return {
|
|
4157
|
+
appName,
|
|
4158
|
+
requiredEnvVars: []
|
|
4159
|
+
};
|
|
4160
|
+
if (app.requiredEnv && app.requiredEnv.length > 0) return {
|
|
4161
|
+
appName,
|
|
4162
|
+
requiredEnvVars: [...app.requiredEnv]
|
|
4163
|
+
};
|
|
4164
|
+
if (app.envParser) {
|
|
4165
|
+
const result = await sniffEnvParser(app.envParser, app.path, workspacePath);
|
|
4166
|
+
if (logWarnings) {
|
|
4167
|
+
if (result.error) console.warn(`[sniffer] ${appName}: envParser threw error during sniffing (env vars still captured): ${result.error.message}`);
|
|
4168
|
+
if (result.unhandledRejections.length > 0) console.warn(`[sniffer] ${appName}: Fire-and-forget rejections during sniffing (suppressed): ${result.unhandledRejections.map((e) => e.message).join(", ")}`);
|
|
4169
|
+
}
|
|
4170
|
+
return {
|
|
4171
|
+
appName,
|
|
4172
|
+
requiredEnvVars: result.envVars
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
return {
|
|
4176
|
+
appName,
|
|
4177
|
+
requiredEnvVars: []
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
/**
|
|
4181
|
+
* Run the SnifferEnvironmentParser on an envParser module to detect
|
|
4182
|
+
* which environment variables it accesses.
|
|
4183
|
+
*
|
|
4184
|
+
* This function handles "fire and forget" async operations by using
|
|
4185
|
+
* the shared sniffWithFireAndForget utility from @geekmidas/envkit.
|
|
4186
|
+
*
|
|
4187
|
+
* @param envParserPath - The envParser config (e.g., './src/config/env#envParser')
|
|
4188
|
+
* @param appPath - The app's path relative to workspace
|
|
4189
|
+
* @param workspacePath - Absolute path to workspace root
|
|
4190
|
+
* @returns SniffResult with env vars and any errors encountered
|
|
4191
|
+
*/
|
|
4192
|
+
async function sniffEnvParser(envParserPath, appPath, workspacePath) {
|
|
4193
|
+
const [modulePath, exportName = "default"] = envParserPath.split("#");
|
|
4194
|
+
if (!modulePath) return {
|
|
4195
|
+
envVars: [],
|
|
4196
|
+
unhandledRejections: []
|
|
4197
|
+
};
|
|
4198
|
+
const fullPath = (0, node_path.resolve)(workspacePath, appPath, modulePath);
|
|
4199
|
+
let SnifferEnvironmentParser;
|
|
4200
|
+
let sniffWithFireAndForget;
|
|
4201
|
+
try {
|
|
4202
|
+
const envkitModule = await import("@geekmidas/envkit/sniffer");
|
|
4203
|
+
SnifferEnvironmentParser = envkitModule.SnifferEnvironmentParser;
|
|
4204
|
+
sniffWithFireAndForget = envkitModule.sniffWithFireAndForget;
|
|
4205
|
+
} catch (error) {
|
|
4206
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4207
|
+
console.warn(`[sniffer] Failed to import SnifferEnvironmentParser: ${message}`);
|
|
4208
|
+
return {
|
|
4209
|
+
envVars: [],
|
|
4210
|
+
unhandledRejections: []
|
|
4211
|
+
};
|
|
4212
|
+
}
|
|
4213
|
+
const sniffer = new SnifferEnvironmentParser();
|
|
4214
|
+
return sniffWithFireAndForget(sniffer, async () => {
|
|
4215
|
+
const moduleUrl = (0, node_url.pathToFileURL)(fullPath).href;
|
|
4216
|
+
const module$1 = await import(moduleUrl);
|
|
4217
|
+
const envParser = module$1[exportName];
|
|
4218
|
+
if (typeof envParser !== "function") {
|
|
4219
|
+
console.warn(`[sniffer] Export "${exportName}" from "${modulePath}" is not a function`);
|
|
4220
|
+
return;
|
|
4221
|
+
}
|
|
4222
|
+
const result = envParser(sniffer);
|
|
4223
|
+
if (result && typeof result.parse === "function") try {
|
|
4224
|
+
result.parse();
|
|
4225
|
+
} catch {}
|
|
4226
|
+
});
|
|
4227
|
+
}
|
|
4228
|
+
/**
|
|
4229
|
+
* Sniff environment requirements for multiple apps.
|
|
4230
|
+
*
|
|
4231
|
+
* @param apps - Map of app name to app config
|
|
4232
|
+
* @param workspacePath - Absolute path to workspace root
|
|
4233
|
+
* @param options - Optional configuration for sniffing behavior
|
|
4234
|
+
* @returns Map of app name to sniffed environment
|
|
4235
|
+
*/
|
|
4236
|
+
async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
4237
|
+
const results = /* @__PURE__ */ new Map();
|
|
4238
|
+
for (const [appName, app] of Object.entries(apps)) {
|
|
4239
|
+
const sniffed = await sniffAppEnvironment(app, appName, workspacePath, options);
|
|
4240
|
+
results.set(appName, sniffed);
|
|
4241
|
+
}
|
|
4242
|
+
return results;
|
|
4243
|
+
}
|
|
4244
|
+
|
|
3808
4245
|
//#endregion
|
|
3809
4246
|
//#region src/deploy/index.ts
|
|
3810
4247
|
const logger$1 = console;
|
|
@@ -3815,7 +4252,7 @@ async function prompt(message, hidden = false) {
|
|
|
3815
4252
|
if (!process.stdin.isTTY) throw new Error("Interactive input required. Please configure manually.");
|
|
3816
4253
|
if (hidden) {
|
|
3817
4254
|
process.stdout.write(message);
|
|
3818
|
-
return new Promise((resolve$
|
|
4255
|
+
return new Promise((resolve$3) => {
|
|
3819
4256
|
let value = "";
|
|
3820
4257
|
const onData = (char) => {
|
|
3821
4258
|
const c = char.toString();
|
|
@@ -3824,7 +4261,7 @@ async function prompt(message, hidden = false) {
|
|
|
3824
4261
|
process.stdin.pause();
|
|
3825
4262
|
process.stdin.removeListener("data", onData);
|
|
3826
4263
|
process.stdout.write("\n");
|
|
3827
|
-
resolve$
|
|
4264
|
+
resolve$3(value);
|
|
3828
4265
|
} else if (c === "") {
|
|
3829
4266
|
process.stdin.setRawMode(false);
|
|
3830
4267
|
process.stdin.pause();
|
|
@@ -4093,14 +4530,20 @@ function generateTag(stage) {
|
|
|
4093
4530
|
}
|
|
4094
4531
|
/**
|
|
4095
4532
|
* Deploy all apps in a workspace to Dokploy.
|
|
4096
|
-
*
|
|
4097
|
-
* -
|
|
4098
|
-
* -
|
|
4099
|
-
* -
|
|
4533
|
+
*
|
|
4534
|
+
* Two-phase orchestration:
|
|
4535
|
+
* - PHASE 1: Deploy backend apps (with encrypted secrets)
|
|
4536
|
+
* - PHASE 2: Deploy frontend apps (with public URLs from backends)
|
|
4537
|
+
*
|
|
4538
|
+
* Security model:
|
|
4539
|
+
* - Backend apps get encrypted secrets embedded at build time
|
|
4540
|
+
* - Only GKM_MASTER_KEY is injected as Dokploy env var
|
|
4541
|
+
* - Frontend apps get public URLs baked in at build time (no secrets)
|
|
4542
|
+
*
|
|
4100
4543
|
* @internal Exported for testing
|
|
4101
4544
|
*/
|
|
4102
4545
|
async function workspaceDeployCommand(workspace, options) {
|
|
4103
|
-
const { provider, stage, tag,
|
|
4546
|
+
const { provider, stage, tag, apps: selectedApps } = options;
|
|
4104
4547
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
4105
4548
|
logger$1.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
4106
4549
|
logger$1.log(` Stage: ${stage}`);
|
|
@@ -4124,11 +4567,20 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4124
4567
|
return true;
|
|
4125
4568
|
});
|
|
4126
4569
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
4127
|
-
if (dokployApps.length !== appsToDeployNames.length) {
|
|
4128
|
-
const skipped = appsToDeployNames.filter((name$1) => !dokployApps.includes(name$1));
|
|
4129
|
-
logger$1.log(` 📌 ${skipped.length} app(s) skipped due to unsupported targets`);
|
|
4130
|
-
}
|
|
4131
4570
|
appsToDeployNames = dokployApps;
|
|
4571
|
+
logger$1.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
4572
|
+
const stageSecrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
4573
|
+
if (!stageSecrets) {
|
|
4574
|
+
logger$1.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
4575
|
+
logger$1.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
4576
|
+
}
|
|
4577
|
+
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
4578
|
+
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
4579
|
+
if (stageSecrets) {
|
|
4580
|
+
const report = generateSecretsReport(encryptedSecrets, sniffedApps);
|
|
4581
|
+
if (report.appsWithSecrets.length > 0) logger$1.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
4582
|
+
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$1.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
4583
|
+
}
|
|
4132
4584
|
let creds = await getDokployCredentials();
|
|
4133
4585
|
if (!creds) {
|
|
4134
4586
|
logger$1.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
@@ -4222,95 +4674,191 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4222
4674
|
logger$1.log("\n🔧 Provisioning infrastructure services...");
|
|
4223
4675
|
await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices);
|
|
4224
4676
|
}
|
|
4225
|
-
const
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
try {
|
|
4229
|
-
await buildCommand({
|
|
4230
|
-
provider: "server",
|
|
4231
|
-
production: true,
|
|
4232
|
-
stage
|
|
4233
|
-
});
|
|
4234
|
-
logger$1.log(" ✓ Workspace build complete");
|
|
4235
|
-
} catch (error) {
|
|
4236
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4237
|
-
logger$1.log(` ✗ Workspace build failed: ${message}`);
|
|
4238
|
-
throw error;
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4241
|
-
logger$1.log("\n📦 Deploying applications...");
|
|
4677
|
+
const backendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "backend");
|
|
4678
|
+
const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "frontend");
|
|
4679
|
+
const publicUrls = {};
|
|
4242
4680
|
const results = [];
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
logger$1.log(
|
|
4246
|
-
|
|
4247
|
-
const
|
|
4248
|
-
|
|
4681
|
+
const dokployConfig = workspace.deploy.dokploy;
|
|
4682
|
+
if (backendApps.length > 0) {
|
|
4683
|
+
logger$1.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
4684
|
+
for (const appName of backendApps) {
|
|
4685
|
+
const app = workspace.apps[appName];
|
|
4686
|
+
logger$1.log(`\n ⚙️ Deploying ${appName}...`);
|
|
4249
4687
|
try {
|
|
4250
|
-
|
|
4251
|
-
|
|
4688
|
+
const dokployAppName = `${workspace.name}-${appName}`;
|
|
4689
|
+
let application;
|
|
4690
|
+
try {
|
|
4691
|
+
application = await api.createApplication(dokployAppName, project.projectId, environmentId);
|
|
4692
|
+
logger$1.log(` Created application: ${application.applicationId}`);
|
|
4693
|
+
} catch (error) {
|
|
4694
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4695
|
+
if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Application already exists`);
|
|
4696
|
+
else throw error;
|
|
4697
|
+
}
|
|
4698
|
+
const appSecrets = encryptedSecrets.get(appName);
|
|
4699
|
+
const buildArgs = [];
|
|
4700
|
+
if (appSecrets && appSecrets.secretCount > 0) {
|
|
4701
|
+
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
4702
|
+
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
4703
|
+
logger$1.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
4704
|
+
}
|
|
4705
|
+
const imageName = `${workspace.name}-${appName}`;
|
|
4706
|
+
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
4707
|
+
logger$1.log(` Building Docker image: ${imageRef}`);
|
|
4708
|
+
await deployDocker({
|
|
4709
|
+
stage,
|
|
4710
|
+
tag: imageTag,
|
|
4711
|
+
skipPush: false,
|
|
4712
|
+
config: {
|
|
4713
|
+
registry,
|
|
4714
|
+
imageName,
|
|
4715
|
+
appName
|
|
4716
|
+
},
|
|
4717
|
+
buildArgs
|
|
4718
|
+
});
|
|
4719
|
+
const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
|
|
4720
|
+
if (appSecrets && appSecrets.masterKey) envVars.push(`GKM_MASTER_KEY=${appSecrets.masterKey}`);
|
|
4721
|
+
if (application) {
|
|
4722
|
+
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
4723
|
+
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
4724
|
+
logger$1.log(` Deploying to Dokploy...`);
|
|
4725
|
+
await api.deployApplication(application.applicationId);
|
|
4726
|
+
try {
|
|
4727
|
+
const host = resolveHost(appName, app, stage, dokployConfig, false);
|
|
4728
|
+
await api.createDomain({
|
|
4729
|
+
host,
|
|
4730
|
+
port: app.port,
|
|
4731
|
+
https: true,
|
|
4732
|
+
certificateType: "letsencrypt",
|
|
4733
|
+
applicationId: application.applicationId
|
|
4734
|
+
});
|
|
4735
|
+
const publicUrl = `https://${host}`;
|
|
4736
|
+
publicUrls[appName] = publicUrl;
|
|
4737
|
+
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4738
|
+
} catch (domainError) {
|
|
4739
|
+
const host = resolveHost(appName, app, stage, dokployConfig, false);
|
|
4740
|
+
publicUrls[appName] = `https://${host}`;
|
|
4741
|
+
logger$1.log(` ℹ Domain already configured: https://${host}`);
|
|
4742
|
+
}
|
|
4743
|
+
results.push({
|
|
4744
|
+
appName,
|
|
4745
|
+
type: app.type,
|
|
4746
|
+
success: true,
|
|
4747
|
+
applicationId: application.applicationId,
|
|
4748
|
+
imageRef
|
|
4749
|
+
});
|
|
4750
|
+
logger$1.log(` ✓ ${appName} deployed successfully`);
|
|
4751
|
+
} else {
|
|
4752
|
+
const host = resolveHost(appName, app, stage, dokployConfig, false);
|
|
4753
|
+
publicUrls[appName] = `https://${host}`;
|
|
4754
|
+
results.push({
|
|
4755
|
+
appName,
|
|
4756
|
+
type: app.type,
|
|
4757
|
+
success: true,
|
|
4758
|
+
imageRef
|
|
4759
|
+
});
|
|
4760
|
+
logger$1.log(` ✓ ${appName} image pushed (app already exists)`);
|
|
4761
|
+
}
|
|
4252
4762
|
} catch (error) {
|
|
4253
4763
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4254
|
-
|
|
4255
|
-
else throw error;
|
|
4256
|
-
}
|
|
4257
|
-
const imageName = `${workspace.name}-${appName}`;
|
|
4258
|
-
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
4259
|
-
logger$1.log(` Building Docker image: ${imageRef}`);
|
|
4260
|
-
await deployDocker({
|
|
4261
|
-
stage,
|
|
4262
|
-
tag: imageTag,
|
|
4263
|
-
skipPush: false,
|
|
4264
|
-
config: {
|
|
4265
|
-
registry,
|
|
4266
|
-
imageName,
|
|
4267
|
-
appName
|
|
4268
|
-
}
|
|
4269
|
-
});
|
|
4270
|
-
const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
|
|
4271
|
-
for (const dep of app.dependencies) {
|
|
4272
|
-
const depUrl = deployedAppUrls[dep];
|
|
4273
|
-
if (depUrl) envVars.push(`${dep.toUpperCase()}_URL=${depUrl}`);
|
|
4274
|
-
}
|
|
4275
|
-
if (app.type === "backend") {
|
|
4276
|
-
if (dockerServices.postgres) envVars.push(`DATABASE_URL=\${DATABASE_URL:-postgresql://postgres:postgres@${workspace.name}-db:5432/app}`);
|
|
4277
|
-
if (dockerServices.redis) envVars.push(`REDIS_URL=\${REDIS_URL:-redis://${workspace.name}-cache:6379}`);
|
|
4278
|
-
}
|
|
4279
|
-
if (application) {
|
|
4280
|
-
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
4281
|
-
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
4282
|
-
logger$1.log(` Deploying to Dokploy...`);
|
|
4283
|
-
await api.deployApplication(application.applicationId);
|
|
4284
|
-
const appUrl = `http://${dokployAppName}:${app.port}`;
|
|
4285
|
-
deployedAppUrls[appName] = appUrl;
|
|
4764
|
+
logger$1.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
4286
4765
|
results.push({
|
|
4287
4766
|
appName,
|
|
4288
4767
|
type: app.type,
|
|
4289
|
-
success:
|
|
4290
|
-
|
|
4291
|
-
|
|
4768
|
+
success: false,
|
|
4769
|
+
error: message
|
|
4770
|
+
});
|
|
4771
|
+
throw new Error(`Backend deployment failed for ${appName}. Aborting to prevent partial deployment.`);
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
if (frontendApps.length > 0) {
|
|
4776
|
+
logger$1.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
4777
|
+
for (const appName of frontendApps) {
|
|
4778
|
+
const app = workspace.apps[appName];
|
|
4779
|
+
logger$1.log(`\n 🌐 Deploying ${appName}...`);
|
|
4780
|
+
try {
|
|
4781
|
+
const dokployAppName = `${workspace.name}-${appName}`;
|
|
4782
|
+
let application;
|
|
4783
|
+
try {
|
|
4784
|
+
application = await api.createApplication(dokployAppName, project.projectId, environmentId);
|
|
4785
|
+
logger$1.log(` Created application: ${application.applicationId}`);
|
|
4786
|
+
} catch (error) {
|
|
4787
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4788
|
+
if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Application already exists`);
|
|
4789
|
+
else throw error;
|
|
4790
|
+
}
|
|
4791
|
+
const buildArgs = generatePublicUrlBuildArgs(app, publicUrls);
|
|
4792
|
+
if (buildArgs.length > 0) logger$1.log(` Public URLs: ${buildArgs.join(", ")}`);
|
|
4793
|
+
const imageName = `${workspace.name}-${appName}`;
|
|
4794
|
+
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
4795
|
+
logger$1.log(` Building Docker image: ${imageRef}`);
|
|
4796
|
+
await deployDocker({
|
|
4797
|
+
stage,
|
|
4798
|
+
tag: imageTag,
|
|
4799
|
+
skipPush: false,
|
|
4800
|
+
config: {
|
|
4801
|
+
registry,
|
|
4802
|
+
imageName,
|
|
4803
|
+
appName
|
|
4804
|
+
},
|
|
4805
|
+
buildArgs,
|
|
4806
|
+
publicUrlArgs: getPublicUrlArgNames(app)
|
|
4292
4807
|
});
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4808
|
+
const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
|
|
4809
|
+
if (application) {
|
|
4810
|
+
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
4811
|
+
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
4812
|
+
logger$1.log(` Deploying to Dokploy...`);
|
|
4813
|
+
await api.deployApplication(application.applicationId);
|
|
4814
|
+
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
4815
|
+
try {
|
|
4816
|
+
const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
4817
|
+
await api.createDomain({
|
|
4818
|
+
host,
|
|
4819
|
+
port: app.port,
|
|
4820
|
+
https: true,
|
|
4821
|
+
certificateType: "letsencrypt",
|
|
4822
|
+
applicationId: application.applicationId
|
|
4823
|
+
});
|
|
4824
|
+
const publicUrl = `https://${host}`;
|
|
4825
|
+
publicUrls[appName] = publicUrl;
|
|
4826
|
+
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4827
|
+
} catch (domainError) {
|
|
4828
|
+
const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
4829
|
+
publicUrls[appName] = `https://${host}`;
|
|
4830
|
+
logger$1.log(` ℹ Domain already configured: https://${host}`);
|
|
4831
|
+
}
|
|
4832
|
+
results.push({
|
|
4833
|
+
appName,
|
|
4834
|
+
type: app.type,
|
|
4835
|
+
success: true,
|
|
4836
|
+
applicationId: application.applicationId,
|
|
4837
|
+
imageRef
|
|
4838
|
+
});
|
|
4839
|
+
logger$1.log(` ✓ ${appName} deployed successfully`);
|
|
4840
|
+
} else {
|
|
4841
|
+
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
4842
|
+
const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
4843
|
+
publicUrls[appName] = `https://${host}`;
|
|
4844
|
+
results.push({
|
|
4845
|
+
appName,
|
|
4846
|
+
type: app.type,
|
|
4847
|
+
success: true,
|
|
4848
|
+
imageRef
|
|
4849
|
+
});
|
|
4850
|
+
logger$1.log(` ✓ ${appName} image pushed (app already exists)`);
|
|
4851
|
+
}
|
|
4852
|
+
} catch (error) {
|
|
4853
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4854
|
+
logger$1.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
4297
4855
|
results.push({
|
|
4298
4856
|
appName,
|
|
4299
4857
|
type: app.type,
|
|
4300
|
-
success:
|
|
4301
|
-
|
|
4858
|
+
success: false,
|
|
4859
|
+
error: message
|
|
4302
4860
|
});
|
|
4303
|
-
logger$1.log(` ✓ ${appName} image pushed (app already exists)`);
|
|
4304
4861
|
}
|
|
4305
|
-
} catch (error) {
|
|
4306
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4307
|
-
logger$1.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
4308
|
-
results.push({
|
|
4309
|
-
appName,
|
|
4310
|
-
type: app.type,
|
|
4311
|
-
success: false,
|
|
4312
|
-
error: message
|
|
4313
|
-
});
|
|
4314
4862
|
}
|
|
4315
4863
|
}
|
|
4316
4864
|
const successCount = results.filter((r) => r.success).length;
|
|
@@ -4320,6 +4868,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4320
4868
|
logger$1.log(` Project: ${project.projectId}`);
|
|
4321
4869
|
logger$1.log(` Successful: ${successCount}`);
|
|
4322
4870
|
if (failedCount > 0) logger$1.log(` Failed: ${failedCount}`);
|
|
4871
|
+
if (Object.keys(publicUrls).length > 0) {
|
|
4872
|
+
logger$1.log("\n 📡 Deployed URLs:");
|
|
4873
|
+
for (const [name$1, url] of Object.entries(publicUrls)) logger$1.log(` ${name$1}: ${url}`);
|
|
4874
|
+
}
|
|
4323
4875
|
return {
|
|
4324
4876
|
apps: results,
|
|
4325
4877
|
projectId: project.projectId,
|
|
@@ -4597,10 +5149,10 @@ const GEEKMIDAS_VERSIONS = {
|
|
|
4597
5149
|
"@geekmidas/cli": CLI_VERSION,
|
|
4598
5150
|
"@geekmidas/client": "~0.5.0",
|
|
4599
5151
|
"@geekmidas/cloud": "~0.2.0",
|
|
4600
|
-
"@geekmidas/constructs": "~0.
|
|
5152
|
+
"@geekmidas/constructs": "~0.7.0",
|
|
4601
5153
|
"@geekmidas/db": "~0.3.0",
|
|
4602
5154
|
"@geekmidas/emailkit": "~0.2.0",
|
|
4603
|
-
"@geekmidas/envkit": "~0.
|
|
5155
|
+
"@geekmidas/envkit": "~0.6.0",
|
|
4604
5156
|
"@geekmidas/errors": "~0.1.0",
|
|
4605
5157
|
"@geekmidas/events": "~0.2.0",
|
|
4606
5158
|
"@geekmidas/logger": "~0.4.0",
|
|
@@ -7474,9 +8026,9 @@ async function testCommand(options = {}) {
|
|
|
7474
8026
|
NODE_ENV: "test"
|
|
7475
8027
|
}
|
|
7476
8028
|
});
|
|
7477
|
-
return new Promise((resolve$
|
|
8029
|
+
return new Promise((resolve$3, reject) => {
|
|
7478
8030
|
vitestProcess.on("close", (code) => {
|
|
7479
|
-
if (code === 0) resolve$
|
|
8031
|
+
if (code === 0) resolve$3();
|
|
7480
8032
|
else reject(new Error(`Tests failed with exit code ${code}`));
|
|
7481
8033
|
});
|
|
7482
8034
|
vitestProcess.on("error", (error) => {
|