@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.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { __require } from "./chunk-Duj1WY3L.mjs";
|
|
3
|
-
import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-
|
|
4
|
-
import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig } from "./config-
|
|
3
|
+
import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-D4z4A4cq.mjs";
|
|
4
|
+
import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, parseModuleConfig } from "./config-jsRYHOHU.mjs";
|
|
5
5
|
import { getCredentialsPath, getDokployCredentials, getDokployRegistryId, getDokployToken, removeDokployCredentials, storeDokployCredentials, storeDokployRegistryId } from "./credentials-s1kLcIzK.mjs";
|
|
6
|
-
import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-
|
|
6
|
+
import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, copyAllClients, copyClientToFrontends, generateOpenApi, getBackendOpenApiPath, isPartitionedRoutes, normalizeRoutes, openapiCommand, resolveOpenApiConfig } from "./openapi-DenF-okj.mjs";
|
|
7
7
|
import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-DmCbr6DI.mjs";
|
|
8
8
|
import { DokployApi } from "./dokploy-api-2ldYoN3i.mjs";
|
|
9
9
|
import { encryptSecrets } from "./encryption-BOH5M-f-.mjs";
|
|
10
10
|
import { CachedStateProvider } from "./CachedStateProvider-BDq5WqSy.mjs";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { createStageSecrets, generateDbPassword, generateDbUrl, generateFullstackCustomSecrets, rotateServicePassword, writeDockerEnvFromSecrets } from "./fullstack-secrets-UZAFWuH4.mjs";
|
|
12
|
+
import { generateReactQueryCommand } from "./openapi-react-query-C4UdILaI.mjs";
|
|
13
|
+
import { isSSMConfigured, pullSecrets, pushSecrets } from "./sync-CbeKrnQV.mjs";
|
|
13
14
|
import { createRequire } from "node:module";
|
|
14
15
|
import { copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
15
16
|
import { basename, dirname, join, parse, relative, resolve } from "node:path";
|
|
@@ -34,7 +35,7 @@ import prompts from "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 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: ${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 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 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 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: ${getCredentialsPath()}`);
|
|
264
265
|
}
|
|
265
266
|
/**
|
|
266
267
|
* Mask a token for display
|
|
@@ -346,7 +347,7 @@ function isEnabled(config$1) {
|
|
|
346
347
|
var CronGenerator = class extends 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 = join(outputDir, "crons");
|
|
@@ -361,7 +362,7 @@ var CronGenerator = class extends 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 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 = join(outputDir, "functions");
|
|
@@ -411,7 +412,7 @@ var FunctionGenerator = class extends 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 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 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 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 = join(workspace.root, backendApp.path, ".gkm", "openapi.ts");
|
|
669
|
-
if (!existsSync(openApiPath)) return results;
|
|
670
|
-
const content = await 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 = join(workspace.root, frontendApp.path);
|
|
687
|
-
const outputDir = join(frontendPath, clientOutput);
|
|
688
|
-
await mkdir(outputDir, { recursive: true });
|
|
689
|
-
const fileName = `${backendAppName}.ts`;
|
|
690
|
-
const outputPath = join(outputDir, fileName);
|
|
691
|
-
const backendRelPath = relative(dirname(outputPath), 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 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;
|
|
@@ -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$1, dirname: dirname$1 } = await import("node:path");
|
|
1837
1805
|
const serverPath = 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 = join(outputDir, "manifest");
|
|
1972
1927
|
await 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 = join(manifestDir, "aws.ts");
|
|
1993
1952
|
await 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: ${relative(process.cwd(), manifestPath)}`);
|
|
1996
1955
|
}
|
|
1997
1956
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
|
|
1998
1957
|
const manifestDir = join(outputDir, "manifest");
|
|
1999
1958
|
await 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 = join(manifestDir, "server.ts");
|
|
2025
1979
|
await 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: ${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$1.routes}`);
|
|
2051
|
-
if (config$1.functions) logger$9.log(`Loading functions from: ${config$1.functions}`);
|
|
2052
|
-
if (config$1.crons) logger$9.log(`Loading crons from: ${config$1.crons}`);
|
|
2053
|
-
if (config$1.subscribers) logger$9.log(`Loading subscribers from: ${config$1.subscribers}`);
|
|
2065
|
+
logger$9.log(`Loading routes from: ${formatRoutes(config$1.routes)}`);
|
|
2066
|
+
if (config$1.functions) logger$9.log(`Loading functions from: ${formatRoutes(config$1.functions)}`);
|
|
2067
|
+
if (config$1.crons) logger$9.log(`Loading crons from: ${formatRoutes(config$1.crons)}`);
|
|
2068
|
+
if (config$1.subscribers) logger$9.log(`Loading subscribers from: ${formatRoutes(config$1.subscribers)}`);
|
|
2054
2069
|
logger$9.log(`Using envParser: ${config$1.envParser}`);
|
|
2055
2070
|
const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config$1.envParser, "envParser");
|
|
2056
2071
|
const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config$1.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: relative(process.cwd(), join(outputDir, "app.ts")),
|
|
2134
2154
|
endpoints: relative(process.cwd(), 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 import("./bundler-
|
|
2160
|
+
const { bundleServer } = await import("./bundler-DQYjKFPm.mjs");
|
|
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 join(appPath, ".next");
|
|
2262
2282
|
else return join(appPath, ".gkm");
|
|
2263
2283
|
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Format routes for logging, handling PartitionedRoutes.
|
|
2286
|
+
*/
|
|
2287
|
+
function formatRoutes(routes) {
|
|
2288
|
+
if (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$1.provider)) return config$1.provider;
|
|
2417
2456
|
const provider = config$1.provider;
|
|
2418
2457
|
if (provider === "hostinger") {
|
|
2419
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
2458
|
+
const { HostingerProvider } = await import("./HostingerProvider-ANWchdiK.mjs");
|
|
2420
2459
|
return new HostingerProvider();
|
|
2421
2460
|
}
|
|
2422
2461
|
if (provider === "route53") {
|
|
2423
|
-
const { Route53Provider } = await import("./Route53Provider-
|
|
2462
|
+
const { Route53Provider } = await import("./Route53Provider-QoPgcXxn.mjs");
|
|
2424
2463
|
const route53Config = config$1;
|
|
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: config$1, workspaceRoot, workspaceName } = options;
|
|
4670
4709
|
if (!config$1) {
|
|
4671
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-
|
|
4710
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-Dp0KkRcw.mjs");
|
|
4672
4711
|
return new LocalStateProvider(workspaceRoot);
|
|
4673
4712
|
}
|
|
4674
4713
|
if (isStateProvider(config$1.provider)) return config$1.provider;
|
|
4675
4714
|
const provider = config$1.provider;
|
|
4676
4715
|
if (provider === "local") {
|
|
4677
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-
|
|
4716
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-Dp0KkRcw.mjs");
|
|
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 import("./LocalStateProvider-
|
|
4683
|
-
const { SSMStateProvider } = await import("./SSMStateProvider-
|
|
4721
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-Dp0KkRcw.mjs");
|
|
4722
|
+
const { SSMStateProvider } = await import("./SSMStateProvider-CksOTB8M.mjs");
|
|
4684
4723
|
const { CachedStateProvider: CachedStateProvider$1 } = await import("./CachedStateProvider-CI61keQ1.mjs");
|
|
4685
4724
|
const ssmConfig = config$1;
|
|
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(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,
|
|
@@ -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 import("./backup-provisioner-
|
|
5822
|
+
const { provisionBackupDestination } = await import("./backup-provisioner-BEXoHTuC.mjs");
|
|
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 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 = join(workspaceRoot, "docker", ".env");
|
|
6598
|
-
await mkdir(dirname(envPath), { recursive: true });
|
|
6599
|
-
await writeFile(envPath, envContent);
|
|
6600
|
-
}
|
|
6601
|
-
|
|
6602
6447
|
//#endregion
|
|
6603
6448
|
//#region src/init/versions.ts
|
|
6604
6449
|
const require$1 = createRequire(import.meta.url);
|
|
@@ -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
|
|
|
@@ -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 import("./config.mjs");
|
|
11946
|
-
const { pushSecrets: pushSecrets$1 } = await import("./sync-
|
|
11791
|
+
const { pushSecrets: pushSecrets$1 } = await import("./sync-6FoT41G3.mjs");
|
|
11792
|
+
const { reconcileMissingSecrets } = await import("./reconcile-D2WCDQue.mjs");
|
|
11793
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Dx_jZbq6.mjs");
|
|
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 import("./config.mjs");
|
|
11960
|
-
const { pullSecrets: pullSecrets$1 } = await import("./sync-
|
|
11816
|
+
const { pullSecrets: pullSecrets$1 } = await import("./sync-6FoT41G3.mjs");
|
|
11961
11817
|
const { writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Dx_jZbq6.mjs");
|
|
11818
|
+
const { reconcileMissingSecrets } = await import("./reconcile-D2WCDQue.mjs");
|
|
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 import("./config.mjs");
|
|
11843
|
+
const { reconcileMissingSecrets } = await import("./reconcile-D2WCDQue.mjs");
|
|
11844
|
+
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Dx_jZbq6.mjs");
|
|
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();
|