@geekmidas/cli 1.9.0 → 1.10.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 +21 -0
- package/README.md +42 -6
- package/dist/{HostingerProvider-CEsQbmpY.cjs → HostingerProvider-5KYmwoK2.cjs} +1 -1
- package/dist/{HostingerProvider-CEsQbmpY.cjs.map → HostingerProvider-5KYmwoK2.cjs.map} +1 -1
- package/dist/{HostingerProvider-DkahM5AP.mjs → HostingerProvider-ANWchdiK.mjs} +1 -1
- package/dist/{HostingerProvider-DkahM5AP.mjs.map → HostingerProvider-ANWchdiK.mjs.map} +1 -1
- package/dist/{LocalStateProvider-Roi202l7.cjs → LocalStateProvider-CLifRC0Y.cjs} +1 -1
- package/dist/{LocalStateProvider-Roi202l7.cjs.map → LocalStateProvider-CLifRC0Y.cjs.map} +1 -1
- package/dist/{LocalStateProvider-DXIwWb7k.mjs → LocalStateProvider-Dp0KkRcw.mjs} +1 -1
- package/dist/{LocalStateProvider-DXIwWb7k.mjs.map → LocalStateProvider-Dp0KkRcw.mjs.map} +1 -1
- package/dist/{Route53Provider-Ckq_n5Be.mjs → Route53Provider-QoPgcXxn.mjs} +1 -1
- package/dist/{Route53Provider-Ckq_n5Be.mjs.map → Route53Provider-QoPgcXxn.mjs.map} +1 -1
- package/dist/{Route53Provider-BqXeHzuc.cjs → Route53Provider-owQQ4pn6.cjs} +1 -1
- package/dist/{Route53Provider-BqXeHzuc.cjs.map → Route53Provider-owQQ4pn6.cjs.map} +1 -1
- package/dist/{SSMStateProvider-BReQA5re.cjs → SSMStateProvider-CT8tjl9o.cjs} +1 -1
- package/dist/{SSMStateProvider-BReQA5re.cjs.map → SSMStateProvider-CT8tjl9o.cjs.map} +1 -1
- package/dist/{SSMStateProvider-wddd0_-d.mjs → SSMStateProvider-CksOTB8M.mjs} +1 -1
- package/dist/{SSMStateProvider-wddd0_-d.mjs.map → SSMStateProvider-CksOTB8M.mjs.map} +1 -1
- package/dist/{backup-provisioner-BAExdDtc.mjs → backup-provisioner-BEXoHTuC.mjs} +1 -1
- package/dist/{backup-provisioner-BAExdDtc.mjs.map → backup-provisioner-BEXoHTuC.mjs.map} +1 -1
- package/dist/{backup-provisioner-C8VK63I-.cjs → backup-provisioner-C4noe75O.cjs} +1 -1
- package/dist/{backup-provisioner-C8VK63I-.cjs.map → backup-provisioner-C4noe75O.cjs.map} +1 -1
- package/dist/{bundler-BxHyDhdt.mjs → bundler-DQYjKFPm.mjs} +1 -1
- package/dist/{bundler-BxHyDhdt.mjs.map → bundler-DQYjKFPm.mjs.map} +1 -1
- package/dist/{bundler-CuMIfXw5.cjs → bundler-NpfYPBUo.cjs} +1 -1
- package/dist/{bundler-CuMIfXw5.cjs.map → bundler-NpfYPBUo.cjs.map} +1 -1
- package/dist/{config-6JHOwLCx.cjs → config-D3ORuiUs.cjs} +2 -2
- package/dist/{config-6JHOwLCx.cjs.map → config-D3ORuiUs.cjs.map} +1 -1
- package/dist/{config-DxASSNjr.mjs → config-jsRYHOHU.mjs} +2 -2
- package/dist/{config-DxASSNjr.mjs.map → config-jsRYHOHU.mjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/fullstack-secrets-COWz084x.cjs +238 -0
- package/dist/fullstack-secrets-COWz084x.cjs.map +1 -0
- package/dist/fullstack-secrets-UZAFWuH4.mjs +202 -0
- package/dist/fullstack-secrets-UZAFWuH4.mjs.map +1 -0
- package/dist/{index-BVNXOydm.d.mts → index-3n-giNaw.d.mts} +18 -6
- package/dist/index-3n-giNaw.d.mts.map +1 -0
- package/dist/{index-Cyk2rTyj.d.cts → index-CiEOtKEX.d.cts} +18 -6
- package/dist/index-CiEOtKEX.d.cts.map +1 -0
- package/dist/index.cjs +322 -433
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +306 -417
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CnvwSRDU.cjs → openapi-BYxAWwok.cjs} +178 -32
- package/dist/openapi-BYxAWwok.cjs.map +1 -0
- package/dist/{openapi-BYlyAbH3.mjs → openapi-DenF-okj.mjs} +148 -32
- package/dist/openapi-DenF-okj.mjs.map +1 -0
- package/dist/{openapi-react-query-DaTMSPD5.mjs → openapi-react-query-C4UdILaI.mjs} +1 -1
- package/dist/{openapi-react-query-DaTMSPD5.mjs.map → openapi-react-query-C4UdILaI.mjs.map} +1 -1
- package/dist/{openapi-react-query-BeXvk-wa.cjs → openapi-react-query-DYbBq-WJ.cjs} +1 -1
- package/dist/{openapi-react-query-BeXvk-wa.cjs.map → openapi-react-query-DYbBq-WJ.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/reconcile-7yarEvmK.cjs +36 -0
- package/dist/reconcile-7yarEvmK.cjs.map +1 -0
- package/dist/reconcile-D2WCDQue.mjs +36 -0
- package/dist/reconcile-D2WCDQue.mjs.map +1 -0
- package/dist/{sync-BnqNNc6O.mjs → sync-6FoT41G3.mjs} +1 -1
- package/dist/{sync-CHfhmXF3.mjs → sync-CbeKrnQV.mjs} +1 -1
- package/dist/{sync-CHfhmXF3.mjs.map → sync-CbeKrnQV.mjs.map} +1 -1
- package/dist/{sync-BOS0jKLn.cjs → sync-DdkKaHqP.cjs} +1 -1
- package/dist/{sync-BOS0jKLn.cjs.map → sync-DdkKaHqP.cjs.map} +1 -1
- package/dist/sync-RsnjXYwG.cjs +4 -0
- package/dist/{types-eTlj5f2M.d.mts → types-C7QJJl9f.d.cts} +6 -2
- package/dist/types-C7QJJl9f.d.cts.map +1 -0
- package/dist/{types-l53qUmGt.d.cts → types-Iqsq_FIG.d.mts} +6 -2
- package/dist/types-Iqsq_FIG.d.mts.map +1 -0
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-D2ocAlpl.cjs → workspace-4SP3Gx4Y.cjs} +11 -3
- package/dist/{workspace-D2ocAlpl.cjs.map → workspace-4SP3Gx4Y.cjs.map} +1 -1
- package/dist/{workspace-9IQIjwkQ.mjs → workspace-D4z4A4cq.mjs} +11 -3
- package/dist/{workspace-9IQIjwkQ.mjs.map → workspace-D4z4A4cq.mjs.map} +1 -1
- package/package.json +2 -2
- package/src/build/__tests__/manifests.spec.ts +171 -0
- package/src/build/__tests__/partitions.spec.ts +110 -0
- package/src/build/index.ts +58 -15
- package/src/build/manifests.ts +153 -32
- package/src/build/partitions.ts +58 -0
- package/src/deploy/sniffer.ts +6 -1
- package/src/dev/__tests__/index.spec.ts +49 -0
- package/src/dev/index.ts +84 -63
- package/src/generators/Generator.ts +27 -7
- package/src/generators/OpenApiTsGenerator.ts +4 -4
- package/src/index.ts +79 -1
- package/src/init/versions.ts +4 -4
- package/src/openapi.ts +2 -1
- package/src/secrets/__tests__/reconcile.spec.ts +123 -0
- package/src/secrets/reconcile.ts +53 -0
- package/src/setup/fullstack-secrets.ts +2 -0
- package/src/types.ts +17 -1
- package/src/workspace/client-generator.ts +6 -3
- package/src/workspace/schema.ts +13 -3
- package/dist/index-BVNXOydm.d.mts.map +0 -1
- package/dist/index-Cyk2rTyj.d.cts.map +0 -1
- package/dist/openapi-BYlyAbH3.mjs.map +0 -1
- package/dist/openapi-CnvwSRDU.cjs.map +0 -1
- package/dist/sync-BxFB34zW.cjs +0 -4
- package/dist/types-eTlj5f2M.d.mts.map +0 -1
- package/dist/types-l53qUmGt.d.cts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
-
const require_workspace = require('./workspace-
|
|
4
|
-
const require_config = require('./config-
|
|
3
|
+
const require_workspace = require('./workspace-4SP3Gx4Y.cjs');
|
|
4
|
+
const require_config = require('./config-D3ORuiUs.cjs');
|
|
5
5
|
const require_credentials = require('./credentials-C8DWtnMY.cjs');
|
|
6
|
-
const require_openapi = require('./openapi-
|
|
6
|
+
const require_openapi = require('./openapi-BYxAWwok.cjs');
|
|
7
7
|
const require_storage = require('./storage-CoCNe0Pt.cjs');
|
|
8
8
|
const require_dokploy_api = require('./dokploy-api-DLgvEQlr.cjs');
|
|
9
9
|
const require_encryption = require('./encryption-BE0UOb8j.cjs');
|
|
10
10
|
const require_CachedStateProvider = require('./CachedStateProvider-D73dCqfH.cjs');
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const require_fullstack_secrets = require('./fullstack-secrets-COWz084x.cjs');
|
|
12
|
+
const require_openapi_react_query = require('./openapi-react-query-DYbBq-WJ.cjs');
|
|
13
|
+
const require_sync = require('./sync-DdkKaHqP.cjs');
|
|
13
14
|
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
14
15
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
15
16
|
const commander = require_chunk.__toESM(require("commander"));
|
|
@@ -34,7 +35,7 @@ const prompts = require_chunk.__toESM(require("prompts"));
|
|
|
34
35
|
|
|
35
36
|
//#region package.json
|
|
36
37
|
var name = "@geekmidas/cli";
|
|
37
|
-
var version = "1.
|
|
38
|
+
var version = "1.9.1";
|
|
38
39
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
39
40
|
var private$1 = false;
|
|
40
41
|
var type = "module";
|
|
@@ -132,7 +133,7 @@ var package_default = {
|
|
|
132
133
|
|
|
133
134
|
//#endregion
|
|
134
135
|
//#region src/auth/index.ts
|
|
135
|
-
const logger$
|
|
136
|
+
const logger$12 = console;
|
|
136
137
|
/**
|
|
137
138
|
* Validate Dokploy token by making a test API call
|
|
138
139
|
*/
|
|
@@ -200,36 +201,36 @@ async function prompt$1(message, hidden = false) {
|
|
|
200
201
|
async function loginCommand(options) {
|
|
201
202
|
const { service, token: providedToken, endpoint: providedEndpoint } = options;
|
|
202
203
|
if (service === "dokploy") {
|
|
203
|
-
logger$
|
|
204
|
+
logger$12.log("\n🔐 Logging in to Dokploy...\n");
|
|
204
205
|
let endpoint = providedEndpoint;
|
|
205
206
|
if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
206
207
|
endpoint = endpoint.replace(/\/$/, "");
|
|
207
208
|
try {
|
|
208
209
|
new URL(endpoint);
|
|
209
210
|
} catch {
|
|
210
|
-
logger$
|
|
211
|
+
logger$12.error("Invalid URL format");
|
|
211
212
|
process.exit(1);
|
|
212
213
|
}
|
|
213
214
|
let token = providedToken;
|
|
214
215
|
if (!token) {
|
|
215
|
-
logger$
|
|
216
|
+
logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
216
217
|
token = await prompt$1("API Token: ", true);
|
|
217
218
|
}
|
|
218
219
|
if (!token) {
|
|
219
|
-
logger$
|
|
220
|
+
logger$12.error("Token is required");
|
|
220
221
|
process.exit(1);
|
|
221
222
|
}
|
|
222
|
-
logger$
|
|
223
|
+
logger$12.log("\nValidating credentials...");
|
|
223
224
|
const isValid = await validateDokployToken(endpoint, token);
|
|
224
225
|
if (!isValid) {
|
|
225
|
-
logger$
|
|
226
|
+
logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
226
227
|
process.exit(1);
|
|
227
228
|
}
|
|
228
229
|
await require_credentials.storeDokployCredentials(token, endpoint);
|
|
229
|
-
logger$
|
|
230
|
-
logger$
|
|
231
|
-
logger$
|
|
232
|
-
logger$
|
|
230
|
+
logger$12.log("\n✓ Successfully logged in to Dokploy!");
|
|
231
|
+
logger$12.log(` Endpoint: ${endpoint}`);
|
|
232
|
+
logger$12.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
|
|
233
|
+
logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
236
|
/**
|
|
@@ -239,28 +240,28 @@ async function logoutCommand(options) {
|
|
|
239
240
|
const { service = "dokploy" } = options;
|
|
240
241
|
if (service === "all") {
|
|
241
242
|
const dokployRemoved = await require_credentials.removeDokployCredentials();
|
|
242
|
-
if (dokployRemoved) logger$
|
|
243
|
-
else logger$
|
|
243
|
+
if (dokployRemoved) logger$12.log("\n✓ Logged out from all services");
|
|
244
|
+
else logger$12.log("\nNo stored credentials found");
|
|
244
245
|
return;
|
|
245
246
|
}
|
|
246
247
|
if (service === "dokploy") {
|
|
247
248
|
const removed = await require_credentials.removeDokployCredentials();
|
|
248
|
-
if (removed) logger$
|
|
249
|
-
else logger$
|
|
249
|
+
if (removed) logger$12.log("\n✓ Logged out from Dokploy");
|
|
250
|
+
else logger$12.log("\nNo Dokploy credentials found");
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
/**
|
|
253
254
|
* Show current login status
|
|
254
255
|
*/
|
|
255
256
|
async function whoamiCommand() {
|
|
256
|
-
logger$
|
|
257
|
+
logger$12.log("\n📋 Current credentials:\n");
|
|
257
258
|
const dokploy = await require_credentials.getDokployCredentials();
|
|
258
259
|
if (dokploy) {
|
|
259
|
-
logger$
|
|
260
|
-
logger$
|
|
261
|
-
logger$
|
|
262
|
-
} else logger$
|
|
263
|
-
logger$
|
|
260
|
+
logger$12.log(" Dokploy:");
|
|
261
|
+
logger$12.log(` Endpoint: ${dokploy.endpoint}`);
|
|
262
|
+
logger$12.log(` Token: ${maskToken(dokploy.token)}`);
|
|
263
|
+
} else logger$12.log(" Dokploy: Not logged in");
|
|
264
|
+
logger$12.log(`\n Credentials file: ${require_credentials.getCredentialsPath()}`);
|
|
264
265
|
}
|
|
265
266
|
/**
|
|
266
267
|
* Mask a token for display
|
|
@@ -346,7 +347,7 @@ function isEnabled(config) {
|
|
|
346
347
|
var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
347
348
|
async build(context, constructs, outputDir, options) {
|
|
348
349
|
const provider = options?.provider || "aws-lambda";
|
|
349
|
-
const logger$
|
|
350
|
+
const logger$13 = console;
|
|
350
351
|
const cronInfos = [];
|
|
351
352
|
if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
|
|
352
353
|
const cronsDir = (0, node_path.join)(outputDir, "crons");
|
|
@@ -361,7 +362,7 @@ var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
361
362
|
memorySize: construct.memorySize,
|
|
362
363
|
environment: await construct.getEnvironment()
|
|
363
364
|
});
|
|
364
|
-
logger$
|
|
365
|
+
logger$13.log(`Generated cron handler: ${key}`);
|
|
365
366
|
}
|
|
366
367
|
return cronInfos;
|
|
367
368
|
}
|
|
@@ -397,7 +398,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
397
398
|
}
|
|
398
399
|
async build(context, constructs, outputDir, options) {
|
|
399
400
|
const provider = options?.provider || "aws-lambda";
|
|
400
|
-
const logger$
|
|
401
|
+
const logger$13 = console;
|
|
401
402
|
const functionInfos = [];
|
|
402
403
|
if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
|
|
403
404
|
const functionsDir = (0, node_path.join)(outputDir, "functions");
|
|
@@ -411,7 +412,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
411
412
|
memorySize: construct.memorySize,
|
|
412
413
|
environment: await construct.getEnvironment()
|
|
413
414
|
});
|
|
414
|
-
logger$
|
|
415
|
+
logger$13.log(`Generated function handler: ${key}`);
|
|
415
416
|
}
|
|
416
417
|
return functionInfos;
|
|
417
418
|
}
|
|
@@ -444,11 +445,11 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
444
445
|
}
|
|
445
446
|
async build(context, constructs, outputDir, options) {
|
|
446
447
|
const provider = options?.provider || "aws-lambda";
|
|
447
|
-
const logger$
|
|
448
|
+
const logger$13 = console;
|
|
448
449
|
const subscriberInfos = [];
|
|
449
450
|
if (provider === "server") {
|
|
450
451
|
await this.generateServerSubscribersFile(outputDir, constructs);
|
|
451
|
-
logger$
|
|
452
|
+
logger$13.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
|
|
452
453
|
return subscriberInfos;
|
|
453
454
|
}
|
|
454
455
|
if (constructs.length === 0) return subscriberInfos;
|
|
@@ -465,7 +466,7 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
465
466
|
memorySize: construct.memorySize,
|
|
466
467
|
environment: await construct.getEnvironment()
|
|
467
468
|
});
|
|
468
|
-
logger$
|
|
469
|
+
logger$13.log(`Generated subscriber handler: ${key}`);
|
|
469
470
|
}
|
|
470
471
|
return subscriberInfos;
|
|
471
472
|
}
|
|
@@ -630,98 +631,6 @@ export async function setupSubscribers(
|
|
|
630
631
|
}
|
|
631
632
|
};
|
|
632
633
|
|
|
633
|
-
//#endregion
|
|
634
|
-
//#region src/workspace/client-generator.ts
|
|
635
|
-
const logger$12 = console;
|
|
636
|
-
/**
|
|
637
|
-
* Get frontend apps that depend on a backend app.
|
|
638
|
-
*/
|
|
639
|
-
function getDependentFrontends(workspace, backendAppName) {
|
|
640
|
-
const dependentApps = [];
|
|
641
|
-
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.includes(backendAppName)) dependentApps.push(appName);
|
|
642
|
-
return dependentApps;
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Get the path to a backend's OpenAPI spec file.
|
|
646
|
-
*/
|
|
647
|
-
function getBackendOpenApiPath(workspace, backendAppName) {
|
|
648
|
-
const app = workspace.apps[backendAppName];
|
|
649
|
-
if (!app || app.type !== "backend") return null;
|
|
650
|
-
return (0, node_path.join)(workspace.root, app.path, ".gkm", "openapi.ts");
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Count endpoints in an OpenAPI spec content.
|
|
654
|
-
*/
|
|
655
|
-
function countEndpoints(content) {
|
|
656
|
-
const endpointMatches = content.match(/'(GET|POST|PUT|PATCH|DELETE)\s+\/[^']+'/g);
|
|
657
|
-
return endpointMatches?.length ?? 0;
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Copy the OpenAPI client from a backend to all dependent frontend apps.
|
|
661
|
-
* Called when the backend's .gkm/openapi.ts file changes.
|
|
662
|
-
*/
|
|
663
|
-
async function copyClientToFrontends(workspace, backendAppName, options = {}) {
|
|
664
|
-
const log = options.silent ? () => {} : logger$12.log.bind(logger$12);
|
|
665
|
-
const results = [];
|
|
666
|
-
const backendApp = workspace.apps[backendAppName];
|
|
667
|
-
if (!backendApp || backendApp.type !== "backend") return results;
|
|
668
|
-
const openApiPath = (0, node_path.join)(workspace.root, backendApp.path, ".gkm", "openapi.ts");
|
|
669
|
-
if (!(0, node_fs.existsSync)(openApiPath)) return results;
|
|
670
|
-
const content = await (0, node_fs_promises.readFile)(openApiPath, "utf-8");
|
|
671
|
-
const endpointCount = countEndpoints(content);
|
|
672
|
-
const dependentFrontends = getDependentFrontends(workspace, backendAppName);
|
|
673
|
-
for (const frontendAppName of dependentFrontends) {
|
|
674
|
-
const frontendApp = workspace.apps[frontendAppName];
|
|
675
|
-
if (!frontendApp || frontendApp.type !== "frontend") continue;
|
|
676
|
-
const clientOutput = frontendApp.client?.output;
|
|
677
|
-
if (!clientOutput) continue;
|
|
678
|
-
const result = {
|
|
679
|
-
frontendApp: frontendAppName,
|
|
680
|
-
backendApp: backendAppName,
|
|
681
|
-
outputPath: "",
|
|
682
|
-
endpointCount,
|
|
683
|
-
success: false
|
|
684
|
-
};
|
|
685
|
-
try {
|
|
686
|
-
const frontendPath = (0, node_path.join)(workspace.root, frontendApp.path);
|
|
687
|
-
const outputDir = (0, node_path.join)(frontendPath, clientOutput);
|
|
688
|
-
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
689
|
-
const fileName = `${backendAppName}.ts`;
|
|
690
|
-
const outputPath = (0, node_path.join)(outputDir, fileName);
|
|
691
|
-
const backendRelPath = (0, node_path.relative)((0, node_path.dirname)(outputPath), (0, node_path.join)(workspace.root, backendApp.path));
|
|
692
|
-
const clientContent = `/**
|
|
693
|
-
* Auto-generated API client for ${backendAppName}
|
|
694
|
-
* Generated from: ${backendRelPath}
|
|
695
|
-
*
|
|
696
|
-
* DO NOT EDIT - This file is automatically regenerated when backend schemas change.
|
|
697
|
-
*/
|
|
698
|
-
|
|
699
|
-
${content}
|
|
700
|
-
`;
|
|
701
|
-
await (0, node_fs_promises.writeFile)(outputPath, clientContent);
|
|
702
|
-
result.outputPath = outputPath;
|
|
703
|
-
result.success = true;
|
|
704
|
-
log(`📦 Copied client to ${frontendAppName} from ${backendAppName} (${endpointCount} endpoints)`);
|
|
705
|
-
} catch (error) {
|
|
706
|
-
result.error = error.message;
|
|
707
|
-
}
|
|
708
|
-
results.push(result);
|
|
709
|
-
}
|
|
710
|
-
return results;
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Copy clients from all backends to their dependent frontends.
|
|
714
|
-
* Useful for initial setup or force refresh.
|
|
715
|
-
*/
|
|
716
|
-
async function copyAllClients(workspace, options = {}) {
|
|
717
|
-
const allResults = [];
|
|
718
|
-
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "backend" && app.routes) {
|
|
719
|
-
const results = await copyClientToFrontends(workspace, appName, options);
|
|
720
|
-
allResults.push(...results);
|
|
721
|
-
}
|
|
722
|
-
return allResults;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
634
|
//#endregion
|
|
726
635
|
//#region src/dev/index.ts
|
|
727
636
|
const logger$11 = console;
|
|
@@ -1374,7 +1283,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1374
1283
|
logger$11.log("✅ Frontend apps validated");
|
|
1375
1284
|
}
|
|
1376
1285
|
if (frontendApps.length > 0 && backendApps.length > 0) {
|
|
1377
|
-
const clientResults = await copyAllClients(workspace);
|
|
1286
|
+
const clientResults = await require_openapi.copyAllClients(workspace);
|
|
1378
1287
|
const copiedCount = clientResults.filter((r) => r.success).length;
|
|
1379
1288
|
if (copiedCount > 0) logger$11.log(`\n📦 Copied ${copiedCount} API client(s)`);
|
|
1380
1289
|
}
|
|
@@ -1442,7 +1351,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1442
1351
|
if (frontendApps.length > 0 && backendApps.length > 0) {
|
|
1443
1352
|
const openApiPaths = [];
|
|
1444
1353
|
for (const [appName] of backendApps) {
|
|
1445
|
-
const openApiPath = getBackendOpenApiPath(workspace, appName);
|
|
1354
|
+
const openApiPath = require_openapi.getBackendOpenApiPath(workspace, appName);
|
|
1446
1355
|
if (openApiPath) openApiPaths.push({
|
|
1447
1356
|
path: openApiPath,
|
|
1448
1357
|
appName
|
|
@@ -1464,7 +1373,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1464
1373
|
if (!backendAppName) return;
|
|
1465
1374
|
logger$11.log(`\n🔄 OpenAPI spec changed for ${backendAppName}`);
|
|
1466
1375
|
try {
|
|
1467
|
-
const results = await copyClientToFrontends(workspace, backendAppName, { silent: true });
|
|
1376
|
+
const results = await require_openapi.copyClientToFrontends(workspace, backendAppName, { silent: true });
|
|
1468
1377
|
for (const result of results) if (result.success) logger$11.log(` 📦 Copied client to ${result.frontendApp} (${result.endpointCount} endpoints)`);
|
|
1469
1378
|
else if (result.error) logger$11.error(` ❌ Failed to copy client to ${result.frontendApp}: ${result.error}`);
|
|
1470
1379
|
} catch (error) {
|
|
@@ -1740,6 +1649,66 @@ var EntryRunner = class {
|
|
|
1740
1649
|
}
|
|
1741
1650
|
}
|
|
1742
1651
|
};
|
|
1652
|
+
/**
|
|
1653
|
+
* Generate the content of the dev server entry file (server.ts).
|
|
1654
|
+
* Uses dynamic import for createApp so Credentials are populated
|
|
1655
|
+
* before any app modules evaluate.
|
|
1656
|
+
* @internal Exported for testing
|
|
1657
|
+
*/
|
|
1658
|
+
function generateServerEntryContent(options) {
|
|
1659
|
+
const { secretsJsonPath, runtime = "node", enableOpenApi = false, appImportPath = "./app.js" } = options;
|
|
1660
|
+
const credentialsInjection = secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
|
|
1661
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
1662
|
+
|
|
1663
|
+
// Inject dev secrets into Credentials (must happen before app import)
|
|
1664
|
+
const secretsPath = '${secretsJsonPath}';
|
|
1665
|
+
if (existsSync(secretsPath)) {
|
|
1666
|
+
Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
` : "";
|
|
1670
|
+
const serveCode = runtime === "bun" ? `Bun.serve({
|
|
1671
|
+
port,
|
|
1672
|
+
fetch: app.fetch,
|
|
1673
|
+
});` : `const { serve } = await import('@hono/node-server');
|
|
1674
|
+
const server = serve({
|
|
1675
|
+
fetch: app.fetch,
|
|
1676
|
+
port,
|
|
1677
|
+
});
|
|
1678
|
+
// Inject WebSocket support if available
|
|
1679
|
+
const injectWs = (app as any).__injectWebSocket;
|
|
1680
|
+
if (injectWs) {
|
|
1681
|
+
injectWs(server);
|
|
1682
|
+
console.log('🔌 Telescope real-time updates enabled');
|
|
1683
|
+
}`;
|
|
1684
|
+
return `#!/usr/bin/env node
|
|
1685
|
+
/**
|
|
1686
|
+
* Development server entry point
|
|
1687
|
+
* This file is auto-generated by 'gkm dev'
|
|
1688
|
+
*/
|
|
1689
|
+
${credentialsInjection}
|
|
1690
|
+
const port = process.argv.includes('--port')
|
|
1691
|
+
? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
|
|
1692
|
+
: 3000;
|
|
1693
|
+
|
|
1694
|
+
// Dynamic import so Credentials are populated before env.ts evaluates
|
|
1695
|
+
const { createApp } = await import('${appImportPath}');
|
|
1696
|
+
|
|
1697
|
+
// createApp is async to support optional WebSocket setup
|
|
1698
|
+
const { app, start } = await createApp(undefined, ${enableOpenApi});
|
|
1699
|
+
|
|
1700
|
+
// Start the server
|
|
1701
|
+
start({
|
|
1702
|
+
port,
|
|
1703
|
+
serve: async (app, port) => {
|
|
1704
|
+
${serveCode}
|
|
1705
|
+
},
|
|
1706
|
+
}).catch((error) => {
|
|
1707
|
+
console.error('Failed to start server:', error);
|
|
1708
|
+
process.exit(1);
|
|
1709
|
+
});
|
|
1710
|
+
`;
|
|
1711
|
+
}
|
|
1743
1712
|
var DevServer = class {
|
|
1744
1713
|
serverProcess = null;
|
|
1745
1714
|
isRunning = false;
|
|
@@ -1833,58 +1802,12 @@ var DevServer = class {
|
|
|
1833
1802
|
}
|
|
1834
1803
|
async createServerEntry() {
|
|
1835
1804
|
const { writeFile: fsWriteFile } = await import("node:fs/promises");
|
|
1836
|
-
const { relative: relative$6, dirname: dirname$11 } = await import("node:path");
|
|
1837
1805
|
const serverPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
const secretsPath = '${this.secretsJsonPath}';
|
|
1844
|
-
if (existsSync(secretsPath)) {
|
|
1845
|
-
Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
` : "";
|
|
1849
|
-
const serveCode = this.runtime === "bun" ? `Bun.serve({
|
|
1850
|
-
port,
|
|
1851
|
-
fetch: app.fetch,
|
|
1852
|
-
});` : `const { serve } = await import('@hono/node-server');
|
|
1853
|
-
const server = serve({
|
|
1854
|
-
fetch: app.fetch,
|
|
1855
|
-
port,
|
|
1856
|
-
});
|
|
1857
|
-
// Inject WebSocket support if available
|
|
1858
|
-
const injectWs = (app as any).__injectWebSocket;
|
|
1859
|
-
if (injectWs) {
|
|
1860
|
-
injectWs(server);
|
|
1861
|
-
console.log('🔌 Telescope real-time updates enabled');
|
|
1862
|
-
}`;
|
|
1863
|
-
const content = `#!/usr/bin/env node
|
|
1864
|
-
/**
|
|
1865
|
-
* Development server entry point
|
|
1866
|
-
* This file is auto-generated by 'gkm dev'
|
|
1867
|
-
*/
|
|
1868
|
-
${credentialsInjection}import { createApp } from './${relativeAppPath.startsWith(".") ? relativeAppPath : `./${relativeAppPath}`}';
|
|
1869
|
-
|
|
1870
|
-
const port = process.argv.includes('--port')
|
|
1871
|
-
? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
|
|
1872
|
-
: 3000;
|
|
1873
|
-
|
|
1874
|
-
// createApp is async to support optional WebSocket setup
|
|
1875
|
-
const { app, start } = await createApp(undefined, ${this.enableOpenApi});
|
|
1876
|
-
|
|
1877
|
-
// Start the server
|
|
1878
|
-
start({
|
|
1879
|
-
port,
|
|
1880
|
-
serve: async (app, port) => {
|
|
1881
|
-
${serveCode}
|
|
1882
|
-
},
|
|
1883
|
-
}).catch((error) => {
|
|
1884
|
-
console.error('Failed to start server:', error);
|
|
1885
|
-
process.exit(1);
|
|
1886
|
-
});
|
|
1887
|
-
`;
|
|
1806
|
+
const content = generateServerEntryContent({
|
|
1807
|
+
secretsJsonPath: this.secretsJsonPath,
|
|
1808
|
+
runtime: this.runtime,
|
|
1809
|
+
enableOpenApi: this.enableOpenApi
|
|
1810
|
+
});
|
|
1888
1811
|
await fsWriteFile(serverPath, content);
|
|
1889
1812
|
}
|
|
1890
1813
|
};
|
|
@@ -1967,22 +1890,58 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1967
1890
|
//#endregion
|
|
1968
1891
|
//#region src/build/manifests.ts
|
|
1969
1892
|
const logger$10 = console;
|
|
1893
|
+
function isPartitioned(field) {
|
|
1894
|
+
return !Array.isArray(field);
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Serialize a manifest field to a TypeScript string.
|
|
1898
|
+
* Flat arrays serialize as JSON arrays, partitioned fields as objects of arrays.
|
|
1899
|
+
*/
|
|
1900
|
+
function serializeField(field, indent = 2) {
|
|
1901
|
+
if (Array.isArray(field)) return JSON.stringify(field, null, indent);
|
|
1902
|
+
const entries = Object.entries(field).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value, null, indent)}`).join(",\n");
|
|
1903
|
+
return `{\n${entries},\n }`;
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Count total items across a manifest field (flat or partitioned).
|
|
1907
|
+
*/
|
|
1908
|
+
function countItems(field) {
|
|
1909
|
+
if (Array.isArray(field)) return field.length;
|
|
1910
|
+
return Object.values(field).reduce((sum, arr) => sum + arr.length, 0);
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Generate derived types for a construct field.
|
|
1914
|
+
* @param fieldName - The field name in the manifest (e.g., 'routes')
|
|
1915
|
+
* @param typeName - The exported type name (e.g., 'Route')
|
|
1916
|
+
* @param partitioned - Whether this field is partitioned
|
|
1917
|
+
*/
|
|
1918
|
+
function generateDerivedType(fieldName, typeName, partitioned) {
|
|
1919
|
+
if (partitioned) {
|
|
1920
|
+
const partitionTypeName = `${typeName}Partition`;
|
|
1921
|
+
return [`export type ${partitionTypeName} = keyof typeof manifest.${fieldName};`, `export type ${typeName}<P extends ${partitionTypeName} = ${partitionTypeName}> = (typeof manifest.${fieldName})[P][number];`].join("\n");
|
|
1922
|
+
}
|
|
1923
|
+
return `export type ${typeName} = (typeof manifest.${fieldName})[number];`;
|
|
1924
|
+
}
|
|
1970
1925
|
async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
|
|
1971
1926
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
1972
1927
|
await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
|
|
1973
|
-
const awsRoutes = routes
|
|
1928
|
+
const awsRoutes = filterAllRoutes(routes);
|
|
1929
|
+
const routesPartitioned = isPartitioned(awsRoutes);
|
|
1930
|
+
const functionsPartitioned = isPartitioned(functions);
|
|
1931
|
+
const cronsPartitioned = isPartitioned(crons);
|
|
1932
|
+
const subscribersPartitioned = isPartitioned(subscribers);
|
|
1974
1933
|
const content = `export const manifest = {
|
|
1975
|
-
routes: ${
|
|
1976
|
-
functions: ${
|
|
1977
|
-
crons: ${
|
|
1978
|
-
subscribers: ${
|
|
1934
|
+
routes: ${serializeField(awsRoutes)},
|
|
1935
|
+
functions: ${serializeField(functions)},
|
|
1936
|
+
crons: ${serializeField(crons)},
|
|
1937
|
+
subscribers: ${serializeField(subscribers)},
|
|
1979
1938
|
} as const;
|
|
1980
1939
|
|
|
1981
1940
|
// Derived types
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1941
|
+
${generateDerivedType("routes", "Route", routesPartitioned)}
|
|
1942
|
+
${generateDerivedType("functions", "Function", functionsPartitioned)}
|
|
1943
|
+
${generateDerivedType("crons", "Cron", cronsPartitioned)}
|
|
1944
|
+
${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
|
|
1986
1945
|
|
|
1987
1946
|
// Useful union types
|
|
1988
1947
|
export type Authorizer = Route['authorizer'];
|
|
@@ -1991,30 +1950,25 @@ export type RoutePath = Route['path'];
|
|
|
1991
1950
|
`;
|
|
1992
1951
|
const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
|
|
1993
1952
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
1994
|
-
logger$10.log(`Generated AWS manifest with ${awsRoutes
|
|
1953
|
+
logger$10.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers`);
|
|
1995
1954
|
logger$10.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
1996
1955
|
}
|
|
1997
1956
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
|
|
1998
1957
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
1999
1958
|
await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
|
|
2000
|
-
const serverRoutes =
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
}));
|
|
2005
|
-
const serverSubscribers = subscribers.map((s) => ({
|
|
2006
|
-
name: s.name,
|
|
2007
|
-
subscribedEvents: s.subscribedEvents
|
|
2008
|
-
}));
|
|
1959
|
+
const serverRoutes = mapRouteMetadata(filterAllRoutes(routes));
|
|
1960
|
+
const serverSubscribers = mapSubscriberMetadata(subscribers);
|
|
1961
|
+
const routesPartitioned = isPartitioned(serverRoutes);
|
|
1962
|
+
const subscribersPartitioned = isPartitioned(serverSubscribers);
|
|
2009
1963
|
const content = `export const manifest = {
|
|
2010
1964
|
app: ${JSON.stringify(appInfo, null, 2)},
|
|
2011
|
-
routes: ${
|
|
2012
|
-
subscribers: ${
|
|
1965
|
+
routes: ${serializeField(serverRoutes)},
|
|
1966
|
+
subscribers: ${serializeField(serverSubscribers)},
|
|
2013
1967
|
} as const;
|
|
2014
1968
|
|
|
2015
1969
|
// Derived types
|
|
2016
|
-
|
|
2017
|
-
|
|
1970
|
+
${generateDerivedType("routes", "Route", routesPartitioned)}
|
|
1971
|
+
${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
|
|
2018
1972
|
|
|
2019
1973
|
// Useful union types
|
|
2020
1974
|
export type Authorizer = Route['authorizer'];
|
|
@@ -2023,9 +1977,70 @@ export type RoutePath = Route['path'];
|
|
|
2023
1977
|
`;
|
|
2024
1978
|
const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
|
|
2025
1979
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2026
|
-
logger$10.log(`Generated server manifest with ${serverRoutes
|
|
1980
|
+
logger$10.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers`);
|
|
2027
1981
|
logger$10.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
2028
1982
|
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
1985
|
+
*/
|
|
1986
|
+
function filterAllRoutes(routes) {
|
|
1987
|
+
if (Array.isArray(routes)) return routes.filter((r) => r.method !== "ALL");
|
|
1988
|
+
const result = {};
|
|
1989
|
+
for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.filter((r) => r.method !== "ALL");
|
|
1990
|
+
return result;
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Map routes to server metadata (path, method, authorizer only).
|
|
1994
|
+
*/
|
|
1995
|
+
function mapRouteMetadata(routes) {
|
|
1996
|
+
const mapFn = (r) => ({
|
|
1997
|
+
path: r.path,
|
|
1998
|
+
method: r.method,
|
|
1999
|
+
authorizer: r.authorizer
|
|
2000
|
+
});
|
|
2001
|
+
if (Array.isArray(routes)) return routes.map(mapFn);
|
|
2002
|
+
const result = {};
|
|
2003
|
+
for (const [partition, partitionRoutes] of Object.entries(routes)) result[partition] = partitionRoutes.map(mapFn);
|
|
2004
|
+
return result;
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Map subscribers to server metadata (name, subscribedEvents only).
|
|
2008
|
+
*/
|
|
2009
|
+
function mapSubscriberMetadata(subscribers) {
|
|
2010
|
+
const mapFn = (s) => ({
|
|
2011
|
+
name: s.name,
|
|
2012
|
+
subscribedEvents: s.subscribedEvents
|
|
2013
|
+
});
|
|
2014
|
+
if (Array.isArray(subscribers)) return subscribers.map(mapFn);
|
|
2015
|
+
const result = {};
|
|
2016
|
+
for (const [partition, partitionSubs] of Object.entries(subscribers)) result[partition] = partitionSubs.map(mapFn);
|
|
2017
|
+
return result;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
//#endregion
|
|
2021
|
+
//#region src/build/partitions.ts
|
|
2022
|
+
const DEFAULT_PARTITION = "default";
|
|
2023
|
+
/**
|
|
2024
|
+
* Check if any construct across the given arrays has a non-undefined partition.
|
|
2025
|
+
* When true, the manifest should use the partitioned shape for that construct type.
|
|
2026
|
+
*/
|
|
2027
|
+
function hasPartitions(constructs) {
|
|
2028
|
+
return constructs.some((c) => c.partition !== void 0);
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Group an info array by partition, using the partition values from
|
|
2032
|
+
* the corresponding construct array. Both arrays must be the same length
|
|
2033
|
+
* and in the same order.
|
|
2034
|
+
*/
|
|
2035
|
+
function groupInfosByPartition(infos, constructs) {
|
|
2036
|
+
const groups = {};
|
|
2037
|
+
for (let i = 0; i < infos.length; i++) {
|
|
2038
|
+
const partition = constructs[i]?.partition ?? DEFAULT_PARTITION;
|
|
2039
|
+
if (!groups[partition]) groups[partition] = [];
|
|
2040
|
+
groups[partition].push(infos[i]);
|
|
2041
|
+
}
|
|
2042
|
+
return groups;
|
|
2043
|
+
}
|
|
2029
2044
|
|
|
2030
2045
|
//#endregion
|
|
2031
2046
|
//#region src/build/index.ts
|
|
@@ -2047,10 +2062,10 @@ async function buildCommand(options) {
|
|
|
2047
2062
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2048
2063
|
if (production) logger$9.log(`🏭 Building for PRODUCTION`);
|
|
2049
2064
|
logger$9.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2050
|
-
logger$9.log(`Loading routes from: ${config.routes}`);
|
|
2051
|
-
if (config.functions) logger$9.log(`Loading functions from: ${config.functions}`);
|
|
2052
|
-
if (config.crons) logger$9.log(`Loading crons from: ${config.crons}`);
|
|
2053
|
-
if (config.subscribers) logger$9.log(`Loading subscribers from: ${config.subscribers}`);
|
|
2065
|
+
logger$9.log(`Loading routes from: ${formatRoutes(config.routes)}`);
|
|
2066
|
+
if (config.functions) logger$9.log(`Loading functions from: ${formatRoutes(config.functions)}`);
|
|
2067
|
+
if (config.crons) logger$9.log(`Loading crons from: ${formatRoutes(config.crons)}`);
|
|
2068
|
+
if (config.subscribers) logger$9.log(`Loading subscribers from: ${formatRoutes(config.subscribers)}`);
|
|
2054
2069
|
logger$9.log(`Using envParser: ${config.envParser}`);
|
|
2055
2070
|
const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
|
|
2056
2071
|
const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
|
|
@@ -2122,6 +2137,10 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2122
2137
|
subscriberGenerator.build(context, subscribers, outputDir, { provider })
|
|
2123
2138
|
]);
|
|
2124
2139
|
logger$9.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
|
|
2140
|
+
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2141
|
+
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2142
|
+
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
2143
|
+
const manifestSubscribers = assembleManifestField(subscriberInfos, subscribers);
|
|
2125
2144
|
if (provider === "server") {
|
|
2126
2145
|
const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
|
|
2127
2146
|
path: construct._path,
|
|
@@ -2129,15 +2148,16 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2129
2148
|
handler: "",
|
|
2130
2149
|
authorizer: construct.authorizer?.name ?? "none"
|
|
2131
2150
|
})));
|
|
2151
|
+
const serverRouteField = assembleManifestField(routeMetadata, endpoints);
|
|
2132
2152
|
const appInfo = {
|
|
2133
2153
|
handler: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "app.ts")),
|
|
2134
2154
|
endpoints: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "endpoints.ts"))
|
|
2135
2155
|
};
|
|
2136
|
-
await generateServerManifest(rootOutputDir, appInfo,
|
|
2156
|
+
await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers);
|
|
2137
2157
|
let masterKey;
|
|
2138
2158
|
if (context.production?.bundle && !skipBundle) {
|
|
2139
2159
|
logger$9.log(`\n📦 Bundling production server...`);
|
|
2140
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
2160
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-NpfYPBUo.cjs"));
|
|
2141
2161
|
const allConstructs = [
|
|
2142
2162
|
...endpoints.map((e) => e.construct),
|
|
2143
2163
|
...functions.map((f) => f.construct),
|
|
@@ -2163,7 +2183,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2163
2183
|
}
|
|
2164
2184
|
}
|
|
2165
2185
|
return { masterKey };
|
|
2166
|
-
} else await generateAwsManifest(rootOutputDir,
|
|
2186
|
+
} else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers);
|
|
2167
2187
|
return {};
|
|
2168
2188
|
}
|
|
2169
2189
|
/**
|
|
@@ -2261,6 +2281,25 @@ function getAppOutputPath(workspace, _appName, app) {
|
|
|
2261
2281
|
if (app.type === "frontend") return (0, node_path.join)(appPath, ".next");
|
|
2262
2282
|
else return (0, node_path.join)(appPath, ".gkm");
|
|
2263
2283
|
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Format routes for logging, handling PartitionedRoutes.
|
|
2286
|
+
*/
|
|
2287
|
+
function formatRoutes(routes) {
|
|
2288
|
+
if (require_openapi.isPartitionedRoutes(routes)) {
|
|
2289
|
+
const paths = Array.isArray(routes.paths) ? routes.paths.join(", ") : routes.paths;
|
|
2290
|
+
return `${paths} (partitioned)`;
|
|
2291
|
+
}
|
|
2292
|
+
return Array.isArray(routes) ? routes.join(", ") : routes;
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Assemble a ManifestField from build infos and constructs.
|
|
2296
|
+
* If any construct has a partition, returns a Record<string, T[]>.
|
|
2297
|
+
* Otherwise, returns a flat T[].
|
|
2298
|
+
*/
|
|
2299
|
+
function assembleManifestField(infos, constructs) {
|
|
2300
|
+
if (!hasPartitions(constructs)) return infos;
|
|
2301
|
+
return groupInfosByPartition(infos, constructs);
|
|
2302
|
+
}
|
|
2264
2303
|
|
|
2265
2304
|
//#endregion
|
|
2266
2305
|
//#region src/deploy/state.ts
|
|
@@ -2416,11 +2455,11 @@ async function createDnsProvider(options) {
|
|
|
2416
2455
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
2417
2456
|
const provider = config.provider;
|
|
2418
2457
|
if (provider === "hostinger") {
|
|
2419
|
-
const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-
|
|
2458
|
+
const { HostingerProvider } = await Promise.resolve().then(() => require("./HostingerProvider-5KYmwoK2.cjs"));
|
|
2420
2459
|
return new HostingerProvider();
|
|
2421
2460
|
}
|
|
2422
2461
|
if (provider === "route53") {
|
|
2423
|
-
const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-
|
|
2462
|
+
const { Route53Provider } = await Promise.resolve().then(() => require("./Route53Provider-owQQ4pn6.cjs"));
|
|
2424
2463
|
const route53Config = config;
|
|
2425
2464
|
return new Route53Provider({
|
|
2426
2465
|
region: route53Config.region,
|
|
@@ -4668,19 +4707,19 @@ function isStateProvider(value) {
|
|
|
4668
4707
|
async function createStateProvider(options) {
|
|
4669
4708
|
const { config, workspaceRoot, workspaceName } = options;
|
|
4670
4709
|
if (!config) {
|
|
4671
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-
|
|
4710
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CLifRC0Y.cjs"));
|
|
4672
4711
|
return new LocalStateProvider(workspaceRoot);
|
|
4673
4712
|
}
|
|
4674
4713
|
if (isStateProvider(config.provider)) return config.provider;
|
|
4675
4714
|
const provider = config.provider;
|
|
4676
4715
|
if (provider === "local") {
|
|
4677
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-
|
|
4716
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CLifRC0Y.cjs"));
|
|
4678
4717
|
return new LocalStateProvider(workspaceRoot);
|
|
4679
4718
|
}
|
|
4680
4719
|
if (provider === "ssm") {
|
|
4681
4720
|
if (!workspaceName) throw new Error("Workspace name is required for SSM state provider. Set \"name\" in gkm.config.ts.");
|
|
4682
|
-
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-
|
|
4683
|
-
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-
|
|
4721
|
+
const { LocalStateProvider } = await Promise.resolve().then(() => require("./LocalStateProvider-CLifRC0Y.cjs"));
|
|
4722
|
+
const { SSMStateProvider } = await Promise.resolve().then(() => require("./SSMStateProvider-CT8tjl9o.cjs"));
|
|
4684
4723
|
const { CachedStateProvider: CachedStateProvider$1 } = await Promise.resolve().then(() => require("./CachedStateProvider-D_uISMmJ.cjs"));
|
|
4685
4724
|
const ssmConfig = config;
|
|
4686
4725
|
const local = new LocalStateProvider(workspaceRoot);
|
|
@@ -4875,7 +4914,7 @@ async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
|
|
|
4875
4914
|
};
|
|
4876
4915
|
}
|
|
4877
4916
|
if (app.routes) {
|
|
4878
|
-
const result = await sniffRouteFiles(app.routes, app.path, workspacePath);
|
|
4917
|
+
const result = await sniffRouteFiles(require_openapi.normalizeRoutes(app.routes), app.path, workspacePath);
|
|
4879
4918
|
if (logWarnings && result.error) console.warn(`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`);
|
|
4880
4919
|
return {
|
|
4881
4920
|
appName,
|
|
@@ -5355,8 +5394,8 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
5355
5394
|
else logger$3.log(` ⚠ Cached ID invalid, will create new`);
|
|
5356
5395
|
}
|
|
5357
5396
|
if (!redis) {
|
|
5358
|
-
const { randomBytes: randomBytes$
|
|
5359
|
-
const databasePassword = randomBytes$
|
|
5397
|
+
const { randomBytes: randomBytes$2 } = await import("node:crypto");
|
|
5398
|
+
const databasePassword = randomBytes$2(16).toString("hex");
|
|
5360
5399
|
const result = await api.findOrCreateRedis(redisName, projectId, environmentId, { databasePassword });
|
|
5361
5400
|
redis = result.redis;
|
|
5362
5401
|
created = result.created;
|
|
@@ -5780,7 +5819,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5780
5819
|
}
|
|
5781
5820
|
if (workspace.deploy?.backups && provisionedPostgres) {
|
|
5782
5821
|
logger$3.log("\n💾 Provisioning backup destination...");
|
|
5783
|
-
const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-
|
|
5822
|
+
const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-C4noe75O.cjs"));
|
|
5784
5823
|
const backupState = await provisionBackupDestination({
|
|
5785
5824
|
api,
|
|
5786
5825
|
projectId: project.projectId,
|
|
@@ -6405,200 +6444,6 @@ function printStateDetails(state) {
|
|
|
6405
6444
|
}
|
|
6406
6445
|
}
|
|
6407
6446
|
|
|
6408
|
-
//#endregion
|
|
6409
|
-
//#region src/secrets/generator.ts
|
|
6410
|
-
/**
|
|
6411
|
-
* Generate a secure random password using URL-safe base64 characters.
|
|
6412
|
-
* @param length Password length (default: 32)
|
|
6413
|
-
*/
|
|
6414
|
-
function generateSecurePassword(length = 32) {
|
|
6415
|
-
return (0, node_crypto.randomBytes)(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
|
|
6416
|
-
}
|
|
6417
|
-
/** Default service configurations */
|
|
6418
|
-
const SERVICE_DEFAULTS = {
|
|
6419
|
-
postgres: {
|
|
6420
|
-
host: "postgres",
|
|
6421
|
-
port: 5432,
|
|
6422
|
-
username: "app",
|
|
6423
|
-
database: "app"
|
|
6424
|
-
},
|
|
6425
|
-
redis: {
|
|
6426
|
-
host: "redis",
|
|
6427
|
-
port: 6379,
|
|
6428
|
-
username: "default"
|
|
6429
|
-
},
|
|
6430
|
-
rabbitmq: {
|
|
6431
|
-
host: "rabbitmq",
|
|
6432
|
-
port: 5672,
|
|
6433
|
-
username: "app",
|
|
6434
|
-
vhost: "/"
|
|
6435
|
-
}
|
|
6436
|
-
};
|
|
6437
|
-
/**
|
|
6438
|
-
* Generate credentials for a specific service.
|
|
6439
|
-
*/
|
|
6440
|
-
function generateServiceCredentials(service) {
|
|
6441
|
-
const defaults = SERVICE_DEFAULTS[service];
|
|
6442
|
-
return {
|
|
6443
|
-
...defaults,
|
|
6444
|
-
password: generateSecurePassword()
|
|
6445
|
-
};
|
|
6446
|
-
}
|
|
6447
|
-
/**
|
|
6448
|
-
* Generate credentials for multiple services.
|
|
6449
|
-
*/
|
|
6450
|
-
function generateServicesCredentials(services) {
|
|
6451
|
-
const result = {};
|
|
6452
|
-
for (const service of services) result[service] = generateServiceCredentials(service);
|
|
6453
|
-
return result;
|
|
6454
|
-
}
|
|
6455
|
-
/**
|
|
6456
|
-
* Generate connection URL for PostgreSQL.
|
|
6457
|
-
*/
|
|
6458
|
-
function generatePostgresUrl(creds) {
|
|
6459
|
-
const { username, password, host, port, database } = creds;
|
|
6460
|
-
return `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
6461
|
-
}
|
|
6462
|
-
/**
|
|
6463
|
-
* Generate connection URL for Redis.
|
|
6464
|
-
*/
|
|
6465
|
-
function generateRedisUrl(creds) {
|
|
6466
|
-
const { password, host, port } = creds;
|
|
6467
|
-
return `redis://:${encodeURIComponent(password)}@${host}:${port}`;
|
|
6468
|
-
}
|
|
6469
|
-
/**
|
|
6470
|
-
* Generate connection URL for RabbitMQ.
|
|
6471
|
-
*/
|
|
6472
|
-
function generateRabbitmqUrl(creds) {
|
|
6473
|
-
const { username, password, host, port, vhost } = creds;
|
|
6474
|
-
const encodedVhost = encodeURIComponent(vhost ?? "/");
|
|
6475
|
-
return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;
|
|
6476
|
-
}
|
|
6477
|
-
/**
|
|
6478
|
-
* Generate connection URLs from service credentials.
|
|
6479
|
-
*/
|
|
6480
|
-
function generateConnectionUrls(services) {
|
|
6481
|
-
const urls = {};
|
|
6482
|
-
if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
|
|
6483
|
-
if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
|
|
6484
|
-
if (services.rabbitmq) urls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);
|
|
6485
|
-
return urls;
|
|
6486
|
-
}
|
|
6487
|
-
/**
|
|
6488
|
-
* Create a new StageSecrets object with generated credentials.
|
|
6489
|
-
*/
|
|
6490
|
-
function createStageSecrets(stage, services) {
|
|
6491
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6492
|
-
const serviceCredentials = generateServicesCredentials(services);
|
|
6493
|
-
const urls = generateConnectionUrls(serviceCredentials);
|
|
6494
|
-
return {
|
|
6495
|
-
stage,
|
|
6496
|
-
createdAt: now,
|
|
6497
|
-
updatedAt: now,
|
|
6498
|
-
services: serviceCredentials,
|
|
6499
|
-
urls,
|
|
6500
|
-
custom: {}
|
|
6501
|
-
};
|
|
6502
|
-
}
|
|
6503
|
-
/**
|
|
6504
|
-
* Rotate password for a specific service.
|
|
6505
|
-
*/
|
|
6506
|
-
function rotateServicePassword(secrets, service) {
|
|
6507
|
-
const currentCreds = secrets.services[service];
|
|
6508
|
-
if (!currentCreds) throw new Error(`Service "${service}" not configured in secrets`);
|
|
6509
|
-
const newCreds = {
|
|
6510
|
-
...currentCreds,
|
|
6511
|
-
password: generateSecurePassword()
|
|
6512
|
-
};
|
|
6513
|
-
const newServices = {
|
|
6514
|
-
...secrets.services,
|
|
6515
|
-
[service]: newCreds
|
|
6516
|
-
};
|
|
6517
|
-
return {
|
|
6518
|
-
...secrets,
|
|
6519
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6520
|
-
services: newServices,
|
|
6521
|
-
urls: generateConnectionUrls(newServices)
|
|
6522
|
-
};
|
|
6523
|
-
}
|
|
6524
|
-
|
|
6525
|
-
//#endregion
|
|
6526
|
-
//#region src/setup/fullstack-secrets.ts
|
|
6527
|
-
/**
|
|
6528
|
-
* Generate a secure random password for database users.
|
|
6529
|
-
* Uses a combination of timestamp and random bytes for uniqueness.
|
|
6530
|
-
*/
|
|
6531
|
-
function generateDbPassword() {
|
|
6532
|
-
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
|
|
6533
|
-
}
|
|
6534
|
-
/**
|
|
6535
|
-
* Generate database URL for an app.
|
|
6536
|
-
* All apps connect to the same database, but use different users/schemas.
|
|
6537
|
-
*/
|
|
6538
|
-
function generateDbUrl(appName, password, projectName, host = "localhost", port = 5432) {
|
|
6539
|
-
const userName = appName.replace(/-/g, "_");
|
|
6540
|
-
const dbName = `${projectName.replace(/-/g, "_")}_dev`;
|
|
6541
|
-
return `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;
|
|
6542
|
-
}
|
|
6543
|
-
/**
|
|
6544
|
-
* Generate fullstack-aware custom secrets for a workspace.
|
|
6545
|
-
*
|
|
6546
|
-
* Generates:
|
|
6547
|
-
* - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET
|
|
6548
|
-
* - Per-app database passwords and URLs for backend apps with db service
|
|
6549
|
-
* - Better-auth secrets for apps using the better-auth framework
|
|
6550
|
-
*/
|
|
6551
|
-
function generateFullstackCustomSecrets(workspace) {
|
|
6552
|
-
const hasDb = !!workspace.services.db;
|
|
6553
|
-
const customs = {
|
|
6554
|
-
NODE_ENV: "development",
|
|
6555
|
-
PORT: "3000",
|
|
6556
|
-
LOG_LEVEL: "debug",
|
|
6557
|
-
JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
6558
|
-
};
|
|
6559
|
-
if (!hasDb) return customs;
|
|
6560
|
-
const frontendPorts = [];
|
|
6561
|
-
for (const [appName, appConfig] of Object.entries(workspace.apps)) {
|
|
6562
|
-
if (appConfig.type === "frontend") {
|
|
6563
|
-
frontendPorts.push(appConfig.port);
|
|
6564
|
-
continue;
|
|
6565
|
-
}
|
|
6566
|
-
const password = generateDbPassword();
|
|
6567
|
-
const upperName = appName.toUpperCase();
|
|
6568
|
-
customs[`${upperName}_DATABASE_URL`] = generateDbUrl(appName, password, workspace.name);
|
|
6569
|
-
customs[`${upperName}_DB_PASSWORD`] = password;
|
|
6570
|
-
if (appConfig.framework === "better-auth") {
|
|
6571
|
-
customs.AUTH_PORT = String(appConfig.port);
|
|
6572
|
-
customs.AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
6573
|
-
customs.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;
|
|
6574
|
-
customs.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
6575
|
-
}
|
|
6576
|
-
}
|
|
6577
|
-
if (customs.BETTER_AUTH_SECRET) {
|
|
6578
|
-
const allPorts = Object.values(workspace.apps).map((a) => a.port);
|
|
6579
|
-
customs.BETTER_AUTH_TRUSTED_ORIGINS = allPorts.map((p) => `http://localhost:${p}`).join(",");
|
|
6580
|
-
}
|
|
6581
|
-
return customs;
|
|
6582
|
-
}
|
|
6583
|
-
/**
|
|
6584
|
-
* Extract *_DB_PASSWORD keys from secrets and write docker/.env.
|
|
6585
|
-
*
|
|
6586
|
-
* The docker/.env file contains database passwords that the PostgreSQL
|
|
6587
|
-
* init script reads to create per-app database users.
|
|
6588
|
-
*/
|
|
6589
|
-
async function writeDockerEnvFromSecrets(secrets, workspaceRoot) {
|
|
6590
|
-
const dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) => key.endsWith("_DB_PASSWORD"));
|
|
6591
|
-
if (dbPasswordEntries.length === 0) return;
|
|
6592
|
-
const envContent = `# Auto-generated docker environment file
|
|
6593
|
-
# Contains database passwords for docker-compose postgres init
|
|
6594
|
-
# This file is gitignored - do not commit to version control
|
|
6595
|
-
${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
6596
|
-
`;
|
|
6597
|
-
const envPath = (0, node_path.join)(workspaceRoot, "docker", ".env");
|
|
6598
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(envPath), { recursive: true });
|
|
6599
|
-
await (0, node_fs_promises.writeFile)(envPath, envContent);
|
|
6600
|
-
}
|
|
6601
|
-
|
|
6602
6447
|
//#endregion
|
|
6603
6448
|
//#region src/init/versions.ts
|
|
6604
6449
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
@@ -6624,14 +6469,14 @@ const GEEKMIDAS_VERSIONS = {
|
|
|
6624
6469
|
"@geekmidas/audit": "~1.0.0",
|
|
6625
6470
|
"@geekmidas/auth": "~1.0.0",
|
|
6626
6471
|
"@geekmidas/cache": "~1.0.0",
|
|
6627
|
-
"@geekmidas/client": "~
|
|
6472
|
+
"@geekmidas/client": "~3.0.0",
|
|
6628
6473
|
"@geekmidas/cloud": "~1.0.0",
|
|
6629
|
-
"@geekmidas/constructs": "~
|
|
6474
|
+
"@geekmidas/constructs": "~2.0.0",
|
|
6630
6475
|
"@geekmidas/db": "~1.0.0",
|
|
6631
6476
|
"@geekmidas/emailkit": "~1.0.0",
|
|
6632
6477
|
"@geekmidas/envkit": "~1.0.3",
|
|
6633
6478
|
"@geekmidas/errors": "~1.0.0",
|
|
6634
|
-
"@geekmidas/events": "~1.
|
|
6479
|
+
"@geekmidas/events": "~1.1.0",
|
|
6635
6480
|
"@geekmidas/logger": "~1.0.0",
|
|
6636
6481
|
"@geekmidas/rate-limit": "~1.0.0",
|
|
6637
6482
|
"@geekmidas/schema": "~1.0.0",
|
|
@@ -6639,7 +6484,7 @@ const GEEKMIDAS_VERSIONS = {
|
|
|
6639
6484
|
"@geekmidas/storage": "~1.0.0",
|
|
6640
6485
|
"@geekmidas/studio": "~1.0.0",
|
|
6641
6486
|
"@geekmidas/telescope": "~1.0.0",
|
|
6642
|
-
"@geekmidas/testkit": "~1.0.
|
|
6487
|
+
"@geekmidas/testkit": "~1.0.2",
|
|
6643
6488
|
"@geekmidas/cli": CLI_VERSION
|
|
6644
6489
|
};
|
|
6645
6490
|
|
|
@@ -10948,10 +10793,10 @@ async function initCommand(projectName, options = {}) {
|
|
|
10948
10793
|
const dbApps = [];
|
|
10949
10794
|
if (isFullstack && services.db) dbApps.push({
|
|
10950
10795
|
name: "api",
|
|
10951
|
-
password: generateDbPassword()
|
|
10796
|
+
password: require_fullstack_secrets.generateDbPassword()
|
|
10952
10797
|
}, {
|
|
10953
10798
|
name: "auth",
|
|
10954
|
-
password: generateDbPassword()
|
|
10799
|
+
password: require_fullstack_secrets.generateDbPassword()
|
|
10955
10800
|
});
|
|
10956
10801
|
const appFiles = baseTemplate ? [
|
|
10957
10802
|
...generatePackageJson(templateOptions, baseTemplate),
|
|
@@ -11000,7 +10845,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
11000
10845
|
const secretServices = [];
|
|
11001
10846
|
if (services.db) secretServices.push("postgres");
|
|
11002
10847
|
if (services.cache) secretServices.push("redis");
|
|
11003
|
-
const devSecrets = createStageSecrets("development", secretServices);
|
|
10848
|
+
const devSecrets = require_fullstack_secrets.createStageSecrets("development", secretServices);
|
|
11004
10849
|
const customSecrets = {
|
|
11005
10850
|
NODE_ENV: "development",
|
|
11006
10851
|
PORT: "3000",
|
|
@@ -11010,7 +10855,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
11010
10855
|
if (isFullstack && dbApps.length > 0) {
|
|
11011
10856
|
for (const app of dbApps) {
|
|
11012
10857
|
const urlKey = `${app.name.toUpperCase()}_DATABASE_URL`;
|
|
11013
|
-
customSecrets[urlKey] = generateDbUrl(app.name, app.password, name$1);
|
|
10858
|
+
customSecrets[urlKey] = require_fullstack_secrets.generateDbUrl(app.name, app.password, name$1);
|
|
11014
10859
|
const passwordKey = `${app.name.toUpperCase()}_DB_PASSWORD`;
|
|
11015
10860
|
customSecrets[passwordKey] = app.password;
|
|
11016
10861
|
}
|
|
@@ -11137,12 +10982,12 @@ async function secretsInitCommand(options) {
|
|
|
11137
10982
|
const config = await require_config.loadConfig();
|
|
11138
10983
|
const services = getServicesFromConfig(config.docker?.compose?.services);
|
|
11139
10984
|
if (services.length === 0) logger$2.warn("No services configured in docker.compose.services. Creating secrets with empty services.");
|
|
11140
|
-
const secrets = createStageSecrets(stage, services);
|
|
10985
|
+
const secrets = require_fullstack_secrets.createStageSecrets(stage, services);
|
|
11141
10986
|
try {
|
|
11142
10987
|
const loaded = await require_config.loadWorkspaceConfig();
|
|
11143
10988
|
const isMultiApp = Object.keys(loaded.workspace.apps).length > 1;
|
|
11144
10989
|
if (isMultiApp) {
|
|
11145
|
-
const customSecrets = generateFullstackCustomSecrets(loaded.workspace);
|
|
10990
|
+
const customSecrets = require_fullstack_secrets.generateFullstackCustomSecrets(loaded.workspace);
|
|
11146
10991
|
secrets.custom = customSecrets;
|
|
11147
10992
|
logger$2.log(" Detected workspace mode — generating per-app secrets");
|
|
11148
10993
|
}
|
|
@@ -11243,13 +11088,13 @@ async function secretsRotateCommand(options) {
|
|
|
11243
11088
|
logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
11244
11089
|
process.exit(1);
|
|
11245
11090
|
}
|
|
11246
|
-
const updated = rotateServicePassword(secrets, service);
|
|
11091
|
+
const updated = require_fullstack_secrets.rotateServicePassword(secrets, service);
|
|
11247
11092
|
await require_storage.writeStageSecrets(updated);
|
|
11248
11093
|
logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
11249
11094
|
} else {
|
|
11250
11095
|
let updated = secrets;
|
|
11251
11096
|
const services = Object.keys(secrets.services);
|
|
11252
|
-
for (const svc of services) updated = rotateServicePassword(updated, svc);
|
|
11097
|
+
for (const svc of services) updated = require_fullstack_secrets.rotateServicePassword(updated, svc);
|
|
11253
11098
|
await require_storage.writeStageSecrets(updated);
|
|
11254
11099
|
logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
11255
11100
|
}
|
|
@@ -11342,7 +11187,7 @@ async function setupCommand(options = {}) {
|
|
|
11342
11187
|
process.exit(1);
|
|
11343
11188
|
}
|
|
11344
11189
|
if (isMultiApp && workspace.services.db) {
|
|
11345
|
-
await writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
11190
|
+
await require_fullstack_secrets.writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
11346
11191
|
logger$1.log("📄 Generated docker/.env with database passwords");
|
|
11347
11192
|
}
|
|
11348
11193
|
if (!options.skipDocker) {
|
|
@@ -11396,10 +11241,10 @@ async function generateFreshSecrets(stage, workspace, options) {
|
|
|
11396
11241
|
const serviceNames = [];
|
|
11397
11242
|
if (workspace.services.db) serviceNames.push("postgres");
|
|
11398
11243
|
if (workspace.services.cache) serviceNames.push("redis");
|
|
11399
|
-
const secrets = createStageSecrets(stage, serviceNames);
|
|
11244
|
+
const secrets = require_fullstack_secrets.createStageSecrets(stage, serviceNames);
|
|
11400
11245
|
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
11401
11246
|
if (isMultiApp) {
|
|
11402
|
-
const customSecrets = generateFullstackCustomSecrets(workspace);
|
|
11247
|
+
const customSecrets = require_fullstack_secrets.generateFullstackCustomSecrets(workspace);
|
|
11403
11248
|
secrets.custom = customSecrets;
|
|
11404
11249
|
} else secrets.custom = {
|
|
11405
11250
|
NODE_ENV: "development",
|
|
@@ -11943,8 +11788,19 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
11943
11788
|
const globalOptions = program.opts();
|
|
11944
11789
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11945
11790
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
11946
|
-
const { pushSecrets: pushSecrets$1 } = await Promise.resolve().then(() => require("./sync-
|
|
11791
|
+
const { pushSecrets: pushSecrets$1 } = await Promise.resolve().then(() => require("./sync-RsnjXYwG.cjs"));
|
|
11792
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-7yarEvmK.cjs"));
|
|
11793
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
|
|
11947
11794
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
11795
|
+
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
11796
|
+
if (secrets) {
|
|
11797
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
11798
|
+
if (result) {
|
|
11799
|
+
await writeStageSecrets$1(result.secrets, workspace.root);
|
|
11800
|
+
console.log(` Reconciled ${result.addedKeys.length} missing secret(s):`);
|
|
11801
|
+
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
11802
|
+
}
|
|
11803
|
+
}
|
|
11948
11804
|
await pushSecrets$1(options.stage, workspace);
|
|
11949
11805
|
console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
|
|
11950
11806
|
} catch (error) {
|
|
@@ -11957,14 +11813,21 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
11957
11813
|
const globalOptions = program.opts();
|
|
11958
11814
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11959
11815
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
11960
|
-
const { pullSecrets: pullSecrets$1 } = await Promise.resolve().then(() => require("./sync-
|
|
11816
|
+
const { pullSecrets: pullSecrets$1 } = await Promise.resolve().then(() => require("./sync-RsnjXYwG.cjs"));
|
|
11961
11817
|
const { writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
|
|
11818
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-7yarEvmK.cjs"));
|
|
11962
11819
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
11963
|
-
|
|
11820
|
+
let secrets = await pullSecrets$1(options.stage, workspace);
|
|
11964
11821
|
if (!secrets) {
|
|
11965
11822
|
console.error(`No remote secrets found for stage "${options.stage}".`);
|
|
11966
11823
|
process.exit(1);
|
|
11967
11824
|
}
|
|
11825
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
11826
|
+
if (result) {
|
|
11827
|
+
secrets = result.secrets;
|
|
11828
|
+
console.log(` Reconciled ${result.addedKeys.length} missing secret(s):`);
|
|
11829
|
+
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
11830
|
+
}
|
|
11968
11831
|
await writeStageSecrets$1(secrets, workspace.root);
|
|
11969
11832
|
console.log(`\n✓ Secrets pulled for stage "${options.stage}"`);
|
|
11970
11833
|
} catch (error) {
|
|
@@ -11972,6 +11835,32 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
11972
11835
|
process.exit(1);
|
|
11973
11836
|
}
|
|
11974
11837
|
});
|
|
11838
|
+
program.command("secrets:reconcile").description("Backfill missing custom secrets from workspace config").option("--stage <stage>", "Stage name", "development").action(async (options) => {
|
|
11839
|
+
try {
|
|
11840
|
+
const globalOptions = program.opts();
|
|
11841
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11842
|
+
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
11843
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-7yarEvmK.cjs"));
|
|
11844
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-C7pmBq1u.cjs"));
|
|
11845
|
+
const { workspace } = await loadWorkspaceConfig$1();
|
|
11846
|
+
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
11847
|
+
if (!secrets) {
|
|
11848
|
+
console.error(`No secrets found for stage "${options.stage}". Run "gkm secrets:init --stage ${options.stage}" first.`);
|
|
11849
|
+
process.exit(1);
|
|
11850
|
+
}
|
|
11851
|
+
const result = reconcileMissingSecrets(secrets, workspace);
|
|
11852
|
+
if (!result) {
|
|
11853
|
+
console.log(`\n✓ Secrets for stage "${options.stage}" are up-to-date`);
|
|
11854
|
+
return;
|
|
11855
|
+
}
|
|
11856
|
+
await writeStageSecrets$1(result.secrets, workspace.root);
|
|
11857
|
+
console.log(`\n✓ Reconciled ${result.addedKeys.length} missing secret(s) for stage "${options.stage}":`);
|
|
11858
|
+
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
11859
|
+
} catch (error) {
|
|
11860
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
11861
|
+
process.exit(1);
|
|
11862
|
+
}
|
|
11863
|
+
});
|
|
11975
11864
|
program.command("deploy").description("Deploy application to a provider").requiredOption("--provider <provider>", "Deploy provider (docker, dokploy, aws-lambda)").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp)").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").action(async (options) => {
|
|
11976
11865
|
try {
|
|
11977
11866
|
const globalOptions = program.opts();
|