@geekmidas/cli 1.10.41 → 1.12.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 +149 -0
- package/README.md +14 -0
- package/dist/{CachedStateProvider-37pG1HBx.cjs → CachedStateProvider-BdX7klRc.cjs} +1 -1
- package/dist/{CachedStateProvider-C8A-cCm5.mjs → CachedStateProvider-CzyFct42.mjs} +1 -1
- package/dist/{CachedStateProvider-C8A-cCm5.mjs.map → CachedStateProvider-CzyFct42.mjs.map} +1 -1
- package/dist/CachedStateProvider-GcGziXh1.mjs +3 -0
- package/dist/{CachedStateProvider-CxsXOKkg.cjs → CachedStateProvider-klLWZXWF.cjs} +1 -1
- package/dist/{CachedStateProvider-CxsXOKkg.cjs.map → CachedStateProvider-klLWZXWF.cjs.map} +1 -1
- package/dist/{HostingerProvider-ANWchdiK.mjs → HostingerProvider-B2ZYz7Yc.mjs} +1 -1
- package/dist/{HostingerProvider-ANWchdiK.mjs.map → HostingerProvider-B2ZYz7Yc.mjs.map} +1 -1
- package/dist/{HostingerProvider-5KYmwoK2.cjs → HostingerProvider-CrHfsgz9.cjs} +1 -1
- package/dist/{HostingerProvider-5KYmwoK2.cjs.map → HostingerProvider-CrHfsgz9.cjs.map} +1 -1
- package/dist/{LocalStateProvider-Dp0KkRcw.mjs → LocalStateProvider-CP49rjzt.mjs} +1 -1
- package/dist/{LocalStateProvider-Dp0KkRcw.mjs.map → LocalStateProvider-CP49rjzt.mjs.map} +1 -1
- package/dist/{LocalStateProvider-CLifRC0Y.cjs → LocalStateProvider-CalzNfNA.cjs} +1 -1
- package/dist/{LocalStateProvider-CLifRC0Y.cjs.map → LocalStateProvider-CalzNfNA.cjs.map} +1 -1
- package/dist/{Route53Provider-owQQ4pn6.cjs → Route53Provider-DwgXiLxt.cjs} +1 -1
- package/dist/{Route53Provider-owQQ4pn6.cjs.map → Route53Provider-DwgXiLxt.cjs.map} +1 -1
- package/dist/{Route53Provider-QoPgcXxn.mjs → Route53Provider-tlgBzrRQ.mjs} +1 -1
- package/dist/{Route53Provider-QoPgcXxn.mjs.map → Route53Provider-tlgBzrRQ.mjs.map} +1 -1
- package/dist/{SSMStateProvider-CksOTB8M.mjs → SSMStateProvider-BlrTiV5m.mjs} +1 -1
- package/dist/{SSMStateProvider-CksOTB8M.mjs.map → SSMStateProvider-BlrTiV5m.mjs.map} +1 -1
- package/dist/{SSMStateProvider-CT8tjl9o.cjs → SSMStateProvider-Bt6r8CTL.cjs} +1 -1
- package/dist/{SSMStateProvider-CT8tjl9o.cjs.map → SSMStateProvider-Bt6r8CTL.cjs.map} +1 -1
- package/dist/{backup-provisioner-C4noe75O.cjs → backup-provisioner-B3HBN-2k.cjs} +1 -1
- package/dist/{backup-provisioner-C4noe75O.cjs.map → backup-provisioner-B3HBN-2k.cjs.map} +1 -1
- package/dist/{backup-provisioner-BEXoHTuC.mjs → backup-provisioner-Xunq-p_8.mjs} +1 -1
- package/dist/{backup-provisioner-BEXoHTuC.mjs.map → backup-provisioner-Xunq-p_8.mjs.map} +1 -1
- package/dist/{bundler-c6QvGUBM.mjs → bundler-DkA4qzUD.mjs} +2 -2
- package/dist/{bundler-c6QvGUBM.mjs.map → bundler-DkA4qzUD.mjs.map} +1 -1
- package/dist/{bundler-BG6P249p.cjs → bundler-Dufq3uvI.cjs} +2 -2
- package/dist/{bundler-BG6P249p.cjs.map → bundler-Dufq3uvI.cjs.map} +1 -1
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/dokploy-api-BPYkG-ZZ.cjs +3 -0
- package/dist/dokploy-api-BiSzWM4v.mjs +3 -0
- package/dist/{dokploy-api-Dvyq-LsM.cjs → dokploy-api-CqOIKHme.cjs} +1 -1
- package/dist/{dokploy-api-Dvyq-LsM.cjs.map → dokploy-api-CqOIKHme.cjs.map} +1 -1
- package/dist/{dokploy-api-BIT0683o.mjs → dokploy-api-sfZFlWuE.mjs} +1 -1
- package/dist/{dokploy-api-BIT0683o.mjs.map → dokploy-api-sfZFlWuE.mjs.map} +1 -1
- package/dist/{encryption-CipsS0KW.cjs → encryption-BVS7Vmzs.cjs} +1 -1
- package/dist/encryption-Bd_m4q3g.mjs +3 -0
- package/dist/{encryption-DSkIlEZ6.mjs → encryption-CO5Q4Bhf.mjs} +1 -1
- package/dist/{encryption-DSkIlEZ6.mjs.map → encryption-CO5Q4Bhf.mjs.map} +1 -1
- package/dist/{encryption-DgKS-Dv9.cjs → encryption-MfXAiFTm.cjs} +1 -1
- package/dist/{encryption-DgKS-Dv9.cjs.map → encryption-MfXAiFTm.cjs.map} +1 -1
- package/dist/{index-BBvGMG_A.d.mts → index-Bw548Bta.d.mts} +2 -2
- package/dist/{index-Dz2a7xQU.d.cts.map → index-Bw548Bta.d.mts.map} +1 -1
- package/dist/{index-Dz2a7xQU.d.cts → index-DTpkeFb8.d.cts} +2 -2
- package/dist/{index-BBvGMG_A.d.mts.map → index-DTpkeFb8.d.cts.map} +1 -1
- package/dist/index.cjs +863 -1330
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +990 -1457
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-DHp49UJV.cjs → openapi-BzNOC7_i.cjs} +14 -2
- package/dist/openapi-BzNOC7_i.cjs.map +1 -0
- package/dist/{openapi-DF9GyhyZ.mjs → openapi-Cfwi0fqF.mjs} +14 -2
- package/dist/openapi-Cfwi0fqF.mjs.map +1 -0
- package/dist/{openapi-react-query-C4UdILaI.mjs → openapi-react-query-C4iIvpPf.mjs} +1 -1
- package/dist/{openapi-react-query-C4UdILaI.mjs.map → openapi-react-query-C4iIvpPf.mjs.map} +1 -1
- package/dist/{openapi-react-query-DYbBq-WJ.cjs → openapi-react-query-CYWik72n.cjs} +1 -1
- package/dist/{openapi-react-query-DYbBq-WJ.cjs.map → openapi-react-query-CYWik72n.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{reconcile-CYGmqXvq.mjs → reconcile-B2FoJE0a.mjs} +1 -1
- package/dist/{reconcile-CYGmqXvq.mjs.map → reconcile-B2FoJE0a.mjs.map} +1 -1
- package/dist/{reconcile-D93YG7G5.cjs → reconcile-BzqKJ2Rt.cjs} +1 -1
- package/dist/{reconcile-D93YG7G5.cjs.map → reconcile-BzqKJ2Rt.cjs.map} +1 -1
- package/dist/setup-DDXUXzzl.mjs +765 -0
- package/dist/setup-DDXUXzzl.mjs.map +1 -0
- package/dist/setup-DLwhfyt-.cjs +8 -0
- package/dist/setup-Dh-B7YTX.mjs +8 -0
- package/dist/setup-WZ-xqAQO.cjs +849 -0
- package/dist/setup-WZ-xqAQO.cjs.map +1 -0
- package/dist/{sync-COnAugP-.mjs → sync-B5UHXwRM.mjs} +1 -1
- package/dist/sync-BLA94rgC.cjs +4 -0
- package/dist/{sync-D_NowTkZ.mjs → sync-CSooqRQ8.mjs} +1 -1
- package/dist/{sync-D_NowTkZ.mjs.map → sync-CSooqRQ8.mjs.map} +1 -1
- package/dist/{sync-DGXXSk2v.cjs → sync-o_CtEbvR.cjs} +1 -1
- package/dist/{sync-DGXXSk2v.cjs.map → sync-o_CtEbvR.cjs.map} +1 -1
- package/dist/{types-DdHfUbxk.d.cts → types-B3GQUg3k.d.mts} +3 -1
- package/dist/{types-DdHfUbxk.d.cts.map → types-B3GQUg3k.d.mts.map} +1 -1
- package/dist/{types-OszPdw9m.d.mts → types-BWQDiooe.d.cts} +3 -1
- package/dist/{types-OszPdw9m.d.mts.map → types-BWQDiooe.d.cts.map} +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +6 -5
- package/src/build/__tests__/index-new.spec.ts +3 -1
- package/src/build/__tests__/manifests.spec.ts +2 -2
- package/src/build/index.ts +61 -21
- package/src/build/manifests.ts +62 -5
- package/src/dev/index.ts +25 -11
- package/src/generators/EndpointGenerator.ts +14 -2
- package/src/generators/QueueGenerator.ts +245 -0
- package/src/generators/SubscriberGenerator.ts +5 -0
- package/src/generators/TopicGenerator.ts +45 -0
- package/src/generators/__tests__/QueueGenerator.spec.ts +141 -0
- package/src/generators/index.ts +2 -0
- package/src/index.ts +4 -0
- package/src/init/versions.ts +6 -6
- package/src/setup/__tests__/auto-setup.spec.ts +147 -0
- package/src/setup/index.ts +46 -6
- package/src/test/index.ts +17 -0
- package/src/types.ts +14 -51
- package/dist/CachedStateProvider-CC0DyO09.mjs +0 -3
- package/dist/dokploy-api-BOx71j6F.cjs +0 -3
- package/dist/dokploy-api-BZGk-lkD.mjs +0 -3
- package/dist/encryption-Db2zvGqM.mjs +0 -3
- package/dist/openapi-DF9GyhyZ.mjs.map +0 -1
- package/dist/openapi-DHp49UJV.cjs.map +0 -1
- package/dist/sync-D1Pa30oV.cjs +0 -4
package/dist/index.cjs
CHANGED
|
@@ -5,12 +5,13 @@ const require_config = require('./config-DujV8DbR.cjs');
|
|
|
5
5
|
const require_credentials = require('./credentials-C8DWtnMY.cjs');
|
|
6
6
|
const require_fullstack_secrets = require('./fullstack-secrets-BYzi6Hwl.cjs');
|
|
7
7
|
const require_storage = require('./storage-B1wvztiJ.cjs');
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
8
|
+
const require_setup = require('./setup-WZ-xqAQO.cjs');
|
|
9
|
+
const require_openapi = require('./openapi-BzNOC7_i.cjs');
|
|
10
|
+
const require_dokploy_api = require('./dokploy-api-CqOIKHme.cjs');
|
|
11
|
+
const require_encryption = require('./encryption-MfXAiFTm.cjs');
|
|
12
|
+
const require_CachedStateProvider = require('./CachedStateProvider-klLWZXWF.cjs');
|
|
13
|
+
const require_openapi_react_query = require('./openapi-react-query-CYWik72n.cjs');
|
|
14
|
+
require('./sync-o_CtEbvR.cjs');
|
|
14
15
|
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
15
16
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
16
17
|
const commander = require_chunk.__toESM(require("commander"));
|
|
@@ -20,13 +21,13 @@ const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
|
20
21
|
const node_child_process = require_chunk.__toESM(require("node:child_process"));
|
|
21
22
|
const chokidar = require_chunk.__toESM(require("chokidar"));
|
|
22
23
|
const fast_glob = require_chunk.__toESM(require("fast-glob"));
|
|
23
|
-
const node_net = require_chunk.__toESM(require("node:net"));
|
|
24
|
-
const dotenv = require_chunk.__toESM(require("dotenv"));
|
|
25
24
|
const yaml = require_chunk.__toESM(require("yaml"));
|
|
26
25
|
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
27
26
|
const __geekmidas_constructs_crons = require_chunk.__toESM(require("@geekmidas/constructs/crons"));
|
|
28
27
|
const __geekmidas_constructs_functions = require_chunk.__toESM(require("@geekmidas/constructs/functions"));
|
|
28
|
+
const __geekmidas_constructs_queue = require_chunk.__toESM(require("@geekmidas/constructs/queue"));
|
|
29
29
|
const __geekmidas_constructs_subscribers = require_chunk.__toESM(require("@geekmidas/constructs/subscribers"));
|
|
30
|
+
const __geekmidas_constructs_topic = require_chunk.__toESM(require("@geekmidas/constructs/topic"));
|
|
30
31
|
const node_url = require_chunk.__toESM(require("node:url"));
|
|
31
32
|
const pg = require_chunk.__toESM(require("pg"));
|
|
32
33
|
const node_dns_promises = require_chunk.__toESM(require("node:dns/promises"));
|
|
@@ -35,7 +36,7 @@ const prompts = require_chunk.__toESM(require("prompts"));
|
|
|
35
36
|
|
|
36
37
|
//#region package.json
|
|
37
38
|
var name = "@geekmidas/cli";
|
|
38
|
-
var version = "1.
|
|
39
|
+
var version = "1.11.0";
|
|
39
40
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
40
41
|
var private$1 = false;
|
|
41
42
|
var type = "module";
|
|
@@ -115,6 +116,7 @@ var dependencies = {
|
|
|
115
116
|
"@geekmidas/envkit": "workspace:~",
|
|
116
117
|
"@geekmidas/errors": "workspace:~",
|
|
117
118
|
"@geekmidas/logger": "workspace:~",
|
|
119
|
+
"@geekmidas/manifest": "workspace:*",
|
|
118
120
|
"@geekmidas/schema": "workspace:~",
|
|
119
121
|
"chokidar": "~4.0.3",
|
|
120
122
|
"commander": "^12.1.0",
|
|
@@ -158,12 +160,12 @@ var package_default = {
|
|
|
158
160
|
|
|
159
161
|
//#endregion
|
|
160
162
|
//#region src/auth/index.ts
|
|
161
|
-
const logger$
|
|
163
|
+
const logger$12 = console;
|
|
162
164
|
/**
|
|
163
165
|
* Validate Dokploy token by making a test API call
|
|
164
166
|
*/
|
|
165
167
|
async function validateDokployToken(endpoint, token) {
|
|
166
|
-
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-
|
|
168
|
+
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-BPYkG-ZZ.cjs"));
|
|
167
169
|
const api = new DokployApi$1({
|
|
168
170
|
baseUrl: endpoint,
|
|
169
171
|
token
|
|
@@ -177,7 +179,7 @@ async function prompt$1(message, hidden = false) {
|
|
|
177
179
|
if (!process.stdin.isTTY) throw new Error("Interactive input required. Please provide --token option.");
|
|
178
180
|
if (hidden) {
|
|
179
181
|
process.stdout.write(message);
|
|
180
|
-
return new Promise((resolve$
|
|
182
|
+
return new Promise((resolve$3, reject) => {
|
|
181
183
|
let value = "";
|
|
182
184
|
const cleanup = () => {
|
|
183
185
|
process.stdin.setRawMode(false);
|
|
@@ -194,7 +196,7 @@ async function prompt$1(message, hidden = false) {
|
|
|
194
196
|
if (c === "\n" || c === "\r") {
|
|
195
197
|
cleanup();
|
|
196
198
|
process.stdout.write("\n");
|
|
197
|
-
resolve$
|
|
199
|
+
resolve$3(value);
|
|
198
200
|
} else if (c === "") {
|
|
199
201
|
cleanup();
|
|
200
202
|
process.stdout.write("\n");
|
|
@@ -226,36 +228,36 @@ async function prompt$1(message, hidden = false) {
|
|
|
226
228
|
async function loginCommand(options) {
|
|
227
229
|
const { service, token: providedToken, endpoint: providedEndpoint } = options;
|
|
228
230
|
if (service === "dokploy") {
|
|
229
|
-
logger$
|
|
231
|
+
logger$12.log("\n🔐 Logging in to Dokploy...\n");
|
|
230
232
|
let endpoint = providedEndpoint;
|
|
231
233
|
if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
232
234
|
endpoint = endpoint.replace(/\/$/, "");
|
|
233
235
|
try {
|
|
234
236
|
new URL(endpoint);
|
|
235
237
|
} catch {
|
|
236
|
-
logger$
|
|
238
|
+
logger$12.error("Invalid URL format");
|
|
237
239
|
process.exit(1);
|
|
238
240
|
}
|
|
239
241
|
let token = providedToken;
|
|
240
242
|
if (!token) {
|
|
241
|
-
logger$
|
|
243
|
+
logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
242
244
|
token = await prompt$1("API Token: ", true);
|
|
243
245
|
}
|
|
244
246
|
if (!token) {
|
|
245
|
-
logger$
|
|
247
|
+
logger$12.error("Token is required");
|
|
246
248
|
process.exit(1);
|
|
247
249
|
}
|
|
248
|
-
logger$
|
|
250
|
+
logger$12.log("\nValidating credentials...");
|
|
249
251
|
const isValid = await validateDokployToken(endpoint, token);
|
|
250
252
|
if (!isValid) {
|
|
251
|
-
logger$
|
|
253
|
+
logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
252
254
|
process.exit(1);
|
|
253
255
|
}
|
|
254
256
|
await require_credentials.storeDokployCredentials(token, endpoint);
|
|
255
|
-
logger$
|
|
256
|
-
logger$
|
|
257
|
-
logger$
|
|
258
|
-
logger$
|
|
257
|
+
logger$12.log("\n✓ Successfully logged in to Dokploy!");
|
|
258
|
+
logger$12.log(` Endpoint: ${endpoint}`);
|
|
259
|
+
logger$12.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
|
|
260
|
+
logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
259
261
|
}
|
|
260
262
|
}
|
|
261
263
|
/**
|
|
@@ -265,28 +267,28 @@ async function logoutCommand(options) {
|
|
|
265
267
|
const { service = "dokploy" } = options;
|
|
266
268
|
if (service === "all") {
|
|
267
269
|
const dokployRemoved = await require_credentials.removeDokployCredentials();
|
|
268
|
-
if (dokployRemoved) logger$
|
|
269
|
-
else logger$
|
|
270
|
+
if (dokployRemoved) logger$12.log("\n✓ Logged out from all services");
|
|
271
|
+
else logger$12.log("\nNo stored credentials found");
|
|
270
272
|
return;
|
|
271
273
|
}
|
|
272
274
|
if (service === "dokploy") {
|
|
273
275
|
const removed = await require_credentials.removeDokployCredentials();
|
|
274
|
-
if (removed) logger$
|
|
275
|
-
else logger$
|
|
276
|
+
if (removed) logger$12.log("\n✓ Logged out from Dokploy");
|
|
277
|
+
else logger$12.log("\nNo Dokploy credentials found");
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
280
|
/**
|
|
279
281
|
* Show current login status
|
|
280
282
|
*/
|
|
281
283
|
async function whoamiCommand() {
|
|
282
|
-
logger$
|
|
284
|
+
logger$12.log("\n📋 Current credentials:\n");
|
|
283
285
|
const dokploy = await require_credentials.getDokployCredentials();
|
|
284
286
|
if (dokploy) {
|
|
285
|
-
logger$
|
|
286
|
-
logger$
|
|
287
|
-
logger$
|
|
288
|
-
} else logger$
|
|
289
|
-
logger$
|
|
287
|
+
logger$12.log(" Dokploy:");
|
|
288
|
+
logger$12.log(` Endpoint: ${dokploy.endpoint}`);
|
|
289
|
+
logger$12.log(` Token: ${maskToken(dokploy.token)}`);
|
|
290
|
+
} else logger$12.log(" Dokploy: Not logged in");
|
|
291
|
+
logger$12.log(`\n Credentials file: ${require_credentials.getCredentialsPath()}`);
|
|
290
292
|
}
|
|
291
293
|
/**
|
|
292
294
|
* Mask a token for display
|
|
@@ -367,477 +369,12 @@ function isEnabled(config) {
|
|
|
367
369
|
return config.enabled !== false;
|
|
368
370
|
}
|
|
369
371
|
|
|
370
|
-
//#endregion
|
|
371
|
-
//#region src/credentials/index.ts
|
|
372
|
-
const logger$13 = console;
|
|
373
|
-
/**
|
|
374
|
-
* Load environment files
|
|
375
|
-
* @internal Exported for testing
|
|
376
|
-
*/
|
|
377
|
-
function loadEnvFiles(envConfig, cwd = process.cwd()) {
|
|
378
|
-
const loaded = [];
|
|
379
|
-
const missing = [];
|
|
380
|
-
const envFiles = envConfig ? Array.isArray(envConfig) ? envConfig : [envConfig] : [".env"];
|
|
381
|
-
for (const envFile of envFiles) {
|
|
382
|
-
const envPath = (0, node_path.resolve)(cwd, envFile);
|
|
383
|
-
if ((0, node_fs.existsSync)(envPath)) {
|
|
384
|
-
(0, dotenv.config)({
|
|
385
|
-
path: envPath,
|
|
386
|
-
override: true,
|
|
387
|
-
quiet: true
|
|
388
|
-
});
|
|
389
|
-
loaded.push(envFile);
|
|
390
|
-
} else if (envConfig) missing.push(envFile);
|
|
391
|
-
}
|
|
392
|
-
return {
|
|
393
|
-
loaded,
|
|
394
|
-
missing
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Check if a port is available
|
|
399
|
-
* @internal Exported for testing
|
|
400
|
-
*/
|
|
401
|
-
async function isPortAvailable(port) {
|
|
402
|
-
return new Promise((resolve$4) => {
|
|
403
|
-
const server = (0, node_net.createServer)();
|
|
404
|
-
server.once("error", (err) => {
|
|
405
|
-
if (err.code === "EADDRINUSE") resolve$4(false);
|
|
406
|
-
else resolve$4(false);
|
|
407
|
-
});
|
|
408
|
-
server.once("listening", () => {
|
|
409
|
-
server.close();
|
|
410
|
-
resolve$4(true);
|
|
411
|
-
});
|
|
412
|
-
server.listen(port);
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Find an available port starting from the preferred port
|
|
417
|
-
* @internal Exported for testing
|
|
418
|
-
*/
|
|
419
|
-
async function findAvailablePort(preferredPort, maxAttempts = 10) {
|
|
420
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
421
|
-
const port = preferredPort + i;
|
|
422
|
-
if (await isPortAvailable(port)) return port;
|
|
423
|
-
logger$13.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
|
|
424
|
-
}
|
|
425
|
-
throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
|
|
426
|
-
}
|
|
427
|
-
const PORT_STATE_PATH = ".gkm/ports.json";
|
|
428
|
-
/**
|
|
429
|
-
* Parse docker-compose.yml and extract all port mappings that use env var interpolation.
|
|
430
|
-
* Entries like `'${POSTGRES_HOST_PORT:-5432}:5432'` are captured.
|
|
431
|
-
* Fixed port mappings like `'5050:80'` are skipped.
|
|
432
|
-
* @internal Exported for testing
|
|
433
|
-
*/
|
|
434
|
-
function parseComposePortMappings(composePath) {
|
|
435
|
-
if (!(0, node_fs.existsSync)(composePath)) return [];
|
|
436
|
-
const content = (0, node_fs.readFileSync)(composePath, "utf-8");
|
|
437
|
-
const compose = (0, yaml.parse)(content);
|
|
438
|
-
if (!compose?.services) return [];
|
|
439
|
-
const results = [];
|
|
440
|
-
for (const [serviceName, serviceConfig] of Object.entries(compose.services)) for (const portMapping of serviceConfig?.ports ?? []) {
|
|
441
|
-
const match = String(portMapping).match(/\$\{(\w+):-(\d+)\}:(\d+)/);
|
|
442
|
-
if (match?.[1] && match[2] && match[3]) results.push({
|
|
443
|
-
service: serviceName,
|
|
444
|
-
envVar: match[1],
|
|
445
|
-
defaultPort: Number(match[2]),
|
|
446
|
-
containerPort: Number(match[3])
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
return results;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Load saved port state from .gkm/ports.json.
|
|
453
|
-
* @internal Exported for testing
|
|
454
|
-
*/
|
|
455
|
-
async function loadPortState(workspaceRoot) {
|
|
456
|
-
try {
|
|
457
|
-
const raw = await (0, node_fs_promises.readFile)((0, node_path.join)(workspaceRoot, PORT_STATE_PATH), "utf-8");
|
|
458
|
-
return JSON.parse(raw);
|
|
459
|
-
} catch {
|
|
460
|
-
return {};
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Save port state to .gkm/ports.json.
|
|
465
|
-
* @internal Exported for testing
|
|
466
|
-
*/
|
|
467
|
-
async function savePortState(workspaceRoot, ports) {
|
|
468
|
-
const dir = (0, node_path.join)(workspaceRoot, ".gkm");
|
|
469
|
-
await (0, node_fs_promises.mkdir)(dir, { recursive: true });
|
|
470
|
-
await (0, node_fs_promises.writeFile)((0, node_path.join)(workspaceRoot, PORT_STATE_PATH), `${JSON.stringify(ports, null, 2)}\n`);
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Check if a project's own Docker container is running and return its host port.
|
|
474
|
-
* Uses `docker compose port` scoped to the project's compose file.
|
|
475
|
-
* @internal Exported for testing
|
|
476
|
-
*/
|
|
477
|
-
function getContainerHostPort(workspaceRoot, service, containerPort) {
|
|
478
|
-
try {
|
|
479
|
-
const result = (0, node_child_process.execSync)(`docker compose port ${service} ${containerPort}`, {
|
|
480
|
-
cwd: workspaceRoot,
|
|
481
|
-
stdio: "pipe"
|
|
482
|
-
}).toString().trim();
|
|
483
|
-
const match = result.match(/:(\d+)$/);
|
|
484
|
-
return match ? Number(match[1]) : null;
|
|
485
|
-
} catch {
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Resolve host ports for Docker services by parsing docker-compose.yml.
|
|
491
|
-
* Priority: running container → saved state → find available port.
|
|
492
|
-
* Persists resolved ports to .gkm/ports.json.
|
|
493
|
-
* @internal Exported for testing
|
|
494
|
-
*/
|
|
495
|
-
async function resolveServicePorts(workspaceRoot) {
|
|
496
|
-
const composePath = (0, node_path.join)(workspaceRoot, "docker-compose.yml");
|
|
497
|
-
const mappings = parseComposePortMappings(composePath);
|
|
498
|
-
if (mappings.length === 0) return {
|
|
499
|
-
dockerEnv: {},
|
|
500
|
-
ports: {},
|
|
501
|
-
mappings: []
|
|
502
|
-
};
|
|
503
|
-
const savedState = await loadPortState(workspaceRoot);
|
|
504
|
-
const dockerEnv = {};
|
|
505
|
-
const ports = {};
|
|
506
|
-
const assignedPorts = /* @__PURE__ */ new Set();
|
|
507
|
-
logger$13.log("\n🔌 Resolving service ports...");
|
|
508
|
-
for (const mapping of mappings) {
|
|
509
|
-
const containerPort = getContainerHostPort(workspaceRoot, mapping.service, mapping.containerPort);
|
|
510
|
-
if (containerPort !== null) {
|
|
511
|
-
ports[mapping.envVar] = containerPort;
|
|
512
|
-
dockerEnv[mapping.envVar] = String(containerPort);
|
|
513
|
-
assignedPorts.add(containerPort);
|
|
514
|
-
logger$13.log(` 🔄 ${mapping.service}:${mapping.containerPort}: reusing existing container on port ${containerPort}`);
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
const savedPort = savedState[mapping.envVar];
|
|
518
|
-
if (savedPort && !assignedPorts.has(savedPort) && await isPortAvailable(savedPort)) {
|
|
519
|
-
ports[mapping.envVar] = savedPort;
|
|
520
|
-
dockerEnv[mapping.envVar] = String(savedPort);
|
|
521
|
-
assignedPorts.add(savedPort);
|
|
522
|
-
logger$13.log(` 💾 ${mapping.service}:${mapping.containerPort}: using saved port ${savedPort}`);
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
let resolvedPort = await findAvailablePort(mapping.defaultPort);
|
|
526
|
-
while (assignedPorts.has(resolvedPort)) resolvedPort = await findAvailablePort(resolvedPort + 1);
|
|
527
|
-
ports[mapping.envVar] = resolvedPort;
|
|
528
|
-
dockerEnv[mapping.envVar] = String(resolvedPort);
|
|
529
|
-
assignedPorts.add(resolvedPort);
|
|
530
|
-
if (resolvedPort !== mapping.defaultPort) logger$13.log(` ⚡ ${mapping.service}:${mapping.containerPort}: port ${mapping.defaultPort} occupied, using port ${resolvedPort}`);
|
|
531
|
-
else logger$13.log(` ✅ ${mapping.service}:${mapping.containerPort}: using default port ${resolvedPort}`);
|
|
532
|
-
}
|
|
533
|
-
await savePortState(workspaceRoot, ports);
|
|
534
|
-
return {
|
|
535
|
-
dockerEnv,
|
|
536
|
-
ports,
|
|
537
|
-
mappings
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Replace a port in a URL string.
|
|
542
|
-
* Handles both `hostname:port` and `localhost:port` patterns.
|
|
543
|
-
* @internal Exported for testing
|
|
544
|
-
*/
|
|
545
|
-
function replacePortInUrl(url, oldPort, newPort) {
|
|
546
|
-
if (oldPort === newPort) return url;
|
|
547
|
-
let result = url.replace(new RegExp(`:${oldPort}(?=[/?#]|$)`, "g"), `:${newPort}`);
|
|
548
|
-
result = result.replace(new RegExp(`%3A${oldPort}(?=[%/?#&]|$)`, "gi"), `%3A${newPort}`);
|
|
549
|
-
return result;
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Rewrite connection URLs and port vars in secrets with resolved ports.
|
|
553
|
-
* Uses the parsed compose mappings to determine which default ports to replace.
|
|
554
|
-
* Pure transform — does not modify secrets on disk.
|
|
555
|
-
* @internal Exported for testing
|
|
556
|
-
*/
|
|
557
|
-
function rewriteUrlsWithPorts(secrets, resolvedPorts) {
|
|
558
|
-
const { ports, mappings } = resolvedPorts;
|
|
559
|
-
const result = { ...secrets };
|
|
560
|
-
const portReplacements = [];
|
|
561
|
-
const serviceNames = /* @__PURE__ */ new Set();
|
|
562
|
-
for (const mapping of mappings) {
|
|
563
|
-
serviceNames.add(mapping.service);
|
|
564
|
-
const resolved = ports[mapping.envVar];
|
|
565
|
-
if (resolved !== void 0) portReplacements.push({
|
|
566
|
-
defaultPort: mapping.defaultPort,
|
|
567
|
-
resolvedPort: resolved
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
for (const [key, value] of Object.entries(result)) {
|
|
571
|
-
if (!key.endsWith("_HOST")) continue;
|
|
572
|
-
if (serviceNames.has(value)) result[key] = "localhost";
|
|
573
|
-
}
|
|
574
|
-
for (const [key, value] of Object.entries(result)) {
|
|
575
|
-
if (!key.endsWith("_PORT")) continue;
|
|
576
|
-
for (const { defaultPort, resolvedPort } of portReplacements) if (value === String(defaultPort)) result[key] = String(resolvedPort);
|
|
577
|
-
}
|
|
578
|
-
for (const [key, value] of Object.entries(result)) {
|
|
579
|
-
if (!key.endsWith("_URL") && !key.endsWith("_ENDPOINT") && !key.endsWith("_CONNECTION_STRING") && key !== "DATABASE_URL") continue;
|
|
580
|
-
let rewritten = value;
|
|
581
|
-
for (const name$1 of serviceNames) rewritten = rewritten.replace(new RegExp(`@${name$1}:`, "g"), "@localhost:");
|
|
582
|
-
for (const { defaultPort, resolvedPort } of portReplacements) rewritten = replacePortInUrl(rewritten, defaultPort, resolvedPort);
|
|
583
|
-
result[key] = rewritten;
|
|
584
|
-
}
|
|
585
|
-
return result;
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Build the environment variables to pass to `docker compose up`.
|
|
589
|
-
* Merges process.env, secrets, and port mappings so that Docker Compose
|
|
590
|
-
* can interpolate variables like ${POSTGRES_USER} correctly.
|
|
591
|
-
* @internal Exported for testing
|
|
592
|
-
*/
|
|
593
|
-
function buildDockerComposeEnv(secretsEnv, portEnv) {
|
|
594
|
-
return {
|
|
595
|
-
...process.env,
|
|
596
|
-
...secretsEnv,
|
|
597
|
-
...portEnv
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Parse all service names from a docker-compose.yml file.
|
|
602
|
-
* @internal Exported for testing
|
|
603
|
-
*/
|
|
604
|
-
function parseComposeServiceNames(composePath) {
|
|
605
|
-
if (!(0, node_fs.existsSync)(composePath)) return [];
|
|
606
|
-
const content = (0, node_fs.readFileSync)(composePath, "utf-8");
|
|
607
|
-
const compose = (0, yaml.parse)(content);
|
|
608
|
-
return Object.keys(compose?.services ?? {});
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Start docker-compose services for a single-app project (no workspace config).
|
|
612
|
-
* Starts all services defined in docker-compose.yml.
|
|
613
|
-
*/
|
|
614
|
-
async function startComposeServices(cwd, portEnv, secretsEnv) {
|
|
615
|
-
const composeFile = (0, node_path.join)(cwd, "docker-compose.yml");
|
|
616
|
-
if (!(0, node_fs.existsSync)(composeFile)) return;
|
|
617
|
-
const servicesToStart = parseComposeServiceNames(composeFile);
|
|
618
|
-
if (servicesToStart.length === 0) return;
|
|
619
|
-
logger$13.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
620
|
-
try {
|
|
621
|
-
(0, node_child_process.execSync)(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
622
|
-
cwd,
|
|
623
|
-
stdio: "inherit",
|
|
624
|
-
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
625
|
-
});
|
|
626
|
-
logger$13.log("✅ Services started");
|
|
627
|
-
} catch (error) {
|
|
628
|
-
logger$13.error("❌ Failed to start services:", error.message);
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Start docker-compose services for a workspace.
|
|
634
|
-
* Discovers all services from docker-compose.yml and starts everything
|
|
635
|
-
* except app services (which are managed by turbo).
|
|
636
|
-
* @internal Exported for testing
|
|
637
|
-
*/
|
|
638
|
-
async function startWorkspaceServices(workspace, portEnv, secretsEnv) {
|
|
639
|
-
const composeFile = (0, node_path.join)(workspace.root, "docker-compose.yml");
|
|
640
|
-
if (!(0, node_fs.existsSync)(composeFile)) return;
|
|
641
|
-
const allServices = parseComposeServiceNames(composeFile);
|
|
642
|
-
const appNames = new Set(Object.keys(workspace.apps));
|
|
643
|
-
const servicesToStart = allServices.filter((name$1) => !appNames.has(name$1));
|
|
644
|
-
if (servicesToStart.length === 0) return;
|
|
645
|
-
logger$13.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
646
|
-
try {
|
|
647
|
-
(0, node_child_process.execSync)(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
648
|
-
cwd: workspace.root,
|
|
649
|
-
stdio: "inherit",
|
|
650
|
-
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
651
|
-
});
|
|
652
|
-
logger$13.log("✅ Services started");
|
|
653
|
-
} catch (error) {
|
|
654
|
-
logger$13.error("❌ Failed to start services:", error.message);
|
|
655
|
-
throw error;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Load and flatten secrets for an app from encrypted storage.
|
|
660
|
-
* For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.
|
|
661
|
-
* @internal Exported for testing
|
|
662
|
-
*/
|
|
663
|
-
async function loadSecretsForApp(secretsRoot, appName, stages = ["dev", "development"]) {
|
|
664
|
-
let secrets = {};
|
|
665
|
-
for (const stage of stages) if (require_storage.secretsExist(stage, secretsRoot)) {
|
|
666
|
-
const stageSecrets = await require_storage.readStageSecrets(stage, secretsRoot);
|
|
667
|
-
if (stageSecrets) {
|
|
668
|
-
logger$13.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
669
|
-
secrets = require_storage.toEmbeddableSecrets(stageSecrets);
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
if (Object.keys(secrets).length === 0) return {};
|
|
674
|
-
if (!appName) return secrets;
|
|
675
|
-
const prefix = appName.toUpperCase();
|
|
676
|
-
const mapped = { ...secrets };
|
|
677
|
-
const appDbUrl = secrets[`${prefix}_DATABASE_URL`];
|
|
678
|
-
if (appDbUrl) mapped.DATABASE_URL = appDbUrl;
|
|
679
|
-
return mapped;
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Walk up the directory tree to find the root containing .gkm/secrets/.
|
|
683
|
-
* @internal Exported for testing
|
|
684
|
-
*/
|
|
685
|
-
function findSecretsRoot(startDir) {
|
|
686
|
-
let dir = startDir;
|
|
687
|
-
while (dir !== "/") {
|
|
688
|
-
if ((0, node_fs.existsSync)((0, node_path.join)(dir, ".gkm", "secrets"))) return dir;
|
|
689
|
-
const parent = (0, node_path.dirname)(dir);
|
|
690
|
-
if (parent === dir) break;
|
|
691
|
-
dir = parent;
|
|
692
|
-
}
|
|
693
|
-
return startDir;
|
|
694
|
-
}
|
|
695
|
-
/**
|
|
696
|
-
* Generate the credentials injection code snippet.
|
|
697
|
-
* This is the common logic used by both entry wrapper and exec preload.
|
|
698
|
-
* @internal
|
|
699
|
-
*/
|
|
700
|
-
function generateCredentialsInjection(secretsJsonPath) {
|
|
701
|
-
return `import { existsSync, readFileSync } from 'node:fs';
|
|
702
|
-
|
|
703
|
-
// Inject dev secrets via globalThis and process.env
|
|
704
|
-
// Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where
|
|
705
|
-
// Object.assign on the Credentials export only mutates one module copy.
|
|
706
|
-
const secretsPath = '${secretsJsonPath}';
|
|
707
|
-
if (existsSync(secretsPath)) {
|
|
708
|
-
const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
|
|
709
|
-
globalThis.__gkm_credentials__ = secrets;
|
|
710
|
-
Object.assign(process.env, secrets);
|
|
711
|
-
}
|
|
712
|
-
`;
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* Create a preload script that injects secrets into Credentials.
|
|
716
|
-
* Used by `gkm exec` to inject secrets before running any command.
|
|
717
|
-
* @internal Exported for testing
|
|
718
|
-
*/
|
|
719
|
-
async function createCredentialsPreload(preloadPath, secretsJsonPath) {
|
|
720
|
-
const content = `/**
|
|
721
|
-
* Credentials preload generated by 'gkm exec'
|
|
722
|
-
* This file is loaded via NODE_OPTIONS="--import <path>"
|
|
723
|
-
*/
|
|
724
|
-
${generateCredentialsInjection(secretsJsonPath)}`;
|
|
725
|
-
await (0, node_fs_promises.writeFile)(preloadPath, content);
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Create a wrapper script that injects secrets before importing the entry file.
|
|
729
|
-
* @internal Exported for testing
|
|
730
|
-
*/
|
|
731
|
-
async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
|
|
732
|
-
const credentialsInjection = secretsJsonPath ? `${generateCredentialsInjection(secretsJsonPath)}
|
|
733
|
-
` : "";
|
|
734
|
-
const content = `#!/usr/bin/env node
|
|
735
|
-
/**
|
|
736
|
-
* Entry wrapper generated by 'gkm dev --entry'
|
|
737
|
-
*/
|
|
738
|
-
${credentialsInjection}// Import and run the user's entry file (dynamic import ensures secrets load first)
|
|
739
|
-
await import('${entryPath}');
|
|
740
|
-
`;
|
|
741
|
-
await (0, node_fs_promises.writeFile)(wrapperPath, content);
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Prepare credentials for dev/exec/test modes.
|
|
745
|
-
* Loads workspace config, secrets, resolves Docker ports, rewrites URLs,
|
|
746
|
-
* injects PORT, dependency URLs, and writes credentials JSON.
|
|
747
|
-
*
|
|
748
|
-
* @param options.resolveDockerPorts - How to resolve Docker ports:
|
|
749
|
-
* - `'full'` (default): probe running containers, saved state, then find available ports. Used by dev/test.
|
|
750
|
-
* - `'readonly'`: check running containers and saved state only, never probe for new ports. Used by exec.
|
|
751
|
-
* @param options.stages - Secret stages to try, in order. Default: ['dev', 'development'].
|
|
752
|
-
* @param options.startDocker - Start Docker Compose services after port resolution. Default: false.
|
|
753
|
-
* @param options.secretsFileName - Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json'.
|
|
754
|
-
* @internal Exported for testing
|
|
755
|
-
*/
|
|
756
|
-
async function prepareEntryCredentials(options) {
|
|
757
|
-
const cwd = options.cwd ?? process.cwd();
|
|
758
|
-
const portMode = options.resolveDockerPorts ?? "full";
|
|
759
|
-
let workspaceAppPort;
|
|
760
|
-
let secretsRoot = cwd;
|
|
761
|
-
let appName;
|
|
762
|
-
let appInfo;
|
|
763
|
-
try {
|
|
764
|
-
appInfo = await require_config.loadWorkspaceAppInfo(cwd);
|
|
765
|
-
workspaceAppPort = appInfo.app.port;
|
|
766
|
-
secretsRoot = appInfo.workspaceRoot;
|
|
767
|
-
appName = appInfo.appName;
|
|
768
|
-
} catch {
|
|
769
|
-
secretsRoot = findSecretsRoot(cwd);
|
|
770
|
-
appName = require_config.getAppNameFromCwd(cwd) ?? void 0;
|
|
771
|
-
}
|
|
772
|
-
const resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3e3;
|
|
773
|
-
const credentials = await loadSecretsForApp(secretsRoot, appName, options.stages);
|
|
774
|
-
credentials.PORT = String(resolvedPort);
|
|
775
|
-
const composePath = (0, node_path.join)(secretsRoot, "docker-compose.yml");
|
|
776
|
-
const mappings = parseComposePortMappings(composePath);
|
|
777
|
-
if (mappings.length > 0) {
|
|
778
|
-
let resolvedPorts;
|
|
779
|
-
if (portMode === "full") resolvedPorts = await resolveServicePorts(secretsRoot);
|
|
780
|
-
else {
|
|
781
|
-
const savedPorts = await loadPortState(secretsRoot);
|
|
782
|
-
const ports = {};
|
|
783
|
-
for (const mapping of mappings) {
|
|
784
|
-
const containerPort = getContainerHostPort(secretsRoot, mapping.service, mapping.containerPort);
|
|
785
|
-
if (containerPort !== null) ports[mapping.envVar] = containerPort;
|
|
786
|
-
else {
|
|
787
|
-
const saved = savedPorts[mapping.envVar];
|
|
788
|
-
if (saved !== void 0) ports[mapping.envVar] = saved;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
resolvedPorts = {
|
|
792
|
-
dockerEnv: {},
|
|
793
|
-
ports,
|
|
794
|
-
mappings
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
if (options.startDocker) if (appInfo) await startWorkspaceServices(appInfo.workspace, resolvedPorts.dockerEnv, credentials);
|
|
798
|
-
else await startComposeServices(secretsRoot, resolvedPorts.dockerEnv, credentials);
|
|
799
|
-
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
800
|
-
const rewritten = rewriteUrlsWithPorts(credentials, resolvedPorts);
|
|
801
|
-
Object.assign(credentials, rewritten);
|
|
802
|
-
logger$13.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
if (appInfo?.appName) {
|
|
806
|
-
const depEnv = require_workspace.getDependencyEnvVars(appInfo.workspace, appInfo.appName);
|
|
807
|
-
Object.assign(credentials, depEnv);
|
|
808
|
-
}
|
|
809
|
-
if (!credentials.EVENT_PUBLISHER_CONNECTION_STRING && !credentials.EVENT_SUBSCRIBER_CONNECTION_STRING && credentials.PGBOSS_DB_USER) {
|
|
810
|
-
const pgbossUrl = require_fullstack_secrets.generatePgBossUrl({
|
|
811
|
-
username: credentials.PGBOSS_DB_USER,
|
|
812
|
-
password: credentials.PGBOSS_DB_PASSWORD ?? "",
|
|
813
|
-
host: credentials.POSTGRES_HOST ?? "localhost",
|
|
814
|
-
port: Number(credentials.POSTGRES_PORT ?? "5432"),
|
|
815
|
-
database: credentials.POSTGRES_DB ?? "app"
|
|
816
|
-
});
|
|
817
|
-
credentials.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;
|
|
818
|
-
credentials.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;
|
|
819
|
-
}
|
|
820
|
-
const secretsDir = (0, node_path.join)(secretsRoot, ".gkm");
|
|
821
|
-
await (0, node_fs_promises.mkdir)(secretsDir, { recursive: true });
|
|
822
|
-
const secretsFileName = options.secretsFileName ?? (appName ? `dev-secrets-${appName}.json` : "dev-secrets.json");
|
|
823
|
-
const secretsJsonPath = (0, node_path.join)(secretsDir, secretsFileName);
|
|
824
|
-
await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(credentials, null, 2));
|
|
825
|
-
return {
|
|
826
|
-
credentials,
|
|
827
|
-
resolvedPort,
|
|
828
|
-
secretsJsonPath,
|
|
829
|
-
appName,
|
|
830
|
-
secretsRoot,
|
|
831
|
-
appInfo
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
|
|
835
372
|
//#endregion
|
|
836
373
|
//#region src/generators/CronGenerator.ts
|
|
837
374
|
var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
838
375
|
async build(context, constructs, outputDir, options) {
|
|
839
376
|
const provider = options?.provider || "aws-lambda";
|
|
840
|
-
const logger$
|
|
377
|
+
const logger$13 = console;
|
|
841
378
|
const cronInfos = [];
|
|
842
379
|
if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
|
|
843
380
|
const cronsDir = (0, node_path.join)(outputDir, "crons");
|
|
@@ -852,7 +389,7 @@ var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
852
389
|
memorySize: construct.memorySize,
|
|
853
390
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
854
391
|
});
|
|
855
|
-
logger$
|
|
392
|
+
logger$13.log(`Generated cron handler: ${key}`);
|
|
856
393
|
}
|
|
857
394
|
return cronInfos;
|
|
858
395
|
}
|
|
@@ -888,7 +425,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
888
425
|
}
|
|
889
426
|
async build(context, constructs, outputDir, options) {
|
|
890
427
|
const provider = options?.provider || "aws-lambda";
|
|
891
|
-
const logger$
|
|
428
|
+
const logger$13 = console;
|
|
892
429
|
const functionInfos = [];
|
|
893
430
|
if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
|
|
894
431
|
const functionsDir = (0, node_path.join)(outputDir, "functions");
|
|
@@ -902,7 +439,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
902
439
|
memorySize: construct.memorySize,
|
|
903
440
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
904
441
|
});
|
|
905
|
-
logger$
|
|
442
|
+
logger$13.log(`Generated function handler: ${key}`);
|
|
906
443
|
}
|
|
907
444
|
return functionInfos;
|
|
908
445
|
}
|
|
@@ -927,6 +464,176 @@ export const handler = adapter.handler;
|
|
|
927
464
|
}
|
|
928
465
|
};
|
|
929
466
|
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/generators/QueueGenerator.ts
|
|
469
|
+
/**
|
|
470
|
+
* Generates the runtime for `q` queue workers.
|
|
471
|
+
*
|
|
472
|
+
* - **server** (`gkm dev`): a single `queues.ts` exposing `setupQueues()` that
|
|
473
|
+
* runs an in-process pg-boss poller alongside the Hono server — no SQS/Lambda.
|
|
474
|
+
* Each queue subscribes by its **name** on the shared event connection.
|
|
475
|
+
* - **aws-lambda**: one handler file per queue wrapping `AWSLambdaQueue`, backed
|
|
476
|
+
* by an SQS event-source mapping.
|
|
477
|
+
*/
|
|
478
|
+
var QueueGenerator = class extends require_openapi.ConstructGenerator {
|
|
479
|
+
isConstruct(value) {
|
|
480
|
+
return __geekmidas_constructs_queue.Queue.isQueue(value);
|
|
481
|
+
}
|
|
482
|
+
async build(context, constructs, outputDir, options) {
|
|
483
|
+
const provider = options?.provider || "aws-lambda";
|
|
484
|
+
const logger$13 = console;
|
|
485
|
+
const queueInfos = [];
|
|
486
|
+
if (provider === "server") {
|
|
487
|
+
await this.generateServerQueuesFile(outputDir, constructs);
|
|
488
|
+
logger$13.log(`Generated server queues file with ${constructs.length} queues (polling mode)`);
|
|
489
|
+
return queueInfos;
|
|
490
|
+
}
|
|
491
|
+
if (constructs.length === 0 || provider !== "aws-lambda") return queueInfos;
|
|
492
|
+
const queuesDir = (0, node_path.join)(outputDir, "queues");
|
|
493
|
+
await (0, node_fs_promises.mkdir)(queuesDir, { recursive: true });
|
|
494
|
+
for (const { key, construct, path } of constructs) {
|
|
495
|
+
const handlerFile = await this.generateQueueHandler(queuesDir, path.relative, key, context);
|
|
496
|
+
queueInfos.push({
|
|
497
|
+
name: construct.name,
|
|
498
|
+
handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
499
|
+
batchSize: construct.batchSize,
|
|
500
|
+
fifo: construct.fifo,
|
|
501
|
+
timeout: construct.timeout,
|
|
502
|
+
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
503
|
+
});
|
|
504
|
+
logger$13.log(`Generated queue handler: ${key}`);
|
|
505
|
+
}
|
|
506
|
+
return queueInfos;
|
|
507
|
+
}
|
|
508
|
+
async generateQueueHandler(outputDir, sourceFile, exportName, context) {
|
|
509
|
+
const handlerPath = (0, node_path.join)(outputDir, `${exportName}.ts`);
|
|
510
|
+
const importPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile).replace(/\.ts$/, ".js");
|
|
511
|
+
const relativeEnvParserPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), context.envParserPath);
|
|
512
|
+
const content = `import { AWSLambdaQueue } from '@geekmidas/constructs/aws';
|
|
513
|
+
import { ${exportName} } from '${importPath}';
|
|
514
|
+
import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
|
|
515
|
+
|
|
516
|
+
const adapter = new AWSLambdaQueue(envParser, ${exportName});
|
|
517
|
+
|
|
518
|
+
export const handler = adapter.handler;
|
|
519
|
+
`;
|
|
520
|
+
await (0, node_fs_promises.writeFile)(handlerPath, content);
|
|
521
|
+
return handlerPath;
|
|
522
|
+
}
|
|
523
|
+
async generateServerQueuesFile(outputDir, queues) {
|
|
524
|
+
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
525
|
+
const queuesPath = (0, node_path.join)(outputDir, "queues.ts");
|
|
526
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const { path, key } of queues) {
|
|
528
|
+
const importPath = (0, node_path.relative)((0, node_path.dirname)(queuesPath), path.relative).replace(/\.ts$/, ".js");
|
|
529
|
+
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
530
|
+
importsByFile.get(importPath)?.push(key);
|
|
531
|
+
}
|
|
532
|
+
const imports = Array.from(importsByFile.entries()).map(([importPath, exports$2]) => `import { ${exports$2.join(", ")} } from '${importPath}';`).join("\n");
|
|
533
|
+
const allExportNames = queues.map(({ key }) => key);
|
|
534
|
+
const content = `/**
|
|
535
|
+
* Generated queue workers setup
|
|
536
|
+
*
|
|
537
|
+
* ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY
|
|
538
|
+
* This runs an in-process pg-boss poller alongside the HTTP server. Each queue
|
|
539
|
+
* subscribes by its name on the shared event connection
|
|
540
|
+
* (EVENT_SUBSCRIBER_CONNECTION_STRING), so producers using
|
|
541
|
+
* <NAME>_PUBLISHER_CONNECTION_STRING (pgboss:// locally) reach it.
|
|
542
|
+
*
|
|
543
|
+
* For production, queues run as AWS Lambda with SQS event-source mappings.
|
|
544
|
+
*/
|
|
545
|
+
import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
546
|
+
import type { Logger } from '@geekmidas/logger';
|
|
547
|
+
import { EventConnectionFactory, Subscriber } from '@geekmidas/events';
|
|
548
|
+
import type { EventConnection, EventSubscriber } from '@geekmidas/events';
|
|
549
|
+
import { ServiceDiscovery } from '@geekmidas/services';
|
|
550
|
+
${imports}
|
|
551
|
+
|
|
552
|
+
const queues = [
|
|
553
|
+
${allExportNames.join(",\n ")}
|
|
554
|
+
];
|
|
555
|
+
|
|
556
|
+
const activeSubscribers: EventSubscriber<any>[] = [];
|
|
557
|
+
|
|
558
|
+
export async function setupQueues(
|
|
559
|
+
envParser: EnvironmentParser<any>,
|
|
560
|
+
logger: Logger,
|
|
561
|
+
): Promise<void> {
|
|
562
|
+
if (queues.length === 0) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
logger.info('Setting up queue workers in polling mode (local development)');
|
|
567
|
+
|
|
568
|
+
const config = envParser.create((get) => ({
|
|
569
|
+
connectionString: get('EVENT_SUBSCRIBER_CONNECTION_STRING').string(),
|
|
570
|
+
})).parse();
|
|
571
|
+
|
|
572
|
+
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
573
|
+
|
|
574
|
+
let connection: EventConnection;
|
|
575
|
+
try {
|
|
576
|
+
connection = await EventConnectionFactory.fromConnectionString(config.connectionString);
|
|
577
|
+
const connectionType = new URL(config.connectionString).protocol.replace(':', '');
|
|
578
|
+
logger.info({ connectionType }, 'Created shared event connection for queues');
|
|
579
|
+
} catch (error) {
|
|
580
|
+
logger.error({ error }, 'Failed to create event connection for queues');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
for (const queue of queues) {
|
|
585
|
+
try {
|
|
586
|
+
const eventSubscriber = await Subscriber.fromConnection(connection);
|
|
587
|
+
|
|
588
|
+
const services = queue.services.length > 0
|
|
589
|
+
? await serviceDiscovery.register(queue.services)
|
|
590
|
+
: {};
|
|
591
|
+
|
|
592
|
+
// A queue subscribes to a single "type" — its own name.
|
|
593
|
+
await eventSubscriber.subscribe([queue.name], async (message) => {
|
|
594
|
+
try {
|
|
595
|
+
const validation = await queue.messageSchema['~standard'].validate(message.payload);
|
|
596
|
+
if (validation.issues) {
|
|
597
|
+
logger.error({ issues: validation.issues, queue: queue.name }, 'Queue message failed validation');
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
await queue.handler({
|
|
602
|
+
messages: [validation.value],
|
|
603
|
+
services: services,
|
|
604
|
+
logger: queue.logger,
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
logger.debug({ queue: queue.name }, 'Successfully processed queue message');
|
|
608
|
+
} catch (error) {
|
|
609
|
+
logger.error({ error, queue: queue.name }, 'Failed to process queue message');
|
|
610
|
+
// Message will become visible again for retry.
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
activeSubscribers.push(eventSubscriber);
|
|
615
|
+
logger.info({ queue: queue.name }, 'Queue worker started polling');
|
|
616
|
+
} catch (error) {
|
|
617
|
+
logger.error({ error, queue: queue.name }, 'Failed to setup queue worker');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const shutdown = () => {
|
|
622
|
+
logger.info('Stopping all queue workers');
|
|
623
|
+
for (const _ of activeSubscribers) {
|
|
624
|
+
connection.stop();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
process.on('SIGTERM', shutdown);
|
|
629
|
+
process.on('SIGINT', shutdown);
|
|
630
|
+
}
|
|
631
|
+
`;
|
|
632
|
+
await (0, node_fs_promises.writeFile)(queuesPath, content);
|
|
633
|
+
return queuesPath;
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
930
637
|
//#endregion
|
|
931
638
|
//#region src/generators/SubscriberGenerator.ts
|
|
932
639
|
var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
@@ -935,11 +642,11 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
935
642
|
}
|
|
936
643
|
async build(context, constructs, outputDir, options) {
|
|
937
644
|
const provider = options?.provider || "aws-lambda";
|
|
938
|
-
const logger$
|
|
645
|
+
const logger$13 = console;
|
|
939
646
|
const subscriberInfos = [];
|
|
940
647
|
if (provider === "server") {
|
|
941
648
|
await this.generateServerSubscribersFile(outputDir, constructs);
|
|
942
|
-
logger$
|
|
649
|
+
logger$13.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
|
|
943
650
|
return subscriberInfos;
|
|
944
651
|
}
|
|
945
652
|
if (constructs.length === 0) return subscriberInfos;
|
|
@@ -952,11 +659,15 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
952
659
|
name: key,
|
|
953
660
|
handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
954
661
|
subscribedEvents: construct.subscribedEvents || [],
|
|
662
|
+
...construct.topicName ? {
|
|
663
|
+
transport: "topic",
|
|
664
|
+
topic: construct.topicName
|
|
665
|
+
} : {},
|
|
955
666
|
timeout: construct.timeout,
|
|
956
667
|
memorySize: construct.memorySize,
|
|
957
668
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
958
669
|
});
|
|
959
|
-
logger$
|
|
670
|
+
logger$13.log(`Generated subscriber handler: ${key}`);
|
|
960
671
|
}
|
|
961
672
|
return subscriberInfos;
|
|
962
673
|
}
|
|
@@ -1120,9 +831,35 @@ export async function setupSubscribers(
|
|
|
1120
831
|
}
|
|
1121
832
|
};
|
|
1122
833
|
|
|
834
|
+
//#endregion
|
|
835
|
+
//#region src/generators/TopicGenerator.ts
|
|
836
|
+
/**
|
|
837
|
+
* Discovers `t` topics into `manifest.topics`. A topic is a *resource*, not a
|
|
838
|
+
* function — it has no handler to generate. Producers reach it via its derived
|
|
839
|
+
* publisher (`topic.publisher`, whose connection string is sniffed onto the
|
|
840
|
+
* producing construct) and subscribers bind via `s.topic(topic)`; this generator
|
|
841
|
+
* only records the topic + its event contract so infra can provision the SNS
|
|
842
|
+
* topic. Identical output for every provider (locally pg-boss routes by event
|
|
843
|
+
* name, so there's nothing to run).
|
|
844
|
+
*/
|
|
845
|
+
var TopicGenerator = class extends require_openapi.ConstructGenerator {
|
|
846
|
+
isConstruct(value) {
|
|
847
|
+
return __geekmidas_constructs_topic.Topic.isTopic(value);
|
|
848
|
+
}
|
|
849
|
+
async build(_context, constructs, _outputDir, _options) {
|
|
850
|
+
const logger$13 = console;
|
|
851
|
+
const topicInfos = constructs.map(({ construct }) => ({
|
|
852
|
+
name: construct.name,
|
|
853
|
+
events: construct.eventTypes
|
|
854
|
+
}));
|
|
855
|
+
for (const topic of topicInfos) logger$13.log(`Discovered topic: ${topic.name}`);
|
|
856
|
+
return topicInfos;
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
|
|
1123
860
|
//#endregion
|
|
1124
861
|
//#region src/exec/index.ts
|
|
1125
|
-
const logger$
|
|
862
|
+
const logger$11 = console;
|
|
1126
863
|
/**
|
|
1127
864
|
* Run a command with secrets injected into Credentials.
|
|
1128
865
|
* Uses Node's --import flag to preload a script that populates Credentials
|
|
@@ -1137,22 +874,22 @@ const logger$12 = console;
|
|
|
1137
874
|
async function execCommand(commandArgs, options = {}) {
|
|
1138
875
|
const cwd = options.cwd ?? process.cwd();
|
|
1139
876
|
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1140
|
-
const defaultEnv = loadEnvFiles(".env");
|
|
1141
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1142
|
-
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({
|
|
877
|
+
const defaultEnv = require_setup.loadEnvFiles(".env");
|
|
878
|
+
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
879
|
+
const { credentials, secretsJsonPath, appName } = await require_setup.prepareEntryCredentials({
|
|
1143
880
|
cwd,
|
|
1144
881
|
resolveDockerPorts: "readonly"
|
|
1145
882
|
});
|
|
1146
|
-
if (appName) logger$
|
|
883
|
+
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
1147
884
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1148
|
-
if (secretCount > 0) logger$
|
|
885
|
+
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1149
886
|
const preloadDir = (0, node_path.join)(cwd, ".gkm");
|
|
1150
887
|
await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
|
|
1151
888
|
const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
|
|
1152
|
-
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
889
|
+
await require_setup.createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1153
890
|
const [cmd, ...args] = commandArgs;
|
|
1154
891
|
if (!cmd) throw new Error("No command specified");
|
|
1155
|
-
logger$
|
|
892
|
+
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
1156
893
|
const existingNodeOptions = (process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim();
|
|
1157
894
|
const preloadImport = `--import=${preloadPath}`;
|
|
1158
895
|
const nodeOptions = [existingNodeOptions, preloadImport].filter(Boolean).join(" ");
|
|
@@ -1165,11 +902,11 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1165
902
|
NODE_OPTIONS: nodeOptions
|
|
1166
903
|
}
|
|
1167
904
|
});
|
|
1168
|
-
const exitCode = await new Promise((resolve$
|
|
1169
|
-
child.on("close", (code) => resolve$
|
|
905
|
+
const exitCode = await new Promise((resolve$3) => {
|
|
906
|
+
child.on("close", (code) => resolve$3(code ?? 0));
|
|
1170
907
|
child.on("error", (error) => {
|
|
1171
|
-
logger$
|
|
1172
|
-
resolve$
|
|
908
|
+
logger$11.error(`Failed to run command: ${error.message}`);
|
|
909
|
+
resolve$3(1);
|
|
1173
910
|
});
|
|
1174
911
|
});
|
|
1175
912
|
if (exitCode !== 0) process.exit(exitCode);
|
|
@@ -1177,7 +914,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1177
914
|
|
|
1178
915
|
//#endregion
|
|
1179
916
|
//#region src/dev/index.ts
|
|
1180
|
-
const logger$
|
|
917
|
+
const logger$10 = console;
|
|
1181
918
|
/**
|
|
1182
919
|
* Normalize telescope configuration
|
|
1183
920
|
* @internal Exported for testing
|
|
@@ -1274,8 +1011,8 @@ function getProductionConfigFromGkm(config) {
|
|
|
1274
1011
|
}
|
|
1275
1012
|
async function devCommand(options) {
|
|
1276
1013
|
if (options.entry) return entryDevCommand(options);
|
|
1277
|
-
const defaultEnv = loadEnvFiles(".env");
|
|
1278
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1014
|
+
const defaultEnv = require_setup.loadEnvFiles(".env");
|
|
1015
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1279
1016
|
const appName = require_config.getAppNameFromCwd();
|
|
1280
1017
|
let config;
|
|
1281
1018
|
let appRoot = process.cwd();
|
|
@@ -1291,9 +1028,9 @@ async function devCommand(options) {
|
|
|
1291
1028
|
workspaceAppName = appConfig.appName;
|
|
1292
1029
|
workspaceAppPort = appConfig.app.port;
|
|
1293
1030
|
workspace = appConfig.workspace;
|
|
1294
|
-
logger$
|
|
1031
|
+
logger$10.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
1295
1032
|
if (appConfig.app.entry) {
|
|
1296
|
-
logger$
|
|
1033
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1297
1034
|
return entryDevCommand({
|
|
1298
1035
|
...options,
|
|
1299
1036
|
entry: appConfig.app.entry,
|
|
@@ -1304,7 +1041,7 @@ async function devCommand(options) {
|
|
|
1304
1041
|
} catch {
|
|
1305
1042
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
1306
1043
|
if (loadedConfig.type === "workspace") {
|
|
1307
|
-
logger$
|
|
1044
|
+
logger$10.log("📦 Detected workspace configuration");
|
|
1308
1045
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1309
1046
|
}
|
|
1310
1047
|
config = loadedConfig.raw;
|
|
@@ -1312,34 +1049,34 @@ async function devCommand(options) {
|
|
|
1312
1049
|
else {
|
|
1313
1050
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
1314
1051
|
if (loadedConfig.type === "workspace") {
|
|
1315
|
-
logger$
|
|
1052
|
+
logger$10.log("📦 Detected workspace configuration");
|
|
1316
1053
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1317
1054
|
}
|
|
1318
1055
|
config = loadedConfig.raw;
|
|
1319
1056
|
}
|
|
1320
1057
|
if (config.env) {
|
|
1321
|
-
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
1322
|
-
if (loaded.length > 0) logger$
|
|
1323
|
-
if (missing.length > 0) logger$
|
|
1058
|
+
const { loaded, missing } = require_setup.loadEnvFiles(config.env, appRoot);
|
|
1059
|
+
if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
1060
|
+
if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1324
1061
|
}
|
|
1325
1062
|
const resolved = resolveProviders(config, { provider: "server" });
|
|
1326
|
-
logger$
|
|
1327
|
-
logger$
|
|
1328
|
-
if (config.functions) logger$
|
|
1329
|
-
if (config.crons) logger$
|
|
1330
|
-
if (config.subscribers) logger$
|
|
1331
|
-
logger$
|
|
1063
|
+
logger$10.log("🚀 Starting development server...");
|
|
1064
|
+
logger$10.log(`Loading routes from: ${config.routes}`);
|
|
1065
|
+
if (config.functions) logger$10.log(`Loading functions from: ${config.functions}`);
|
|
1066
|
+
if (config.crons) logger$10.log(`Loading crons from: ${config.crons}`);
|
|
1067
|
+
if (config.subscribers) logger$10.log(`Loading subscribers from: ${config.subscribers}`);
|
|
1068
|
+
logger$10.log(`Using envParser: ${config.envParser}`);
|
|
1332
1069
|
const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
|
|
1333
1070
|
const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
|
|
1334
1071
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1335
|
-
if (telescope) logger$
|
|
1072
|
+
if (telescope) logger$10.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
1336
1073
|
const studio = normalizeStudioConfig(config.studio);
|
|
1337
|
-
if (studio) logger$
|
|
1074
|
+
if (studio) logger$10.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
1338
1075
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
1339
|
-
if (hooks) logger$
|
|
1076
|
+
if (hooks) logger$10.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
1340
1077
|
const openApiConfig = require_openapi.resolveOpenApiConfig(config);
|
|
1341
1078
|
const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
|
|
1342
|
-
if (enableOpenApi) logger$
|
|
1079
|
+
if (enableOpenApi) logger$10.log(`📄 OpenAPI output: ${require_openapi.OPENAPI_OUTPUT_PATH}`);
|
|
1343
1080
|
const buildContext = {
|
|
1344
1081
|
envParserPath,
|
|
1345
1082
|
envParserImportPattern,
|
|
@@ -1353,12 +1090,12 @@ async function devCommand(options) {
|
|
|
1353
1090
|
if (enableOpenApi) await require_openapi.generateOpenApi(config);
|
|
1354
1091
|
const runtime = config.runtime ?? "node";
|
|
1355
1092
|
let secretsJsonPath;
|
|
1356
|
-
const appSecrets = await loadSecretsForApp(secretsRoot, workspaceAppName);
|
|
1357
|
-
const resolvedPorts = await resolveServicePorts(secretsRoot);
|
|
1093
|
+
const appSecrets = await require_setup.loadSecretsForApp(secretsRoot, workspaceAppName);
|
|
1094
|
+
const resolvedPorts = await require_setup.resolveServicePorts(secretsRoot);
|
|
1358
1095
|
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
1359
|
-
const rewritten = rewriteUrlsWithPorts(appSecrets, resolvedPorts);
|
|
1096
|
+
const rewritten = require_setup.rewriteUrlsWithPorts(appSecrets, resolvedPorts);
|
|
1360
1097
|
Object.assign(appSecrets, rewritten);
|
|
1361
|
-
logger$
|
|
1098
|
+
logger$10.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
1362
1099
|
}
|
|
1363
1100
|
if (workspace && workspaceAppName) {
|
|
1364
1101
|
const depEnv = require_workspace.getDependencyEnvVars(workspace, workspaceAppName);
|
|
@@ -1370,7 +1107,7 @@ async function devCommand(options) {
|
|
|
1370
1107
|
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
1371
1108
|
secretsJsonPath = (0, node_path.join)(secretsDir, secretsFileName);
|
|
1372
1109
|
await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1373
|
-
logger$
|
|
1110
|
+
logger$10.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1374
1111
|
}
|
|
1375
1112
|
const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath);
|
|
1376
1113
|
await devServer.start();
|
|
@@ -1388,7 +1125,7 @@ async function devCommand(options) {
|
|
|
1388
1125
|
...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
|
|
1389
1126
|
].flat().filter((p) => typeof p === "string");
|
|
1390
1127
|
const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
|
|
1391
|
-
logger$
|
|
1128
|
+
logger$10.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
1392
1129
|
const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
|
|
1393
1130
|
cwd: appRoot,
|
|
1394
1131
|
absolute: false,
|
|
@@ -1398,7 +1135,7 @@ async function devCommand(options) {
|
|
|
1398
1135
|
const parts = f.split("/");
|
|
1399
1136
|
return parts.slice(0, -1).join("/");
|
|
1400
1137
|
}))];
|
|
1401
|
-
logger$
|
|
1138
|
+
logger$10.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
1402
1139
|
const watcher = chokidar.default.watch([...resolvedFiles, ...dirsToWatch], {
|
|
1403
1140
|
ignored: /(^|[/\\])\../,
|
|
1404
1141
|
persistent: true,
|
|
@@ -1406,29 +1143,29 @@ async function devCommand(options) {
|
|
|
1406
1143
|
cwd: appRoot
|
|
1407
1144
|
});
|
|
1408
1145
|
watcher.on("ready", () => {
|
|
1409
|
-
logger$
|
|
1146
|
+
logger$10.log("🔍 File watcher ready");
|
|
1410
1147
|
});
|
|
1411
1148
|
watcher.on("error", (error) => {
|
|
1412
|
-
logger$
|
|
1149
|
+
logger$10.error("❌ Watcher error:", error);
|
|
1413
1150
|
});
|
|
1414
1151
|
let rebuildTimeout = null;
|
|
1415
1152
|
watcher.on("change", async (path) => {
|
|
1416
|
-
logger$
|
|
1153
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1417
1154
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
1418
1155
|
rebuildTimeout = setTimeout(async () => {
|
|
1419
1156
|
try {
|
|
1420
|
-
logger$
|
|
1157
|
+
logger$10.log("🔄 Rebuilding...");
|
|
1421
1158
|
await buildServer(config, buildContext, resolved.providers[0], enableOpenApi, appRoot, true);
|
|
1422
1159
|
if (enableOpenApi) await require_openapi.generateOpenApi(config, {
|
|
1423
1160
|
silent: true,
|
|
1424
1161
|
bustCache: true
|
|
1425
1162
|
});
|
|
1426
|
-
logger$
|
|
1163
|
+
logger$10.log("✅ Rebuild complete, restarting server...");
|
|
1427
1164
|
await devServer.restart();
|
|
1428
1165
|
} catch (error) {
|
|
1429
1166
|
const err = error;
|
|
1430
|
-
logger$
|
|
1431
|
-
if (err.stack) logger$
|
|
1167
|
+
logger$10.error(`❌ Rebuild failed: ${err.message}`);
|
|
1168
|
+
if (err.stack) logger$10.error(err.stack);
|
|
1432
1169
|
}
|
|
1433
1170
|
}, 300);
|
|
1434
1171
|
});
|
|
@@ -1436,9 +1173,9 @@ async function devCommand(options) {
|
|
|
1436
1173
|
const shutdown = () => {
|
|
1437
1174
|
if (isShuttingDown) return;
|
|
1438
1175
|
isShuttingDown = true;
|
|
1439
|
-
logger$
|
|
1176
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1440
1177
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
1441
|
-
logger$
|
|
1178
|
+
logger$10.error("Error during shutdown:", err);
|
|
1442
1179
|
}).finally(() => {
|
|
1443
1180
|
process.exit(0);
|
|
1444
1181
|
});
|
|
@@ -1637,11 +1374,11 @@ async function loadDevSecrets(workspace) {
|
|
|
1637
1374
|
for (const stage of stages) if (require_storage.secretsExist(stage, workspace.root)) {
|
|
1638
1375
|
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
1639
1376
|
if (secrets) {
|
|
1640
|
-
logger$
|
|
1377
|
+
logger$10.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1641
1378
|
return require_storage.toEmbeddableSecrets(secrets);
|
|
1642
1379
|
}
|
|
1643
1380
|
}
|
|
1644
|
-
logger$
|
|
1381
|
+
logger$10.warn("⚠️ Secrets enabled but no dev/development secrets found. Run \"gkm setup\" to initialize your development environment");
|
|
1645
1382
|
return {};
|
|
1646
1383
|
}
|
|
1647
1384
|
/**
|
|
@@ -1658,39 +1395,39 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1658
1395
|
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
1659
1396
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
1660
1397
|
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
1661
|
-
logger$
|
|
1398
|
+
logger$10.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
1662
1399
|
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
1663
1400
|
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
1664
|
-
logger$
|
|
1401
|
+
logger$10.log(` ${counts.join(", ")} app(s)`);
|
|
1665
1402
|
const conflicts = checkPortConflicts(workspace);
|
|
1666
1403
|
if (conflicts.length > 0) {
|
|
1667
|
-
for (const conflict of conflicts) logger$
|
|
1404
|
+
for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
1668
1405
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
1669
1406
|
}
|
|
1670
1407
|
if (frontendApps.length > 0) {
|
|
1671
|
-
logger$
|
|
1408
|
+
logger$10.log("\n🔍 Validating frontend apps...");
|
|
1672
1409
|
const validationResults = await validateFrontendApps(workspace);
|
|
1673
1410
|
let hasErrors = false;
|
|
1674
1411
|
for (const result of validationResults) {
|
|
1675
1412
|
if (!result.valid) {
|
|
1676
1413
|
hasErrors = true;
|
|
1677
|
-
logger$
|
|
1678
|
-
for (const error of result.errors) logger$
|
|
1414
|
+
logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1415
|
+
for (const error of result.errors) logger$10.error(` • ${error}`);
|
|
1679
1416
|
}
|
|
1680
|
-
for (const warning of result.warnings) logger$
|
|
1417
|
+
for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
1681
1418
|
}
|
|
1682
1419
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1683
|
-
logger$
|
|
1420
|
+
logger$10.log("✅ Frontend apps validated");
|
|
1684
1421
|
}
|
|
1685
|
-
const resolvedPorts = await resolveServicePorts(workspace.root);
|
|
1422
|
+
const resolvedPorts = await require_setup.resolveServicePorts(workspace.root);
|
|
1686
1423
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1687
|
-
await startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
|
|
1688
|
-
const secretsEnv = rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
|
|
1689
|
-
if (Object.keys(secretsEnv).length > 0) logger$
|
|
1424
|
+
await require_setup.startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
|
|
1425
|
+
const secretsEnv = require_setup.rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
|
|
1426
|
+
if (Object.keys(secretsEnv).length > 0) logger$10.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
|
|
1690
1427
|
const dependencyEnv = generateAllDependencyEnvVars(workspace);
|
|
1691
1428
|
if (Object.keys(dependencyEnv).length > 0) {
|
|
1692
|
-
logger$
|
|
1693
|
-
for (const [key, value] of Object.entries(dependencyEnv)) logger$
|
|
1429
|
+
logger$10.log("📡 Dependency URLs:");
|
|
1430
|
+
for (const [key, value] of Object.entries(dependencyEnv)) logger$10.log(` ${key}=${value}`);
|
|
1694
1431
|
}
|
|
1695
1432
|
let turboFilter = [];
|
|
1696
1433
|
if (options.app) {
|
|
@@ -1699,19 +1436,19 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1699
1436
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
1700
1437
|
}
|
|
1701
1438
|
turboFilter = ["--filter", options.app];
|
|
1702
|
-
logger$
|
|
1439
|
+
logger$10.log(`\n🎯 Running single app: ${options.app}`);
|
|
1703
1440
|
} else if (options.filter) {
|
|
1704
1441
|
turboFilter = ["--filter", options.filter];
|
|
1705
|
-
logger$
|
|
1706
|
-
} else logger$
|
|
1442
|
+
logger$10.log(`\n🔍 Using filter: ${options.filter}`);
|
|
1443
|
+
} else logger$10.log(`\n🎯 Running all ${appCount} apps`);
|
|
1707
1444
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
1708
|
-
logger$
|
|
1445
|
+
logger$10.log("\n📋 Apps (in dependency order):");
|
|
1709
1446
|
for (const appName of buildOrder) {
|
|
1710
1447
|
const app = workspace.apps[appName];
|
|
1711
1448
|
if (!app) continue;
|
|
1712
1449
|
const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
|
|
1713
1450
|
const icon = app.type === "backend" ? "🔧" : app.type === "mobile" ? "📱" : "🌐";
|
|
1714
|
-
logger$
|
|
1451
|
+
logger$10.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
|
|
1715
1452
|
}
|
|
1716
1453
|
const configFiles = [
|
|
1717
1454
|
"gkm.config.ts",
|
|
@@ -1733,7 +1470,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1733
1470
|
NODE_ENV: "development",
|
|
1734
1471
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1735
1472
|
};
|
|
1736
|
-
logger$
|
|
1473
|
+
logger$10.log("\n🏃 Starting turbo run dev...\n");
|
|
1737
1474
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
1738
1475
|
"turbo",
|
|
1739
1476
|
"run",
|
|
@@ -1749,7 +1486,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1749
1486
|
const shutdown = () => {
|
|
1750
1487
|
if (isShuttingDown) return;
|
|
1751
1488
|
isShuttingDown = true;
|
|
1752
|
-
logger$
|
|
1489
|
+
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1753
1490
|
const pid = turboProcess.pid;
|
|
1754
1491
|
if (pid) try {
|
|
1755
1492
|
process.kill(-pid, "SIGTERM");
|
|
@@ -1771,14 +1508,14 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1771
1508
|
};
|
|
1772
1509
|
process.on("SIGINT", shutdown);
|
|
1773
1510
|
process.on("SIGTERM", shutdown);
|
|
1774
|
-
return new Promise((resolve$
|
|
1511
|
+
return new Promise((resolve$3, reject) => {
|
|
1775
1512
|
turboProcess.on("error", (error) => {
|
|
1776
|
-
logger$
|
|
1513
|
+
logger$10.error("❌ Turbo error:", error);
|
|
1777
1514
|
reject(error);
|
|
1778
1515
|
});
|
|
1779
1516
|
turboProcess.on("exit", (code) => {
|
|
1780
1517
|
if (code !== null && code !== 0) reject(new Error(`Turbo exited with code ${code}`));
|
|
1781
|
-
else resolve$
|
|
1518
|
+
else resolve$3();
|
|
1782
1519
|
});
|
|
1783
1520
|
});
|
|
1784
1521
|
}
|
|
@@ -1787,11 +1524,15 @@ async function buildServer(config, context, provider, enableOpenApi, appRoot = p
|
|
|
1787
1524
|
const functionGenerator = new FunctionGenerator();
|
|
1788
1525
|
const cronGenerator = new CronGenerator();
|
|
1789
1526
|
const subscriberGenerator = new SubscriberGenerator();
|
|
1790
|
-
const
|
|
1527
|
+
const queueGenerator = new QueueGenerator();
|
|
1528
|
+
const topicGenerator = new TopicGenerator();
|
|
1529
|
+
const [allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics] = await Promise.all([
|
|
1791
1530
|
endpointGenerator.load(config.routes, appRoot, bustCache),
|
|
1792
1531
|
config.functions ? functionGenerator.load(config.functions, appRoot, bustCache) : [],
|
|
1793
1532
|
config.crons ? cronGenerator.load(config.crons, appRoot, bustCache) : [],
|
|
1794
|
-
config.subscribers ? subscriberGenerator.load(config.subscribers, appRoot, bustCache) : []
|
|
1533
|
+
config.subscribers ? subscriberGenerator.load(config.subscribers, appRoot, bustCache) : [],
|
|
1534
|
+
config.queues ? queueGenerator.load(config.queues, appRoot, bustCache) : [],
|
|
1535
|
+
config.topics ? topicGenerator.load(config.topics, appRoot, bustCache) : []
|
|
1795
1536
|
]);
|
|
1796
1537
|
const outputDir = (0, node_path.join)(appRoot, ".gkm", provider);
|
|
1797
1538
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
@@ -1802,7 +1543,9 @@ async function buildServer(config, context, provider, enableOpenApi, appRoot = p
|
|
|
1802
1543
|
}),
|
|
1803
1544
|
functionGenerator.build(context, allFunctions, outputDir, { provider }),
|
|
1804
1545
|
cronGenerator.build(context, allCrons, outputDir, { provider }),
|
|
1805
|
-
subscriberGenerator.build(context, allSubscribers, outputDir, { provider })
|
|
1546
|
+
subscriberGenerator.build(context, allSubscribers, outputDir, { provider }),
|
|
1547
|
+
queueGenerator.build(context, allQueues, outputDir, { provider }),
|
|
1548
|
+
topicGenerator.build(context, allTopics, outputDir, { provider })
|
|
1806
1549
|
]);
|
|
1807
1550
|
}
|
|
1808
1551
|
/**
|
|
@@ -1814,23 +1557,23 @@ async function entryDevCommand(options) {
|
|
|
1814
1557
|
if (!entry) throw new Error("--entry requires a file path");
|
|
1815
1558
|
const entryPath = (0, node_path.resolve)(process.cwd(), entry);
|
|
1816
1559
|
if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1817
|
-
const defaultEnv = loadEnvFiles(".env");
|
|
1818
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1819
|
-
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1820
|
-
if (appName) logger$
|
|
1821
|
-
logger$
|
|
1822
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1560
|
+
const defaultEnv = require_setup.loadEnvFiles(".env");
|
|
1561
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1562
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } = await require_setup.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1563
|
+
if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1564
|
+
logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1565
|
+
if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1823
1566
|
const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
1824
1567
|
await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
|
|
1825
1568
|
const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
|
|
1826
|
-
await createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
1569
|
+
await require_setup.createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
1827
1570
|
const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
|
|
1828
1571
|
await runner.start();
|
|
1829
1572
|
let isShuttingDown = false;
|
|
1830
1573
|
const shutdown = () => {
|
|
1831
1574
|
if (isShuttingDown) return;
|
|
1832
1575
|
isShuttingDown = true;
|
|
1833
|
-
logger$
|
|
1576
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1834
1577
|
runner.stop();
|
|
1835
1578
|
process.exit(0);
|
|
1836
1579
|
};
|
|
@@ -1862,14 +1605,14 @@ var EntryRunner = class {
|
|
|
1862
1605
|
});
|
|
1863
1606
|
let restartTimeout = null;
|
|
1864
1607
|
this.watcher.on("change", (path) => {
|
|
1865
|
-
logger$
|
|
1608
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1866
1609
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1867
1610
|
restartTimeout = setTimeout(async () => {
|
|
1868
|
-
logger$
|
|
1611
|
+
logger$10.log("🔄 Restarting...");
|
|
1869
1612
|
await this.restart();
|
|
1870
1613
|
}, 300);
|
|
1871
1614
|
});
|
|
1872
|
-
logger$
|
|
1615
|
+
logger$10.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1873
1616
|
}
|
|
1874
1617
|
}
|
|
1875
1618
|
async runProcess() {
|
|
@@ -1884,22 +1627,22 @@ var EntryRunner = class {
|
|
|
1884
1627
|
});
|
|
1885
1628
|
this.isRunning = true;
|
|
1886
1629
|
this.childProcess.on("error", (error) => {
|
|
1887
|
-
logger$
|
|
1630
|
+
logger$10.error("❌ Process error:", error);
|
|
1888
1631
|
});
|
|
1889
1632
|
this.childProcess.on("exit", (code) => {
|
|
1890
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1633
|
+
if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
|
|
1891
1634
|
this.isRunning = false;
|
|
1892
1635
|
});
|
|
1893
|
-
await new Promise((resolve$
|
|
1636
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 500));
|
|
1894
1637
|
if (this.isRunning) {
|
|
1895
|
-
logger$
|
|
1896
|
-
logger$
|
|
1897
|
-
logger$
|
|
1638
|
+
logger$10.log("");
|
|
1639
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
1640
|
+
logger$10.log("");
|
|
1898
1641
|
}
|
|
1899
1642
|
}
|
|
1900
1643
|
async restart() {
|
|
1901
1644
|
this.stopProcess();
|
|
1902
|
-
await new Promise((resolve$
|
|
1645
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 500));
|
|
1903
1646
|
await this.runProcess();
|
|
1904
1647
|
}
|
|
1905
1648
|
stop() {
|
|
@@ -2003,16 +1746,16 @@ var DevServer = class {
|
|
|
2003
1746
|
this.startTime = Date.now();
|
|
2004
1747
|
if (this.isRunning) await this.stop();
|
|
2005
1748
|
if (this.portExplicit) {
|
|
2006
|
-
const available = await isPortAvailable(this.requestedPort);
|
|
1749
|
+
const available = await require_setup.isPortAvailable(this.requestedPort);
|
|
2007
1750
|
if (!available) throw new Error(`Port ${this.requestedPort} is already in use. Either stop the process using that port or omit -p/--port to auto-select an available port.`);
|
|
2008
1751
|
this.actualPort = this.requestedPort;
|
|
2009
1752
|
} else {
|
|
2010
|
-
this.actualPort = await findAvailablePort(this.requestedPort);
|
|
2011
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1753
|
+
this.actualPort = await require_setup.findAvailablePort(this.requestedPort);
|
|
1754
|
+
if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
2012
1755
|
}
|
|
2013
1756
|
const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
2014
1757
|
await this.createServerEntry();
|
|
2015
|
-
logger$
|
|
1758
|
+
logger$10.log(`\n⏳ Starting server...`);
|
|
2016
1759
|
this.serverProcess = (0, node_child_process.spawn)("npx", [
|
|
2017
1760
|
"tsx",
|
|
2018
1761
|
serverEntryPath,
|
|
@@ -2028,13 +1771,13 @@ var DevServer = class {
|
|
|
2028
1771
|
});
|
|
2029
1772
|
this.isRunning = true;
|
|
2030
1773
|
this.serverProcess.on("error", (error) => {
|
|
2031
|
-
logger$
|
|
1774
|
+
logger$10.error("❌ Server error:", error);
|
|
2032
1775
|
});
|
|
2033
1776
|
this.serverProcess.on("exit", (code, signal) => {
|
|
2034
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1777
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
|
|
2035
1778
|
this.isRunning = false;
|
|
2036
1779
|
});
|
|
2037
|
-
await new Promise((resolve$
|
|
1780
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 1e3));
|
|
2038
1781
|
if (this.isRunning) {
|
|
2039
1782
|
const base = `http://localhost:${this.actualPort}`;
|
|
2040
1783
|
const lines = [` Local: ${base}`];
|
|
@@ -2044,13 +1787,13 @@ var DevServer = class {
|
|
|
2044
1787
|
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
2045
1788
|
const pad = (s) => s.padEnd(maxLen);
|
|
2046
1789
|
const border = "─".repeat(maxLen + 2);
|
|
2047
|
-
logger$
|
|
2048
|
-
logger$
|
|
2049
|
-
logger$
|
|
2050
|
-
logger$
|
|
2051
|
-
for (const line of lines) logger$
|
|
2052
|
-
logger$
|
|
2053
|
-
logger$
|
|
1790
|
+
logger$10.log("");
|
|
1791
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m in ${((Date.now() - this.startTime) / 1e3).toFixed(1)}s`);
|
|
1792
|
+
logger$10.log("");
|
|
1793
|
+
logger$10.log(` ┌${border}┐`);
|
|
1794
|
+
for (const line of lines) logger$10.log(` │ ${pad(line)} │`);
|
|
1795
|
+
logger$10.log(` └${border}┘`);
|
|
1796
|
+
logger$10.log("");
|
|
2054
1797
|
}
|
|
2055
1798
|
}
|
|
2056
1799
|
async stop() {
|
|
@@ -2079,8 +1822,8 @@ var DevServer = class {
|
|
|
2079
1822
|
await this.stop();
|
|
2080
1823
|
let attempts = 0;
|
|
2081
1824
|
while (attempts < 30) {
|
|
2082
|
-
if (await isPortAvailable(portToReuse)) break;
|
|
2083
|
-
await new Promise((resolve$
|
|
1825
|
+
if (await require_setup.isPortAvailable(portToReuse)) break;
|
|
1826
|
+
await new Promise((resolve$3) => setTimeout(resolve$3, 100));
|
|
2084
1827
|
attempts++;
|
|
2085
1828
|
}
|
|
2086
1829
|
this.requestedPort = portToReuse;
|
|
@@ -2100,7 +1843,7 @@ var DevServer = class {
|
|
|
2100
1843
|
|
|
2101
1844
|
//#endregion
|
|
2102
1845
|
//#region src/build/manifests.ts
|
|
2103
|
-
const logger$
|
|
1846
|
+
const logger$9 = console;
|
|
2104
1847
|
function isPartitioned(field) {
|
|
2105
1848
|
return !Array.isArray(field);
|
|
2106
1849
|
}
|
|
@@ -2133,7 +1876,7 @@ function generateDerivedType(fieldName, typeName, partitioned) {
|
|
|
2133
1876
|
}
|
|
2134
1877
|
return `export type ${typeName} = (typeof manifest.${fieldName})[number];`;
|
|
2135
1878
|
}
|
|
2136
|
-
async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
|
|
1879
|
+
async function generateAwsManifest(outputDir, routes, functions, crons, subscribers, queues = [], topics = []) {
|
|
2137
1880
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
2138
1881
|
await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
|
|
2139
1882
|
const awsRoutes = filterAllRoutes(routes);
|
|
@@ -2141,11 +1884,15 @@ async function generateAwsManifest(outputDir, routes, functions, crons, subscrib
|
|
|
2141
1884
|
const functionsPartitioned = isPartitioned(functions);
|
|
2142
1885
|
const cronsPartitioned = isPartitioned(crons);
|
|
2143
1886
|
const subscribersPartitioned = isPartitioned(subscribers);
|
|
1887
|
+
const queuesPartitioned = isPartitioned(queues);
|
|
1888
|
+
const topicsPartitioned = isPartitioned(topics);
|
|
2144
1889
|
const content = `export const manifest = {
|
|
2145
1890
|
routes: ${serializeField(awsRoutes)},
|
|
2146
1891
|
functions: ${serializeField(functions)},
|
|
2147
1892
|
crons: ${serializeField(crons)},
|
|
2148
1893
|
subscribers: ${serializeField(subscribers)},
|
|
1894
|
+
queues: ${serializeField(queues)},
|
|
1895
|
+
topics: ${serializeField(topics)},
|
|
2149
1896
|
} as const;
|
|
2150
1897
|
|
|
2151
1898
|
// Derived types
|
|
@@ -2153,6 +1900,8 @@ ${generateDerivedType("routes", "Route", routesPartitioned)}
|
|
|
2153
1900
|
${generateDerivedType("functions", "Function", functionsPartitioned)}
|
|
2154
1901
|
${generateDerivedType("crons", "Cron", cronsPartitioned)}
|
|
2155
1902
|
${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
|
|
1903
|
+
${generateDerivedType("queues", "Queue", queuesPartitioned)}
|
|
1904
|
+
${generateDerivedType("topics", "Topic", topicsPartitioned)}
|
|
2156
1905
|
|
|
2157
1906
|
// Useful union types
|
|
2158
1907
|
export type Authorizer = Route['authorizer'];
|
|
@@ -2161,25 +1910,33 @@ export type RoutePath = Route['path'];
|
|
|
2161
1910
|
`;
|
|
2162
1911
|
const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
|
|
2163
1912
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2164
|
-
logger$
|
|
2165
|
-
logger$
|
|
1913
|
+
logger$9.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers, ${countItems(queues)} queues, ${countItems(topics)} topics`);
|
|
1914
|
+
logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
2166
1915
|
}
|
|
2167
|
-
async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
|
|
1916
|
+
async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = []) {
|
|
2168
1917
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
2169
1918
|
await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
|
|
2170
1919
|
const serverRoutes = mapRouteMetadata(filterAllRoutes(routes));
|
|
2171
1920
|
const serverSubscribers = mapSubscriberMetadata(subscribers);
|
|
1921
|
+
const serverQueues = mapQueueMetadata(queues);
|
|
1922
|
+
const serverTopics = mapTopicMetadata(topics);
|
|
2172
1923
|
const routesPartitioned = isPartitioned(serverRoutes);
|
|
2173
1924
|
const subscribersPartitioned = isPartitioned(serverSubscribers);
|
|
1925
|
+
const queuesPartitioned = isPartitioned(serverQueues);
|
|
1926
|
+
const topicsPartitioned = isPartitioned(serverTopics);
|
|
2174
1927
|
const content = `export const manifest = {
|
|
2175
1928
|
app: ${JSON.stringify(appInfo, null, 2)},
|
|
2176
1929
|
routes: ${serializeField(serverRoutes)},
|
|
2177
1930
|
subscribers: ${serializeField(serverSubscribers)},
|
|
1931
|
+
queues: ${serializeField(serverQueues)},
|
|
1932
|
+
topics: ${serializeField(serverTopics)},
|
|
2178
1933
|
} as const;
|
|
2179
1934
|
|
|
2180
1935
|
// Derived types
|
|
2181
1936
|
${generateDerivedType("routes", "Route", routesPartitioned)}
|
|
2182
1937
|
${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
|
|
1938
|
+
${generateDerivedType("queues", "Queue", queuesPartitioned)}
|
|
1939
|
+
${generateDerivedType("topics", "Topic", topicsPartitioned)}
|
|
2183
1940
|
|
|
2184
1941
|
// Useful union types
|
|
2185
1942
|
export type Authorizer = Route['authorizer'];
|
|
@@ -2188,8 +1945,8 @@ export type RoutePath = Route['path'];
|
|
|
2188
1945
|
`;
|
|
2189
1946
|
const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
|
|
2190
1947
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2191
|
-
logger$
|
|
2192
|
-
logger$
|
|
1948
|
+
logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
|
|
1949
|
+
logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
2193
1950
|
}
|
|
2194
1951
|
/**
|
|
2195
1952
|
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
@@ -2227,6 +1984,23 @@ function mapSubscriberMetadata(subscribers) {
|
|
|
2227
1984
|
for (const [partition, partitionSubs] of Object.entries(subscribers)) result[partition] = partitionSubs.map(mapFn);
|
|
2228
1985
|
return result;
|
|
2229
1986
|
}
|
|
1987
|
+
function mapQueueMetadata(queues) {
|
|
1988
|
+
const mapFn = (q) => ({ name: q.name });
|
|
1989
|
+
if (Array.isArray(queues)) return queues.map(mapFn);
|
|
1990
|
+
const result = {};
|
|
1991
|
+
for (const [partition, partitionQueues] of Object.entries(queues)) result[partition] = partitionQueues.map(mapFn);
|
|
1992
|
+
return result;
|
|
1993
|
+
}
|
|
1994
|
+
function mapTopicMetadata(topics) {
|
|
1995
|
+
const mapFn = (t) => ({
|
|
1996
|
+
name: t.name,
|
|
1997
|
+
events: t.events
|
|
1998
|
+
});
|
|
1999
|
+
if (Array.isArray(topics)) return topics.map(mapFn);
|
|
2000
|
+
const result = {};
|
|
2001
|
+
for (const [partition, partitionTopics] of Object.entries(topics)) result[partition] = partitionTopics.map(mapFn);
|
|
2002
|
+
return result;
|
|
2003
|
+
}
|
|
2230
2004
|
|
|
2231
2005
|
//#endregion
|
|
2232
2006
|
//#region src/build/partitions.ts
|
|
@@ -2255,7 +2029,7 @@ function groupInfosByPartition(infos, constructs) {
|
|
|
2255
2029
|
|
|
2256
2030
|
//#endregion
|
|
2257
2031
|
//#region src/build/index.ts
|
|
2258
|
-
const logger$
|
|
2032
|
+
const logger$8 = console;
|
|
2259
2033
|
async function buildCommand(options) {
|
|
2260
2034
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
2261
2035
|
if (loadedConfig.type === "workspace") {
|
|
@@ -2263,7 +2037,7 @@ async function buildCommand(options) {
|
|
|
2263
2037
|
const workspaceRoot = (0, node_path.resolve)(loadedConfig.workspace.root);
|
|
2264
2038
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2265
2039
|
if (isAtWorkspaceRoot) {
|
|
2266
|
-
logger$
|
|
2040
|
+
logger$8.log("📦 Detected workspace configuration");
|
|
2267
2041
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2268
2042
|
}
|
|
2269
2043
|
}
|
|
@@ -2271,21 +2045,21 @@ async function buildCommand(options) {
|
|
|
2271
2045
|
const resolved = resolveProviders(config, options);
|
|
2272
2046
|
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2273
2047
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2274
|
-
if (production) logger$
|
|
2275
|
-
logger$
|
|
2276
|
-
logger$
|
|
2277
|
-
if (config.functions) logger$
|
|
2278
|
-
if (config.crons) logger$
|
|
2279
|
-
if (config.subscribers) logger$
|
|
2280
|
-
logger$
|
|
2048
|
+
if (production) logger$8.log(`🏭 Building for PRODUCTION`);
|
|
2049
|
+
logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2050
|
+
logger$8.log(`Loading routes from: ${formatRoutes(config.routes)}`);
|
|
2051
|
+
if (config.functions) logger$8.log(`Loading functions from: ${formatRoutes(config.functions)}`);
|
|
2052
|
+
if (config.crons) logger$8.log(`Loading crons from: ${formatRoutes(config.crons)}`);
|
|
2053
|
+
if (config.subscribers) logger$8.log(`Loading subscribers from: ${formatRoutes(config.subscribers)}`);
|
|
2054
|
+
logger$8.log(`Using envParser: ${config.envParser}`);
|
|
2281
2055
|
const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
|
|
2282
2056
|
const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
|
|
2283
2057
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2284
|
-
if (telescope) logger$
|
|
2058
|
+
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2285
2059
|
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2286
|
-
if (studio) logger$
|
|
2060
|
+
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2287
2061
|
const hooks = normalizeHooksConfig(config.hooks);
|
|
2288
|
-
if (hooks) logger$
|
|
2062
|
+
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2289
2063
|
const services = config.docker?.compose?.services;
|
|
2290
2064
|
const dockerServices = services ? Array.isArray(services) ? {
|
|
2291
2065
|
postgres: services.includes("postgres"),
|
|
@@ -2312,47 +2086,57 @@ async function buildCommand(options) {
|
|
|
2312
2086
|
const functionGenerator = new FunctionGenerator();
|
|
2313
2087
|
const cronGenerator = new CronGenerator();
|
|
2314
2088
|
const subscriberGenerator = new SubscriberGenerator();
|
|
2315
|
-
const
|
|
2089
|
+
const queueGenerator = new QueueGenerator();
|
|
2090
|
+
const topicGenerator = new TopicGenerator();
|
|
2091
|
+
const [allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics] = await Promise.all([
|
|
2316
2092
|
endpointGenerator.load(config.routes),
|
|
2317
2093
|
config.functions ? functionGenerator.load(config.functions) : [],
|
|
2318
2094
|
config.crons ? cronGenerator.load(config.crons) : [],
|
|
2319
|
-
config.subscribers ? subscriberGenerator.load(config.subscribers) : []
|
|
2095
|
+
config.subscribers ? subscriberGenerator.load(config.subscribers) : [],
|
|
2096
|
+
config.queues ? queueGenerator.load(config.queues) : [],
|
|
2097
|
+
config.topics ? topicGenerator.load(config.topics) : []
|
|
2320
2098
|
]);
|
|
2321
|
-
logger$
|
|
2322
|
-
logger$
|
|
2323
|
-
logger$
|
|
2324
|
-
logger$
|
|
2325
|
-
|
|
2326
|
-
|
|
2099
|
+
logger$8.log(`Found ${allEndpoints.length} endpoints`);
|
|
2100
|
+
logger$8.log(`Found ${allFunctions.length} functions`);
|
|
2101
|
+
logger$8.log(`Found ${allCrons.length} crons`);
|
|
2102
|
+
logger$8.log(`Found ${allSubscribers.length} subscribers`);
|
|
2103
|
+
logger$8.log(`Found ${allQueues.length} queues`);
|
|
2104
|
+
logger$8.log(`Found ${allTopics.length} topics`);
|
|
2105
|
+
if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
2106
|
+
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
2327
2107
|
return {};
|
|
2328
2108
|
}
|
|
2329
2109
|
const rootOutputDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
2330
2110
|
await (0, node_fs_promises.mkdir)(rootOutputDir, { recursive: true });
|
|
2331
2111
|
let result = {};
|
|
2332
2112
|
for (const provider of resolved.providers) {
|
|
2333
|
-
const providerResult = await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, resolved.enableOpenApi, options.skipBundle ?? false, options.stage);
|
|
2113
|
+
const providerResult = await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, resolved.enableOpenApi, options.skipBundle ?? false, options.stage);
|
|
2334
2114
|
if (providerResult.masterKey) result = providerResult;
|
|
2335
2115
|
}
|
|
2336
2116
|
await require_openapi.generateOpenApi(config, { bustCache: true });
|
|
2337
2117
|
return result;
|
|
2338
2118
|
}
|
|
2339
|
-
async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi, skipBundle, stage) {
|
|
2119
|
+
async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
|
|
2340
2120
|
const outputDir = (0, node_path.join)(process.cwd(), ".gkm", provider);
|
|
2341
2121
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
2342
|
-
logger$
|
|
2343
|
-
const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
|
|
2122
|
+
logger$8.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2123
|
+
const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
|
|
2344
2124
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2345
2125
|
provider,
|
|
2346
2126
|
enableOpenApi
|
|
2347
2127
|
}),
|
|
2348
2128
|
functionGenerator.build(context, functions, outputDir, { provider }),
|
|
2349
2129
|
cronGenerator.build(context, crons, outputDir, { provider }),
|
|
2350
|
-
subscriberGenerator.build(context, subscribers, outputDir, { provider })
|
|
2130
|
+
subscriberGenerator.build(context, subscribers, outputDir, { provider }),
|
|
2131
|
+
queueGenerator.build(context, queues, outputDir, { provider }),
|
|
2132
|
+
topicGenerator.build(context, topics, outputDir, { provider })
|
|
2351
2133
|
]);
|
|
2352
|
-
logger$
|
|
2134
|
+
logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
|
|
2353
2135
|
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2354
2136
|
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2355
2137
|
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
2138
|
+
const manifestQueues = assembleManifestField(queueInfos, queues);
|
|
2139
|
+
const manifestTopics = assembleManifestField(topicInfos, topics);
|
|
2356
2140
|
const manifestSubscribers = assembleManifestField(subscriberInfos, subscribers);
|
|
2357
2141
|
if (provider === "server") {
|
|
2358
2142
|
const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
|
|
@@ -2366,16 +2150,17 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2366
2150
|
handler: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "app.ts")),
|
|
2367
2151
|
endpoints: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "endpoints.ts"))
|
|
2368
2152
|
};
|
|
2369
|
-
await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers);
|
|
2153
|
+
await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2370
2154
|
let masterKey;
|
|
2371
2155
|
if (context.production?.bundle && !skipBundle) {
|
|
2372
|
-
logger$
|
|
2373
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
2156
|
+
logger$8.log(`\n📦 Bundling production server...`);
|
|
2157
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-Dufq3uvI.cjs"));
|
|
2374
2158
|
const allConstructs = [
|
|
2375
2159
|
...endpoints.map((e) => e.construct),
|
|
2376
2160
|
...functions.map((f) => f.construct),
|
|
2377
2161
|
...crons.map((c) => c.construct),
|
|
2378
|
-
...subscribers.map((s) => s.construct)
|
|
2162
|
+
...subscribers.map((s) => s.construct),
|
|
2163
|
+
...queues.map((q) => q.construct)
|
|
2379
2164
|
];
|
|
2380
2165
|
const dockerServices = context.dockerServices;
|
|
2381
2166
|
const bundleResult = await bundleServer({
|
|
@@ -2389,14 +2174,14 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2389
2174
|
dockerServices
|
|
2390
2175
|
});
|
|
2391
2176
|
masterKey = bundleResult.masterKey;
|
|
2392
|
-
logger$
|
|
2177
|
+
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2393
2178
|
if (masterKey) {
|
|
2394
|
-
logger$
|
|
2395
|
-
logger$
|
|
2179
|
+
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2180
|
+
logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2396
2181
|
}
|
|
2397
2182
|
}
|
|
2398
2183
|
return { masterKey };
|
|
2399
|
-
} else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers);
|
|
2184
|
+
} else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2400
2185
|
return {};
|
|
2401
2186
|
}
|
|
2402
2187
|
/**
|
|
@@ -2429,18 +2214,18 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2429
2214
|
const apps = Object.entries(workspace.apps);
|
|
2430
2215
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2431
2216
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2432
|
-
logger$
|
|
2433
|
-
logger$
|
|
2434
|
-
logger$
|
|
2435
|
-
if (options.production) logger$
|
|
2217
|
+
logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
2218
|
+
logger$8.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2219
|
+
logger$8.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2220
|
+
if (options.production) logger$8.log(` 🏭 Production mode enabled`);
|
|
2436
2221
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
2437
|
-
logger$
|
|
2222
|
+
logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2438
2223
|
const pm = detectPackageManager$2();
|
|
2439
|
-
logger$
|
|
2224
|
+
logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2440
2225
|
try {
|
|
2441
2226
|
const turboCommand = getTurboCommand(pm);
|
|
2442
|
-
logger$
|
|
2443
|
-
await new Promise((resolve$
|
|
2227
|
+
logger$8.log(`Running: ${turboCommand}`);
|
|
2228
|
+
await new Promise((resolve$3, reject) => {
|
|
2444
2229
|
const child = (0, node_child_process.spawn)(turboCommand, {
|
|
2445
2230
|
shell: true,
|
|
2446
2231
|
cwd: workspace.root,
|
|
@@ -2451,7 +2236,7 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2451
2236
|
}
|
|
2452
2237
|
});
|
|
2453
2238
|
child.on("close", (code) => {
|
|
2454
|
-
if (code === 0) resolve$
|
|
2239
|
+
if (code === 0) resolve$3();
|
|
2455
2240
|
else reject(new Error(`Turbo build failed with exit code ${code}`));
|
|
2456
2241
|
});
|
|
2457
2242
|
child.on("error", (err) => {
|
|
@@ -2467,16 +2252,16 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2467
2252
|
outputPath
|
|
2468
2253
|
});
|
|
2469
2254
|
}
|
|
2470
|
-
logger$
|
|
2255
|
+
logger$8.log(`\n✅ Workspace build complete!`);
|
|
2471
2256
|
await require_openapi.openapiCommand({ cwd: workspace.root });
|
|
2472
|
-
logger$
|
|
2257
|
+
logger$8.log(`\n📋 Build Summary:`);
|
|
2473
2258
|
for (const result of results) {
|
|
2474
2259
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2475
|
-
logger$
|
|
2260
|
+
logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2476
2261
|
}
|
|
2477
2262
|
} catch (error) {
|
|
2478
2263
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2479
|
-
logger$
|
|
2264
|
+
logger$8.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2480
2265
|
for (const [appName, app] of apps) results.push({
|
|
2481
2266
|
appName,
|
|
2482
2267
|
type: app.type,
|
|
@@ -2703,11 +2488,11 @@ async function createDnsProvider(options) {
|
|
|
2703
2488
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
2704
2489
|
const provider = config.provider;
|
|
2705
2490
|
if (provider === "hostinger") {
|
|
2706
|
-
const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-
|
|
2491
|
+
const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-CrHfsgz9.cjs"));
|
|
2707
2492
|
return new HostingerProvider();
|
|
2708
2493
|
}
|
|
2709
2494
|
if (provider === "route53") {
|
|
2710
|
-
const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-
|
|
2495
|
+
const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-DwgXiLxt.cjs"));
|
|
2711
2496
|
const route53Config = config;
|
|
2712
2497
|
return new Route53Provider({
|
|
2713
2498
|
region: route53Config.region,
|
|
@@ -2721,7 +2506,7 @@ async function createDnsProvider(options) {
|
|
|
2721
2506
|
|
|
2722
2507
|
//#endregion
|
|
2723
2508
|
//#region src/deploy/dns/index.ts
|
|
2724
|
-
const logger$
|
|
2509
|
+
const logger$7 = console;
|
|
2725
2510
|
/**
|
|
2726
2511
|
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
2727
2512
|
*/
|
|
@@ -2758,7 +2543,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
|
2758
2543
|
for (const [appName, hostname] of appHostnames) {
|
|
2759
2544
|
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
2760
2545
|
if (!rootDomain) {
|
|
2761
|
-
logger$
|
|
2546
|
+
logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
2762
2547
|
continue;
|
|
2763
2548
|
}
|
|
2764
2549
|
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
@@ -2810,10 +2595,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
|
2810
2595
|
* Print DNS records table
|
|
2811
2596
|
*/
|
|
2812
2597
|
function printDnsRecordsTable(records, rootDomain) {
|
|
2813
|
-
logger$
|
|
2814
|
-
logger$
|
|
2815
|
-
logger$
|
|
2816
|
-
logger$
|
|
2598
|
+
logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
|
|
2599
|
+
logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
2600
|
+
logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
2601
|
+
logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
2817
2602
|
for (const record of records) {
|
|
2818
2603
|
const subdomain = record.subdomain.padEnd(35);
|
|
2819
2604
|
const type$1 = record.type.padEnd(4);
|
|
@@ -2823,18 +2608,18 @@ function printDnsRecordsTable(records, rootDomain) {
|
|
|
2823
2608
|
else if (record.created) status = "✓ new";
|
|
2824
2609
|
else if (record.existed) status = "✓";
|
|
2825
2610
|
else status = "?";
|
|
2826
|
-
logger$
|
|
2611
|
+
logger$7.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
|
|
2827
2612
|
}
|
|
2828
|
-
logger$
|
|
2613
|
+
logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
2829
2614
|
}
|
|
2830
2615
|
/**
|
|
2831
2616
|
* Print DNS records in a simple format for manual setup
|
|
2832
2617
|
*/
|
|
2833
2618
|
function printDnsRecordsSimple(records, rootDomain) {
|
|
2834
|
-
logger$
|
|
2835
|
-
logger$
|
|
2836
|
-
for (const record of records) logger$
|
|
2837
|
-
logger$
|
|
2619
|
+
logger$7.log("\n 📋 Required DNS Records:");
|
|
2620
|
+
logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
2621
|
+
for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
2622
|
+
logger$7.log("");
|
|
2838
2623
|
}
|
|
2839
2624
|
/**
|
|
2840
2625
|
* Create DNS records for a single domain using its configured provider
|
|
@@ -2846,7 +2631,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2846
2631
|
provider = await createDnsProvider({ config: providerConfig });
|
|
2847
2632
|
} catch (error) {
|
|
2848
2633
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2849
|
-
logger$
|
|
2634
|
+
logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
2850
2635
|
return records.map((r) => ({
|
|
2851
2636
|
...r,
|
|
2852
2637
|
error: message
|
|
@@ -2900,7 +2685,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2900
2685
|
}
|
|
2901
2686
|
} catch (error) {
|
|
2902
2687
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2903
|
-
logger$
|
|
2688
|
+
logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
2904
2689
|
return records.map((r) => ({
|
|
2905
2690
|
hostname: r.hostname,
|
|
2906
2691
|
subdomain: r.subdomain,
|
|
@@ -2927,20 +2712,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2927
2712
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
2928
2713
|
if (!dnsConfig) return null;
|
|
2929
2714
|
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
2930
|
-
logger$
|
|
2715
|
+
logger$7.log("\n🌐 Setting up DNS records...");
|
|
2931
2716
|
let serverIp;
|
|
2932
2717
|
try {
|
|
2933
2718
|
const endpointUrl = new URL(dokployEndpoint);
|
|
2934
2719
|
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
2935
|
-
logger$
|
|
2720
|
+
logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
2936
2721
|
} catch (error) {
|
|
2937
2722
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2938
|
-
logger$
|
|
2723
|
+
logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
2939
2724
|
return null;
|
|
2940
2725
|
}
|
|
2941
2726
|
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
2942
2727
|
if (groupedHostnames.size === 0) {
|
|
2943
|
-
logger$
|
|
2728
|
+
logger$7.log(" No DNS records needed (no hostnames match configured domains)");
|
|
2944
2729
|
return {
|
|
2945
2730
|
records: [],
|
|
2946
2731
|
success: true,
|
|
@@ -2952,22 +2737,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
2952
2737
|
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
2953
2738
|
const providerConfig = normalizedConfig[rootDomain];
|
|
2954
2739
|
if (!providerConfig) {
|
|
2955
|
-
logger$
|
|
2740
|
+
logger$7.log(` ⚠ No provider config for ${rootDomain}`);
|
|
2956
2741
|
continue;
|
|
2957
2742
|
}
|
|
2958
2743
|
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
2959
2744
|
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
2960
2745
|
if (requiredRecords.length === 0) continue;
|
|
2961
|
-
logger$
|
|
2746
|
+
logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
2962
2747
|
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
2963
2748
|
allRecords.push(...domainRecords);
|
|
2964
2749
|
const created = domainRecords.filter((r) => r.created).length;
|
|
2965
2750
|
const existed = domainRecords.filter((r) => r.existed).length;
|
|
2966
2751
|
const failed = domainRecords.filter((r) => r.error).length;
|
|
2967
|
-
if (created > 0) logger$
|
|
2968
|
-
if (existed > 0) logger$
|
|
2752
|
+
if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
2753
|
+
if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
2969
2754
|
if (failed > 0) {
|
|
2970
|
-
logger$
|
|
2755
|
+
logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
2971
2756
|
hasFailures = true;
|
|
2972
2757
|
}
|
|
2973
2758
|
if (state) {
|
|
@@ -3004,10 +2789,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
3004
2789
|
*/
|
|
3005
2790
|
async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
3006
2791
|
const results = [];
|
|
3007
|
-
logger$
|
|
2792
|
+
logger$7.log("\n🔍 Verifying DNS records...");
|
|
3008
2793
|
for (const [appName, hostname] of appHostnames) {
|
|
3009
2794
|
if (isDnsVerified(state, hostname, serverIp)) {
|
|
3010
|
-
logger$
|
|
2795
|
+
logger$7.log(` ✓ ${hostname} (previously verified)`);
|
|
3011
2796
|
results.push({
|
|
3012
2797
|
hostname,
|
|
3013
2798
|
appName,
|
|
@@ -3021,7 +2806,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3021
2806
|
const resolvedIp = await resolveHostnameToIp(hostname);
|
|
3022
2807
|
if (resolvedIp === serverIp) {
|
|
3023
2808
|
setDnsVerification(state, hostname, serverIp);
|
|
3024
|
-
logger$
|
|
2809
|
+
logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
|
|
3025
2810
|
results.push({
|
|
3026
2811
|
hostname,
|
|
3027
2812
|
appName,
|
|
@@ -3030,7 +2815,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3030
2815
|
expectedIp: serverIp
|
|
3031
2816
|
});
|
|
3032
2817
|
} else {
|
|
3033
|
-
logger$
|
|
2818
|
+
logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
|
|
3034
2819
|
results.push({
|
|
3035
2820
|
hostname,
|
|
3036
2821
|
appName,
|
|
@@ -3041,7 +2826,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3041
2826
|
}
|
|
3042
2827
|
} catch (error) {
|
|
3043
2828
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3044
|
-
logger$
|
|
2829
|
+
logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
|
|
3045
2830
|
results.push({
|
|
3046
2831
|
hostname,
|
|
3047
2832
|
appName,
|
|
@@ -3055,9 +2840,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3055
2840
|
const skipped = results.filter((r) => r.skipped).length;
|
|
3056
2841
|
const pending = results.filter((r) => !r.verified).length;
|
|
3057
2842
|
if (pending > 0) {
|
|
3058
|
-
logger$
|
|
3059
|
-
logger$
|
|
3060
|
-
} else if (skipped > 0) logger$
|
|
2843
|
+
logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
|
|
2844
|
+
logger$7.log(" DNS changes may take 5-30 minutes to propagate");
|
|
2845
|
+
} else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
|
|
3061
2846
|
return results;
|
|
3062
2847
|
}
|
|
3063
2848
|
|
|
@@ -4528,7 +4313,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
4528
4313
|
|
|
4529
4314
|
//#endregion
|
|
4530
4315
|
//#region src/docker/index.ts
|
|
4531
|
-
const logger$
|
|
4316
|
+
const logger$6 = console;
|
|
4532
4317
|
/**
|
|
4533
4318
|
* Docker command implementation
|
|
4534
4319
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -4539,7 +4324,7 @@ const logger$7 = console;
|
|
|
4539
4324
|
async function dockerCommand(options) {
|
|
4540
4325
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
4541
4326
|
if (loadedConfig.type === "workspace") {
|
|
4542
|
-
logger$
|
|
4327
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
4543
4328
|
return workspaceDockerCommand(loadedConfig.workspace, options);
|
|
4544
4329
|
}
|
|
4545
4330
|
const config = await require_config.loadConfig();
|
|
@@ -4560,14 +4345,14 @@ async function dockerCommand(options) {
|
|
|
4560
4345
|
let useTurbo = options.turbo ?? false;
|
|
4561
4346
|
if (inMonorepo && !useSlim) if (hasTurbo) {
|
|
4562
4347
|
useTurbo = true;
|
|
4563
|
-
logger$
|
|
4348
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
4564
4349
|
} else throw new Error("Monorepo detected but turbo.json not found.\n\nDocker builds in monorepos require Turborepo for proper dependency isolation.\n\nTo fix this:\n 1. Install turbo: pnpm add -Dw turbo\n 2. Create turbo.json in your monorepo root\n 3. Run this command again\n\nSee: https://turbo.build/repo/docs/guides/tools/docker");
|
|
4565
4350
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
4566
4351
|
if (useTurbo && !options.turboPackage) try {
|
|
4567
4352
|
const pkg$1 = require(`${process.cwd()}/package.json`);
|
|
4568
4353
|
if (pkg$1.name) {
|
|
4569
4354
|
turboPackage = pkg$1.name;
|
|
4570
|
-
logger$
|
|
4355
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
4571
4356
|
}
|
|
4572
4357
|
} catch {}
|
|
4573
4358
|
const templateOptions = {
|
|
@@ -4584,7 +4369,7 @@ async function dockerCommand(options) {
|
|
|
4584
4369
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
4585
4370
|
const dockerfilePath = (0, node_path.join)(dockerDir, "Dockerfile");
|
|
4586
4371
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
4587
|
-
logger$
|
|
4372
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
4588
4373
|
const composeOptions = {
|
|
4589
4374
|
imageName: dockerConfig.imageName,
|
|
4590
4375
|
registry: options.registry ?? dockerConfig.registry,
|
|
@@ -4596,15 +4381,15 @@ async function dockerCommand(options) {
|
|
|
4596
4381
|
const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
|
|
4597
4382
|
const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
|
|
4598
4383
|
await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
|
|
4599
|
-
logger$
|
|
4384
|
+
logger$6.log("Generated: .gkm/docker/docker-compose.yml");
|
|
4600
4385
|
const dockerignore = generateDockerignore();
|
|
4601
4386
|
const dockerignorePath = (0, node_path.join)(process.cwd(), ".dockerignore");
|
|
4602
4387
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
4603
|
-
logger$
|
|
4388
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
4604
4389
|
const entrypoint = generateDockerEntrypoint();
|
|
4605
4390
|
const entrypointPath = (0, node_path.join)(dockerDir, "docker-entrypoint.sh");
|
|
4606
4391
|
await (0, node_fs_promises.writeFile)(entrypointPath, entrypoint);
|
|
4607
|
-
logger$
|
|
4392
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
4608
4393
|
const result = {
|
|
4609
4394
|
dockerfile: dockerfilePath,
|
|
4610
4395
|
dockerCompose: composePath,
|
|
@@ -4623,13 +4408,13 @@ async function dockerCommand(options) {
|
|
|
4623
4408
|
function ensureLockfile(cwd) {
|
|
4624
4409
|
const lockfilePath = findLockfilePath(cwd);
|
|
4625
4410
|
if (!lockfilePath) {
|
|
4626
|
-
logger$
|
|
4411
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
4627
4412
|
return null;
|
|
4628
4413
|
}
|
|
4629
4414
|
const lockfileName = (0, node_path.basename)(lockfilePath);
|
|
4630
4415
|
const localLockfile = (0, node_path.join)(cwd, lockfileName);
|
|
4631
4416
|
if (lockfilePath === localLockfile) return null;
|
|
4632
|
-
logger$
|
|
4417
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
4633
4418
|
(0, node_fs.copyFileSync)(lockfilePath, localLockfile);
|
|
4634
4419
|
return () => {
|
|
4635
4420
|
try {
|
|
@@ -4645,7 +4430,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
4645
4430
|
const tag = options.tag ?? "latest";
|
|
4646
4431
|
const registry = options.registry;
|
|
4647
4432
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
4648
|
-
logger$
|
|
4433
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
4649
4434
|
const cwd = process.cwd();
|
|
4650
4435
|
const cleanup = ensureLockfile(cwd);
|
|
4651
4436
|
try {
|
|
@@ -4657,7 +4442,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
4657
4442
|
DOCKER_BUILDKIT: "1"
|
|
4658
4443
|
}
|
|
4659
4444
|
});
|
|
4660
|
-
logger$
|
|
4445
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
4661
4446
|
} catch (error) {
|
|
4662
4447
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4663
4448
|
} finally {
|
|
@@ -4672,13 +4457,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
4672
4457
|
const registry = options.registry;
|
|
4673
4458
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
4674
4459
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
4675
|
-
logger$
|
|
4460
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
4676
4461
|
try {
|
|
4677
4462
|
(0, node_child_process.execSync)(`docker push ${fullImageName}`, {
|
|
4678
4463
|
cwd: process.cwd(),
|
|
4679
4464
|
stdio: "inherit"
|
|
4680
4465
|
});
|
|
4681
|
-
logger$
|
|
4466
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
4682
4467
|
} catch (error) {
|
|
4683
4468
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4684
4469
|
}
|
|
@@ -4704,14 +4489,14 @@ function getAppPackageName(appPath) {
|
|
|
4704
4489
|
async function workspaceDockerCommand(workspace, options) {
|
|
4705
4490
|
const results = [];
|
|
4706
4491
|
const apps = Object.entries(workspace.apps);
|
|
4707
|
-
logger$
|
|
4492
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
4708
4493
|
const dockerDir = (0, node_path.join)(workspace.root, ".gkm", "docker");
|
|
4709
4494
|
await (0, node_fs_promises.mkdir)(dockerDir, { recursive: true });
|
|
4710
4495
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
4711
|
-
logger$
|
|
4496
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
4712
4497
|
for (const [appName, app] of apps) {
|
|
4713
4498
|
if (app.type === "mobile") {
|
|
4714
|
-
logger$
|
|
4499
|
+
logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
4715
4500
|
continue;
|
|
4716
4501
|
}
|
|
4717
4502
|
const appPath = app.path;
|
|
@@ -4720,7 +4505,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4720
4505
|
const imageName = appName;
|
|
4721
4506
|
const hasEntry = !!app.entry;
|
|
4722
4507
|
const buildType = hasEntry ? "entry" : app.type;
|
|
4723
|
-
logger$
|
|
4508
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
4724
4509
|
let dockerfile;
|
|
4725
4510
|
if (app.type === "web") {
|
|
4726
4511
|
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
@@ -4764,7 +4549,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4764
4549
|
});
|
|
4765
4550
|
const dockerfilePath = (0, node_path.join)(dockerDir, `Dockerfile.${appName}`);
|
|
4766
4551
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
4767
|
-
logger$
|
|
4552
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
4768
4553
|
results.push({
|
|
4769
4554
|
appName,
|
|
4770
4555
|
type: app.type,
|
|
@@ -4775,19 +4560,19 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4775
4560
|
const dockerignore = generateDockerignore();
|
|
4776
4561
|
const dockerignorePath = (0, node_path.join)(workspace.root, ".dockerignore");
|
|
4777
4562
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
4778
|
-
logger$
|
|
4563
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
4779
4564
|
const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
|
|
4780
4565
|
const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
|
|
4781
4566
|
await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
|
|
4782
|
-
logger$
|
|
4783
|
-
logger$
|
|
4784
|
-
logger$
|
|
4567
|
+
logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
|
|
4568
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
|
|
4569
|
+
logger$6.log("\n📋 Build commands:");
|
|
4785
4570
|
for (const result of results) {
|
|
4786
4571
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
4787
|
-
logger$
|
|
4572
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
4788
4573
|
}
|
|
4789
|
-
logger$
|
|
4790
|
-
logger$
|
|
4574
|
+
logger$6.log("\n📋 Run all services:");
|
|
4575
|
+
logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
|
|
4791
4576
|
return {
|
|
4792
4577
|
apps: results,
|
|
4793
4578
|
dockerCompose: composePath,
|
|
@@ -4827,7 +4612,7 @@ function getAppNameFromPackageJson() {
|
|
|
4827
4612
|
} catch {}
|
|
4828
4613
|
return void 0;
|
|
4829
4614
|
}
|
|
4830
|
-
const logger$
|
|
4615
|
+
const logger$5 = console;
|
|
4831
4616
|
/**
|
|
4832
4617
|
* Get the full image reference
|
|
4833
4618
|
*/
|
|
@@ -4842,18 +4627,18 @@ function getImageRef(registry, imageName, tag) {
|
|
|
4842
4627
|
* @param buildArgs - Build arguments to pass to docker build
|
|
4843
4628
|
*/
|
|
4844
4629
|
async function buildImage(imageRef, appName, buildArgs) {
|
|
4845
|
-
logger$
|
|
4630
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
4846
4631
|
const cwd = process.cwd();
|
|
4847
4632
|
const lockfilePath = findLockfilePath(cwd);
|
|
4848
4633
|
const lockfileDir = lockfilePath ? (0, node_path.dirname)(lockfilePath) : cwd;
|
|
4849
4634
|
const inMonorepo = lockfileDir !== cwd;
|
|
4850
|
-
if (appName || inMonorepo) logger$
|
|
4851
|
-
else logger$
|
|
4635
|
+
if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
|
|
4636
|
+
else logger$5.log(" Generating Dockerfile...");
|
|
4852
4637
|
await dockerCommand({});
|
|
4853
4638
|
const dockerfileSuffix = appName ? `.${appName}` : "";
|
|
4854
4639
|
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
4855
4640
|
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
4856
|
-
if (buildCwd !== cwd) logger$
|
|
4641
|
+
if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
|
|
4857
4642
|
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
4858
4643
|
try {
|
|
4859
4644
|
const cmd = [
|
|
@@ -4872,7 +4657,7 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
4872
4657
|
DOCKER_BUILDKIT: "1"
|
|
4873
4658
|
}
|
|
4874
4659
|
});
|
|
4875
|
-
logger$
|
|
4660
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
4876
4661
|
} catch (error) {
|
|
4877
4662
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4878
4663
|
}
|
|
@@ -4881,13 +4666,13 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
4881
4666
|
* Push Docker image to registry
|
|
4882
4667
|
*/
|
|
4883
4668
|
async function pushImage(imageRef) {
|
|
4884
|
-
logger$
|
|
4669
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
4885
4670
|
try {
|
|
4886
4671
|
(0, node_child_process.execSync)(`docker push ${imageRef}`, {
|
|
4887
4672
|
cwd: process.cwd(),
|
|
4888
4673
|
stdio: "inherit"
|
|
4889
4674
|
});
|
|
4890
|
-
logger$
|
|
4675
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
4891
4676
|
} catch (error) {
|
|
4892
4677
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4893
4678
|
}
|
|
@@ -4900,17 +4685,17 @@ async function deployDocker(options) {
|
|
|
4900
4685
|
const imageName = config.imageName;
|
|
4901
4686
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
4902
4687
|
await buildImage(imageRef, config.appName, buildArgs);
|
|
4903
|
-
if (!skipPush) if (!config.registry) logger$
|
|
4688
|
+
if (!skipPush) if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
4904
4689
|
else await pushImage(imageRef);
|
|
4905
|
-
logger$
|
|
4906
|
-
logger$
|
|
4907
|
-
logger$
|
|
4908
|
-
logger$
|
|
4690
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
4691
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
4692
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
4693
|
+
logger$5.log(` Stage: ${stage}`);
|
|
4909
4694
|
if (masterKey) {
|
|
4910
|
-
logger$
|
|
4911
|
-
logger$
|
|
4912
|
-
logger$
|
|
4913
|
-
logger$
|
|
4695
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
4696
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
4697
|
+
logger$5.log("\n Example docker run:");
|
|
4698
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
4914
4699
|
}
|
|
4915
4700
|
return {
|
|
4916
4701
|
imageRef,
|
|
@@ -4937,7 +4722,7 @@ function resolveDockerConfig(config) {
|
|
|
4937
4722
|
|
|
4938
4723
|
//#endregion
|
|
4939
4724
|
//#region src/deploy/dokploy.ts
|
|
4940
|
-
const logger$
|
|
4725
|
+
const logger$4 = console;
|
|
4941
4726
|
/**
|
|
4942
4727
|
* Get the Dokploy API token from stored credentials or environment
|
|
4943
4728
|
*/
|
|
@@ -4961,25 +4746,25 @@ async function createApi$1(endpoint) {
|
|
|
4961
4746
|
*/
|
|
4962
4747
|
async function deployDokploy(options) {
|
|
4963
4748
|
const { stage, imageRef, masterKey, config } = options;
|
|
4964
|
-
logger$
|
|
4965
|
-
logger$
|
|
4966
|
-
logger$
|
|
4749
|
+
logger$4.log(`\n🎯 Deploying to Dokploy...`);
|
|
4750
|
+
logger$4.log(` Endpoint: ${config.endpoint}`);
|
|
4751
|
+
logger$4.log(` Application: ${config.applicationId}`);
|
|
4967
4752
|
const api = await createApi$1(config.endpoint);
|
|
4968
|
-
logger$
|
|
4753
|
+
logger$4.log(` Configuring Docker image: ${imageRef}`);
|
|
4969
4754
|
const registryOptions = {};
|
|
4970
4755
|
if (config.registryId) {
|
|
4971
4756
|
registryOptions.registryId = config.registryId;
|
|
4972
|
-
logger$
|
|
4757
|
+
logger$4.log(` Using Dokploy registry: ${config.registryId}`);
|
|
4973
4758
|
} else {
|
|
4974
4759
|
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
4975
4760
|
if (storedRegistryId) {
|
|
4976
4761
|
registryOptions.registryId = storedRegistryId;
|
|
4977
|
-
logger$
|
|
4762
|
+
logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
4978
4763
|
} else if (config.registryCredentials) {
|
|
4979
4764
|
registryOptions.username = config.registryCredentials.username;
|
|
4980
4765
|
registryOptions.password = config.registryCredentials.password;
|
|
4981
4766
|
registryOptions.registryUrl = config.registryCredentials.registryUrl;
|
|
4982
|
-
logger$
|
|
4767
|
+
logger$4.log(` Using registry credentials for: ${config.registryCredentials.registryUrl}`);
|
|
4983
4768
|
} else {
|
|
4984
4769
|
const username = process.env.DOCKER_REGISTRY_USERNAME;
|
|
4985
4770
|
const password = process.env.DOCKER_REGISTRY_PASSWORD;
|
|
@@ -4988,31 +4773,31 @@ async function deployDokploy(options) {
|
|
|
4988
4773
|
registryOptions.username = username;
|
|
4989
4774
|
registryOptions.password = password;
|
|
4990
4775
|
registryOptions.registryUrl = registryUrl;
|
|
4991
|
-
logger$
|
|
4776
|
+
logger$4.log(` Using registry credentials from environment`);
|
|
4992
4777
|
}
|
|
4993
4778
|
}
|
|
4994
4779
|
}
|
|
4995
4780
|
await api.saveDockerProvider(config.applicationId, imageRef, registryOptions);
|
|
4996
|
-
logger$
|
|
4781
|
+
logger$4.log(" ✓ Docker provider configured");
|
|
4997
4782
|
const envVars = {};
|
|
4998
4783
|
if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
|
|
4999
4784
|
if (Object.keys(envVars).length > 0) {
|
|
5000
|
-
logger$
|
|
4785
|
+
logger$4.log(" Updating environment variables...");
|
|
5001
4786
|
const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
5002
4787
|
await api.saveApplicationEnv(config.applicationId, envString);
|
|
5003
|
-
logger$
|
|
4788
|
+
logger$4.log(" ✓ Environment variables updated");
|
|
5004
4789
|
}
|
|
5005
|
-
logger$
|
|
4790
|
+
logger$4.log(" Triggering deployment...");
|
|
5006
4791
|
await api.deployApplication(config.applicationId);
|
|
5007
|
-
logger$
|
|
5008
|
-
logger$
|
|
5009
|
-
logger$
|
|
5010
|
-
logger$
|
|
5011
|
-
logger$
|
|
5012
|
-
logger$
|
|
5013
|
-
if (masterKey) logger$
|
|
4792
|
+
logger$4.log(" ✓ Deployment triggered");
|
|
4793
|
+
logger$4.log("\n✅ Dokploy deployment initiated!");
|
|
4794
|
+
logger$4.log(`\n📋 Deployment details:`);
|
|
4795
|
+
logger$4.log(` Image: ${imageRef}`);
|
|
4796
|
+
logger$4.log(` Stage: ${stage}`);
|
|
4797
|
+
logger$4.log(` Application ID: ${config.applicationId}`);
|
|
4798
|
+
if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
|
|
5014
4799
|
const deploymentUrl = `${config.endpoint}/project/${config.projectId}`;
|
|
5015
|
-
logger$
|
|
4800
|
+
logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
|
|
5016
4801
|
return {
|
|
5017
4802
|
imageRef,
|
|
5018
4803
|
masterKey,
|
|
@@ -5129,7 +4914,7 @@ function validateEnvVars(requiredVars, context) {
|
|
|
5129
4914
|
|
|
5130
4915
|
//#endregion
|
|
5131
4916
|
//#region src/deploy/init.ts
|
|
5132
|
-
const logger$
|
|
4917
|
+
const logger$3 = console;
|
|
5133
4918
|
/**
|
|
5134
4919
|
* Get the Dokploy API token from stored credentials or environment
|
|
5135
4920
|
*/
|
|
@@ -5163,20 +4948,20 @@ async function createApi(endpoint) {
|
|
|
5163
4948
|
async function updateConfig(config, cwd = process.cwd()) {
|
|
5164
4949
|
const configPath = (0, node_path.join)(cwd, "gkm.config.ts");
|
|
5165
4950
|
if (!(0, node_fs.existsSync)(configPath)) {
|
|
5166
|
-
logger$
|
|
5167
|
-
logger$
|
|
5168
|
-
logger$
|
|
5169
|
-
logger$
|
|
5170
|
-
logger$
|
|
5171
|
-
logger$
|
|
5172
|
-
logger$
|
|
5173
|
-
logger$
|
|
4951
|
+
logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
|
|
4952
|
+
logger$3.log(` providers: {`);
|
|
4953
|
+
logger$3.log(` dokploy: {`);
|
|
4954
|
+
logger$3.log(` endpoint: '${config.endpoint}',`);
|
|
4955
|
+
logger$3.log(` projectId: '${config.projectId}',`);
|
|
4956
|
+
logger$3.log(` applicationId: '${config.applicationId}',`);
|
|
4957
|
+
logger$3.log(` },`);
|
|
4958
|
+
logger$3.log(` },`);
|
|
5174
4959
|
return;
|
|
5175
4960
|
}
|
|
5176
4961
|
const content = await (0, node_fs_promises.readFile)(configPath, "utf-8");
|
|
5177
4962
|
if (content.includes("dokploy:") && content.includes("applicationId:")) {
|
|
5178
|
-
logger$
|
|
5179
|
-
logger$
|
|
4963
|
+
logger$3.log("\n Dokploy config already exists in gkm.config.ts");
|
|
4964
|
+
logger$3.log(" Updating with new values...");
|
|
5180
4965
|
}
|
|
5181
4966
|
const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
|
|
5182
4967
|
const dokployConfigStr = `dokploy: {
|
|
@@ -5193,7 +4978,7 @@ async function updateConfig(config, cwd = process.cwd()) {
|
|
|
5193
4978
|
},
|
|
5194
4979
|
});`);
|
|
5195
4980
|
await (0, node_fs_promises.writeFile)(configPath, newContent);
|
|
5196
|
-
logger$
|
|
4981
|
+
logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
|
|
5197
4982
|
}
|
|
5198
4983
|
/**
|
|
5199
4984
|
* Initialize Dokploy deployment configuration
|
|
@@ -5202,24 +4987,24 @@ async function deployInitCommand(options) {
|
|
|
5202
4987
|
const { projectName, appName, projectId: existingProjectId, registryId } = options;
|
|
5203
4988
|
const endpoint = await getEndpoint(options.endpoint);
|
|
5204
4989
|
const api = await createApi(endpoint);
|
|
5205
|
-
logger$
|
|
5206
|
-
logger$
|
|
4990
|
+
logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
|
|
4991
|
+
logger$3.log(` Endpoint: ${endpoint}`);
|
|
5207
4992
|
let projectId;
|
|
5208
4993
|
if (existingProjectId) {
|
|
5209
4994
|
projectId = existingProjectId;
|
|
5210
|
-
logger$
|
|
4995
|
+
logger$3.log(`\n📁 Using existing project: ${projectId}`);
|
|
5211
4996
|
} else {
|
|
5212
|
-
logger$
|
|
4997
|
+
logger$3.log(`\n📁 Looking for project: ${projectName}`);
|
|
5213
4998
|
const projects = await api.listProjects();
|
|
5214
4999
|
const existingProject = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
5215
5000
|
if (existingProject) {
|
|
5216
5001
|
projectId = existingProject.projectId;
|
|
5217
|
-
logger$
|
|
5002
|
+
logger$3.log(` Found existing project: ${projectId}`);
|
|
5218
5003
|
} else {
|
|
5219
|
-
logger$
|
|
5004
|
+
logger$3.log(` Creating new project...`);
|
|
5220
5005
|
const result = await api.createProject(projectName);
|
|
5221
5006
|
projectId = result.project.projectId;
|
|
5222
|
-
logger$
|
|
5007
|
+
logger$3.log(` ✓ Created project: ${projectId}`);
|
|
5223
5008
|
}
|
|
5224
5009
|
}
|
|
5225
5010
|
const project = await api.getProject(projectId);
|
|
@@ -5227,23 +5012,23 @@ async function deployInitCommand(options) {
|
|
|
5227
5012
|
const firstEnv = project.environments?.[0];
|
|
5228
5013
|
if (firstEnv) environmentId = firstEnv.environmentId;
|
|
5229
5014
|
else {
|
|
5230
|
-
logger$
|
|
5015
|
+
logger$3.log(` Creating production environment...`);
|
|
5231
5016
|
const env = await api.createEnvironment(projectId, "production");
|
|
5232
5017
|
environmentId = env.environmentId;
|
|
5233
5018
|
}
|
|
5234
|
-
logger$
|
|
5019
|
+
logger$3.log(`\n📦 Creating application: ${appName}`);
|
|
5235
5020
|
const application = await api.createApplication(appName, projectId, environmentId);
|
|
5236
|
-
logger$
|
|
5021
|
+
logger$3.log(` ✓ Created application: ${application.applicationId}`);
|
|
5237
5022
|
if (registryId) {
|
|
5238
|
-
logger$
|
|
5023
|
+
logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
|
|
5239
5024
|
await api.updateApplication(application.applicationId, { registryId });
|
|
5240
|
-
logger$
|
|
5025
|
+
logger$3.log(` ✓ Registry configured`);
|
|
5241
5026
|
} else try {
|
|
5242
5027
|
const registries = await api.listRegistries();
|
|
5243
5028
|
if (registries.length > 0) {
|
|
5244
|
-
logger$
|
|
5245
|
-
for (const reg of registries) logger$
|
|
5246
|
-
logger$
|
|
5029
|
+
logger$3.log(`\n📋 Available registries:`);
|
|
5030
|
+
for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
|
|
5031
|
+
logger$3.log(`\n To use a registry, run with --registry-id <id>`);
|
|
5247
5032
|
}
|
|
5248
5033
|
} catch {}
|
|
5249
5034
|
const config = {
|
|
@@ -5252,14 +5037,14 @@ async function deployInitCommand(options) {
|
|
|
5252
5037
|
applicationId: application.applicationId
|
|
5253
5038
|
};
|
|
5254
5039
|
await updateConfig(config);
|
|
5255
|
-
logger$
|
|
5256
|
-
logger$
|
|
5257
|
-
logger$
|
|
5258
|
-
logger$
|
|
5259
|
-
logger$
|
|
5260
|
-
logger$
|
|
5261
|
-
logger$
|
|
5262
|
-
logger$
|
|
5040
|
+
logger$3.log(`\n✅ Dokploy deployment initialized!`);
|
|
5041
|
+
logger$3.log(`\n📋 Configuration:`);
|
|
5042
|
+
logger$3.log(` Project ID: ${projectId}`);
|
|
5043
|
+
logger$3.log(` Application ID: ${application.applicationId}`);
|
|
5044
|
+
logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
|
|
5045
|
+
logger$3.log(`\n📝 Next steps:`);
|
|
5046
|
+
logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
|
|
5047
|
+
logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
|
|
5263
5048
|
return config;
|
|
5264
5049
|
}
|
|
5265
5050
|
/**
|
|
@@ -5270,32 +5055,32 @@ async function deployListCommand(options) {
|
|
|
5270
5055
|
const api = await createApi(endpoint);
|
|
5271
5056
|
const { resource } = options;
|
|
5272
5057
|
if (resource === "projects") {
|
|
5273
|
-
logger$
|
|
5058
|
+
logger$3.log(`\n📁 Projects in ${endpoint}:`);
|
|
5274
5059
|
const projects = await api.listProjects();
|
|
5275
5060
|
if (projects.length === 0) {
|
|
5276
|
-
logger$
|
|
5061
|
+
logger$3.log(" No projects found");
|
|
5277
5062
|
return;
|
|
5278
5063
|
}
|
|
5279
5064
|
for (const project of projects) {
|
|
5280
|
-
logger$
|
|
5281
|
-
if (project.description) logger$
|
|
5065
|
+
logger$3.log(`\n ${project.name} (${project.projectId})`);
|
|
5066
|
+
if (project.description) logger$3.log(` ${project.description}`);
|
|
5282
5067
|
}
|
|
5283
5068
|
} else if (resource === "registries") {
|
|
5284
|
-
logger$
|
|
5069
|
+
logger$3.log(`\n🐳 Registries in ${endpoint}:`);
|
|
5285
5070
|
const registries = await api.listRegistries();
|
|
5286
5071
|
if (registries.length === 0) {
|
|
5287
|
-
logger$
|
|
5288
|
-
logger$
|
|
5072
|
+
logger$3.log(" No registries configured");
|
|
5073
|
+
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
5289
5074
|
return;
|
|
5290
5075
|
}
|
|
5291
5076
|
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
5292
5077
|
for (const registry of registries) {
|
|
5293
5078
|
const isDefault = registry.registryId === storedRegistryId;
|
|
5294
5079
|
const marker = isDefault ? " (default)" : "";
|
|
5295
|
-
logger$
|
|
5296
|
-
logger$
|
|
5297
|
-
logger$
|
|
5298
|
-
if (registry.imagePrefix) logger$
|
|
5080
|
+
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
5081
|
+
logger$3.log(` URL: ${registry.registryUrl}`);
|
|
5082
|
+
logger$3.log(` Username: ${registry.username}`);
|
|
5083
|
+
if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
|
|
5299
5084
|
}
|
|
5300
5085
|
}
|
|
5301
5086
|
}
|
|
@@ -5318,20 +5103,20 @@ function isStateProvider(value) {
|
|
|
5318
5103
|
async function createStateProvider(options) {
|
|
5319
5104
|
const { config, workspaceRoot, workspaceName } = options;
|
|
5320
5105
|
if (!config) {
|
|
5321
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-
|
|
5106
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CalzNfNA.cjs"));
|
|
5322
5107
|
return new LocalStateProvider(workspaceRoot);
|
|
5323
5108
|
}
|
|
5324
5109
|
if (isStateProvider(config.provider)) return config.provider;
|
|
5325
5110
|
const provider = config.provider;
|
|
5326
5111
|
if (provider === "local") {
|
|
5327
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-
|
|
5112
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CalzNfNA.cjs"));
|
|
5328
5113
|
return new LocalStateProvider(workspaceRoot);
|
|
5329
5114
|
}
|
|
5330
5115
|
if (provider === "ssm") {
|
|
5331
5116
|
if (!workspaceName) throw new Error("Workspace name is required for SSM state provider. Set \"name\" in gkm.config.ts.");
|
|
5332
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-
|
|
5333
|
-
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-
|
|
5334
|
-
const { CachedStateProvider: CachedStateProvider$1 } = await Promise.resolve().then(() => require("./CachedStateProvider-
|
|
5117
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CalzNfNA.cjs"));
|
|
5118
|
+
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-Bt6r8CTL.cjs"));
|
|
5119
|
+
const { CachedStateProvider: CachedStateProvider$1 } = await Promise.resolve().then(() => require("./CachedStateProvider-BdX7klRc.cjs"));
|
|
5335
5120
|
const ssmConfig = config;
|
|
5336
5121
|
const local = new LocalStateProvider(workspaceRoot);
|
|
5337
5122
|
const ssm = SSMStateProvider.create({
|
|
@@ -5791,7 +5576,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
|
5791
5576
|
|
|
5792
5577
|
//#endregion
|
|
5793
5578
|
//#region src/deploy/index.ts
|
|
5794
|
-
const logger$
|
|
5579
|
+
const logger$2 = console;
|
|
5795
5580
|
/**
|
|
5796
5581
|
* Prompt for input
|
|
5797
5582
|
*/
|
|
@@ -5799,7 +5584,7 @@ async function prompt(message, hidden = false) {
|
|
|
5799
5584
|
if (!process.stdin.isTTY) throw new Error("Interactive input required. Please configure manually.");
|
|
5800
5585
|
if (hidden) {
|
|
5801
5586
|
process.stdout.write(message);
|
|
5802
|
-
return new Promise((resolve$
|
|
5587
|
+
return new Promise((resolve$3) => {
|
|
5803
5588
|
let value = "";
|
|
5804
5589
|
const onData = (char) => {
|
|
5805
5590
|
const c = char.toString();
|
|
@@ -5808,7 +5593,7 @@ async function prompt(message, hidden = false) {
|
|
|
5808
5593
|
process.stdin.pause();
|
|
5809
5594
|
process.stdin.removeListener("data", onData);
|
|
5810
5595
|
process.stdout.write("\n");
|
|
5811
|
-
resolve$
|
|
5596
|
+
resolve$3(value);
|
|
5812
5597
|
} else if (c === "") {
|
|
5813
5598
|
process.stdin.setRawMode(false);
|
|
5814
5599
|
process.stdin.pause();
|
|
@@ -5863,7 +5648,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
5863
5648
|
return;
|
|
5864
5649
|
} catch {
|
|
5865
5650
|
if (i < maxRetries - 1) {
|
|
5866
|
-
logger$
|
|
5651
|
+
logger$2.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
|
|
5867
5652
|
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
5868
5653
|
}
|
|
5869
5654
|
}
|
|
@@ -5898,12 +5683,12 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
5898
5683
|
* ```
|
|
5899
5684
|
*/
|
|
5900
5685
|
async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
5901
|
-
logger$
|
|
5686
|
+
logger$2.log("\n🔧 Initializing database users...");
|
|
5902
5687
|
const externalPort = 5432;
|
|
5903
|
-
logger$
|
|
5688
|
+
logger$2.log(` Enabling external port ${externalPort}...`);
|
|
5904
5689
|
await api.savePostgresExternalPort(postgres.postgresId, externalPort);
|
|
5905
5690
|
await api.deployPostgres(postgres.postgresId);
|
|
5906
|
-
logger$
|
|
5691
|
+
logger$2.log(` Waiting for Postgres to be accessible at ${serverHostname}:${externalPort}...`);
|
|
5907
5692
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
|
|
5908
5693
|
const client = new pg.Client({
|
|
5909
5694
|
host: serverHostname,
|
|
@@ -5916,7 +5701,7 @@ async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
|
5916
5701
|
await client.connect();
|
|
5917
5702
|
for (const user of users) {
|
|
5918
5703
|
const schemaName = user.usePublicSchema ? "public" : user.name;
|
|
5919
|
-
logger$
|
|
5704
|
+
logger$2.log(` Creating user "${user.name}" with schema "${schemaName}"...`);
|
|
5920
5705
|
if (user.usePublicSchema) {
|
|
5921
5706
|
await client.query(`
|
|
5922
5707
|
DO $$ BEGIN
|
|
@@ -5951,15 +5736,15 @@ async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
|
5951
5736
|
ALTER DEFAULT PRIVILEGES IN SCHEMA "${schemaName}" GRANT ALL ON TABLES TO "${user.name}";
|
|
5952
5737
|
`);
|
|
5953
5738
|
}
|
|
5954
|
-
logger$
|
|
5739
|
+
logger$2.log(` ✓ User "${user.name}" configured`);
|
|
5955
5740
|
}
|
|
5956
5741
|
} finally {
|
|
5957
5742
|
await client.end();
|
|
5958
5743
|
}
|
|
5959
|
-
logger$
|
|
5744
|
+
logger$2.log(" Disabling external port...");
|
|
5960
5745
|
await api.savePostgresExternalPort(postgres.postgresId, null);
|
|
5961
5746
|
await api.deployPostgres(postgres.postgresId);
|
|
5962
|
-
logger$
|
|
5747
|
+
logger$2.log(" ✓ Database users initialized");
|
|
5963
5748
|
}
|
|
5964
5749
|
/**
|
|
5965
5750
|
* Get the server hostname from the Dokploy endpoint URL
|
|
@@ -5973,24 +5758,24 @@ function getServerHostname(endpoint) {
|
|
|
5973
5758
|
* @internal Exported for testing
|
|
5974
5759
|
*/
|
|
5975
5760
|
async function provisionServices(api, projectId, environmentId, projectName, services, existingServiceIds) {
|
|
5976
|
-
logger$
|
|
5761
|
+
logger$2.log(`\n🔍 provisionServices called: services=${JSON.stringify(services)}, envId=${environmentId}`);
|
|
5977
5762
|
if (!services || !environmentId) {
|
|
5978
|
-
logger$
|
|
5763
|
+
logger$2.log(" Skipping: no services or no environmentId");
|
|
5979
5764
|
return void 0;
|
|
5980
5765
|
}
|
|
5981
5766
|
const serviceUrls = {};
|
|
5982
5767
|
const serviceIds = {};
|
|
5983
5768
|
if (services.postgres) {
|
|
5984
|
-
logger$
|
|
5769
|
+
logger$2.log("\n🐘 Checking PostgreSQL...");
|
|
5985
5770
|
const postgresName = "db";
|
|
5986
5771
|
try {
|
|
5987
5772
|
let postgres = null;
|
|
5988
5773
|
let created = false;
|
|
5989
5774
|
if (existingServiceIds?.postgresId) {
|
|
5990
|
-
logger$
|
|
5775
|
+
logger$2.log(` Using cached ID: ${existingServiceIds.postgresId}`);
|
|
5991
5776
|
postgres = await api.getPostgres(existingServiceIds.postgresId);
|
|
5992
|
-
if (postgres) logger$
|
|
5993
|
-
else logger$
|
|
5777
|
+
if (postgres) logger$2.log(` ✓ PostgreSQL found: ${postgres.postgresId}`);
|
|
5778
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
5994
5779
|
}
|
|
5995
5780
|
if (!postgres) {
|
|
5996
5781
|
const databasePassword = (0, node_crypto.randomBytes)(16).toString("hex");
|
|
@@ -6002,10 +5787,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6002
5787
|
postgres = result.postgres;
|
|
6003
5788
|
created = result.created;
|
|
6004
5789
|
if (created) {
|
|
6005
|
-
logger$
|
|
5790
|
+
logger$2.log(` ✓ Created PostgreSQL: ${postgres.postgresId}`);
|
|
6006
5791
|
await api.deployPostgres(postgres.postgresId);
|
|
6007
|
-
logger$
|
|
6008
|
-
} else logger$
|
|
5792
|
+
logger$2.log(" ✓ PostgreSQL deployed");
|
|
5793
|
+
} else logger$2.log(` ✓ PostgreSQL already exists: ${postgres.postgresId}`);
|
|
6009
5794
|
}
|
|
6010
5795
|
serviceIds.postgresId = postgres.postgresId;
|
|
6011
5796
|
serviceUrls.DATABASE_HOST = postgres.appName;
|
|
@@ -6014,23 +5799,23 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6014
5799
|
serviceUrls.DATABASE_USER = postgres.databaseUser;
|
|
6015
5800
|
serviceUrls.DATABASE_PASSWORD = postgres.databasePassword;
|
|
6016
5801
|
serviceUrls.DATABASE_URL = `postgresql://${postgres.databaseUser}:${postgres.databasePassword}@${postgres.appName}:5432/${postgres.databaseName}`;
|
|
6017
|
-
logger$
|
|
5802
|
+
logger$2.log(` ✓ Database credentials configured`);
|
|
6018
5803
|
} catch (error) {
|
|
6019
5804
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6020
|
-
logger$
|
|
5805
|
+
logger$2.log(` ⚠ Failed to provision PostgreSQL: ${message}`);
|
|
6021
5806
|
}
|
|
6022
5807
|
}
|
|
6023
5808
|
if (services.redis) {
|
|
6024
|
-
logger$
|
|
5809
|
+
logger$2.log("\n🔴 Checking Redis...");
|
|
6025
5810
|
const redisName = "cache";
|
|
6026
5811
|
try {
|
|
6027
5812
|
let redis = null;
|
|
6028
5813
|
let created = false;
|
|
6029
5814
|
if (existingServiceIds?.redisId) {
|
|
6030
|
-
logger$
|
|
5815
|
+
logger$2.log(` Using cached ID: ${existingServiceIds.redisId}`);
|
|
6031
5816
|
redis = await api.getRedis(existingServiceIds.redisId);
|
|
6032
|
-
if (redis) logger$
|
|
6033
|
-
else logger$
|
|
5817
|
+
if (redis) logger$2.log(` ✓ Redis found: ${redis.redisId}`);
|
|
5818
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6034
5819
|
}
|
|
6035
5820
|
if (!redis) {
|
|
6036
5821
|
const { randomBytes: randomBytes$2 } = await import("node:crypto");
|
|
@@ -6039,10 +5824,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6039
5824
|
redis = result.redis;
|
|
6040
5825
|
created = result.created;
|
|
6041
5826
|
if (created) {
|
|
6042
|
-
logger$
|
|
5827
|
+
logger$2.log(` ✓ Created Redis: ${redis.redisId}`);
|
|
6043
5828
|
await api.deployRedis(redis.redisId);
|
|
6044
|
-
logger$
|
|
6045
|
-
} else logger$
|
|
5829
|
+
logger$2.log(" ✓ Redis deployed");
|
|
5830
|
+
} else logger$2.log(` ✓ Redis already exists: ${redis.redisId}`);
|
|
6046
5831
|
}
|
|
6047
5832
|
serviceIds.redisId = redis.redisId;
|
|
6048
5833
|
serviceUrls.REDIS_HOST = redis.appName;
|
|
@@ -6050,10 +5835,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6050
5835
|
if (redis.databasePassword) serviceUrls.REDIS_PASSWORD = redis.databasePassword;
|
|
6051
5836
|
const password = redis.databasePassword ? `:${redis.databasePassword}@` : "";
|
|
6052
5837
|
serviceUrls.REDIS_URL = `redis://${password}${redis.appName}:6379`;
|
|
6053
|
-
logger$
|
|
5838
|
+
logger$2.log(` ✓ Redis credentials configured`);
|
|
6054
5839
|
} catch (error) {
|
|
6055
5840
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6056
|
-
logger$
|
|
5841
|
+
logger$2.log(` ⚠ Failed to provision Redis: ${message}`);
|
|
6057
5842
|
}
|
|
6058
5843
|
}
|
|
6059
5844
|
return Object.keys(serviceUrls).length > 0 ? {
|
|
@@ -6065,10 +5850,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6065
5850
|
* Ensure Dokploy is fully configured, recovering/creating resources as needed
|
|
6066
5851
|
*/
|
|
6067
5852
|
async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
6068
|
-
logger$
|
|
5853
|
+
logger$2.log("\n🔧 Checking Dokploy setup...");
|
|
6069
5854
|
let creds = await require_credentials.getDokployCredentials();
|
|
6070
5855
|
if (!creds) {
|
|
6071
|
-
logger$
|
|
5856
|
+
logger$2.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
6072
5857
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
6073
5858
|
const normalizedEndpoint = endpoint.replace(/\/$/, "");
|
|
6074
5859
|
try {
|
|
@@ -6076,9 +5861,9 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
6076
5861
|
} catch {
|
|
6077
5862
|
throw new Error("Invalid URL format");
|
|
6078
5863
|
}
|
|
6079
|
-
logger$
|
|
5864
|
+
logger$2.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
6080
5865
|
const token = await prompt("API Token: ", true);
|
|
6081
|
-
logger$
|
|
5866
|
+
logger$2.log("\nValidating credentials...");
|
|
6082
5867
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
6083
5868
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
6084
5869
|
await require_credentials.storeDokployCredentials(token, normalizedEndpoint);
|
|
@@ -6086,7 +5871,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
6086
5871
|
token,
|
|
6087
5872
|
endpoint: normalizedEndpoint
|
|
6088
5873
|
};
|
|
6089
|
-
logger$
|
|
5874
|
+
logger$2.log("✓ Credentials saved");
|
|
6090
5875
|
}
|
|
6091
5876
|
const api = new require_dokploy_api.DokployApi({
|
|
6092
5877
|
baseUrl: creds.endpoint,
|
|
@@ -6094,20 +5879,20 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
6094
5879
|
});
|
|
6095
5880
|
const existingConfig = config.providers?.dokploy;
|
|
6096
5881
|
if (existingConfig && typeof existingConfig !== "boolean" && existingConfig.applicationId && existingConfig.projectId) {
|
|
6097
|
-
logger$
|
|
5882
|
+
logger$2.log("✓ Dokploy config found in gkm.config.ts");
|
|
6098
5883
|
try {
|
|
6099
5884
|
const projectDetails = await api.getProject(existingConfig.projectId);
|
|
6100
|
-
logger$
|
|
5885
|
+
logger$2.log("✓ Project verified");
|
|
6101
5886
|
const storedRegistryId = existingConfig.registryId ?? await require_credentials.getDokployRegistryId();
|
|
6102
5887
|
const environments = projectDetails.environments ?? [];
|
|
6103
5888
|
let environment = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6104
5889
|
if (!environment) {
|
|
6105
|
-
logger$
|
|
5890
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6106
5891
|
environment = await api.createEnvironment(existingConfig.projectId, stage);
|
|
6107
|
-
logger$
|
|
5892
|
+
logger$2.log(` ✓ Created environment: ${environment.environmentId}`);
|
|
6108
5893
|
}
|
|
6109
5894
|
const environmentId$1 = environment.environmentId;
|
|
6110
|
-
logger$
|
|
5895
|
+
logger$2.log(` Services config: ${JSON.stringify(services)}, envId: ${environmentId$1}`);
|
|
6111
5896
|
const provisionResult$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.appName, services, void 0);
|
|
6112
5897
|
return {
|
|
6113
5898
|
config: {
|
|
@@ -6120,97 +5905,97 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
6120
5905
|
serviceUrls: provisionResult$1?.serviceUrls
|
|
6121
5906
|
};
|
|
6122
5907
|
} catch {
|
|
6123
|
-
logger$
|
|
5908
|
+
logger$2.log("⚠ Project not found, will recover...");
|
|
6124
5909
|
}
|
|
6125
5910
|
}
|
|
6126
|
-
logger$
|
|
5911
|
+
logger$2.log("\n📁 Looking for project...");
|
|
6127
5912
|
const projectName = dockerConfig.projectName;
|
|
6128
5913
|
const projects = await api.listProjects();
|
|
6129
5914
|
let project = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
6130
5915
|
let environmentId;
|
|
6131
5916
|
if (project) {
|
|
6132
|
-
logger$
|
|
5917
|
+
logger$2.log(` Found existing project: ${project.name} (${project.projectId})`);
|
|
6133
5918
|
const projectDetails = await api.getProject(project.projectId);
|
|
6134
5919
|
const environments = projectDetails.environments ?? [];
|
|
6135
5920
|
const matchingEnv = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6136
5921
|
if (matchingEnv) {
|
|
6137
5922
|
environmentId = matchingEnv.environmentId;
|
|
6138
|
-
logger$
|
|
5923
|
+
logger$2.log(` Using environment: ${matchingEnv.name}`);
|
|
6139
5924
|
} else {
|
|
6140
|
-
logger$
|
|
5925
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6141
5926
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6142
5927
|
environmentId = env.environmentId;
|
|
6143
|
-
logger$
|
|
5928
|
+
logger$2.log(` ✓ Created environment: ${stage}`);
|
|
6144
5929
|
}
|
|
6145
5930
|
} else {
|
|
6146
|
-
logger$
|
|
5931
|
+
logger$2.log(` Creating project: ${projectName}`);
|
|
6147
5932
|
const result = await api.createProject(projectName);
|
|
6148
5933
|
project = result.project;
|
|
6149
5934
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
6150
|
-
logger$
|
|
5935
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6151
5936
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6152
5937
|
environmentId = env.environmentId;
|
|
6153
5938
|
} else environmentId = result.environment.environmentId;
|
|
6154
|
-
logger$
|
|
6155
|
-
logger$
|
|
5939
|
+
logger$2.log(` ✓ Created project: ${project.projectId}`);
|
|
5940
|
+
logger$2.log(` ✓ Using environment: ${stage}`);
|
|
6156
5941
|
}
|
|
6157
|
-
logger$
|
|
5942
|
+
logger$2.log("\n📦 Looking for application...");
|
|
6158
5943
|
const appName = dockerConfig.appName;
|
|
6159
5944
|
let applicationId;
|
|
6160
5945
|
if (existingConfig && typeof existingConfig !== "boolean" && existingConfig.applicationId) {
|
|
6161
5946
|
applicationId = existingConfig.applicationId;
|
|
6162
|
-
logger$
|
|
5947
|
+
logger$2.log(` Using application from config: ${applicationId}`);
|
|
6163
5948
|
} else {
|
|
6164
|
-
logger$
|
|
5949
|
+
logger$2.log(` Creating application: ${appName}`);
|
|
6165
5950
|
const app = await api.createApplication(appName, project.projectId, environmentId);
|
|
6166
5951
|
applicationId = app.applicationId;
|
|
6167
|
-
logger$
|
|
5952
|
+
logger$2.log(` ✓ Created application: ${applicationId}`);
|
|
6168
5953
|
}
|
|
6169
|
-
logger$
|
|
5954
|
+
logger$2.log("\n🐳 Checking registry...");
|
|
6170
5955
|
let registryId = await require_credentials.getDokployRegistryId();
|
|
6171
5956
|
if (registryId) try {
|
|
6172
5957
|
const registry = await api.getRegistry(registryId);
|
|
6173
|
-
logger$
|
|
5958
|
+
logger$2.log(` Using registry: ${registry.registryName}`);
|
|
6174
5959
|
} catch {
|
|
6175
|
-
logger$
|
|
5960
|
+
logger$2.log(" ⚠ Stored registry not found, clearing...");
|
|
6176
5961
|
registryId = void 0;
|
|
6177
5962
|
await require_credentials.storeDokployRegistryId("");
|
|
6178
5963
|
}
|
|
6179
5964
|
if (!registryId) {
|
|
6180
5965
|
const registries = await api.listRegistries();
|
|
6181
5966
|
if (registries.length === 0) if (dockerConfig.registry) {
|
|
6182
|
-
logger$
|
|
6183
|
-
logger$
|
|
5967
|
+
logger$2.log(" No registries found in Dokploy. Let's create one.");
|
|
5968
|
+
logger$2.log(` Registry URL: ${dockerConfig.registry}`);
|
|
6184
5969
|
const username = await prompt("Registry username: ");
|
|
6185
5970
|
const password = await prompt("Registry password/token: ", true);
|
|
6186
5971
|
const registry = await api.createRegistry("Default Registry", dockerConfig.registry, username, password);
|
|
6187
5972
|
registryId = registry.registryId;
|
|
6188
5973
|
await require_credentials.storeDokployRegistryId(registryId);
|
|
6189
|
-
logger$
|
|
6190
|
-
} else logger$
|
|
5974
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
5975
|
+
} else logger$2.log(" ⚠ No registry configured. Set docker.registry in gkm.config.ts");
|
|
6191
5976
|
else {
|
|
6192
|
-
logger$
|
|
5977
|
+
logger$2.log(" Available registries:");
|
|
6193
5978
|
registries.forEach((reg, i) => {
|
|
6194
|
-
logger$
|
|
5979
|
+
logger$2.log(` ${i + 1}. ${reg.registryName} (${reg.registryUrl})`);
|
|
6195
5980
|
});
|
|
6196
|
-
if (dockerConfig.registry) logger$
|
|
5981
|
+
if (dockerConfig.registry) logger$2.log(` ${registries.length + 1}. Create new registry`);
|
|
6197
5982
|
const maxOption = dockerConfig.registry ? registries.length + 1 : registries.length;
|
|
6198
5983
|
const selection = await prompt(` Select registry (1-${maxOption}): `);
|
|
6199
5984
|
const index = parseInt(selection, 10) - 1;
|
|
6200
5985
|
if (index >= 0 && index < registries.length) {
|
|
6201
5986
|
registryId = registries[index].registryId;
|
|
6202
5987
|
await require_credentials.storeDokployRegistryId(registryId);
|
|
6203
|
-
logger$
|
|
5988
|
+
logger$2.log(` ✓ Selected: ${registries[index].registryName}`);
|
|
6204
5989
|
} else if (dockerConfig.registry && index === registries.length) {
|
|
6205
|
-
logger$
|
|
6206
|
-
logger$
|
|
5990
|
+
logger$2.log(`\n Creating new registry...`);
|
|
5991
|
+
logger$2.log(` Registry URL: ${dockerConfig.registry}`);
|
|
6207
5992
|
const username = await prompt(" Registry username: ");
|
|
6208
5993
|
const password = await prompt(" Registry password/token: ", true);
|
|
6209
5994
|
const registry = await api.createRegistry(dockerConfig.registry.replace(/^https?:\/\//, ""), dockerConfig.registry, username, password);
|
|
6210
5995
|
registryId = registry.registryId;
|
|
6211
5996
|
await require_credentials.storeDokployRegistryId(registryId);
|
|
6212
|
-
logger$
|
|
6213
|
-
} else logger$
|
|
5997
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
5998
|
+
} else logger$2.log(" ⚠ Invalid selection, skipping registry setup");
|
|
6214
5999
|
}
|
|
6215
6000
|
}
|
|
6216
6001
|
const dokployConfig = {
|
|
@@ -6220,10 +6005,10 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
6220
6005
|
registryId: registryId ?? void 0
|
|
6221
6006
|
};
|
|
6222
6007
|
await updateConfig(dokployConfig);
|
|
6223
|
-
logger$
|
|
6224
|
-
logger$
|
|
6225
|
-
logger$
|
|
6226
|
-
if (registryId) logger$
|
|
6008
|
+
logger$2.log("\n✅ Dokploy setup complete!");
|
|
6009
|
+
logger$2.log(` Project: ${project.projectId}`);
|
|
6010
|
+
logger$2.log(` Application: ${applicationId}`);
|
|
6011
|
+
if (registryId) logger$2.log(` Registry: ${registryId}`);
|
|
6227
6012
|
const provisionResult = await provisionServices(api, project.projectId, environmentId, dockerConfig.appName, services, void 0);
|
|
6228
6013
|
return {
|
|
6229
6014
|
config: dokployConfig,
|
|
@@ -6254,45 +6039,45 @@ function generateTag(stage) {
|
|
|
6254
6039
|
async function workspaceDeployCommand(workspace, options) {
|
|
6255
6040
|
const { provider, stage, tag, apps: selectedApps } = options;
|
|
6256
6041
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
6257
|
-
logger$
|
|
6258
|
-
logger$
|
|
6042
|
+
logger$2.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
6043
|
+
logger$2.log(` Stage: ${stage}`);
|
|
6259
6044
|
const imageTag = tag ?? generateTag(stage);
|
|
6260
|
-
logger$
|
|
6045
|
+
logger$2.log(` Tag: ${imageTag}`);
|
|
6261
6046
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
6262
6047
|
let appsToDeployNames = buildOrder;
|
|
6263
6048
|
if (selectedApps && selectedApps.length > 0) {
|
|
6264
6049
|
const invalidApps = selectedApps.filter((name$1) => !workspace.apps[name$1]);
|
|
6265
6050
|
if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
6266
6051
|
appsToDeployNames = buildOrder.filter((name$1) => selectedApps.includes(name$1));
|
|
6267
|
-
logger$
|
|
6268
|
-
} else logger$
|
|
6052
|
+
logger$2.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
|
|
6053
|
+
} else logger$2.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
|
|
6269
6054
|
const dokployApps = appsToDeployNames.filter((name$1) => {
|
|
6270
6055
|
const app = workspace.apps[name$1];
|
|
6271
6056
|
const target = app.resolvedDeployTarget;
|
|
6272
6057
|
if (!require_workspace.isDeployTargetSupported(target)) {
|
|
6273
|
-
logger$
|
|
6058
|
+
logger$2.log(` ⚠️ Skipping ${name$1}: ${require_workspace.getDeployTargetError(target, name$1)}`);
|
|
6274
6059
|
return false;
|
|
6275
6060
|
}
|
|
6276
6061
|
return true;
|
|
6277
6062
|
});
|
|
6278
6063
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
6279
6064
|
appsToDeployNames = dokployApps;
|
|
6280
|
-
logger$
|
|
6065
|
+
logger$2.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
6281
6066
|
const stageSecrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
6282
6067
|
if (!stageSecrets) {
|
|
6283
|
-
logger$
|
|
6284
|
-
logger$
|
|
6068
|
+
logger$2.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
6069
|
+
logger$2.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
6285
6070
|
}
|
|
6286
6071
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
6287
6072
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
6288
6073
|
if (stageSecrets) {
|
|
6289
6074
|
const report = generateSecretsReport(encryptedSecrets, sniffedApps);
|
|
6290
|
-
if (report.appsWithSecrets.length > 0) logger$
|
|
6291
|
-
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$
|
|
6075
|
+
if (report.appsWithSecrets.length > 0) logger$2.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
6076
|
+
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$2.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
6292
6077
|
}
|
|
6293
6078
|
let creds = await require_credentials.getDokployCredentials();
|
|
6294
6079
|
if (!creds) {
|
|
6295
|
-
logger$
|
|
6080
|
+
logger$2.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
6296
6081
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
6297
6082
|
const normalizedEndpoint = endpoint.replace(/\/$/, "");
|
|
6298
6083
|
try {
|
|
@@ -6300,9 +6085,9 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6300
6085
|
} catch {
|
|
6301
6086
|
throw new Error("Invalid URL format");
|
|
6302
6087
|
}
|
|
6303
|
-
logger$
|
|
6088
|
+
logger$2.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
6304
6089
|
const token = await prompt("API Token: ", true);
|
|
6305
|
-
logger$
|
|
6090
|
+
logger$2.log("\nValidating credentials...");
|
|
6306
6091
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
6307
6092
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
6308
6093
|
await require_credentials.storeDokployCredentials(token, normalizedEndpoint);
|
|
@@ -6310,43 +6095,43 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6310
6095
|
token,
|
|
6311
6096
|
endpoint: normalizedEndpoint
|
|
6312
6097
|
};
|
|
6313
|
-
logger$
|
|
6098
|
+
logger$2.log("✓ Credentials saved");
|
|
6314
6099
|
}
|
|
6315
6100
|
const api = new require_dokploy_api.DokployApi({
|
|
6316
6101
|
baseUrl: creds.endpoint,
|
|
6317
6102
|
token: creds.token
|
|
6318
6103
|
});
|
|
6319
|
-
logger$
|
|
6104
|
+
logger$2.log("\n📁 Setting up Dokploy project...");
|
|
6320
6105
|
const projectName = workspace.name;
|
|
6321
6106
|
const projects = await api.listProjects();
|
|
6322
6107
|
let project = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
6323
6108
|
let environmentId;
|
|
6324
6109
|
if (project) {
|
|
6325
|
-
logger$
|
|
6110
|
+
logger$2.log(` Found existing project: ${project.name}`);
|
|
6326
6111
|
const projectDetails = await api.getProject(project.projectId);
|
|
6327
6112
|
const environments = projectDetails.environments ?? [];
|
|
6328
6113
|
const matchingEnv = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6329
6114
|
if (matchingEnv) {
|
|
6330
6115
|
environmentId = matchingEnv.environmentId;
|
|
6331
|
-
logger$
|
|
6116
|
+
logger$2.log(` Using environment: ${matchingEnv.name}`);
|
|
6332
6117
|
} else {
|
|
6333
|
-
logger$
|
|
6118
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6334
6119
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6335
6120
|
environmentId = env.environmentId;
|
|
6336
|
-
logger$
|
|
6121
|
+
logger$2.log(` ✓ Created environment: ${stage}`);
|
|
6337
6122
|
}
|
|
6338
6123
|
} else {
|
|
6339
|
-
logger$
|
|
6124
|
+
logger$2.log(` Creating project: ${projectName}`);
|
|
6340
6125
|
const result = await api.createProject(projectName);
|
|
6341
6126
|
project = result.project;
|
|
6342
6127
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
6343
|
-
logger$
|
|
6128
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6344
6129
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6345
6130
|
environmentId = env.environmentId;
|
|
6346
6131
|
} else environmentId = result.environment.environmentId;
|
|
6347
|
-
logger$
|
|
6132
|
+
logger$2.log(` ✓ Created project: ${project.projectId}`);
|
|
6348
6133
|
}
|
|
6349
|
-
logger$
|
|
6134
|
+
logger$2.log("\n📋 Loading deploy state...");
|
|
6350
6135
|
const stateProvider = await createStateProvider({
|
|
6351
6136
|
config: workspace.state,
|
|
6352
6137
|
workspaceRoot: workspace.root,
|
|
@@ -6354,27 +6139,27 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6354
6139
|
});
|
|
6355
6140
|
let state = await stateProvider.read(stage);
|
|
6356
6141
|
if (state) {
|
|
6357
|
-
logger$
|
|
6142
|
+
logger$2.log(` Found existing state for stage "${stage}"`);
|
|
6358
6143
|
if (state.projectId !== project.projectId) {
|
|
6359
|
-
logger$
|
|
6144
|
+
logger$2.log(` ⚠ Project ID changed, updating state`);
|
|
6360
6145
|
state.projectId = project.projectId;
|
|
6361
6146
|
}
|
|
6362
6147
|
if (state.environmentId !== environmentId) {
|
|
6363
|
-
logger$
|
|
6148
|
+
logger$2.log(` ⚠ Environment ID changed, updating state`);
|
|
6364
6149
|
state.environmentId = environmentId;
|
|
6365
6150
|
}
|
|
6366
6151
|
} else {
|
|
6367
|
-
logger$
|
|
6152
|
+
logger$2.log(` Creating new state for stage "${stage}"`);
|
|
6368
6153
|
state = createEmptyState(stage, project.projectId, environmentId);
|
|
6369
6154
|
}
|
|
6370
|
-
logger$
|
|
6155
|
+
logger$2.log("\n🐳 Checking registry...");
|
|
6371
6156
|
let registryId = await require_credentials.getDokployRegistryId();
|
|
6372
6157
|
const registry = workspace.deploy.dokploy?.registry;
|
|
6373
6158
|
if (registryId) try {
|
|
6374
6159
|
const reg = await api.getRegistry(registryId);
|
|
6375
|
-
logger$
|
|
6160
|
+
logger$2.log(` Using registry: ${reg.registryName}`);
|
|
6376
6161
|
} catch {
|
|
6377
|
-
logger$
|
|
6162
|
+
logger$2.log(" ⚠ Stored registry not found, clearing...");
|
|
6378
6163
|
registryId = void 0;
|
|
6379
6164
|
await require_credentials.storeDokployRegistryId("");
|
|
6380
6165
|
}
|
|
@@ -6383,17 +6168,17 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6383
6168
|
if (registries.length > 0) {
|
|
6384
6169
|
registryId = registries[0].registryId;
|
|
6385
6170
|
await require_credentials.storeDokployRegistryId(registryId);
|
|
6386
|
-
logger$
|
|
6171
|
+
logger$2.log(` Using registry: ${registries[0].registryName}`);
|
|
6387
6172
|
} else if (registry) {
|
|
6388
|
-
logger$
|
|
6389
|
-
logger$
|
|
6173
|
+
logger$2.log(" No registries found in Dokploy. Let's create one.");
|
|
6174
|
+
logger$2.log(` Registry URL: ${registry}`);
|
|
6390
6175
|
const username = await prompt("Registry username: ");
|
|
6391
6176
|
const password = await prompt("Registry password/token: ", true);
|
|
6392
6177
|
const reg = await api.createRegistry("Default Registry", registry, username, password);
|
|
6393
6178
|
registryId = reg.registryId;
|
|
6394
6179
|
await require_credentials.storeDokployRegistryId(registryId);
|
|
6395
|
-
logger$
|
|
6396
|
-
} else logger$
|
|
6180
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
6181
|
+
} else logger$2.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
6397
6182
|
}
|
|
6398
6183
|
const services = workspace.services;
|
|
6399
6184
|
const dockerServices = {
|
|
@@ -6403,7 +6188,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6403
6188
|
let provisionedPostgres = null;
|
|
6404
6189
|
let provisionedRedis = null;
|
|
6405
6190
|
if (dockerServices.postgres || dockerServices.redis) {
|
|
6406
|
-
logger$
|
|
6191
|
+
logger$2.log("\n🔧 Provisioning infrastructure services...");
|
|
6407
6192
|
const existingServiceIds = {
|
|
6408
6193
|
postgresId: getPostgresId(state),
|
|
6409
6194
|
redisId: getRedisId(state)
|
|
@@ -6421,7 +6206,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6421
6206
|
}
|
|
6422
6207
|
}
|
|
6423
6208
|
const skippedMobileApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "mobile");
|
|
6424
|
-
if (skippedMobileApps.length > 0) logger$
|
|
6209
|
+
if (skippedMobileApps.length > 0) logger$2.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
6425
6210
|
const backendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "backend");
|
|
6426
6211
|
const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "web");
|
|
6427
6212
|
const perAppDbCredentials = /* @__PURE__ */ new Map();
|
|
@@ -6431,13 +6216,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6431
6216
|
return requirements?.requiredEnvVars.includes("DATABASE_URL");
|
|
6432
6217
|
});
|
|
6433
6218
|
if (appsNeedingDb.length > 0) {
|
|
6434
|
-
logger$
|
|
6435
|
-
logger$
|
|
6219
|
+
logger$2.log(`\n🔐 Setting up per-app database credentials...`);
|
|
6220
|
+
logger$2.log(` Apps needing DATABASE_URL: ${appsNeedingDb.join(", ")}`);
|
|
6436
6221
|
const existingCredentials = getAllAppCredentials(state);
|
|
6437
6222
|
const usersToCreate = [];
|
|
6438
6223
|
for (const appName of appsNeedingDb) {
|
|
6439
6224
|
let credentials = existingCredentials[appName];
|
|
6440
|
-
if (credentials) logger$
|
|
6225
|
+
if (credentials) logger$2.log(` ${appName}: Using existing credentials from state`);
|
|
6441
6226
|
else {
|
|
6442
6227
|
const password = (0, node_crypto.randomBytes)(16).toString("hex");
|
|
6443
6228
|
credentials = {
|
|
@@ -6445,7 +6230,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6445
6230
|
dbPassword: password
|
|
6446
6231
|
};
|
|
6447
6232
|
setAppCredentials(state, appName, credentials);
|
|
6448
|
-
logger$
|
|
6233
|
+
logger$2.log(` ${appName}: Generated new credentials`);
|
|
6449
6234
|
}
|
|
6450
6235
|
perAppDbCredentials.set(appName, credentials);
|
|
6451
6236
|
usersToCreate.push({
|
|
@@ -6459,8 +6244,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6459
6244
|
}
|
|
6460
6245
|
}
|
|
6461
6246
|
if (workspace.deploy?.backups && provisionedPostgres) {
|
|
6462
|
-
logger$
|
|
6463
|
-
const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-
|
|
6247
|
+
logger$2.log("\n💾 Provisioning backup destination...");
|
|
6248
|
+
const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-B3HBN-2k.cjs"));
|
|
6464
6249
|
const backupState = await provisionBackupDestination({
|
|
6465
6250
|
api,
|
|
6466
6251
|
projectId: project.projectId,
|
|
@@ -6468,13 +6253,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6468
6253
|
stage,
|
|
6469
6254
|
config: workspace.deploy.backups,
|
|
6470
6255
|
existingState: getBackupState(state),
|
|
6471
|
-
logger: logger$
|
|
6256
|
+
logger: logger$2
|
|
6472
6257
|
});
|
|
6473
6258
|
setBackupState(state, backupState);
|
|
6474
6259
|
if (!backupState.postgresBackupId) {
|
|
6475
6260
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
6476
6261
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
6477
|
-
logger$
|
|
6262
|
+
logger$2.log(" Creating postgres backup schedule...");
|
|
6478
6263
|
const backup = await api.createPostgresBackup({
|
|
6479
6264
|
schedule: backupSchedule,
|
|
6480
6265
|
prefix: `${stage}/postgres`,
|
|
@@ -6485,8 +6270,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6485
6270
|
keepLatestCount: backupRetention
|
|
6486
6271
|
});
|
|
6487
6272
|
setPostgresBackupId(state, backup.backupId);
|
|
6488
|
-
logger$
|
|
6489
|
-
} else logger$
|
|
6273
|
+
logger$2.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
6274
|
+
} else logger$2.log(" ✓ Using existing postgres backup schedule");
|
|
6490
6275
|
}
|
|
6491
6276
|
const publicUrls = {};
|
|
6492
6277
|
const results = [];
|
|
@@ -6501,25 +6286,25 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6501
6286
|
frontendUrls.push(`https://${hostname}`);
|
|
6502
6287
|
}
|
|
6503
6288
|
if (backendApps.length > 0) {
|
|
6504
|
-
logger$
|
|
6289
|
+
logger$2.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
6505
6290
|
for (const appName of backendApps) {
|
|
6506
6291
|
const app = workspace.apps[appName];
|
|
6507
|
-
logger$
|
|
6292
|
+
logger$2.log(`\n ⚙️ Deploying ${appName}...`);
|
|
6508
6293
|
try {
|
|
6509
6294
|
const dokployAppName = appName;
|
|
6510
6295
|
let application = null;
|
|
6511
6296
|
const cachedAppId = getApplicationId(state, appName);
|
|
6512
6297
|
if (cachedAppId) {
|
|
6513
|
-
logger$
|
|
6298
|
+
logger$2.log(` Using cached ID: ${cachedAppId}`);
|
|
6514
6299
|
application = await api.getApplication(cachedAppId);
|
|
6515
|
-
if (application) logger$
|
|
6516
|
-
else logger$
|
|
6300
|
+
if (application) logger$2.log(` ✓ Application found: ${application.applicationId}`);
|
|
6301
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6517
6302
|
}
|
|
6518
6303
|
if (!application) {
|
|
6519
6304
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
6520
6305
|
application = result.application;
|
|
6521
|
-
if (result.created) logger$
|
|
6522
|
-
else logger$
|
|
6306
|
+
if (result.created) logger$2.log(` Created application: ${application.applicationId}`);
|
|
6307
|
+
else logger$2.log(` Found existing application: ${application.applicationId}`);
|
|
6523
6308
|
}
|
|
6524
6309
|
setApplicationId(state, appName, application.applicationId);
|
|
6525
6310
|
const appSecrets = encryptedSecrets.get(appName);
|
|
@@ -6527,11 +6312,11 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6527
6312
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
6528
6313
|
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
6529
6314
|
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
6530
|
-
logger$
|
|
6315
|
+
logger$2.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
6531
6316
|
}
|
|
6532
6317
|
const imageName = `${workspace.name}-${appName}`;
|
|
6533
6318
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6534
|
-
logger$
|
|
6319
|
+
logger$2.log(` Building Docker image: ${imageRef}`);
|
|
6535
6320
|
await deployDocker({
|
|
6536
6321
|
stage,
|
|
6537
6322
|
tag: imageTag,
|
|
@@ -6581,10 +6366,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6581
6366
|
const { valid, missing, resolved } = validateEnvVars(requiredVars, envContext);
|
|
6582
6367
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
6583
6368
|
const envVars = Object.entries(resolved).map(([key, value]) => `${key}=${value}`);
|
|
6584
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
6369
|
+
if (Object.keys(resolved).length > 0) logger$2.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
6585
6370
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
6586
6371
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
6587
|
-
logger$
|
|
6372
|
+
logger$2.log(` Deploying to Dokploy...`);
|
|
6588
6373
|
await api.deployApplication(application.applicationId);
|
|
6589
6374
|
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
6590
6375
|
const existingDomain = existingDomains.find((d) => d.host === backendHost);
|
|
@@ -6592,7 +6377,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6592
6377
|
appHostnames.set(appName, backendHost);
|
|
6593
6378
|
appDomainIds.set(appName, existingDomain.domainId);
|
|
6594
6379
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6595
|
-
logger$
|
|
6380
|
+
logger$2.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
6596
6381
|
} else try {
|
|
6597
6382
|
const domain = await api.createDomain({
|
|
6598
6383
|
host: backendHost,
|
|
@@ -6604,10 +6389,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6604
6389
|
appHostnames.set(appName, backendHost);
|
|
6605
6390
|
appDomainIds.set(appName, domain.domainId);
|
|
6606
6391
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6607
|
-
logger$
|
|
6392
|
+
logger$2.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
6608
6393
|
} catch (domainError) {
|
|
6609
6394
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
6610
|
-
logger$
|
|
6395
|
+
logger$2.log(` ⚠ Domain creation failed: ${message}`);
|
|
6611
6396
|
appHostnames.set(appName, backendHost);
|
|
6612
6397
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6613
6398
|
}
|
|
@@ -6618,10 +6403,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6618
6403
|
applicationId: application.applicationId,
|
|
6619
6404
|
imageRef
|
|
6620
6405
|
});
|
|
6621
|
-
logger$
|
|
6406
|
+
logger$2.log(` ✓ ${appName} deployed successfully`);
|
|
6622
6407
|
} catch (error) {
|
|
6623
6408
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6624
|
-
logger$
|
|
6409
|
+
logger$2.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
6625
6410
|
results.push({
|
|
6626
6411
|
appName,
|
|
6627
6412
|
type: app.type,
|
|
@@ -6633,25 +6418,25 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6633
6418
|
}
|
|
6634
6419
|
}
|
|
6635
6420
|
if (frontendApps.length > 0) {
|
|
6636
|
-
logger$
|
|
6421
|
+
logger$2.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
6637
6422
|
for (const appName of frontendApps) {
|
|
6638
6423
|
const app = workspace.apps[appName];
|
|
6639
|
-
logger$
|
|
6424
|
+
logger$2.log(`\n 🌐 Deploying ${appName}...`);
|
|
6640
6425
|
try {
|
|
6641
6426
|
const dokployAppName = appName;
|
|
6642
6427
|
let application = null;
|
|
6643
6428
|
const cachedAppId = getApplicationId(state, appName);
|
|
6644
6429
|
if (cachedAppId) {
|
|
6645
|
-
logger$
|
|
6430
|
+
logger$2.log(` Using cached ID: ${cachedAppId}`);
|
|
6646
6431
|
application = await api.getApplication(cachedAppId);
|
|
6647
|
-
if (application) logger$
|
|
6648
|
-
else logger$
|
|
6432
|
+
if (application) logger$2.log(` ✓ Application found: ${application.applicationId}`);
|
|
6433
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6649
6434
|
}
|
|
6650
6435
|
if (!application) {
|
|
6651
6436
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
6652
6437
|
application = result.application;
|
|
6653
|
-
if (result.created) logger$
|
|
6654
|
-
else logger$
|
|
6438
|
+
if (result.created) logger$2.log(` Created application: ${application.applicationId}`);
|
|
6439
|
+
else logger$2.log(` Found existing application: ${application.applicationId}`);
|
|
6655
6440
|
}
|
|
6656
6441
|
setApplicationId(state, appName, application.applicationId);
|
|
6657
6442
|
const dependencyUrls = {};
|
|
@@ -6673,7 +6458,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6673
6458
|
const sniffedVars = sniffedApps.get(appName)?.requiredEnvVars ?? [];
|
|
6674
6459
|
const { valid, missing, resolved } = validateEnvVars(sniffedVars, envContext);
|
|
6675
6460
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
6676
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
6461
|
+
if (Object.keys(resolved).length > 0) logger$2.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
6677
6462
|
const publicPrefix = require_workspace.getPublicEnvPrefix(app.framework);
|
|
6678
6463
|
const buildArgs = [];
|
|
6679
6464
|
const publicUrlArgNames = [];
|
|
@@ -6683,10 +6468,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6683
6468
|
publicUrlArgNames.push(key);
|
|
6684
6469
|
}
|
|
6685
6470
|
}
|
|
6686
|
-
if (buildArgs.length > 0) logger$
|
|
6471
|
+
if (buildArgs.length > 0) logger$2.log(` Build args: ${publicUrlArgNames.join(", ")}`);
|
|
6687
6472
|
const imageName = `${workspace.name}-${appName}`;
|
|
6688
6473
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6689
|
-
logger$
|
|
6474
|
+
logger$2.log(` Building Docker image: ${imageRef}`);
|
|
6690
6475
|
await deployDocker({
|
|
6691
6476
|
stage,
|
|
6692
6477
|
tag: imageTag,
|
|
@@ -6707,7 +6492,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6707
6492
|
for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
|
|
6708
6493
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
6709
6494
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
6710
|
-
logger$
|
|
6495
|
+
logger$2.log(` Deploying to Dokploy...`);
|
|
6711
6496
|
await api.deployApplication(application.applicationId);
|
|
6712
6497
|
const existingFrontendDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
6713
6498
|
const existingFrontendDomain = existingFrontendDomains.find((d) => d.host === frontendHost);
|
|
@@ -6715,7 +6500,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6715
6500
|
appHostnames.set(appName, frontendHost);
|
|
6716
6501
|
appDomainIds.set(appName, existingFrontendDomain.domainId);
|
|
6717
6502
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6718
|
-
logger$
|
|
6503
|
+
logger$2.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
6719
6504
|
} else try {
|
|
6720
6505
|
const domain = await api.createDomain({
|
|
6721
6506
|
host: frontendHost,
|
|
@@ -6727,10 +6512,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6727
6512
|
appHostnames.set(appName, frontendHost);
|
|
6728
6513
|
appDomainIds.set(appName, domain.domainId);
|
|
6729
6514
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6730
|
-
logger$
|
|
6515
|
+
logger$2.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
6731
6516
|
} catch (domainError) {
|
|
6732
6517
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
6733
|
-
logger$
|
|
6518
|
+
logger$2.log(` ⚠ Domain creation failed: ${message}`);
|
|
6734
6519
|
appHostnames.set(appName, frontendHost);
|
|
6735
6520
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6736
6521
|
}
|
|
@@ -6741,10 +6526,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6741
6526
|
applicationId: application.applicationId,
|
|
6742
6527
|
imageRef
|
|
6743
6528
|
});
|
|
6744
|
-
logger$
|
|
6529
|
+
logger$2.log(` ✓ ${appName} deployed successfully`);
|
|
6745
6530
|
} catch (error) {
|
|
6746
6531
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6747
|
-
logger$
|
|
6532
|
+
logger$2.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
6748
6533
|
results.push({
|
|
6749
6534
|
appName,
|
|
6750
6535
|
type: app.type,
|
|
@@ -6754,9 +6539,9 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6754
6539
|
}
|
|
6755
6540
|
}
|
|
6756
6541
|
}
|
|
6757
|
-
logger$
|
|
6542
|
+
logger$2.log("\n📋 Saving deploy state...");
|
|
6758
6543
|
await stateProvider.write(stage, state);
|
|
6759
|
-
logger$
|
|
6544
|
+
logger$2.log(" ✓ State saved");
|
|
6760
6545
|
const dnsConfig = workspace.deploy.dns;
|
|
6761
6546
|
if (dnsConfig && appHostnames.size > 0) {
|
|
6762
6547
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
@@ -6765,27 +6550,27 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6765
6550
|
await stateProvider.write(stage, state);
|
|
6766
6551
|
}
|
|
6767
6552
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
6768
|
-
logger$
|
|
6553
|
+
logger$2.log("\n🔒 Validating domains for SSL certificates...");
|
|
6769
6554
|
for (const [appName, hostname] of appHostnames) try {
|
|
6770
6555
|
const result = await api.validateDomain(hostname);
|
|
6771
|
-
if (result.isValid) logger$
|
|
6772
|
-
else logger$
|
|
6556
|
+
if (result.isValid) logger$2.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
6557
|
+
else logger$2.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
6773
6558
|
} catch (validationError) {
|
|
6774
6559
|
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
6775
|
-
logger$
|
|
6560
|
+
logger$2.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
6776
6561
|
}
|
|
6777
6562
|
}
|
|
6778
6563
|
}
|
|
6779
6564
|
const successCount = results.filter((r) => r.success).length;
|
|
6780
6565
|
const failedCount = results.filter((r) => !r.success).length;
|
|
6781
|
-
logger$
|
|
6782
|
-
logger$
|
|
6783
|
-
logger$
|
|
6784
|
-
logger$
|
|
6785
|
-
if (failedCount > 0) logger$
|
|
6566
|
+
logger$2.log(`\n${"─".repeat(50)}`);
|
|
6567
|
+
logger$2.log(`\n✅ Workspace deployment complete!`);
|
|
6568
|
+
logger$2.log(` Project: ${project.projectId}`);
|
|
6569
|
+
logger$2.log(` Successful: ${successCount}`);
|
|
6570
|
+
if (failedCount > 0) logger$2.log(` Failed: ${failedCount}`);
|
|
6786
6571
|
if (Object.keys(publicUrls).length > 0) {
|
|
6787
|
-
logger$
|
|
6788
|
-
for (const [name$1, url] of Object.entries(publicUrls)) logger$
|
|
6572
|
+
logger$2.log("\n 📡 Deployed URLs:");
|
|
6573
|
+
for (const [name$1, url] of Object.entries(publicUrls)) logger$2.log(` ${name$1}: ${url}`);
|
|
6789
6574
|
}
|
|
6790
6575
|
return {
|
|
6791
6576
|
apps: results,
|
|
@@ -6801,14 +6586,14 @@ async function deployCommand(options) {
|
|
|
6801
6586
|
const { provider, stage, tag, skipPush, skipBuild } = options;
|
|
6802
6587
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
6803
6588
|
if (loadedConfig.type === "workspace") {
|
|
6804
|
-
logger$
|
|
6589
|
+
logger$2.log("📦 Detected workspace configuration");
|
|
6805
6590
|
return workspaceDeployCommand(loadedConfig.workspace, options);
|
|
6806
6591
|
}
|
|
6807
|
-
logger$
|
|
6808
|
-
logger$
|
|
6592
|
+
logger$2.log(`\n🚀 Deploying to ${provider}...`);
|
|
6593
|
+
logger$2.log(` Stage: ${stage}`);
|
|
6809
6594
|
const config = await require_config.loadConfig();
|
|
6810
6595
|
const imageTag = tag ?? generateTag(stage);
|
|
6811
|
-
logger$
|
|
6596
|
+
logger$2.log(` Tag: ${imageTag}`);
|
|
6812
6597
|
const dockerConfig = resolveDockerConfig(config);
|
|
6813
6598
|
const imageName = dockerConfig.imageName;
|
|
6814
6599
|
const registry = dockerConfig.registry;
|
|
@@ -6817,7 +6602,7 @@ async function deployCommand(options) {
|
|
|
6817
6602
|
let finalRegistry = registry;
|
|
6818
6603
|
if (provider === "dokploy") {
|
|
6819
6604
|
const composeServices = config.docker?.compose?.services;
|
|
6820
|
-
logger$
|
|
6605
|
+
logger$2.log(`\n🔍 Docker compose config: ${JSON.stringify(config.docker?.compose)}`);
|
|
6821
6606
|
const dockerServices = composeServices ? Array.isArray(composeServices) ? {
|
|
6822
6607
|
postgres: composeServices.includes("postgres"),
|
|
6823
6608
|
redis: composeServices.includes("redis"),
|
|
@@ -6834,7 +6619,7 @@ async function deployCommand(options) {
|
|
|
6834
6619
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
6835
6620
|
let secrets = await readStageSecrets$1(stage);
|
|
6836
6621
|
if (!secrets) {
|
|
6837
|
-
logger$
|
|
6622
|
+
logger$2.log(` Creating secrets file for stage "${stage}"...`);
|
|
6838
6623
|
secrets = initStageSecrets(stage);
|
|
6839
6624
|
}
|
|
6840
6625
|
let updated = false;
|
|
@@ -6849,12 +6634,12 @@ async function deployCommand(options) {
|
|
|
6849
6634
|
const urlKey = key;
|
|
6850
6635
|
if (!secrets.urls[urlKey]) {
|
|
6851
6636
|
secrets.urls[urlKey] = value;
|
|
6852
|
-
logger$
|
|
6637
|
+
logger$2.log(` Saved ${key} to secrets.urls`);
|
|
6853
6638
|
updated = true;
|
|
6854
6639
|
}
|
|
6855
6640
|
} else if (!secrets.custom[key]) {
|
|
6856
6641
|
secrets.custom[key] = value;
|
|
6857
|
-
logger$
|
|
6642
|
+
logger$2.log(` Saved ${key} to secrets.custom`);
|
|
6858
6643
|
updated = true;
|
|
6859
6644
|
}
|
|
6860
6645
|
}
|
|
@@ -6863,14 +6648,14 @@ async function deployCommand(options) {
|
|
|
6863
6648
|
}
|
|
6864
6649
|
let masterKey;
|
|
6865
6650
|
if (!skipBuild) {
|
|
6866
|
-
logger$
|
|
6651
|
+
logger$2.log(`\n📦 Building for production...`);
|
|
6867
6652
|
const buildResult = await buildCommand({
|
|
6868
6653
|
provider: "server",
|
|
6869
6654
|
production: true,
|
|
6870
6655
|
stage
|
|
6871
6656
|
});
|
|
6872
6657
|
masterKey = buildResult.masterKey;
|
|
6873
|
-
} else logger$
|
|
6658
|
+
} else logger$2.log(`\n⏭️ Skipping build (--skip-build)`);
|
|
6874
6659
|
let result;
|
|
6875
6660
|
switch (provider) {
|
|
6876
6661
|
case "docker": {
|
|
@@ -6906,8 +6691,8 @@ async function deployCommand(options) {
|
|
|
6906
6691
|
break;
|
|
6907
6692
|
}
|
|
6908
6693
|
case "aws-lambda": {
|
|
6909
|
-
logger$
|
|
6910
|
-
logger$
|
|
6694
|
+
logger$2.log("\n⚠️ AWS Lambda deployment is not yet implemented.");
|
|
6695
|
+
logger$2.log(" Use SST or AWS CDK for Lambda deployments.");
|
|
6911
6696
|
result = {
|
|
6912
6697
|
imageRef,
|
|
6913
6698
|
masterKey
|
|
@@ -6916,7 +6701,7 @@ async function deployCommand(options) {
|
|
|
6916
6701
|
}
|
|
6917
6702
|
default: throw new Error(`Unknown deploy provider: ${provider}\nSupported providers: docker, dokploy, aws-lambda`);
|
|
6918
6703
|
}
|
|
6919
|
-
logger$
|
|
6704
|
+
logger$2.log("\n✅ Deployment complete!");
|
|
6920
6705
|
return result;
|
|
6921
6706
|
}
|
|
6922
6707
|
|
|
@@ -7113,22 +6898,22 @@ const GEEKMIDAS_VERSIONS = {
|
|
|
7113
6898
|
"@geekmidas/audit": "~2.0.1",
|
|
7114
6899
|
"@geekmidas/auth": "~2.0.1",
|
|
7115
6900
|
"@geekmidas/cache": "~1.1.1",
|
|
7116
|
-
"@geekmidas/client": "~
|
|
6901
|
+
"@geekmidas/client": "~6.0.0",
|
|
7117
6902
|
"@geekmidas/cloud": "~1.0.1",
|
|
7118
|
-
"@geekmidas/constructs": "~
|
|
6903
|
+
"@geekmidas/constructs": "~4.0.1",
|
|
7119
6904
|
"@geekmidas/db": "~1.0.2",
|
|
7120
6905
|
"@geekmidas/emailkit": "~1.0.1",
|
|
7121
6906
|
"@geekmidas/envkit": "~1.0.7",
|
|
7122
6907
|
"@geekmidas/errors": "~1.0.1",
|
|
7123
6908
|
"@geekmidas/events": "~1.1.4",
|
|
7124
6909
|
"@geekmidas/logger": "~1.0.2",
|
|
7125
|
-
"@geekmidas/rate-limit": "~
|
|
7126
|
-
"@geekmidas/schema": "~1.0.
|
|
7127
|
-
"@geekmidas/services": "~1.
|
|
6910
|
+
"@geekmidas/rate-limit": "~3.0.0",
|
|
6911
|
+
"@geekmidas/schema": "~1.0.3",
|
|
6912
|
+
"@geekmidas/services": "~1.1.1",
|
|
7128
6913
|
"@geekmidas/storage": "~2.0.3",
|
|
7129
6914
|
"@geekmidas/studio": "~1.0.1",
|
|
7130
6915
|
"@geekmidas/telescope": "~1.0.1",
|
|
7131
|
-
"@geekmidas/testkit": "~
|
|
6916
|
+
"@geekmidas/testkit": "~2.0.0",
|
|
7132
6917
|
"@geekmidas/cli": CLI_VERSION
|
|
7133
6918
|
};
|
|
7134
6919
|
|
|
@@ -12868,7 +12653,7 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
12868
12653
|
|
|
12869
12654
|
//#endregion
|
|
12870
12655
|
//#region src/secrets/index.ts
|
|
12871
|
-
const logger$
|
|
12656
|
+
const logger$1 = console;
|
|
12872
12657
|
/**
|
|
12873
12658
|
* Extract service names from compose config.
|
|
12874
12659
|
*/
|
|
@@ -12884,12 +12669,12 @@ function getServicesFromConfig(services) {
|
|
|
12884
12669
|
async function secretsInitCommand(options) {
|
|
12885
12670
|
const { stage, force } = options;
|
|
12886
12671
|
if (!force && require_storage.secretsExist(stage)) {
|
|
12887
|
-
logger$
|
|
12672
|
+
logger$1.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
12888
12673
|
process.exit(1);
|
|
12889
12674
|
}
|
|
12890
12675
|
const config = await require_config.loadConfig();
|
|
12891
12676
|
const services = getServicesFromConfig(config.docker?.compose?.services);
|
|
12892
|
-
if (services.length === 0) logger$
|
|
12677
|
+
if (services.length === 0) logger$1.warn("No services configured in docker.compose.services. Creating secrets with empty services.");
|
|
12893
12678
|
let projectName;
|
|
12894
12679
|
let workspaceSecrets;
|
|
12895
12680
|
try {
|
|
@@ -12898,23 +12683,23 @@ async function secretsInitCommand(options) {
|
|
|
12898
12683
|
const isMultiApp = Object.keys(loaded.workspace.apps).length > 1;
|
|
12899
12684
|
if (isMultiApp) {
|
|
12900
12685
|
workspaceSecrets = require_fullstack_secrets.generateFullstackCustomSecrets(loaded.workspace);
|
|
12901
|
-
logger$
|
|
12686
|
+
logger$1.log(" Detected workspace mode — generating per-app secrets");
|
|
12902
12687
|
}
|
|
12903
12688
|
} catch {}
|
|
12904
12689
|
const secrets = require_fullstack_secrets.createStageSecrets(stage, services, { projectName });
|
|
12905
12690
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
12906
12691
|
await require_storage.writeStageSecrets(secrets);
|
|
12907
|
-
logger$
|
|
12908
|
-
logger$
|
|
12909
|
-
logger$
|
|
12910
|
-
for (const service of services) logger$
|
|
12911
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
12912
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
12913
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
12914
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
12915
|
-
if (Object.keys(secrets.custom).length > 0) logger$
|
|
12916
|
-
logger$
|
|
12917
|
-
logger$
|
|
12692
|
+
logger$1.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
12693
|
+
logger$1.log(` Location: .gkm/secrets/${stage}.json`);
|
|
12694
|
+
logger$1.log("\n Generated credentials for:");
|
|
12695
|
+
for (const service of services) logger$1.log(` - ${service}`);
|
|
12696
|
+
if (secrets.urls.DATABASE_URL) logger$1.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
12697
|
+
if (secrets.urls.REDIS_URL) logger$1.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
12698
|
+
if (secrets.urls.RABBITMQ_URL) logger$1.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
12699
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$1.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
12700
|
+
if (Object.keys(secrets.custom).length > 0) logger$1.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
12701
|
+
logger$1.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
12702
|
+
logger$1.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
|
|
12918
12703
|
}
|
|
12919
12704
|
/**
|
|
12920
12705
|
* Read all data from stdin.
|
|
@@ -12933,21 +12718,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
12933
12718
|
let secretValue = value;
|
|
12934
12719
|
if (!secretValue) {
|
|
12935
12720
|
if (process.stdin.isTTY) {
|
|
12936
|
-
logger$
|
|
12937
|
-
logger$
|
|
12721
|
+
logger$1.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
|
|
12722
|
+
logger$1.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
|
|
12938
12723
|
process.exit(1);
|
|
12939
12724
|
}
|
|
12940
12725
|
secretValue = await readStdin();
|
|
12941
12726
|
if (!secretValue) {
|
|
12942
|
-
logger$
|
|
12727
|
+
logger$1.error("No value received from stdin");
|
|
12943
12728
|
process.exit(1);
|
|
12944
12729
|
}
|
|
12945
12730
|
}
|
|
12946
12731
|
try {
|
|
12947
12732
|
await require_storage.setCustomSecret(stage, key, secretValue);
|
|
12948
|
-
logger$
|
|
12733
|
+
logger$1.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
|
|
12949
12734
|
} catch (error) {
|
|
12950
|
-
logger$
|
|
12735
|
+
logger$1.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
12951
12736
|
process.exit(1);
|
|
12952
12737
|
}
|
|
12953
12738
|
}
|
|
@@ -12958,34 +12743,34 @@ async function secretsShowCommand(options) {
|
|
|
12958
12743
|
const { stage, reveal } = options;
|
|
12959
12744
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
12960
12745
|
if (!secrets) {
|
|
12961
|
-
logger$
|
|
12746
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
12962
12747
|
process.exit(1);
|
|
12963
12748
|
}
|
|
12964
|
-
logger$
|
|
12965
|
-
logger$
|
|
12966
|
-
logger$
|
|
12967
|
-
logger$
|
|
12749
|
+
logger$1.log(`\nSecrets for stage "${stage}":`);
|
|
12750
|
+
logger$1.log(` Created: ${secrets.createdAt}`);
|
|
12751
|
+
logger$1.log(` Updated: ${secrets.updatedAt}`);
|
|
12752
|
+
logger$1.log("\nService Credentials:");
|
|
12968
12753
|
for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
|
|
12969
|
-
logger$
|
|
12970
|
-
logger$
|
|
12971
|
-
logger$
|
|
12972
|
-
logger$
|
|
12973
|
-
logger$
|
|
12974
|
-
if (creds.database) logger$
|
|
12975
|
-
if (creds.vhost) logger$
|
|
12976
|
-
if (creds.bucket) logger$
|
|
12977
|
-
}
|
|
12978
|
-
logger$
|
|
12979
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
12980
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
12981
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
12982
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
12754
|
+
logger$1.log(`\n ${service}:`);
|
|
12755
|
+
logger$1.log(` host: ${creds.host}`);
|
|
12756
|
+
logger$1.log(` port: ${creds.port}`);
|
|
12757
|
+
logger$1.log(` username: ${creds.username}`);
|
|
12758
|
+
logger$1.log(` password: ${reveal ? creds.password : require_storage.maskPassword(creds.password)}`);
|
|
12759
|
+
if (creds.database) logger$1.log(` database: ${creds.database}`);
|
|
12760
|
+
if (creds.vhost) logger$1.log(` vhost: ${creds.vhost}`);
|
|
12761
|
+
if (creds.bucket) logger$1.log(` bucket: ${creds.bucket}`);
|
|
12762
|
+
}
|
|
12763
|
+
logger$1.log("\nConnection URLs:");
|
|
12764
|
+
if (secrets.urls.DATABASE_URL) logger$1.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
12765
|
+
if (secrets.urls.REDIS_URL) logger$1.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
12766
|
+
if (secrets.urls.RABBITMQ_URL) logger$1.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
12767
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$1.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
12983
12768
|
const customKeys = Object.keys(secrets.custom);
|
|
12984
12769
|
if (customKeys.length > 0) {
|
|
12985
|
-
logger$
|
|
12986
|
-
for (const [key, value] of Object.entries(secrets.custom)) logger$
|
|
12770
|
+
logger$1.log("\nCustom Secrets:");
|
|
12771
|
+
for (const [key, value] of Object.entries(secrets.custom)) logger$1.log(` ${key}: ${reveal ? value : require_storage.maskPassword(value)}`);
|
|
12987
12772
|
}
|
|
12988
|
-
if (!reveal) logger$
|
|
12773
|
+
if (!reveal) logger$1.log("\nUse --reveal to show actual values");
|
|
12989
12774
|
}
|
|
12990
12775
|
/**
|
|
12991
12776
|
* Rotate passwords for services.
|
|
@@ -12994,25 +12779,25 @@ async function secretsRotateCommand(options) {
|
|
|
12994
12779
|
const { stage, service } = options;
|
|
12995
12780
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
12996
12781
|
if (!secrets) {
|
|
12997
|
-
logger$
|
|
12782
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
12998
12783
|
process.exit(1);
|
|
12999
12784
|
}
|
|
13000
12785
|
if (service) {
|
|
13001
12786
|
if (!secrets.services[service]) {
|
|
13002
|
-
logger$
|
|
12787
|
+
logger$1.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
13003
12788
|
process.exit(1);
|
|
13004
12789
|
}
|
|
13005
12790
|
const updated = require_fullstack_secrets.rotateServicePassword(secrets, service);
|
|
13006
12791
|
await require_storage.writeStageSecrets(updated);
|
|
13007
|
-
logger$
|
|
12792
|
+
logger$1.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
13008
12793
|
} else {
|
|
13009
12794
|
let updated = secrets;
|
|
13010
12795
|
const services = Object.keys(secrets.services);
|
|
13011
12796
|
for (const svc of services) updated = require_fullstack_secrets.rotateServicePassword(updated, svc);
|
|
13012
12797
|
await require_storage.writeStageSecrets(updated);
|
|
13013
|
-
logger$
|
|
12798
|
+
logger$1.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
13014
12799
|
}
|
|
13015
|
-
logger$
|
|
12800
|
+
logger$1.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
13016
12801
|
}
|
|
13017
12802
|
/**
|
|
13018
12803
|
* Import secrets from a JSON file.
|
|
@@ -13020,7 +12805,7 @@ async function secretsRotateCommand(options) {
|
|
|
13020
12805
|
async function secretsImportCommand(file, options) {
|
|
13021
12806
|
const { stage, merge = true } = options;
|
|
13022
12807
|
if (!(0, node_fs.existsSync)(file)) {
|
|
13023
|
-
logger$
|
|
12808
|
+
logger$1.error(`File not found: ${file}`);
|
|
13024
12809
|
process.exit(1);
|
|
13025
12810
|
}
|
|
13026
12811
|
let importedSecrets;
|
|
@@ -13030,12 +12815,12 @@ async function secretsImportCommand(file, options) {
|
|
|
13030
12815
|
if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
|
|
13031
12816
|
for (const [key, value] of Object.entries(importedSecrets)) if (typeof value !== "string") throw new Error(`Value for "${key}" must be a string, got ${typeof value}`);
|
|
13032
12817
|
} catch (error) {
|
|
13033
|
-
logger$
|
|
12818
|
+
logger$1.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
13034
12819
|
process.exit(1);
|
|
13035
12820
|
}
|
|
13036
12821
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
13037
12822
|
if (!secrets) {
|
|
13038
|
-
logger$
|
|
12823
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
13039
12824
|
process.exit(1);
|
|
13040
12825
|
}
|
|
13041
12826
|
const updatedCustom = merge ? {
|
|
@@ -13050,10 +12835,10 @@ async function secretsImportCommand(file, options) {
|
|
|
13050
12835
|
await require_storage.writeStageSecrets(updated);
|
|
13051
12836
|
const importedCount = Object.keys(importedSecrets).length;
|
|
13052
12837
|
const totalCount = Object.keys(updatedCustom).length;
|
|
13053
|
-
logger$
|
|
13054
|
-
if (merge && totalCount > importedCount) logger$
|
|
13055
|
-
logger$
|
|
13056
|
-
for (const key of Object.keys(importedSecrets)) logger$
|
|
12838
|
+
logger$1.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
12839
|
+
if (merge && totalCount > importedCount) logger$1.log(` Total custom secrets: ${totalCount}`);
|
|
12840
|
+
logger$1.log("\n Imported keys:");
|
|
12841
|
+
for (const key of Object.keys(importedSecrets)) logger$1.log(` - ${key}`);
|
|
13057
12842
|
}
|
|
13058
12843
|
/**
|
|
13059
12844
|
* Mask password in a URL for display.
|
|
@@ -13068,264 +12853,6 @@ function maskUrl(url) {
|
|
|
13068
12853
|
}
|
|
13069
12854
|
}
|
|
13070
12855
|
|
|
13071
|
-
//#endregion
|
|
13072
|
-
//#region src/setup/index.ts
|
|
13073
|
-
const logger$1 = console;
|
|
13074
|
-
/**
|
|
13075
|
-
* Setup development environment.
|
|
13076
|
-
*
|
|
13077
|
-
* Orchestrates:
|
|
13078
|
-
* 1. Load workspace config
|
|
13079
|
-
* 2. Resolve secrets (local → SSM → generate fresh)
|
|
13080
|
-
* 3. Write docker/.env from secrets
|
|
13081
|
-
* 4. Start Docker services
|
|
13082
|
-
*/
|
|
13083
|
-
async function setupCommand(options = {}) {
|
|
13084
|
-
const stage = options.stage ?? "development";
|
|
13085
|
-
logger$1.log("\n🔧 Setting up development environment...\n");
|
|
13086
|
-
let loadedConfig;
|
|
13087
|
-
try {
|
|
13088
|
-
loadedConfig = await require_config.loadWorkspaceConfig();
|
|
13089
|
-
} catch {
|
|
13090
|
-
logger$1.error("❌ No gkm.config.ts found. Run this command from a workspace root.");
|
|
13091
|
-
process.exit(1);
|
|
13092
|
-
}
|
|
13093
|
-
const { workspace } = loadedConfig;
|
|
13094
|
-
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
13095
|
-
logger$1.log(`📦 Workspace: ${workspace.name}`);
|
|
13096
|
-
logger$1.log(`📱 Apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
13097
|
-
logger$1.log(`🔑 Stage: ${stage}\n`);
|
|
13098
|
-
const secrets = await resolveSecrets(stage, workspace, options);
|
|
13099
|
-
if (!secrets) {
|
|
13100
|
-
logger$1.error("❌ Failed to resolve secrets. Exiting.");
|
|
13101
|
-
process.exit(1);
|
|
13102
|
-
}
|
|
13103
|
-
if (isMultiApp && workspace.services.db) {
|
|
13104
|
-
await require_fullstack_secrets.writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
13105
|
-
logger$1.log("📄 Generated docker/.env with database passwords");
|
|
13106
|
-
}
|
|
13107
|
-
if (!options.skipDocker) {
|
|
13108
|
-
const composeFile = (0, node_path.join)(workspace.root, "docker-compose.yml");
|
|
13109
|
-
if ((0, node_fs.existsSync)(composeFile)) {
|
|
13110
|
-
logger$1.log("");
|
|
13111
|
-
const resolvedPorts = await resolveServicePorts(workspace.root);
|
|
13112
|
-
await startWorkspaceServices(workspace, resolvedPorts.dockerEnv);
|
|
13113
|
-
} else logger$1.log("⚠️ No docker-compose.yml found. Skipping Docker services.");
|
|
13114
|
-
}
|
|
13115
|
-
printSummary(workspace, stage);
|
|
13116
|
-
}
|
|
13117
|
-
/**
|
|
13118
|
-
* Resolve secrets with priority:
|
|
13119
|
-
* 1. Local secrets exist → use them (preserves manual additions)
|
|
13120
|
-
* 2. SSM configured and has secrets → pull and use
|
|
13121
|
-
* 3. Neither → generate fresh secrets
|
|
13122
|
-
*
|
|
13123
|
-
* --force skips checks 1 and 2 and always regenerates.
|
|
13124
|
-
*/
|
|
13125
|
-
async function resolveSecrets(stage, workspace, options) {
|
|
13126
|
-
if (options.force) {
|
|
13127
|
-
logger$1.log("🔐 Generating fresh secrets (--force)...");
|
|
13128
|
-
return generateFreshSecrets(stage, workspace, options);
|
|
13129
|
-
}
|
|
13130
|
-
if (require_storage.secretsExist(stage, workspace.root)) {
|
|
13131
|
-
logger$1.log("🔐 Using existing local secrets");
|
|
13132
|
-
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
13133
|
-
if (secrets) {
|
|
13134
|
-
const reconciled = reconcileSecrets(secrets, workspace);
|
|
13135
|
-
if (reconciled) await require_storage.writeStageSecrets(reconciled, workspace.root);
|
|
13136
|
-
return reconciled ?? secrets;
|
|
13137
|
-
}
|
|
13138
|
-
}
|
|
13139
|
-
if (require_sync.isSSMConfigured(workspace)) {
|
|
13140
|
-
logger$1.log("☁️ Checking for remote secrets in SSM...");
|
|
13141
|
-
try {
|
|
13142
|
-
const remoteSecrets = await require_sync.pullSecrets(stage, workspace);
|
|
13143
|
-
if (remoteSecrets) {
|
|
13144
|
-
logger$1.log("✅ Pulled secrets from SSM");
|
|
13145
|
-
await require_storage.writeStageSecrets(remoteSecrets, workspace.root);
|
|
13146
|
-
return remoteSecrets;
|
|
13147
|
-
}
|
|
13148
|
-
logger$1.log(" No remote secrets found");
|
|
13149
|
-
} catch (error) {
|
|
13150
|
-
logger$1.warn(`⚠️ Failed to pull from SSM: ${error.message}`);
|
|
13151
|
-
}
|
|
13152
|
-
}
|
|
13153
|
-
logger$1.log("🔐 Generating fresh development secrets...");
|
|
13154
|
-
return generateFreshSecrets(stage, workspace, options);
|
|
13155
|
-
}
|
|
13156
|
-
/**
|
|
13157
|
-
* Reconcile existing secrets with expected workspace-derived keys.
|
|
13158
|
-
* Adds missing keys (e.g. BETTER_AUTH_*) without overwriting existing values.
|
|
13159
|
-
* Returns the updated secrets if changes were made, or null if no changes needed.
|
|
13160
|
-
* @internal Exported for testing
|
|
13161
|
-
*/
|
|
13162
|
-
function reconcileSecrets(secrets, workspace) {
|
|
13163
|
-
let changed = false;
|
|
13164
|
-
let result = { ...secrets };
|
|
13165
|
-
const serviceMap = [
|
|
13166
|
-
{
|
|
13167
|
-
key: "db",
|
|
13168
|
-
name: "postgres"
|
|
13169
|
-
},
|
|
13170
|
-
{
|
|
13171
|
-
key: "cache",
|
|
13172
|
-
name: "redis"
|
|
13173
|
-
},
|
|
13174
|
-
{
|
|
13175
|
-
key: "storage",
|
|
13176
|
-
name: "minio"
|
|
13177
|
-
},
|
|
13178
|
-
{
|
|
13179
|
-
key: "mail",
|
|
13180
|
-
name: "mailpit"
|
|
13181
|
-
}
|
|
13182
|
-
];
|
|
13183
|
-
for (const { key, name: name$1 } of serviceMap) if (workspace.services[key] && !result.services[name$1]) {
|
|
13184
|
-
const creds = require_fullstack_secrets.generateServiceCredentials(name$1);
|
|
13185
|
-
if (name$1 === "minio") {
|
|
13186
|
-
creds.bucket = workspace.name;
|
|
13187
|
-
creds.username = workspace.name;
|
|
13188
|
-
}
|
|
13189
|
-
result = {
|
|
13190
|
-
...result,
|
|
13191
|
-
services: {
|
|
13192
|
-
...result.services,
|
|
13193
|
-
[name$1]: creds
|
|
13194
|
-
}
|
|
13195
|
-
};
|
|
13196
|
-
result.urls = require_fullstack_secrets.generateConnectionUrls(result.services, result.eventsBackend);
|
|
13197
|
-
logger$1.log(` 🔄 Adding missing service credentials: ${name$1}`);
|
|
13198
|
-
changed = true;
|
|
13199
|
-
}
|
|
13200
|
-
if (result.services.postgres && !result.services.pgboss) {
|
|
13201
|
-
result = {
|
|
13202
|
-
...result,
|
|
13203
|
-
services: {
|
|
13204
|
-
...result.services,
|
|
13205
|
-
pgboss: {
|
|
13206
|
-
host: result.services.postgres.host,
|
|
13207
|
-
port: result.services.postgres.port,
|
|
13208
|
-
username: "pgboss",
|
|
13209
|
-
password: require_fullstack_secrets.generateSecurePassword(),
|
|
13210
|
-
database: result.services.postgres.database ?? "app"
|
|
13211
|
-
}
|
|
13212
|
-
}
|
|
13213
|
-
};
|
|
13214
|
-
result.urls = require_fullstack_secrets.generateConnectionUrls(result.services, result.eventsBackend);
|
|
13215
|
-
logger$1.log(" 🔄 Adding missing service credentials: pgboss");
|
|
13216
|
-
changed = true;
|
|
13217
|
-
}
|
|
13218
|
-
const eventsBackend = workspace.services.events;
|
|
13219
|
-
if (eventsBackend && result.eventsBackend !== eventsBackend) {
|
|
13220
|
-
result.eventsBackend = eventsBackend;
|
|
13221
|
-
if (eventsBackend === "sns" && !result.services.localstack) {
|
|
13222
|
-
result = {
|
|
13223
|
-
...result,
|
|
13224
|
-
services: {
|
|
13225
|
-
...result.services,
|
|
13226
|
-
localstack: require_fullstack_secrets.generateLocalStackCredentials()
|
|
13227
|
-
}
|
|
13228
|
-
};
|
|
13229
|
-
logger$1.log(" 🔄 Adding missing service credentials: localstack");
|
|
13230
|
-
changed = true;
|
|
13231
|
-
}
|
|
13232
|
-
if (eventsBackend === "rabbitmq" && !result.services.rabbitmq) {
|
|
13233
|
-
result = {
|
|
13234
|
-
...result,
|
|
13235
|
-
services: {
|
|
13236
|
-
...result.services,
|
|
13237
|
-
rabbitmq: require_fullstack_secrets.generateServiceCredentials("rabbitmq")
|
|
13238
|
-
}
|
|
13239
|
-
};
|
|
13240
|
-
logger$1.log(" 🔄 Adding missing service credentials: rabbitmq");
|
|
13241
|
-
changed = true;
|
|
13242
|
-
}
|
|
13243
|
-
result.urls = require_fullstack_secrets.generateConnectionUrls(result.services, eventsBackend);
|
|
13244
|
-
changed = true;
|
|
13245
|
-
}
|
|
13246
|
-
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
13247
|
-
if (isMultiApp) {
|
|
13248
|
-
const expected = require_fullstack_secrets.generateFullstackCustomSecrets(workspace);
|
|
13249
|
-
const missing = {};
|
|
13250
|
-
for (const [key, value] of Object.entries(expected)) if (!(key in result.custom)) missing[key] = value;
|
|
13251
|
-
if (Object.keys(missing).length > 0) {
|
|
13252
|
-
logger$1.log(` 🔄 Adding missing secrets: ${Object.keys(missing).join(", ")}`);
|
|
13253
|
-
result = {
|
|
13254
|
-
...result,
|
|
13255
|
-
custom: {
|
|
13256
|
-
...result.custom,
|
|
13257
|
-
...missing
|
|
13258
|
-
}
|
|
13259
|
-
};
|
|
13260
|
-
changed = true;
|
|
13261
|
-
}
|
|
13262
|
-
}
|
|
13263
|
-
if (!changed) return null;
|
|
13264
|
-
return {
|
|
13265
|
-
...result,
|
|
13266
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13267
|
-
};
|
|
13268
|
-
}
|
|
13269
|
-
/**
|
|
13270
|
-
* Generate fresh secrets for the workspace.
|
|
13271
|
-
*/
|
|
13272
|
-
async function generateFreshSecrets(stage, workspace, options) {
|
|
13273
|
-
const serviceNames = [];
|
|
13274
|
-
if (workspace.services.db) serviceNames.push("postgres");
|
|
13275
|
-
if (workspace.services.cache) serviceNames.push("redis");
|
|
13276
|
-
if (workspace.services.storage) serviceNames.push("minio");
|
|
13277
|
-
if (workspace.services.mail) serviceNames.push("mailpit");
|
|
13278
|
-
if (workspace.services.events === "sns") serviceNames.push("localstack");
|
|
13279
|
-
if (workspace.services.events === "rabbitmq") serviceNames.push("rabbitmq");
|
|
13280
|
-
const secrets = require_fullstack_secrets.createStageSecrets(stage, serviceNames, {
|
|
13281
|
-
projectName: workspace.name,
|
|
13282
|
-
eventsBackend: workspace.services.events
|
|
13283
|
-
});
|
|
13284
|
-
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
13285
|
-
if (isMultiApp) {
|
|
13286
|
-
const customSecrets = require_fullstack_secrets.generateFullstackCustomSecrets(workspace);
|
|
13287
|
-
secrets.custom = customSecrets;
|
|
13288
|
-
} else secrets.custom = {
|
|
13289
|
-
NODE_ENV: "development",
|
|
13290
|
-
PORT: "3000",
|
|
13291
|
-
LOG_LEVEL: "debug",
|
|
13292
|
-
JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
13293
|
-
};
|
|
13294
|
-
await require_storage.writeStageSecrets(secrets, workspace.root);
|
|
13295
|
-
logger$1.log(` Secrets written to .gkm/secrets/${stage}.json`);
|
|
13296
|
-
if (require_sync.isSSMConfigured(workspace) && !options.yes) {
|
|
13297
|
-
const { shouldPush } = await (0, prompts.default)({
|
|
13298
|
-
type: "confirm",
|
|
13299
|
-
name: "shouldPush",
|
|
13300
|
-
message: "Push secrets to SSM for team sharing?",
|
|
13301
|
-
initial: true
|
|
13302
|
-
});
|
|
13303
|
-
if (shouldPush) try {
|
|
13304
|
-
await require_sync.pushSecrets(stage, workspace);
|
|
13305
|
-
logger$1.log("☁️ Secrets pushed to SSM");
|
|
13306
|
-
} catch (error) {
|
|
13307
|
-
logger$1.warn(`⚠️ Failed to push to SSM: ${error.message}`);
|
|
13308
|
-
}
|
|
13309
|
-
}
|
|
13310
|
-
return secrets;
|
|
13311
|
-
}
|
|
13312
|
-
/**
|
|
13313
|
-
* Print setup summary with next steps.
|
|
13314
|
-
*/
|
|
13315
|
-
function printSummary(workspace, stage) {
|
|
13316
|
-
logger$1.log(`\n${"─".repeat(50)}`);
|
|
13317
|
-
logger$1.log("\n✅ Development environment ready!\n");
|
|
13318
|
-
logger$1.log("📋 Apps:");
|
|
13319
|
-
for (const [name$1, app] of Object.entries(workspace.apps)) {
|
|
13320
|
-
const icon = app.type === "web" ? "🌐" : app.type === "mobile" ? "📱" : "🔧";
|
|
13321
|
-
logger$1.log(` ${icon} ${name$1} → http://localhost:${app.port}`);
|
|
13322
|
-
}
|
|
13323
|
-
logger$1.log("\n🚀 Next steps:");
|
|
13324
|
-
logger$1.log(" gkm dev # Start all apps");
|
|
13325
|
-
logger$1.log(` gkm secrets:show --stage ${stage} # View secrets`);
|
|
13326
|
-
logger$1.log("");
|
|
13327
|
-
}
|
|
13328
|
-
|
|
13329
12856
|
//#endregion
|
|
13330
12857
|
//#region src/test/index.ts
|
|
13331
12858
|
/**
|
|
@@ -13336,9 +12863,15 @@ async function testCommand(options = {}) {
|
|
|
13336
12863
|
const stage = options.stage ?? "development";
|
|
13337
12864
|
const cwd = process.cwd();
|
|
13338
12865
|
console.log(`\n🧪 Running tests with ${stage} environment...\n`);
|
|
13339
|
-
const
|
|
12866
|
+
const autoSetup = options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP);
|
|
12867
|
+
if (autoSetup) {
|
|
12868
|
+
const { ensureStageSecrets } = await Promise.resolve().then(() => require("./setup-DLwhfyt-.cjs"));
|
|
12869
|
+
const generated = await ensureStageSecrets(stage, cwd);
|
|
12870
|
+
if (generated) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
|
|
12871
|
+
}
|
|
12872
|
+
const defaultEnv = require_setup.loadEnvFiles(".env");
|
|
13340
12873
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
13341
|
-
const result = await prepareEntryCredentials({
|
|
12874
|
+
const result = await require_setup.prepareEntryCredentials({
|
|
13342
12875
|
stages: [stage],
|
|
13343
12876
|
startDocker: true,
|
|
13344
12877
|
secretsFileName: "test-secrets.json",
|
|
@@ -13360,7 +12893,7 @@ async function testCommand(options = {}) {
|
|
|
13360
12893
|
await (0, node_fs_promises.writeFile)(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
|
|
13361
12894
|
const gkmDir = (0, node_path.join)(cwd, ".gkm");
|
|
13362
12895
|
const preloadPath = (0, node_path.join)(gkmDir, "test-credentials-preload.ts");
|
|
13363
|
-
await createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
12896
|
+
await require_setup.createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
13364
12897
|
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
13365
12898
|
const tsxImport = "--import=tsx";
|
|
13366
12899
|
const preloadImport = `--import=${preloadPath}`;
|
|
@@ -13385,9 +12918,9 @@ async function testCommand(options = {}) {
|
|
|
13385
12918
|
NODE_OPTIONS: nodeOptions
|
|
13386
12919
|
}
|
|
13387
12920
|
});
|
|
13388
|
-
return new Promise((resolve$
|
|
12921
|
+
return new Promise((resolve$3, reject) => {
|
|
13389
12922
|
vitestProcess.on("close", (code) => {
|
|
13390
|
-
if (code === 0) resolve$
|
|
12923
|
+
if (code === 0) resolve$3();
|
|
13391
12924
|
else reject(new Error(`Tests failed with exit code ${code}`));
|
|
13392
12925
|
});
|
|
13393
12926
|
vitestProcess.on("error", (error) => {
|
|
@@ -13580,7 +13113,7 @@ program.command("setup").description("Setup development environment (secrets, Do
|
|
|
13580
13113
|
try {
|
|
13581
13114
|
const globalOptions = program.opts();
|
|
13582
13115
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13583
|
-
await setupCommand(options);
|
|
13116
|
+
await require_setup.setupCommand(options);
|
|
13584
13117
|
} catch (error) {
|
|
13585
13118
|
console.error(formatError(error));
|
|
13586
13119
|
process.exit(1);
|
|
@@ -13648,7 +13181,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
13648
13181
|
process.exit(1);
|
|
13649
13182
|
}
|
|
13650
13183
|
});
|
|
13651
|
-
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from", "development").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
|
|
13184
|
+
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from", "development").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").option("--auto-setup", "Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
|
|
13652
13185
|
try {
|
|
13653
13186
|
const globalOptions = program.opts();
|
|
13654
13187
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -13790,8 +13323,8 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
13790
13323
|
const globalOptions = program.opts();
|
|
13791
13324
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13792
13325
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
13793
|
-
const { pushSecrets
|
|
13794
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
13326
|
+
const { pushSecrets } = await Promise.resolve().then(() => require("./sync-BLA94rgC.cjs"));
|
|
13327
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-BzqKJ2Rt.cjs"));
|
|
13795
13328
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
13796
13329
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13797
13330
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
@@ -13803,7 +13336,7 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
13803
13336
|
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
13804
13337
|
}
|
|
13805
13338
|
}
|
|
13806
|
-
await pushSecrets
|
|
13339
|
+
await pushSecrets(options.stage, workspace);
|
|
13807
13340
|
console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
|
|
13808
13341
|
} catch (error) {
|
|
13809
13342
|
console.error(formatError(error));
|
|
@@ -13815,11 +13348,11 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
13815
13348
|
const globalOptions = program.opts();
|
|
13816
13349
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13817
13350
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
13818
|
-
const { pullSecrets
|
|
13351
|
+
const { pullSecrets } = await Promise.resolve().then(() => require("./sync-BLA94rgC.cjs"));
|
|
13819
13352
|
const { writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
13820
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
13353
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-BzqKJ2Rt.cjs"));
|
|
13821
13354
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13822
|
-
let secrets = await pullSecrets
|
|
13355
|
+
let secrets = await pullSecrets(options.stage, workspace);
|
|
13823
13356
|
if (!secrets) {
|
|
13824
13357
|
console.error(`No remote secrets found for stage "${options.stage}".`);
|
|
13825
13358
|
process.exit(1);
|
|
@@ -13842,7 +13375,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
13842
13375
|
const globalOptions = program.opts();
|
|
13843
13376
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13844
13377
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
13845
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
13378
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-BzqKJ2Rt.cjs"));
|
|
13846
13379
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
13847
13380
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13848
13381
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|